@adatechnology/game-urban 0.2.8 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/index.d.ts +46 -20
- package/dist/index.global.js +2452 -135
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.global.js
CHANGED
|
@@ -25,6 +25,7 @@ var GameUrban = (() => {
|
|
|
25
25
|
DEFAULT_CITY_PLANS: () => DEFAULT_CITY_PLANS,
|
|
26
26
|
DEFAULT_DISTRICT_KINDS: () => DEFAULT_DISTRICT_KINDS,
|
|
27
27
|
DEFAULT_FACADES: () => DEFAULT_FACADES,
|
|
28
|
+
DEFAULT_LIGHT_ALLOWANCE: () => DEFAULT_LIGHT_ALLOWANCE,
|
|
28
29
|
DEFAULT_LIGHT_BUDGET: () => DEFAULT_LIGHT_BUDGET,
|
|
29
30
|
DEFAULT_MAX_FLOORS: () => DEFAULT_MAX_FLOORS,
|
|
30
31
|
DEFAULT_PATTERNS: () => DEFAULT_PATTERNS,
|
|
@@ -44,9 +45,10 @@ var GameUrban = (() => {
|
|
|
44
45
|
TOWER_DEFAULTS: () => TOWER_DEFAULTS,
|
|
45
46
|
TOWER_PATTERNS: () => TOWER_PATTERNS,
|
|
46
47
|
TOWER_SAMPLE_CELLS: () => TOWER_SAMPLE_CELLS,
|
|
47
|
-
clamp: () =>
|
|
48
|
+
clamp: () => clamp2,
|
|
48
49
|
composePattern: () => composePattern,
|
|
49
50
|
createCities: () => createCities,
|
|
51
|
+
createLightAllowance: () => createLightAllowance,
|
|
50
52
|
createLightBudget: () => createLightBudget,
|
|
51
53
|
createPlantProvider: () => createPlantProvider,
|
|
52
54
|
createSilhouetteHouses: () => createSilhouetteHouses,
|
|
@@ -85,37 +87,2297 @@ var GameUrban = (() => {
|
|
|
85
87
|
});
|
|
86
88
|
|
|
87
89
|
// src/budget.ts
|
|
88
|
-
function
|
|
89
|
-
const
|
|
90
|
+
function createLightAllowance(count) {
|
|
91
|
+
const allowance = {
|
|
90
92
|
left: count,
|
|
91
93
|
take() {
|
|
92
|
-
return
|
|
94
|
+
return allowance.left-- > 0;
|
|
93
95
|
}
|
|
94
96
|
};
|
|
95
|
-
return
|
|
97
|
+
return allowance;
|
|
96
98
|
}
|
|
99
|
+
var createLightBudget = createLightAllowance;
|
|
100
|
+
|
|
101
|
+
// ../game-contracts/dist/index.js
|
|
102
|
+
var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
|
|
103
|
+
var DEFERRED_QUEUE_ID = {
|
|
104
|
+
DESTRUCTION_EVENTS: "destruction.events",
|
|
105
|
+
BREACH_PAINT: "floorplan.breach-paint",
|
|
106
|
+
AUDIO_VOICES: "audio.voices",
|
|
107
|
+
VEHICLE_CREASE: "vehicles.crease"
|
|
108
|
+
};
|
|
109
|
+
var DEFERRED_PRIORITY = {
|
|
110
|
+
[DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
|
|
111
|
+
[DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
|
|
112
|
+
[DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
|
|
113
|
+
[DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
|
|
114
|
+
};
|
|
115
|
+
var WARNED_KEY = /* @__PURE__ */ Symbol.for("@adatechnology/game-contracts.warned");
|
|
116
|
+
var warnedKeys = () => {
|
|
117
|
+
const holder = globalThis;
|
|
118
|
+
return holder[WARNED_KEY] ?? (holder[WARNED_KEY] = /* @__PURE__ */ new Set());
|
|
119
|
+
};
|
|
120
|
+
var isProduction = () => {
|
|
121
|
+
try {
|
|
122
|
+
return process.env.NODE_ENV === "production";
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var isQuiet = () => globalThis.__GAME_KIT_QUIET__ === true;
|
|
128
|
+
var warnOnce = (key, message) => {
|
|
129
|
+
if (isQuiet() || isProduction()) return;
|
|
130
|
+
const seen = warnedKeys();
|
|
131
|
+
if (seen.has(key)) return;
|
|
132
|
+
seen.add(key);
|
|
133
|
+
console.warn(`[game-kit] ${message}`);
|
|
134
|
+
};
|
|
135
|
+
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
136
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
137
|
+
var deepMerge = (base, patch) => {
|
|
138
|
+
if (!patch) return base;
|
|
139
|
+
const result = { ...base };
|
|
140
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
141
|
+
if (value === void 0 || UNSAFE_KEYS.has(key)) continue;
|
|
142
|
+
const current = result[key];
|
|
143
|
+
result[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
};
|
|
147
|
+
var mergeById = (shipped, extra) => {
|
|
148
|
+
const entries = /* @__PURE__ */ new Map();
|
|
149
|
+
for (const entry of [...shipped, ...extra ?? []]) entries.set(entry.id, entry);
|
|
150
|
+
return entries;
|
|
151
|
+
};
|
|
152
|
+
var applyPatches = (entries, patches) => {
|
|
153
|
+
for (const [id, patch] of Object.entries(patches ?? {})) {
|
|
154
|
+
const entry = entries.get(id);
|
|
155
|
+
if (entry) entries.set(id, deepMerge(entry, patch));
|
|
156
|
+
else warnOnce(`registry.patch.${id}`, `patch for "${id}" ignored: no entry has that id`);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var removeIds = (entries, ids) => {
|
|
160
|
+
for (const id of ids ?? []) {
|
|
161
|
+
if (!entries.delete(id)) warnOnce(`registry.remove.${id}`, `remove of "${id}" ignored: no entry has that id`);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
|
|
165
|
+
var GAIT_IDS = ["walk", "run"];
|
|
166
|
+
var LANDING_IDS = ["light", "heavy"];
|
|
167
|
+
var BODY_SOUND_IDS = [
|
|
168
|
+
"jump",
|
|
169
|
+
"cloth_rustle",
|
|
170
|
+
"body_fall",
|
|
171
|
+
"fall_damage",
|
|
172
|
+
"player_hurt",
|
|
173
|
+
"player_breath_run",
|
|
174
|
+
"player_death",
|
|
175
|
+
"bite",
|
|
176
|
+
"grab",
|
|
177
|
+
"punch_swing",
|
|
178
|
+
"punch_hit_body",
|
|
179
|
+
"punch_hit_face",
|
|
180
|
+
"punch_hit_object",
|
|
181
|
+
"kick_swing",
|
|
182
|
+
"kick_hit",
|
|
183
|
+
"kick_hit_object",
|
|
184
|
+
"shove_two_hands",
|
|
185
|
+
"knife_swing",
|
|
186
|
+
"knife_stab",
|
|
187
|
+
"knife_slash",
|
|
188
|
+
"knife_hit_object",
|
|
189
|
+
"blunt_swing",
|
|
190
|
+
"blunt_hit",
|
|
191
|
+
"blunt_hit_object",
|
|
192
|
+
"axe_swing",
|
|
193
|
+
"axe_chop",
|
|
194
|
+
"axe_hit_object"
|
|
195
|
+
];
|
|
196
|
+
var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
|
|
197
|
+
var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
|
|
198
|
+
var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing) => `land_${surface}_${landing}`));
|
|
199
|
+
var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
|
|
97
200
|
|
|
98
201
|
// ../game-floorplan/dist/index.js
|
|
99
|
-
var
|
|
100
|
-
|
|
101
|
-
|
|
202
|
+
var createRegistries = () => ({
|
|
203
|
+
CATALOG: {},
|
|
204
|
+
LIBRARIES: {},
|
|
205
|
+
LIBRARY_ALIAS: {},
|
|
206
|
+
TEMPLATES: {},
|
|
207
|
+
STYLES: {},
|
|
208
|
+
STYLE_ALIAS: {},
|
|
209
|
+
PALETTE_STYLES: {},
|
|
210
|
+
ROOM_COLORS: [],
|
|
211
|
+
PASSES: [],
|
|
212
|
+
DECOR_PROFILES: {},
|
|
213
|
+
GROUND_OVERLAYS: [],
|
|
214
|
+
STATE_PLUGINS: { normal: { apply: () => [] } },
|
|
215
|
+
WALL_TOP_HEIGHTS: { none: 0 },
|
|
216
|
+
SEED_KEYS: { rooms: {}, items: {} },
|
|
217
|
+
stateDistribution: { weights: null, seedKey: "state" },
|
|
218
|
+
RESULT_EXTENSIONS: [],
|
|
219
|
+
FINDS_PROVIDERS: [],
|
|
220
|
+
VIEWER_LAYERS: [],
|
|
221
|
+
LOCALES: {},
|
|
222
|
+
ZONES: {},
|
|
223
|
+
PERMEABLE_GROUNDS: /* @__PURE__ */ new Set()
|
|
224
|
+
});
|
|
225
|
+
var defaultRegistries;
|
|
226
|
+
var activeRegistries;
|
|
227
|
+
var getDefaultRegistries = () => defaultRegistries ?? (defaultRegistries = createRegistries());
|
|
228
|
+
var registries = () => activeRegistries ?? getDefaultRegistries();
|
|
229
|
+
function withRegistries(scope, run) {
|
|
230
|
+
const previous = activeRegistries;
|
|
231
|
+
activeRegistries = scope;
|
|
232
|
+
try {
|
|
233
|
+
return run();
|
|
234
|
+
} finally {
|
|
235
|
+
activeRegistries = previous;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
102
238
|
function registerItem(def) {
|
|
103
239
|
const h = def.h;
|
|
104
240
|
const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
|
|
105
|
-
CATALOG[def.type] = item;
|
|
241
|
+
registries().CATALOG[def.type] = item;
|
|
106
242
|
return item;
|
|
107
243
|
}
|
|
108
244
|
function registerItems(list) {
|
|
109
245
|
for (const d of list) registerItem(d);
|
|
110
246
|
}
|
|
111
|
-
|
|
247
|
+
function registerLibrary(id, library) {
|
|
248
|
+
registries().LIBRARIES[id] = library;
|
|
249
|
+
}
|
|
250
|
+
function registerLibraryAlias(alias, id) {
|
|
251
|
+
registries().LIBRARY_ALIAS[alias] = id;
|
|
252
|
+
}
|
|
253
|
+
function registerTemplate(t) {
|
|
254
|
+
registries().TEMPLATES[t.id] = t;
|
|
255
|
+
return t;
|
|
256
|
+
}
|
|
257
|
+
function registerStyle(style) {
|
|
258
|
+
registries().STYLES[style.id] = style;
|
|
259
|
+
for (const a of style.aliases || []) registries().STYLE_ALIAS[a] = style.id;
|
|
260
|
+
}
|
|
261
|
+
function registerPaletteStyle(id, def) {
|
|
262
|
+
registries().PALETTE_STYLES[id] = def;
|
|
263
|
+
}
|
|
264
|
+
function registerRoomColors(list) {
|
|
265
|
+
for (const d of list) {
|
|
266
|
+
const i = registries().ROOM_COLORS.findIndex((x) => x.type === d.type);
|
|
267
|
+
if (i >= 0) registries().ROOM_COLORS.splice(i, 1);
|
|
268
|
+
registries().ROOM_COLORS.push(d);
|
|
269
|
+
}
|
|
270
|
+
registries().ROOM_COLORS.sort((a, b) => a.order - b.order);
|
|
271
|
+
}
|
|
272
|
+
function registerDecorProfile(styleId, profile) {
|
|
273
|
+
registries().DECOR_PROFILES[styleId] = profile;
|
|
274
|
+
}
|
|
275
|
+
function registerPass(name, run) {
|
|
276
|
+
const i = registries().PASSES.findIndex((p) => p.name === name);
|
|
277
|
+
if (i >= 0) registries().PASSES[i] = { name, run };
|
|
278
|
+
else registries().PASSES.push({ name, run });
|
|
279
|
+
}
|
|
280
|
+
function registerGroundOverlay(rule) {
|
|
281
|
+
registries().GROUND_OVERLAYS.push(rule);
|
|
282
|
+
}
|
|
283
|
+
function registerWallTop(id, height) {
|
|
284
|
+
registries().WALL_TOP_HEIGHTS[id] = height;
|
|
285
|
+
}
|
|
286
|
+
function registerSeedKeys(keys) {
|
|
287
|
+
Object.assign(registries().SEED_KEYS.rooms, keys.rooms);
|
|
288
|
+
Object.assign(registries().SEED_KEYS.items, keys.items);
|
|
289
|
+
}
|
|
290
|
+
function registerState(name, plugin) {
|
|
291
|
+
registries().STATE_PLUGINS[name] = plugin;
|
|
292
|
+
}
|
|
293
|
+
function setStateDistribution(weights, seedKey = "state") {
|
|
294
|
+
registries().stateDistribution.weights = weights;
|
|
295
|
+
registries().stateDistribution.seedKey = seedKey;
|
|
296
|
+
}
|
|
297
|
+
var listStates = () => Object.keys(registries().STATE_PLUGINS);
|
|
298
|
+
function resolveStyleId(id) {
|
|
299
|
+
const s = id ?? "";
|
|
300
|
+
return registries().STYLE_ALIAS[s] ?? s;
|
|
301
|
+
}
|
|
302
|
+
function styleOf(id) {
|
|
303
|
+
return registries().STYLES[id] || registries().STYLES.budget || Object.values(registries().STYLES)[0];
|
|
304
|
+
}
|
|
305
|
+
function registerResultExtension(name, run, options = {}) {
|
|
306
|
+
const entry = { name, run, after: options.after || [], seedKey: options.seedKey ?? name };
|
|
307
|
+
const i = registries().RESULT_EXTENSIONS.findIndex((x) => x.name === name);
|
|
308
|
+
if (i >= 0) registries().RESULT_EXTENSIONS[i] = entry;
|
|
309
|
+
else registries().RESULT_EXTENSIONS.push(entry);
|
|
310
|
+
}
|
|
311
|
+
function registerFindsProvider(provider) {
|
|
312
|
+
registries().FINDS_PROVIDERS.push(provider);
|
|
313
|
+
}
|
|
314
|
+
function registerViewerLayer(layer) {
|
|
315
|
+
const i = registries().VIEWER_LAYERS.findIndex((x) => x.id === layer.id);
|
|
316
|
+
if (i >= 0) registries().VIEWER_LAYERS[i] = layer;
|
|
317
|
+
else registries().VIEWER_LAYERS.push(layer);
|
|
318
|
+
}
|
|
319
|
+
function mulberry32(a) {
|
|
320
|
+
return function() {
|
|
321
|
+
a |= 0;
|
|
322
|
+
a = a + 1831565813 | 0;
|
|
323
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
324
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
325
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function hashString(input) {
|
|
329
|
+
const s = String(input);
|
|
330
|
+
let h = 1779033703 ^ s.length;
|
|
331
|
+
for (let i = 0; i < s.length; i++) {
|
|
332
|
+
h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
|
|
333
|
+
h = h << 13 | h >>> 19;
|
|
334
|
+
}
|
|
335
|
+
h = Math.imul(h ^ h >>> 16, 2246822507);
|
|
336
|
+
h = Math.imul(h ^ h >>> 13, 3266489909);
|
|
337
|
+
return (h ^ h >>> 16) >>> 0;
|
|
338
|
+
}
|
|
339
|
+
var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
|
|
340
|
+
function pickWeighted(rng, weights) {
|
|
341
|
+
const keys = Object.keys(weights);
|
|
342
|
+
let total = 0;
|
|
343
|
+
for (const k of keys) total += weights[k];
|
|
344
|
+
let x = rng() * total;
|
|
345
|
+
for (const k of keys) {
|
|
346
|
+
x -= weights[k];
|
|
347
|
+
if (x < 0) return k;
|
|
348
|
+
}
|
|
349
|
+
return keys[keys.length - 1];
|
|
350
|
+
}
|
|
351
|
+
function weightedOrder(candidates, rng) {
|
|
352
|
+
const arr = candidates.slice();
|
|
353
|
+
const out = [];
|
|
354
|
+
while (arr.length) {
|
|
355
|
+
let total = 0;
|
|
356
|
+
for (const c of arr) total += c.weight;
|
|
357
|
+
let x = rng() * total;
|
|
358
|
+
let i = 0;
|
|
359
|
+
for (; i < arr.length - 1; i++) {
|
|
360
|
+
x -= arr[i].weight;
|
|
361
|
+
if (x < 0) break;
|
|
362
|
+
}
|
|
363
|
+
out.push(arr.splice(i, 1)[0]);
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
function shuffle(arr, rng) {
|
|
368
|
+
const a = arr.slice();
|
|
369
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
370
|
+
const j = Math.floor(rng() * (i + 1));
|
|
371
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
372
|
+
}
|
|
373
|
+
return a;
|
|
374
|
+
}
|
|
375
|
+
function orderOf(extensions) {
|
|
376
|
+
const ordered = [];
|
|
377
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
378
|
+
const visit = (extension) => {
|
|
379
|
+
if (ordered.includes(extension)) return;
|
|
380
|
+
if (visiting.has(extension.name)) throw new Error("Result extensions depend on each other: " + extension.name);
|
|
381
|
+
visiting.add(extension.name);
|
|
382
|
+
for (const name of extension.after) {
|
|
383
|
+
const before = extensions.find((x) => x.name === name);
|
|
384
|
+
if (before) visit(before);
|
|
385
|
+
}
|
|
386
|
+
visiting.delete(extension.name);
|
|
387
|
+
ordered.push(extension);
|
|
388
|
+
};
|
|
389
|
+
extensions.forEach(visit);
|
|
390
|
+
return ordered;
|
|
391
|
+
}
|
|
392
|
+
function runResultExtensions(plan, result, seed, options) {
|
|
393
|
+
for (const extension of orderOf(registries().RESULT_EXTENSIONS)) {
|
|
394
|
+
result[extension.name] = extension.run({ plan, result, seed, rng: createRng(seed, extension.seedKey), options });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function findsFor(query) {
|
|
398
|
+
return answerOf(query) ?? [];
|
|
399
|
+
}
|
|
400
|
+
function findsAnswer(query) {
|
|
401
|
+
return answerOf(query);
|
|
402
|
+
}
|
|
403
|
+
function answerOf(query) {
|
|
404
|
+
for (const provider of registries().FINDS_PROVIDERS) {
|
|
405
|
+
const stacks = provider.findsFor(query);
|
|
406
|
+
if (stacks !== null) return stacks;
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
var WALL_THICKNESS = 0.15;
|
|
411
|
+
var MARGIN = WALL_THICKNESS / 2;
|
|
412
|
+
var EPS = 1e-6;
|
|
413
|
+
var TOL = 1e-3;
|
|
414
|
+
var DOOR_CLEARANCE = 0.9;
|
|
415
|
+
var PLAYER_RADIUS = 0.24;
|
|
416
|
+
var DOOR_EDGE_CLEARANCE = 0.12;
|
|
417
|
+
var WINDOW_EDGE_CLEARANCE = 0.3;
|
|
418
|
+
var OVERHEAD_TOP = 2.15;
|
|
419
|
+
var WINDOWLESS_ROOM_TYPES = /* @__PURE__ */ new Set([
|
|
420
|
+
"stockroom",
|
|
421
|
+
"kitchen",
|
|
422
|
+
"laundry",
|
|
423
|
+
"bathroom",
|
|
424
|
+
"void",
|
|
425
|
+
"elevator",
|
|
426
|
+
"stairs",
|
|
427
|
+
"pantry",
|
|
428
|
+
"closet",
|
|
429
|
+
"corridor",
|
|
430
|
+
"hall",
|
|
431
|
+
"balcony",
|
|
432
|
+
"terrace",
|
|
433
|
+
"garage",
|
|
434
|
+
"storage",
|
|
435
|
+
"porch",
|
|
436
|
+
"tent",
|
|
437
|
+
"outhouse"
|
|
438
|
+
]);
|
|
439
|
+
var CAR_CLEARANCE = { driver: 0.8, side: 0.6, end: 0.6 };
|
|
440
|
+
var r2 = (x) => Math.round(x * 100) / 100;
|
|
441
|
+
var clamp = (x, a, b) => Math.max(a, Math.min(b, x));
|
|
442
|
+
var overlaps = (a, b, e = EPS) => a[0] < b[2] - e && a[2] > b[0] + e && a[1] < b[3] - e && a[3] > b[1] + e;
|
|
443
|
+
var inner = (p) => [p[0] + MARGIN, p[1] + MARGIN, p[2] - MARGIN, p[3] - MARGIN];
|
|
444
|
+
var eq = (a, b) => Math.abs(a - b) < TOL;
|
|
445
|
+
var inflate = (r, e) => [r[0] - e, r[1] - e, r[2] + e, r[3] + e];
|
|
446
|
+
var areaOf = (r) => (r[2] - r[0]) * (r[3] - r[1]);
|
|
447
|
+
var topOf = (m) => (m.y || 0) + m.h;
|
|
448
|
+
var extent = (size, f) => Math.abs(f[1]) === 1 ? [size[0], size[1]] : [size[1], size[0]];
|
|
449
|
+
var cardinal = (dx, dz) => Math.abs(dx) > Math.abs(dz) ? [Math.sign(dx), 0] : [0, Math.sign(dz) || 1];
|
|
450
|
+
function clearRect(r, f, clearance) {
|
|
451
|
+
return f[1] === 1 ? [r[0], r[3], r[2], r[3] + clearance] : f[1] === -1 ? [r[0], r[1] - clearance, r[2], r[1]] : f[0] === 1 ? [r[2], r[1], r[2] + clearance, r[3]] : [r[0] - clearance, r[1], r[0], r[3]];
|
|
452
|
+
}
|
|
453
|
+
function insideUnion(r, inners) {
|
|
454
|
+
const e = 1e-4;
|
|
455
|
+
const points = [
|
|
456
|
+
[r[0] + e, r[1] + e],
|
|
457
|
+
[r[2] - e, r[1] + e],
|
|
458
|
+
[r[0] + e, r[3] - e],
|
|
459
|
+
[r[2] - e, r[3] - e],
|
|
460
|
+
[(r[0] + r[2]) / 2, (r[1] + r[3]) / 2]
|
|
461
|
+
];
|
|
462
|
+
return points.every((p) => inners.some((q) => p[0] >= q[0] - 1e-6 && p[0] <= q[2] + 1e-6 && p[1] >= q[1] - 1e-6 && p[1] <= q[3] + 1e-6));
|
|
463
|
+
}
|
|
464
|
+
function cutIntervals(intervals, a, b) {
|
|
465
|
+
const out = [];
|
|
466
|
+
for (const [x, y] of intervals) {
|
|
467
|
+
if (b <= x || a >= y) {
|
|
468
|
+
out.push([x, y]);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (a > x) out.push([x, a]);
|
|
472
|
+
if (b < y) out.push([b, y]);
|
|
473
|
+
}
|
|
474
|
+
return out;
|
|
475
|
+
}
|
|
476
|
+
function exteriorEdge(room, side, w, d, c) {
|
|
477
|
+
const cand = [];
|
|
478
|
+
for (const p of room.parts) {
|
|
479
|
+
if (side === "front" && eq(p[1], 0)) cand.push({ axis: "u", pos: 0, a0: p[0], a1: p[2] });
|
|
480
|
+
else if (side === "back" && eq(p[3], d)) cand.push({ axis: "u", pos: d, a0: p[0], a1: p[2] });
|
|
481
|
+
else if (side === "left" && eq(p[0], 0)) cand.push({ axis: "v", pos: 0, a0: p[1], a1: p[3] });
|
|
482
|
+
else if (side === "right" && eq(p[2], w)) cand.push({ axis: "v", pos: w, a0: p[1], a1: p[3] });
|
|
483
|
+
}
|
|
484
|
+
if (!cand.length) return null;
|
|
485
|
+
if (c != null) {
|
|
486
|
+
const k = cand.find((x) => c >= x.a0 - TOL && c <= x.a1 + TOL);
|
|
487
|
+
if (k) return k;
|
|
488
|
+
}
|
|
489
|
+
return cand.sort((x, y) => y.a1 - y.a0 - (x.a1 - x.a0))[0];
|
|
490
|
+
}
|
|
491
|
+
function sharedEdge(A, B, c) {
|
|
492
|
+
const cand = [];
|
|
493
|
+
for (const pa of A.parts)
|
|
494
|
+
for (const pb of B.parts) {
|
|
495
|
+
let o;
|
|
496
|
+
if (eq(pa[2], pb[0]) || eq(pa[0], pb[2])) {
|
|
497
|
+
o = [Math.max(pa[1], pb[1]), Math.min(pa[3], pb[3])];
|
|
498
|
+
if (o[1] - o[0] > TOL) cand.push({ axis: "v", pos: eq(pa[2], pb[0]) ? pa[2] : pa[0], a0: o[0], a1: o[1] });
|
|
499
|
+
}
|
|
500
|
+
if (eq(pa[3], pb[1]) || eq(pa[1], pb[3])) {
|
|
501
|
+
o = [Math.max(pa[0], pb[0]), Math.min(pa[2], pb[2])];
|
|
502
|
+
if (o[1] - o[0] > TOL) cand.push({ axis: "u", pos: eq(pa[3], pb[1]) ? pa[3] : pa[1], a0: o[0], a1: o[1] });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (!cand.length) return null;
|
|
506
|
+
if (c != null) {
|
|
507
|
+
const k = cand.find((x) => c >= x.a0 - TOL && c <= x.a1 + TOL);
|
|
508
|
+
if (k) return k;
|
|
509
|
+
}
|
|
510
|
+
return cand.sort((x, y) => y.a1 - y.a0 - (x.a1 - x.a0))[0];
|
|
511
|
+
}
|
|
512
|
+
function openingZones(u, v, axis, width, depth) {
|
|
513
|
+
const h = width / 2;
|
|
514
|
+
return axis === "u" ? [[u - h, v - depth, u + h, v], [u - h, v, u + h, v + depth]] : [[u - depth, v - h, u, v + h], [u, v - h, u + depth, v + h]];
|
|
515
|
+
}
|
|
516
|
+
var openingDepth = (width, leaf) => Math.max(0.9, leaf) + MARGIN;
|
|
517
|
+
function entryPoints(ab) {
|
|
518
|
+
return ab.axis === "u" ? [[ab.u, ab.v - 0.55], [ab.u, ab.v + 0.55]] : [[ab.u - 0.55, ab.v], [ab.u + 0.55, ab.v]];
|
|
519
|
+
}
|
|
520
|
+
function normalizeRoom(c, floorIndex) {
|
|
521
|
+
const parts = [c.rect].concat(c.parts || []).map((p) => p.map(r2));
|
|
522
|
+
return {
|
|
523
|
+
id: c.id,
|
|
524
|
+
type: c.type,
|
|
525
|
+
label: c.label,
|
|
526
|
+
floor: floorIndex,
|
|
527
|
+
parts,
|
|
528
|
+
library: c.library || null,
|
|
529
|
+
without: c.without || [],
|
|
530
|
+
overrides: c.overrides || {},
|
|
531
|
+
extras: c.extras || [],
|
|
532
|
+
slots: c.slots || null,
|
|
533
|
+
link: c.link || null,
|
|
534
|
+
outdoor: !!c.outdoor,
|
|
535
|
+
ceiling: c.ceiling || null,
|
|
536
|
+
seedKey: c.seedKey
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function usableParts(room, extra) {
|
|
540
|
+
const m = MARGIN + (extra || 0);
|
|
541
|
+
const out = [];
|
|
542
|
+
for (const p of room.parts) {
|
|
543
|
+
out.push([p[0] + m, p[1] + m, p[2] - m, p[3] - m]);
|
|
544
|
+
for (let k = 0; k < 4; k++) {
|
|
545
|
+
const spans = [];
|
|
546
|
+
for (const q of room.parts) {
|
|
547
|
+
if (q === p) continue;
|
|
548
|
+
if (k === 0 && eq(q[2], p[0])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
549
|
+
else if (k === 2 && eq(q[0], p[2])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
550
|
+
else if (k === 1 && eq(q[3], p[1])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
551
|
+
else if (k === 3 && eq(q[1], p[3])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
552
|
+
}
|
|
553
|
+
spans.sort((x, y) => x[0] - y[0]);
|
|
554
|
+
const merged = [];
|
|
555
|
+
for (const span of spans) {
|
|
556
|
+
const last = merged[merged.length - 1];
|
|
557
|
+
if (last && span[0] <= last[1] + TOL) last[1] = Math.max(last[1], span[1]);
|
|
558
|
+
else merged.push([span[0], span[1]]);
|
|
559
|
+
}
|
|
560
|
+
for (const [a, b] of merged) {
|
|
561
|
+
if (b - a <= 0.3) continue;
|
|
562
|
+
const x0 = a + m;
|
|
563
|
+
const x1 = b - m;
|
|
564
|
+
if (x1 <= x0) continue;
|
|
565
|
+
if (k === 0) out.push([p[0], x0, p[0] + m, x1]);
|
|
566
|
+
else if (k === 2) out.push([p[2] - m, x0, p[2], x1]);
|
|
567
|
+
else if (k === 1) out.push([x0, p[1], x1, p[1] + m]);
|
|
568
|
+
else out.push([x0, p[3] - m, x1, p[3]]);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return out;
|
|
573
|
+
}
|
|
574
|
+
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);
|
|
575
|
+
function buildWalls(rooms, doors, openPairs, floorIndex) {
|
|
576
|
+
const groups = {};
|
|
577
|
+
const add = (axis, pos, a, b, id, side) => {
|
|
578
|
+
const k = axis + "|" + pos.toFixed(2);
|
|
579
|
+
(groups[k] = groups[k] || { axis, pos, es: [] }).es.push({ a, b, id, side });
|
|
580
|
+
};
|
|
581
|
+
for (const c of rooms)
|
|
582
|
+
for (const p of c.parts) {
|
|
583
|
+
add("u", p[1], p[0], p[2], c.id, 1);
|
|
584
|
+
add("u", p[3], p[0], p[2], c.id, -1);
|
|
585
|
+
add("v", p[0], p[1], p[3], c.id, 1);
|
|
586
|
+
add("v", p[2], p[1], p[3], c.id, -1);
|
|
587
|
+
}
|
|
588
|
+
const par = (x, y) => openPairs.some((o) => o[0] === x && o[1] === y || o[0] === y && o[1] === x);
|
|
589
|
+
const typeOf = {};
|
|
590
|
+
for (const c of rooms) typeOf[c.id] = c.type;
|
|
591
|
+
const out = [];
|
|
592
|
+
for (const g of Object.values(groups)) {
|
|
593
|
+
const breaks = [...new Set(g.es.flatMap((e) => [r2(e.a), r2(e.b)]))].sort((x, y) => x - y);
|
|
594
|
+
const segs = [];
|
|
595
|
+
for (let i = 0; i < breaks.length - 1; i++) {
|
|
596
|
+
const p = breaks[i];
|
|
597
|
+
const q = breaks[i + 1];
|
|
598
|
+
const m = (p + q) / 2;
|
|
599
|
+
const after = g.es.find((e) => e.side === 1 && m > e.a && m < e.b);
|
|
600
|
+
const before = g.es.find((e) => e.side === -1 && m > e.a && m < e.b);
|
|
601
|
+
if (!after && !before) continue;
|
|
602
|
+
const A = before ? before.id : "outside";
|
|
603
|
+
const B = after ? after.id : "outside";
|
|
604
|
+
if (A === B || A !== "outside" && B !== "outside" && par(A, B)) continue;
|
|
605
|
+
const last = segs[segs.length - 1];
|
|
606
|
+
if (last && last.A === A && last.B === B && eq(last.q, p)) last.q = q;
|
|
607
|
+
else segs.push({ A, B, p, q });
|
|
608
|
+
}
|
|
609
|
+
for (const s of segs) {
|
|
610
|
+
let pieces = [[s.p, s.q]];
|
|
611
|
+
for (const pt of doors) {
|
|
612
|
+
if (pt.axis !== g.axis || !eq(pt.pos, g.pos)) continue;
|
|
613
|
+
const c0 = pt.at - pt.width / 2;
|
|
614
|
+
const c1 = pt.at + pt.width / 2;
|
|
615
|
+
const n = [];
|
|
616
|
+
for (const [x, y] of pieces) {
|
|
617
|
+
if (c1 <= x + TOL || c0 >= y - TOL) {
|
|
618
|
+
n.push([x, y]);
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (c0 > x + TOL) n.push([x, c0]);
|
|
622
|
+
if (c1 < y - TOL) n.push([c1, y]);
|
|
623
|
+
}
|
|
624
|
+
pieces = n;
|
|
625
|
+
}
|
|
626
|
+
for (const [x, y] of pieces) {
|
|
627
|
+
const isOpen = (t) => t === "balcony" || t === "terrace" || t === "porch";
|
|
628
|
+
const isVoid = typeOf[s.A] === "void" || typeOf[s.B] === "void";
|
|
629
|
+
if (isVoid && (s.A === "outside" || s.B === "outside")) continue;
|
|
630
|
+
const railing = isVoid || isOpen(typeOf[s.A]) && s.B === "outside" || isOpen(typeOf[s.B]) && s.A === "outside";
|
|
631
|
+
out.push({
|
|
632
|
+
floor: floorIndex,
|
|
633
|
+
axis: g.axis,
|
|
634
|
+
u0: g.axis === "u" ? x : g.pos,
|
|
635
|
+
v0: g.axis === "u" ? g.pos : x,
|
|
636
|
+
u1: g.axis === "u" ? y : g.pos,
|
|
637
|
+
v1: g.axis === "u" ? g.pos : y,
|
|
638
|
+
thickness: WALL_THICKNESS,
|
|
639
|
+
sides: [s.A, s.B],
|
|
640
|
+
exterior: s.A === "outside" || s.B === "outside",
|
|
641
|
+
type: railing ? "railing" : "wall",
|
|
642
|
+
h: railing ? 1.1 : 0
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return out;
|
|
648
|
+
}
|
|
649
|
+
var STEP = 0.05;
|
|
650
|
+
var MAX_CANDIDATES = 80;
|
|
651
|
+
function carCandidates(slot, item, car) {
|
|
652
|
+
const front = [slot.frontU, slot.frontV];
|
|
653
|
+
if (!slot._cell) throw new Error("Slot without a cell: " + slot.id);
|
|
654
|
+
const zone = slot._cell.R;
|
|
655
|
+
const alongV = Math.abs(front[1]) === 1;
|
|
656
|
+
const [extentU, extentV] = extent([item.w, item.d], front);
|
|
657
|
+
const across = alongV ? extentU : extentV;
|
|
658
|
+
const along = alongV ? extentV : extentU;
|
|
659
|
+
const a = alongV ? 0 : 1;
|
|
660
|
+
const b = alongV ? 1 : 0;
|
|
661
|
+
const driverSign = alongV ? front[1] : -front[0];
|
|
662
|
+
const frontSign = alongV ? front[1] : front[0];
|
|
663
|
+
const lowClear = driverSign < 0 ? car.driver : car.side;
|
|
664
|
+
const highClear = driverSign < 0 ? car.side : car.driver;
|
|
665
|
+
const lo = zone[a] + lowClear;
|
|
666
|
+
const hi = zone[a + 2] - highClear - across;
|
|
667
|
+
const alongLo = zone[b] + car.end;
|
|
668
|
+
const alongHi = zone[b + 2] - car.end - along;
|
|
669
|
+
if (hi < lo - 1e-6 || alongHi < alongLo - 1e-6) return [];
|
|
670
|
+
const preferred = (alongV ? slot.x : slot.z) - across / 2;
|
|
671
|
+
const alongStart = frontSign < 0 ? alongHi : alongLo;
|
|
672
|
+
const positions = [];
|
|
673
|
+
for (let p = lo; p <= hi + 1e-6; p += STEP) positions.push(Math.min(p, hi));
|
|
674
|
+
positions.sort((x, y) => Math.abs(x - preferred) - Math.abs(y - preferred));
|
|
675
|
+
return positions.slice(0, MAX_CANDIDATES).map((p) => {
|
|
676
|
+
const rect = alongV ? [p, alongStart, p + across, alongStart + along] : [alongStart, p, alongStart + along, p + across];
|
|
677
|
+
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];
|
|
678
|
+
return { rect, clear };
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
function zonesOfFloor(plan, floorIndex) {
|
|
682
|
+
const out = [];
|
|
683
|
+
if (floorIndex >= 0) {
|
|
684
|
+
for (const p of plan.doors) if (p.floor === floorIndex) out.push(...p.zones);
|
|
685
|
+
} else {
|
|
686
|
+
for (const p of plan.doors) if (p.floor === 0 && p.exterior) out.push(...p.zones);
|
|
687
|
+
for (const g of plan.gates) out.push(...g.zones);
|
|
688
|
+
for (const c of plan.paths) out.push(...c.rects);
|
|
689
|
+
out.push(...plan.reservations);
|
|
690
|
+
}
|
|
691
|
+
return out;
|
|
692
|
+
}
|
|
693
|
+
function openingsOfFloor(plan, floorIndex) {
|
|
694
|
+
if (floorIndex >= 0) return plan.doors.filter((p) => p.floor === floorIndex);
|
|
695
|
+
return plan.doors.filter((p) => p.floor === 0 && p.exterior).concat(plan.gates);
|
|
696
|
+
}
|
|
697
|
+
function windowZone(j, outside) {
|
|
698
|
+
const h = j.width / 2 + 0.1;
|
|
699
|
+
const depth = 0.5;
|
|
700
|
+
let n;
|
|
701
|
+
if (j.side === "front") n = [0, 1];
|
|
702
|
+
else if (j.side === "back") n = [0, -1];
|
|
703
|
+
else if (j.side === "left") n = [1, 0];
|
|
704
|
+
else n = [-1, 0];
|
|
705
|
+
if (outside) n = [-n[0], -n[1]];
|
|
706
|
+
const u = j.u;
|
|
707
|
+
const v = j.v;
|
|
708
|
+
if (j.axis === "u") return n[1] > 0 ? [u - h, v - MARGIN, u + h, v + depth] : [u - h, v - depth, u + h, v + MARGIN];
|
|
709
|
+
return n[0] > 0 ? [u - MARGIN, v - h, u + depth, v + h] : [u - depth, v - h, u + MARGIN, v + h];
|
|
710
|
+
}
|
|
711
|
+
function blocksWindow(it, win, overheadOff) {
|
|
712
|
+
const height = Math.max(it.h, it.windowHeight || 0, it.overhead && !overheadOff ? OVERHEAD_TOP : 0);
|
|
713
|
+
return !it.window || it.h > 1 + EPS || height > win.y0 - 0.05 + EPS;
|
|
714
|
+
}
|
|
715
|
+
function windowsOfRoom(plan, room) {
|
|
716
|
+
if (!room.outdoor) return plan.windows.filter((j) => j.room === room.id);
|
|
717
|
+
return plan.windows.filter(
|
|
718
|
+
(j) => j.floor === 0 && room.parts.some((p) => {
|
|
719
|
+
if (j.side === "front") return eq(p[3], j.pos) && j.u >= p[0] && j.u <= p[2];
|
|
720
|
+
if (j.side === "back") return eq(p[1], j.pos) && j.u >= p[0] && j.u <= p[2];
|
|
721
|
+
if (j.side === "left") return eq(p[2], j.pos) && j.v >= p[1] && j.v <= p[3];
|
|
722
|
+
return eq(p[0], j.pos) && j.v >= p[1] && j.v <= p[3];
|
|
723
|
+
})
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
function roomAt(plan, u, v, floorIndex) {
|
|
727
|
+
const list = floorIndex < 0 ? plan.zones : plan.rooms;
|
|
728
|
+
return list.find((c) => c.floor === floorIndex && inRect(c, u, v)) || null;
|
|
729
|
+
}
|
|
730
|
+
function isOpenSide(plan, room, pu, pv) {
|
|
731
|
+
const o = roomAt(plan, pu, pv, room.floor);
|
|
732
|
+
if (!o) return false;
|
|
733
|
+
if (o.id === room.id) return true;
|
|
734
|
+
if (room.outdoor) return true;
|
|
735
|
+
return (plan.openPairs[room.floor] || []).some((x) => x[0] === room.id && x[1] === o.id || x[1] === room.id && x[0] === o.id);
|
|
736
|
+
}
|
|
737
|
+
function wallBodies(plan, floor) {
|
|
738
|
+
const cache = plan.wallBodies = plan.wallBodies || {};
|
|
739
|
+
if (cache[floor]) return cache[floor];
|
|
740
|
+
const doors = plan.doors.filter((p) => p.floor === floor);
|
|
741
|
+
return cache[floor] = plan.walls.filter((p) => p.floor === floor && (p.type === "wall" || p.type === "railing")).map((p) => {
|
|
742
|
+
const a0 = p.axis === "u" ? p.u0 : p.v0;
|
|
743
|
+
const a1 = p.axis === "u" ? p.u1 : p.v1;
|
|
744
|
+
const pos = p.axis === "u" ? p.v0 : p.u0;
|
|
745
|
+
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)));
|
|
746
|
+
const e0 = atDoor(a0) ? 0 : MARGIN;
|
|
747
|
+
const e1 = atDoor(a1) ? 0 : MARGIN;
|
|
748
|
+
return p.axis === "u" ? [a0 - e0, pos - MARGIN, a1 + e1, pos + MARGIN] : [pos - MARGIN, a0 - e0, pos + MARGIN, a1 + e1];
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
function createGrid(plan, room, ignored, radius = plan.playerRadius) {
|
|
752
|
+
const gs = room.outdoor ? 0.3 : 0.1;
|
|
753
|
+
let u0 = 1e9;
|
|
754
|
+
let v0 = 1e9;
|
|
755
|
+
let u1 = -1e9;
|
|
756
|
+
let v1 = -1e9;
|
|
757
|
+
for (const p of room.parts) {
|
|
758
|
+
u0 = Math.min(u0, p[0]);
|
|
759
|
+
v0 = Math.min(v0, p[1]);
|
|
760
|
+
u1 = Math.max(u1, p[2]);
|
|
761
|
+
v1 = Math.max(v1, p[3]);
|
|
762
|
+
}
|
|
763
|
+
const nx = Math.ceil((u1 - u0) / gs);
|
|
764
|
+
const nz = Math.ceil((v1 - v0) / gs);
|
|
765
|
+
const insideCells = new Uint8Array(nx * nz);
|
|
766
|
+
const insets = room.outdoor ? usableParts(room, 0.08) : room.parts;
|
|
767
|
+
const bodies = room.outdoor ? null : wallBodies(plan, room.floor).filter((r) => r[0] - radius <= u1 && r[2] + radius >= u0 && r[1] - radius <= v1 && r[3] + radius >= v0);
|
|
768
|
+
const tooClose = (u, v) => bodies.some((r) => {
|
|
769
|
+
const dx = Math.max(r[0] - u, 0, u - r[2]);
|
|
770
|
+
const dz = Math.max(r[1] - v, 0, v - r[3]);
|
|
771
|
+
return dx * dx + dz * dz < radius * radius - 1e-9;
|
|
772
|
+
});
|
|
773
|
+
for (let j = 0; j < nz; j++)
|
|
774
|
+
for (let i = 0; i < nx; i++) {
|
|
775
|
+
const u = u0 + (i + 0.5) * gs;
|
|
776
|
+
const v = v0 + (j + 0.5) * gs;
|
|
777
|
+
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;
|
|
778
|
+
}
|
|
779
|
+
const entries = [];
|
|
780
|
+
for (const ab of openingsOfFloor(plan, room.floor))
|
|
781
|
+
if (!(ignored && ignored.has(ab.id)))
|
|
782
|
+
for (const pt of entryPoints(ab)) {
|
|
783
|
+
if (!inRect(room, pt[0], pt[1])) continue;
|
|
784
|
+
const i = Math.floor((pt[0] - u0) / gs);
|
|
785
|
+
const j = Math.floor((pt[1] - v0) / gs);
|
|
786
|
+
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
787
|
+
}
|
|
788
|
+
if (!room.outdoor)
|
|
789
|
+
for (const pair of plan.openPairs[room.floor] || []) {
|
|
790
|
+
const otherId = pair[0] === room.id ? pair[1] : pair[1] === room.id ? pair[0] : null;
|
|
791
|
+
const other = otherId && plan.rooms.find((c) => c.id === otherId && c.floor === room.floor);
|
|
792
|
+
if (!other) continue;
|
|
793
|
+
const edge = sharedEdge(room, other, null);
|
|
794
|
+
if (!edge || edge.a1 - edge.a0 > 2) continue;
|
|
795
|
+
const mid = (edge.a0 + edge.a1) / 2;
|
|
796
|
+
for (const offset of [-0.4, 0.4]) {
|
|
797
|
+
const pt = edge.axis === "u" ? [mid, edge.pos + offset] : [edge.pos + offset, mid];
|
|
798
|
+
if (!inRect(room, pt[0], pt[1])) continue;
|
|
799
|
+
const i = Math.floor((pt[0] - u0) / gs);
|
|
800
|
+
const j = Math.floor((pt[1] - v0) / gs);
|
|
801
|
+
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const R = inner(room.parts[0]);
|
|
805
|
+
return { gs, u0, v0, nx, nz, inside: insideCells, entries, center: [(R[0] + R[2]) / 2, (R[1] + R[3]) / 2] };
|
|
806
|
+
}
|
|
807
|
+
function reachable(g, solids, inflation) {
|
|
808
|
+
const { gs, u0, v0, nx, nz, inside } = g;
|
|
809
|
+
const blocked = new Uint8Array(nx * nz);
|
|
810
|
+
for (const r of solids) {
|
|
811
|
+
const i0 = Math.max(0, Math.floor((r[0] - inflation - u0) / gs));
|
|
812
|
+
const i1 = Math.min(nx - 1, Math.floor((r[2] + inflation - u0) / gs));
|
|
813
|
+
const j0 = Math.max(0, Math.floor((r[1] - inflation - v0) / gs));
|
|
814
|
+
const j1 = Math.min(nz - 1, Math.floor((r[3] + inflation - v0) / gs));
|
|
815
|
+
for (let j = j0; j <= j1; j++)
|
|
816
|
+
for (let i = i0; i <= i1; i++) {
|
|
817
|
+
const u = u0 + (i + 0.5) * gs;
|
|
818
|
+
const v = v0 + (j + 0.5) * gs;
|
|
819
|
+
if (u > r[0] - inflation && u < r[2] + inflation && v > r[1] - inflation && v < r[3] + inflation) blocked[j * nx + i] = 1;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
let best = -1;
|
|
823
|
+
let bestDist = 1e9;
|
|
824
|
+
for (let j = 0; j < nz; j++)
|
|
825
|
+
for (let i = 0; i < nx; i++) {
|
|
826
|
+
const k = j * nx + i;
|
|
827
|
+
if (!inside[k] || blocked[k]) continue;
|
|
828
|
+
const dist = Math.hypot(u0 + (i + 0.5) * gs - g.center[0], v0 + (j + 0.5) * gs - g.center[1]);
|
|
829
|
+
if (dist < bestDist) {
|
|
830
|
+
bestDist = dist;
|
|
831
|
+
best = k;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (best < 0) return false;
|
|
835
|
+
if (!g.entries.length) return true;
|
|
836
|
+
for (const [i, j] of g.entries) if (blocked[j * nx + i]) return false;
|
|
837
|
+
const visited = new Uint8Array(nx * nz);
|
|
838
|
+
const queue = new Int32Array(nx * nz);
|
|
839
|
+
let h = 0;
|
|
840
|
+
let t = 0;
|
|
841
|
+
const s = g.entries[0][1] * nx + g.entries[0][0];
|
|
842
|
+
visited[s] = 1;
|
|
843
|
+
queue[t++] = s;
|
|
844
|
+
while (h < t) {
|
|
845
|
+
const k = queue[h++];
|
|
846
|
+
const i = k % nx;
|
|
847
|
+
const j = (k - i) / nx;
|
|
848
|
+
for (const [di, dj] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
849
|
+
const a = i + di;
|
|
850
|
+
const b = j + dj;
|
|
851
|
+
if (a < 0 || b < 0 || a >= nx || b >= nz) continue;
|
|
852
|
+
const q = b * nx + a;
|
|
853
|
+
if (visited[q] || !inside[q] || blocked[q]) continue;
|
|
854
|
+
visited[q] = 1;
|
|
855
|
+
queue[t++] = q;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (!visited[best]) return false;
|
|
859
|
+
for (const [i, j] of g.entries) if (!visited[j * nx + i]) return false;
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
function candidatesOf(cats) {
|
|
863
|
+
const out = [];
|
|
864
|
+
for (const [cat, weight] of Object.entries(cats)) {
|
|
865
|
+
const exact = cat[0] === "=";
|
|
866
|
+
const its = Object.values(registries().CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
|
|
867
|
+
for (const it of its) out.push({ it, weight: weight / its.length });
|
|
868
|
+
}
|
|
869
|
+
return out;
|
|
870
|
+
}
|
|
871
|
+
function itemRect(slot, it, shift) {
|
|
872
|
+
const c = slot._cell;
|
|
873
|
+
if (!c) throw new Error("Slot without a cell: " + slot.id);
|
|
874
|
+
const f = c.f;
|
|
875
|
+
const [extentU, extentV] = extent([it.w, it.d], f);
|
|
876
|
+
const R = c.R;
|
|
877
|
+
let u0 = (c.rect ? (c.rect[0] + c.rect[2]) / 2 : slot.x) - extentU / 2;
|
|
878
|
+
let v0 = (c.rect ? (c.rect[1] + c.rect[3]) / 2 : slot.z) - extentV / 2;
|
|
879
|
+
if (shift) {
|
|
880
|
+
if (slot.side === "left" || slot.side === "right") v0 += shift;
|
|
881
|
+
else u0 += shift;
|
|
882
|
+
}
|
|
883
|
+
for (const s of c.enclosed) {
|
|
884
|
+
if (s === "left") u0 = R[0];
|
|
885
|
+
else if (s === "right") u0 = R[2] - extentU;
|
|
886
|
+
else if (s === "front") v0 = R[1];
|
|
887
|
+
else if (s === "back") v0 = R[3] - extentV;
|
|
888
|
+
}
|
|
889
|
+
return [u0, v0, u0 + extentU, v0 + extentV];
|
|
890
|
+
}
|
|
891
|
+
var RELAXED_RADIUS = 0.12;
|
|
892
|
+
var MESH_FRONT = { "+z": [0, 1], "-z": [0, -1], "+x": [1, 0], "-x": [-1, 0] };
|
|
893
|
+
function rotationFor(f, front) {
|
|
894
|
+
const meshFront = MESH_FRONT[front] || [0, 1];
|
|
895
|
+
let ry = Math.atan2(f[0], f[1]) - Math.atan2(meshFront[0], meshFront[1]);
|
|
896
|
+
if (ry > Math.PI + EPS) ry -= 2 * Math.PI;
|
|
897
|
+
if (ry < -Math.PI - EPS) ry += 2 * Math.PI;
|
|
898
|
+
return r2(ry);
|
|
899
|
+
}
|
|
900
|
+
function fillRoom(plan, room, slots2, rng) {
|
|
901
|
+
const items = [];
|
|
902
|
+
const missing = [];
|
|
903
|
+
const state = {};
|
|
904
|
+
const solids = [];
|
|
905
|
+
const reserves = [];
|
|
906
|
+
const rims = [];
|
|
907
|
+
const inners = usableParts(room, 0);
|
|
908
|
+
const grid = createGrid(plan, room);
|
|
909
|
+
const relaxedGrid = room.outdoor ? grid : createGrid(plan, room, null, RELAXED_RADIUS);
|
|
910
|
+
const zones = zonesOfFloor(plan, room.floor);
|
|
911
|
+
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
912
|
+
const inflation = room.outdoor ? 0.15 : plan.playerRadius;
|
|
913
|
+
const units = [];
|
|
914
|
+
const seen = {};
|
|
915
|
+
for (const s of slots2) {
|
|
916
|
+
if (s.group) {
|
|
917
|
+
if (seen[s.group]) {
|
|
918
|
+
seen[s.group].members.push(s);
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
const u = { group: s.group, members: [s] };
|
|
922
|
+
seen[s.group] = u;
|
|
923
|
+
units.push(u);
|
|
924
|
+
} else units.push({ group: null, members: [s] });
|
|
925
|
+
}
|
|
926
|
+
for (const u of units) u.required = u.members.some((m) => m.required);
|
|
927
|
+
const queue = units.filter((u) => u.required).concat(units.filter((u) => !u.required));
|
|
928
|
+
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)));
|
|
929
|
+
const done = /* @__PURE__ */ new Set();
|
|
930
|
+
const ready = (u) => dependencies(u).every((id) => done.has(id));
|
|
931
|
+
const make = (s, it, r, parent, overheadOff) => {
|
|
932
|
+
const f = [s.frontU, s.frontV];
|
|
933
|
+
const id = room.id + "." + s.id;
|
|
934
|
+
const m = {
|
|
935
|
+
id,
|
|
936
|
+
type: it.type,
|
|
937
|
+
category: it.category,
|
|
938
|
+
x: r2((r[0] + r[2]) / 2),
|
|
939
|
+
z: r2((r[1] + r[3]) / 2),
|
|
940
|
+
ry: rotationFor(f, it.front),
|
|
941
|
+
w: it.w,
|
|
942
|
+
d: it.d,
|
|
943
|
+
h: it.h,
|
|
944
|
+
y: parent ? r2((parent.y || 0) + parent.h) : 0,
|
|
945
|
+
frontU: f[0],
|
|
946
|
+
frontV: f[1],
|
|
947
|
+
room: room.id,
|
|
948
|
+
floor: room.floor,
|
|
949
|
+
outdoor: !!room.outdoor,
|
|
950
|
+
slot: s.id,
|
|
951
|
+
on: parent ? s.on : null,
|
|
952
|
+
mode: parent ? s.anchor === "inside" ? "inside" : "on" : null,
|
|
953
|
+
clearance: s.clearance,
|
|
954
|
+
flat: it.flat,
|
|
955
|
+
low: it.low,
|
|
956
|
+
aabb: r.map(r2),
|
|
957
|
+
_r: r,
|
|
958
|
+
_seedId: registries().SEED_KEYS.items[id] ?? id
|
|
959
|
+
};
|
|
960
|
+
if (it.overhead) m.overhead = !overheadOff;
|
|
961
|
+
if (it.category === "car") {
|
|
962
|
+
m.driver = [f[1], -f[0]];
|
|
963
|
+
m.carClearance = plan.carClearance;
|
|
964
|
+
}
|
|
965
|
+
if (parent && s.anchor === "inside") m.y = r2(parent.y || 0);
|
|
966
|
+
if (room.link) m.link = { from: room.link[0], to: room.link[1] };
|
|
967
|
+
state[s.id] = m;
|
|
968
|
+
items.push(m);
|
|
969
|
+
return m;
|
|
970
|
+
};
|
|
971
|
+
const tryFit = (s, relax, overheadOff) => {
|
|
972
|
+
const cands = weightedOrder(candidatesOf(s.cats), rng);
|
|
973
|
+
if (s.anchor === "on" || s.anchor === "inside") {
|
|
974
|
+
const parent = state[s.on];
|
|
975
|
+
if (!parent) return null;
|
|
976
|
+
for (const { it } of cands) {
|
|
977
|
+
if (it.w > parent.w + EPS || it.d > parent.d + EPS) continue;
|
|
978
|
+
const f = [parent.frontU ?? 0, parent.frontV ?? 1];
|
|
979
|
+
const r = parent.aabb;
|
|
980
|
+
const [extentU, extentV] = extent([it.w, it.d], f);
|
|
981
|
+
const cu = (r[0] + r[2]) / 2 - f[0] * (parent.d / 2 - it.d / 2);
|
|
982
|
+
const cv = (r[1] + r[3]) / 2 - f[1] * (parent.d / 2 - it.d / 2);
|
|
983
|
+
return make(s, it, [cu - extentU / 2, cv - extentV / 2, cu + extentU / 2, cv + extentV / 2], parent);
|
|
984
|
+
}
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
for (const { it } of cands) {
|
|
988
|
+
if (it.w > s.w + EPS || it.d > s.d + EPS) continue;
|
|
989
|
+
const f = [s.frontU, s.frontV];
|
|
990
|
+
const slide = s.anchor === "wall" ? s.slide == null ? 2 : s.slide : 0;
|
|
991
|
+
const shifts = [0];
|
|
992
|
+
for (let k = 1; k * 0.1 <= slide + 1e-9; k++) shifts.push(k * 0.1, -k * 0.1);
|
|
993
|
+
let found = null;
|
|
994
|
+
const car = it.category === "car" && !relax;
|
|
995
|
+
const tries = car ? carCandidates(s, it, plan.carClearance) : shifts.map((shift) => ({ rect: itemRect(s, it, shift) }));
|
|
996
|
+
for (const attempt of tries) {
|
|
997
|
+
const r3 = attempt.rect;
|
|
998
|
+
if (!insideUnion(r3, inners)) continue;
|
|
999
|
+
if (car && (!insideUnion(attempt.clear, inners) || solids.some((o) => overlaps(attempt.clear, o)))) continue;
|
|
1000
|
+
if (it.flat) {
|
|
1001
|
+
found = { rect: r3 };
|
|
1002
|
+
break;
|
|
1003
|
+
}
|
|
1004
|
+
if (zones.some((z) => overlaps(r3, z))) continue;
|
|
1005
|
+
if (windowZones.some(({ z, j }) => blocksWindow(it, j, overheadOff) && overlaps(r3, z))) continue;
|
|
1006
|
+
if (solids.some((o) => overlaps(r3, o))) continue;
|
|
1007
|
+
if (!it.deck && rims.some((a) => overlaps(r3, a))) continue;
|
|
1008
|
+
let cl2 = car ? attempt.clear : null;
|
|
1009
|
+
if (!car && !relax && s.clearance > 0 && !it.low) {
|
|
1010
|
+
cl2 = clearRect(r3, f, s.clearance);
|
|
1011
|
+
if (!insideUnion(cl2, inners)) continue;
|
|
1012
|
+
if (items.some((m2) => !m2.low && !m2.flat && !m2.on && overlaps(cl2, m2._r ?? m2.aabb))) continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (!it.low && reserves.some((q) => overlaps(r3, q))) continue;
|
|
1015
|
+
let rimRect2 = null;
|
|
1016
|
+
if (it.rim) {
|
|
1017
|
+
rimRect2 = [r3[0] - it.rim, r3[1] - it.rim, r3[2] + it.rim, r3[3] + it.rim];
|
|
1018
|
+
if (!insideUnion(rimRect2, inners) || solids.some((o) => overlaps(rimRect2, o)) || zones.some((z) => overlaps(rimRect2, z))) continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (!(relax ? reachable(relaxedGrid, solids.concat([r3]), RELAXED_RADIUS) : reachable(grid, solids.concat([r3]), inflation))) continue;
|
|
1021
|
+
found = { rect: r3, cl: cl2, rimRect: rimRect2 };
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
if (!found) continue;
|
|
1025
|
+
const { rect: r, cl, rimRect } = found;
|
|
1026
|
+
if (it.flat) return make(s, it, r, null);
|
|
1027
|
+
const m = make(s, it, r, null, overheadOff);
|
|
1028
|
+
if (cl) reserves.push(cl);
|
|
1029
|
+
if (rimRect) rims.push(rimRect);
|
|
1030
|
+
solids.push(r);
|
|
1031
|
+
return m;
|
|
1032
|
+
}
|
|
1033
|
+
return null;
|
|
1034
|
+
};
|
|
1035
|
+
const process2 = (u) => {
|
|
1036
|
+
const pr = u.members[0].chance;
|
|
1037
|
+
const x = rng();
|
|
1038
|
+
const arr = u.members.slice();
|
|
1039
|
+
const members = [];
|
|
1040
|
+
while (arr.length) members.push(arr.splice(arr.length > 1 ? Math.floor(rng() * arr.length) : 0, 1)[0]);
|
|
1041
|
+
const applicable = members.filter((m) => m.valid && (!m.requires || state[m.requires]) && (!m.on || state[m.on]));
|
|
1042
|
+
if (!u.required && x >= pr) return;
|
|
1043
|
+
let ok = false;
|
|
1044
|
+
for (const m of applicable) {
|
|
1045
|
+
if (tryFit(m, false)) {
|
|
1046
|
+
ok = true;
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (!ok)
|
|
1051
|
+
for (const m of applicable) {
|
|
1052
|
+
if (tryFit(m, false, true)) {
|
|
1053
|
+
ok = true;
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
if (!ok && u.required)
|
|
1058
|
+
for (const m of applicable) {
|
|
1059
|
+
if (tryFit(m, true, true)) {
|
|
1060
|
+
ok = true;
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (!ok && u.required && (applicable.length || !u.members.some((m) => m.requires))) missing.push({ room: room.id, slot: u.group || u.members[0].id });
|
|
1065
|
+
};
|
|
1066
|
+
const pending = queue.slice();
|
|
1067
|
+
while (pending.length) {
|
|
1068
|
+
let i = pending.findIndex(ready);
|
|
1069
|
+
if (i < 0) i = 0;
|
|
1070
|
+
const u = pending.splice(i, 1)[0];
|
|
1071
|
+
process2(u);
|
|
1072
|
+
for (const m of u.members) done.add(m.id);
|
|
1073
|
+
}
|
|
1074
|
+
return { items, missing };
|
|
1075
|
+
}
|
|
1076
|
+
var STREAM = {
|
|
1077
|
+
facade: "fachada",
|
|
1078
|
+
lot: "lote",
|
|
1079
|
+
decor: "decor",
|
|
1080
|
+
roomPrefix: "c:",
|
|
1081
|
+
palettePrefix: "paleta:"
|
|
1082
|
+
};
|
|
1083
|
+
var placementKit = {
|
|
1084
|
+
MARGIN,
|
|
1085
|
+
EPS,
|
|
1086
|
+
r2,
|
|
1087
|
+
clamp,
|
|
1088
|
+
overlaps,
|
|
1089
|
+
inner,
|
|
1090
|
+
inflate,
|
|
1091
|
+
areaOf,
|
|
1092
|
+
topOf,
|
|
1093
|
+
extent,
|
|
1094
|
+
insideUnion,
|
|
1095
|
+
usableParts,
|
|
1096
|
+
zonesOfFloor,
|
|
1097
|
+
windowsOfRoom,
|
|
1098
|
+
windowZone,
|
|
1099
|
+
roomAt,
|
|
1100
|
+
reachable,
|
|
1101
|
+
createGrid,
|
|
1102
|
+
clearRect,
|
|
1103
|
+
pickWeighted,
|
|
1104
|
+
createRng,
|
|
1105
|
+
roomSeedKey: (room) => room.seedKey ?? registries().SEED_KEYS.rooms[room.id] ?? room.id,
|
|
1106
|
+
shuffle,
|
|
1107
|
+
facingRotation: (f, type) => rotationFor(f, registries().CATALOG[type].front),
|
|
1108
|
+
cutIntervals,
|
|
1109
|
+
blocksWindow
|
|
1110
|
+
};
|
|
1111
|
+
function createPlacementContext(plan, items) {
|
|
1112
|
+
const added = [];
|
|
1113
|
+
const counters = {};
|
|
1114
|
+
const make = (room, type, r, f, o) => {
|
|
1115
|
+
const it = registries().CATALOG[type];
|
|
1116
|
+
const n = counters[room.id + type] = (counters[room.id + type] || 0) + 1;
|
|
1117
|
+
return Object.assign(
|
|
1118
|
+
{
|
|
1119
|
+
id: room.id + ".dec_" + type + n,
|
|
1120
|
+
type,
|
|
1121
|
+
category: it.category,
|
|
1122
|
+
x: r2((r[0] + r[2]) / 2),
|
|
1123
|
+
z: r2((r[1] + r[3]) / 2),
|
|
1124
|
+
ry: r2(Math.atan2(f[0], f[1])),
|
|
1125
|
+
w: it.w,
|
|
1126
|
+
d: it.d,
|
|
1127
|
+
h: it.h,
|
|
1128
|
+
y: 0,
|
|
1129
|
+
frontU: f[0],
|
|
1130
|
+
frontV: f[1],
|
|
1131
|
+
room: room.id,
|
|
1132
|
+
floor: room.floor,
|
|
1133
|
+
outdoor: !!room.outdoor,
|
|
1134
|
+
slot: "dec_" + type + n,
|
|
1135
|
+
on: null,
|
|
1136
|
+
mode: null,
|
|
1137
|
+
clearance: 0,
|
|
1138
|
+
flat: !!it.flat,
|
|
1139
|
+
low: it.low,
|
|
1140
|
+
aabb: r.map(r2),
|
|
1141
|
+
_r: r,
|
|
1142
|
+
decor: true,
|
|
1143
|
+
mount: it.mount
|
|
1144
|
+
},
|
|
1145
|
+
o || {}
|
|
1146
|
+
);
|
|
1147
|
+
};
|
|
1148
|
+
const add = (m) => {
|
|
1149
|
+
added.push(m);
|
|
1150
|
+
items.push(m);
|
|
1151
|
+
return m;
|
|
1152
|
+
};
|
|
1153
|
+
const itemsOfRoom = (c) => items.filter((m) => m.room === c.id && m.floor === c.floor);
|
|
1154
|
+
const extraSolids = {};
|
|
1155
|
+
const solidsOfRoom = (c) => itemsOfRoom(c).filter((m) => !m.flat && !m.on && (!m.decor || m.mount === "floor")).map((m) => m._r).concat(extraSolids[c.id] || []);
|
|
1156
|
+
const gridCache = {};
|
|
1157
|
+
let ignored = null;
|
|
1158
|
+
const gridOf = (c) => gridCache[c.id] || (gridCache[c.id] = createGrid(plan, c, ignored));
|
|
1159
|
+
function placeOnFloor(room, type, rng2, optsIn) {
|
|
1160
|
+
const opts = Object.assign({}, optsIn || {});
|
|
1161
|
+
const it0 = registries().CATALOG[type];
|
|
1162
|
+
if (it0.flat) opts.flat = true;
|
|
1163
|
+
const it = registries().CATALOG[type];
|
|
1164
|
+
const inners = usableParts(room, 0);
|
|
1165
|
+
const solidList = solidsOfRoom(room);
|
|
1166
|
+
const zones = zonesOfFloor(plan, room.floor);
|
|
1167
|
+
const occupied = itemsOfRoom(room);
|
|
1168
|
+
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
1169
|
+
const landings = it0.flat ? [] : items.filter((m) => m.type === "stairs" && m.floor === room.floor && m.aabb).map((m) => {
|
|
1170
|
+
const r = m.aabb;
|
|
1171
|
+
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]];
|
|
1172
|
+
});
|
|
1173
|
+
const cand = opts.cands ? opts.cands.slice() : [];
|
|
1174
|
+
if (!opts.cands)
|
|
1175
|
+
for (const p of inners) {
|
|
1176
|
+
const extentU = opts.dim ? opts.dim[0] : it.w;
|
|
1177
|
+
const extentV = opts.dim ? opts.dim[1] : it.d;
|
|
1178
|
+
for (const [x, z] of [[p[0], p[1]], [p[2] - extentU, p[1]], [p[0], p[3] - extentV], [p[2] - extentU, p[3] - extentV]]) cand.push([x, z, x + extentU, z + extentV]);
|
|
1179
|
+
for (let k = 0; k < 6; k++) {
|
|
1180
|
+
const t = rng2();
|
|
1181
|
+
const side = Math.floor(rng2() * 4);
|
|
1182
|
+
const x = side < 2 ? p[0] + t * Math.max(0, p[2] - p[0] - extentU) : side === 2 ? p[0] : p[2] - extentU;
|
|
1183
|
+
const z = side >= 2 ? p[1] + t * Math.max(0, p[3] - p[1] - extentV) : side === 0 ? p[1] : p[3] - extentV;
|
|
1184
|
+
cand.push([x, z, x + extentU, z + extentV]);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
for (const r of opts.ordered ? cand : shuffle(cand, rng2)) {
|
|
1188
|
+
if (!insideUnion(r, inners)) continue;
|
|
1189
|
+
if (zones.some((z) => overlaps(r, z)) || landings.some((z) => overlaps(r, z))) continue;
|
|
1190
|
+
if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r, z))) continue;
|
|
1191
|
+
if (solidList.some((o) => overlaps(r, o))) continue;
|
|
1192
|
+
if (opts.flat) {
|
|
1193
|
+
if (!(opts.without && opts.without(r))) return r;
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (occupied.some((m) => !m.low && !m.flat && !m.on && !m.decor && (m.clearance ?? 0) > 0 && overlaps(r, clearRect(m._r, [m.frontU ?? 0, m.frontV ?? 1], m.clearance ?? 0)))) continue;
|
|
1197
|
+
if (opts.without && opts.without(r)) continue;
|
|
1198
|
+
if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), room.outdoor ? 0.16 : plan.playerRadius)) continue;
|
|
1199
|
+
return r;
|
|
1200
|
+
}
|
|
1201
|
+
return null;
|
|
1202
|
+
}
|
|
1203
|
+
const roomById = (id) => plan.rooms.find((c) => c.id === id) || plan.zones.find((c) => c.id === id);
|
|
1204
|
+
return {
|
|
1205
|
+
make,
|
|
1206
|
+
add,
|
|
1207
|
+
placeOnFloor,
|
|
1208
|
+
itemsOfRoom,
|
|
1209
|
+
solidsOfRoom,
|
|
1210
|
+
gridOf,
|
|
1211
|
+
extraSolids,
|
|
1212
|
+
roomById,
|
|
1213
|
+
setIgnored: (s) => {
|
|
1214
|
+
ignored = s;
|
|
1215
|
+
for (const k of Object.keys(gridCache)) delete gridCache[k];
|
|
1216
|
+
},
|
|
1217
|
+
added
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function groundCover(plan, items, ctx) {
|
|
1221
|
+
if (!plan.lot) return;
|
|
1222
|
+
const polygonOf = (r) => [[r[0], r[1]], [r[2], r[1]], [r[2], r[3]], [r[0], r[3]]].map((p) => [r2(p[0]), r2(p[1])]);
|
|
1223
|
+
const groundPiece = (z, type, r, layer, i) => {
|
|
1224
|
+
const m = ctx.make(z, type, r, [0, 1], {
|
|
1225
|
+
id: z.id + ".ground" + (layer ? "_overlay" : "") + i,
|
|
1226
|
+
slot: "ground" + (layer ? "_overlay" : "") + i,
|
|
1227
|
+
w: r2(r[2] - r[0]),
|
|
1228
|
+
d: r2(r[3] - r[1]),
|
|
1229
|
+
h: 0,
|
|
1230
|
+
polygon: polygonOf(r),
|
|
1231
|
+
layer,
|
|
1232
|
+
zone: z.id,
|
|
1233
|
+
outdoor: true,
|
|
1234
|
+
floor: -1
|
|
1235
|
+
});
|
|
1236
|
+
return ctx.add(m);
|
|
1237
|
+
};
|
|
1238
|
+
for (const z of plan.zones) {
|
|
1239
|
+
z.parts.forEach((p, i) => groundPiece(z, z.ground, p, 0, i));
|
|
1240
|
+
let k = 0;
|
|
1241
|
+
const clip = (r) => z.parts.map((p) => [Math.max(r[0], p[0]), Math.max(r[1], p[1]), Math.min(r[2], p[2]), Math.min(r[3], p[3])]).filter((q) => q[2] - q[0] > 0.3 && q[3] - q[1] > 0.3).sort((a, b) => areaOf(b) - areaOf(a))[0];
|
|
1242
|
+
for (const rule of registries().GROUND_OVERLAYS) {
|
|
1243
|
+
const triggers = items.filter((m) => m.room === z.id && m.category === rule.category);
|
|
1244
|
+
if (rule.merge) {
|
|
1245
|
+
if (triggers.length && z.ground !== rule.unlessGround) {
|
|
1246
|
+
const u = triggers.reduce((a, m) => [Math.min(a[0], m._r[0]), Math.min(a[1], m._r[1]), Math.max(a[2], m._r[2]), Math.max(a[3], m._r[3])], [1e9, 1e9, -1e9, -1e9]);
|
|
1247
|
+
const q = clip(inflate(u, rule.inflate));
|
|
1248
|
+
if (q) groundPiece(z, rule.ground, q, 1, k++);
|
|
1249
|
+
}
|
|
1250
|
+
} else {
|
|
1251
|
+
for (const m of triggers) {
|
|
1252
|
+
const q = clip(inflate(m._r, rule.inflate));
|
|
1253
|
+
if (q) groundPiece(z, rule.ground, q, 1, k++);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
function runPostProcess(plan, items, seed) {
|
|
1260
|
+
const ctx = createPlacementContext(plan, items);
|
|
1261
|
+
const rng = createRng(seed, STREAM.decor);
|
|
1262
|
+
for (const pass of registries().PASSES) pass.run({ plan, items, seed, rng, ctx, kit: placementKit, catalog: registries().CATALOG, profiles: registries().DECOR_PROFILES });
|
|
1263
|
+
groundCover(plan, items, ctx);
|
|
1264
|
+
return ctx;
|
|
1265
|
+
}
|
|
112
1266
|
function registerLocale(code, table) {
|
|
1267
|
+
const { LOCALES: locales } = registries();
|
|
113
1268
|
const cur = locales[code] = locales[code] || {};
|
|
114
1269
|
for (const ns of Object.keys(table)) cur[ns] = Object.assign(cur[ns] || {}, table[ns]);
|
|
115
1270
|
}
|
|
116
|
-
var
|
|
1271
|
+
var listLocales = () => Object.keys(registries().LOCALES);
|
|
1272
|
+
function translate(ns, key, locale = "en", vars) {
|
|
1273
|
+
const { LOCALES: locales } = registries();
|
|
1274
|
+
const raw = locales[locale]?.[ns]?.[key] ?? locales.en?.[ns]?.[key] ?? key;
|
|
1275
|
+
return vars ? raw.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m) : raw;
|
|
1276
|
+
}
|
|
1277
|
+
var CLOSE = 1e-6;
|
|
1278
|
+
var rounded = (r) => r.map(r2);
|
|
1279
|
+
var touches = (a, r) => a[0] <= r[2] + CLOSE && a[2] >= r[0] - CLOSE && a[1] <= r[3] + CLOSE && a[3] >= r[1] - CLOSE;
|
|
1280
|
+
function addExteriorDoorPaths(plan, c) {
|
|
1281
|
+
const { gates, paths, zones, setback, marginLeft, marginRight, backyard, w } = c;
|
|
1282
|
+
const social = gates.find((g) => g.type === "pedestrian");
|
|
1283
|
+
const socialPath = social ? paths.find((p) => p.from === social.id && p.type === "pedestrian") : null;
|
|
1284
|
+
const parkingZone = zones.find((z) => z.id === "parking");
|
|
1285
|
+
const parking = parkingZone ? parkingZone.parts[0] : null;
|
|
1286
|
+
const midY = setback > 0 ? -setback / 2 : 0;
|
|
1287
|
+
const joinToSocial = (x, width, rects, points) => {
|
|
1288
|
+
if (!socialPath) return;
|
|
1289
|
+
const h = width / 2;
|
|
1290
|
+
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];
|
|
1291
|
+
const targetX = x < horizontal[0] ? horizontal[0] + h : x > horizontal[2] ? horizontal[2] - h : x;
|
|
1292
|
+
if (Math.abs(targetX - x) > CLOSE) {
|
|
1293
|
+
rects.push([Math.min(x, targetX) - h, midY - h, Math.max(x, targetX) + h, midY + h]);
|
|
1294
|
+
points.push([targetX, midY]);
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
for (const door of plan.doors.filter((q) => q.floor === 0 && q.exterior)) {
|
|
1298
|
+
if (paths.some((p) => p.to === door.id)) continue;
|
|
1299
|
+
const half = Math.max(1, Math.min(door.width, 2)) / 2;
|
|
1300
|
+
const rects = [];
|
|
1301
|
+
const points = [];
|
|
1302
|
+
const isTarget = c.target && door.a === c.target.a && door.side === c.target.side;
|
|
1303
|
+
let fromId = social ? social.id : null;
|
|
1304
|
+
let width = 1;
|
|
1305
|
+
const id = isTarget ? "entrance_path" : "path_" + door.id;
|
|
1306
|
+
if (door.side === "front") {
|
|
1307
|
+
if (setback > 0) {
|
|
1308
|
+
const stub = Math.min(1, setback);
|
|
1309
|
+
rects.push([door.u - half, -stub, door.u + half, 0]);
|
|
1310
|
+
points.push([door.u, 0], [door.u, -stub]);
|
|
1311
|
+
if (-stub > midY) {
|
|
1312
|
+
rects.push([door.u - width / 2, midY - width / 2, door.u + width / 2, -stub]);
|
|
1313
|
+
points.push([door.u, midY]);
|
|
1314
|
+
}
|
|
1315
|
+
joinToSocial(door.u, width, rects, points);
|
|
1316
|
+
} else {
|
|
1317
|
+
rects.push([door.u - half, -1.2, door.u + half, 0]);
|
|
1318
|
+
points.push([door.u, 0], [door.u, -1.2]);
|
|
1319
|
+
}
|
|
1320
|
+
} else {
|
|
1321
|
+
const lanes = [];
|
|
1322
|
+
if (marginLeft > 0.5) lanes.push({ x: -marginLeft / 2, width: marginLeft, side: "left" });
|
|
1323
|
+
if (marginRight > 0.5) lanes.push({ x: w + marginRight / 2, width: marginRight, side: "right" });
|
|
1324
|
+
const targets = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
1325
|
+
let best = null;
|
|
1326
|
+
for (const lane of lanes) {
|
|
1327
|
+
if (door.side === "left" && lane.side !== "left" || door.side === "right" && lane.side !== "right") continue;
|
|
1328
|
+
const laneWidth = Math.min(0.9, lane.width - 0.1);
|
|
1329
|
+
const h = laneWidth / 2;
|
|
1330
|
+
const rearStrip = Math.min(0.8, backyard);
|
|
1331
|
+
const rearY = door.v + rearStrip + laneWidth / 2;
|
|
1332
|
+
const overlapsParking = !!parking && lane.x + h > parking[0] && lane.x - h < parking[2];
|
|
1333
|
+
const frontY = overlapsParking || setback <= 0 ? 0 : -0.6;
|
|
1334
|
+
let frontStub = null;
|
|
1335
|
+
if (!overlapsParking && setback > 0) {
|
|
1336
|
+
const dir = social && social.u < lane.x ? -1 : 1;
|
|
1337
|
+
const band = [-0.6 - h, -0.6 + h];
|
|
1338
|
+
let nearestX = social ? social.u : lane.x;
|
|
1339
|
+
let nearestGap = Infinity;
|
|
1340
|
+
for (const r of targets) {
|
|
1341
|
+
if (!(r[1] <= band[1] && r[3] >= band[0])) continue;
|
|
1342
|
+
const edge = dir < 0 ? r[2] : r[0];
|
|
1343
|
+
const gap = Math.abs(edge - lane.x);
|
|
1344
|
+
if ((dir < 0 ? edge <= lane.x : edge >= lane.x) && gap < nearestGap) {
|
|
1345
|
+
nearestGap = gap;
|
|
1346
|
+
nearestX = edge;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
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];
|
|
1350
|
+
}
|
|
1351
|
+
const vertical = [lane.x - h, frontY - (frontY < 0 ? h : 0), lane.x + h, (door.side === "back" ? rearY : door.v) + laneWidth / 2];
|
|
1352
|
+
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;
|
|
1353
|
+
const cost = (horizontal ? horizontal[2] - horizontal[0] : 0) + (frontStub ? frontStub[2] - frontStub[0] : 0);
|
|
1354
|
+
if (!best || cost < best.cost) best = { lane, laneWidth, half: h, vertical, horizontal, frontStub, cost, rearStrip, rearY, frontY, overlapsParking };
|
|
1355
|
+
}
|
|
1356
|
+
if (best) {
|
|
1357
|
+
const { lane, laneWidth, half: h, vertical, horizontal, frontStub, rearStrip, rearY, frontY, overlapsParking } = best;
|
|
1358
|
+
width = laneWidth;
|
|
1359
|
+
if (overlapsParking) fromId = (gates.find((g) => g.type === "garage") || social || {}).id || null;
|
|
1360
|
+
if (door.side === "back") {
|
|
1361
|
+
rects.push([door.u - half, door.v, door.u + half, door.v + rearStrip + laneWidth], horizontal, vertical);
|
|
1362
|
+
points.push([door.u, door.v], [door.u, rearY], [lane.x, rearY], [lane.x, frontY]);
|
|
1363
|
+
} else {
|
|
1364
|
+
const x0 = door.side === "left" ? lane.x - h : w;
|
|
1365
|
+
const x1 = door.side === "left" ? 0 : lane.x + h;
|
|
1366
|
+
rects.push([Math.min(x0, x1), door.v - half, Math.max(x0, x1), door.v + half], vertical);
|
|
1367
|
+
points.push([door.u, door.v], [lane.x, door.v], [lane.x, frontY]);
|
|
1368
|
+
}
|
|
1369
|
+
if (frontStub) {
|
|
1370
|
+
rects.push(frontStub);
|
|
1371
|
+
points.push([frontStub[0] < lane.x ? frontStub[0] : frontStub[2], -0.6]);
|
|
1372
|
+
}
|
|
1373
|
+
} else {
|
|
1374
|
+
const stub = 1;
|
|
1375
|
+
if (door.side === "back") rects.push([door.u - half, door.v, door.u + half, door.v + Math.min(stub, backyard)]);
|
|
1376
|
+
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]);
|
|
1377
|
+
points.push([door.u, door.v]);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
const reached = rects.slice();
|
|
1381
|
+
const pool = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
1382
|
+
let grows = true;
|
|
1383
|
+
while (grows) {
|
|
1384
|
+
grows = false;
|
|
1385
|
+
for (const r of pool) {
|
|
1386
|
+
if (!reached.includes(r) && reached.some((a) => touches(a, r))) {
|
|
1387
|
+
reached.push(r);
|
|
1388
|
+
grows = true;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
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);
|
|
1393
|
+
const gate = gates.find((q) => q.type === "pedestrian" && inside(q)) || gates.find((q) => q.type !== "cargo" && inside(q));
|
|
1394
|
+
if (gate) fromId = gate.id;
|
|
1395
|
+
paths.push({ id, type: "pedestrian", from: fromId, to: door.id, width, rects: rects.map(rounded), points: points.map((q) => q.map(r2)) });
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
function defineZone(zone) {
|
|
1399
|
+
if (!zone || !zone.id) throw new Error("defineZone: id is required");
|
|
1400
|
+
registries().ZONES[zone.id] = zone;
|
|
1401
|
+
return zone;
|
|
1402
|
+
}
|
|
1403
|
+
var getZone = (id) => registries().ZONES[id];
|
|
1404
|
+
var listZones = () => Object.values(registries().ZONES);
|
|
117
1405
|
function registerPermeableGround(...types) {
|
|
118
|
-
for (const t of types) PERMEABLE_GROUNDS.add(t);
|
|
1406
|
+
for (const t of types) registries().PERMEABLE_GROUNDS.add(t);
|
|
1407
|
+
}
|
|
1408
|
+
function resolveZone(z) {
|
|
1409
|
+
if (!z) return null;
|
|
1410
|
+
if (typeof z === "string") {
|
|
1411
|
+
const found = registries().ZONES[z];
|
|
1412
|
+
if (!found) throw new Error("Unknown zone: " + z);
|
|
1413
|
+
return found;
|
|
1414
|
+
}
|
|
1415
|
+
return z;
|
|
1416
|
+
}
|
|
1417
|
+
var floorsOfTemplate = (tpl, op) => (tpl.floorsFn ? tpl.floorsFn(op) : tpl.floors || []).length;
|
|
1418
|
+
var useOf = (tpl) => tpl.use || "residential_single";
|
|
1419
|
+
function measure(plan) {
|
|
1420
|
+
const floors = plan.floors;
|
|
1421
|
+
const top = floors.reduce((m, f) => Math.max(m, f.y0 + f.ceiling), 0);
|
|
1422
|
+
const number = plan.floorNumber != null ? plan.floorNumber : floors.length;
|
|
1423
|
+
const rooms = (plan.rooms || []).filter((r) => r.floor >= 0);
|
|
1424
|
+
const area = (r) => r.parts.reduce((s, p) => s + (p[2] - p[0]) * (p[3] - p[1]), 0);
|
|
1425
|
+
const footprint = plan.w * plan.d;
|
|
1426
|
+
const built = rooms.reduce((s, r) => s + area(r), 0);
|
|
1427
|
+
return { storeys: number, height: r2(top), footprint, built };
|
|
1428
|
+
}
|
|
1429
|
+
function checkLot(zoneIn, lot, plan) {
|
|
1430
|
+
const zone = resolveZone(zoneIn);
|
|
1431
|
+
const out = [];
|
|
1432
|
+
const m = measure(plan);
|
|
1433
|
+
const use = plan.use || "residential_single";
|
|
1434
|
+
if (!zone.allowedUses.includes(use)) out.push({ rule: "use", actual: use, limit: zone.allowedUses.join(",") });
|
|
1435
|
+
if (m.storeys > zone.maxFloors) out.push({ rule: "floors", actual: m.storeys, limit: zone.maxFloors });
|
|
1436
|
+
if (m.height > zone.maxHeight + 1e-9) out.push({ rule: "height", actual: m.height, limit: zone.maxHeight });
|
|
1437
|
+
if (!lot) return out;
|
|
1438
|
+
const [u0, v0, u1, v1] = lot.rect;
|
|
1439
|
+
const width = r2(u1 - u0);
|
|
1440
|
+
const depth = r2(v1 - v0);
|
|
1441
|
+
const area = width * depth;
|
|
1442
|
+
const party = plan.partyWalls || [];
|
|
1443
|
+
const sides = [party.includes("left") ? Infinity : lot.sideYard.left, party.includes("right") ? Infinity : lot.sideYard.right];
|
|
1444
|
+
const left = sides[0];
|
|
1445
|
+
const right = sides[1];
|
|
1446
|
+
if (lot.setback < zone.setbacks.front - 1e-9) out.push({ rule: "setback_front", actual: lot.setback, limit: zone.setbacks.front });
|
|
1447
|
+
if (Math.min(left, right) < zone.setbacks.side - 1e-9) out.push({ rule: "setback_side", actual: Math.min(left, right), limit: zone.setbacks.side });
|
|
1448
|
+
if (lot.backyard < zone.setbacks.back - 1e-9) out.push({ rule: "setback_back", actual: lot.backyard, limit: zone.setbacks.back });
|
|
1449
|
+
if (area < zone.minLot - 1e-9) out.push({ rule: "min_lot", actual: r2(area), limit: zone.minLot });
|
|
1450
|
+
if (width < zone.minFrontage - 1e-9) out.push({ rule: "min_frontage", actual: width, limit: zone.minFrontage });
|
|
1451
|
+
const ground = m.footprint;
|
|
1452
|
+
const coverage = ground / area;
|
|
1453
|
+
if (coverage > zone.maxCoverage + 1e-9) out.push({ rule: "coverage", actual: r2(coverage), limit: zone.maxCoverage });
|
|
1454
|
+
const far = m.built / area;
|
|
1455
|
+
if (far > zone.maxFar + 1e-9) out.push({ rule: "far", actual: r2(far), limit: zone.maxFar });
|
|
1456
|
+
let permeable = 0;
|
|
1457
|
+
for (const z of lot.zones) if (z.ground && registries().PERMEABLE_GROUNDS.has(z.ground)) for (const p of z.parts) permeable += (p[2] - p[0]) * (p[3] - p[1]);
|
|
1458
|
+
const permeability = permeable / area;
|
|
1459
|
+
if (permeability < zone.permeableMin - 1e-9) out.push({ rule: "permeable", actual: r2(permeability), limit: zone.permeableMin });
|
|
1460
|
+
return out;
|
|
1461
|
+
}
|
|
1462
|
+
function clampOptionsToZone(tpl, op, zone, adjustments) {
|
|
1463
|
+
if (tpl.singleFloor) {
|
|
1464
|
+
const ceiling2 = (tpl.floors || tpl.floorsFn?.(op) || [])[0]?.ceiling ?? 2.8;
|
|
1465
|
+
const current2 = op.floor || 3;
|
|
1466
|
+
const limit2 = Math.max(1, Math.min(zone.maxFloors, Math.floor(zone.maxHeight / ceiling2 + 1e-9)));
|
|
1467
|
+
if (current2 <= limit2) return op;
|
|
1468
|
+
adjustments.push({ field: "floor", from: current2, to: limit2, reason: current2 > zone.maxFloors ? "max_floors" : "max_height" });
|
|
1469
|
+
return Object.assign({}, op, { floor: limit2 });
|
|
1470
|
+
}
|
|
1471
|
+
if (!tpl.floorRange || !tpl.floorsFn) return op;
|
|
1472
|
+
const current = floorsOfTemplate(tpl, op);
|
|
1473
|
+
const floors = tpl.floorsFn(op);
|
|
1474
|
+
const ceiling = floors.reduce((m, f) => Math.max(m, f.ceiling), 0) || 2.8;
|
|
1475
|
+
const byHeight = Math.max(1, Math.floor(zone.maxHeight / ceiling + 1e-9));
|
|
1476
|
+
const limit = Math.max(tpl.floorRange[0], Math.min(zone.maxFloors, byHeight, tpl.floorRange[1]));
|
|
1477
|
+
if (current <= limit) return op;
|
|
1478
|
+
adjustments.push({ field: "floors", from: current, to: limit, reason: current > zone.maxFloors ? "max_floors" : "max_height" });
|
|
1479
|
+
return Object.assign({}, op, { floors: limit });
|
|
1480
|
+
}
|
|
1481
|
+
function clampLotToZone(cfg, zone, adjustments, partyWalls = []) {
|
|
1482
|
+
const raise = (field, key, min, reason) => {
|
|
1483
|
+
if (cfg[key] < min) {
|
|
1484
|
+
adjustments.push({ field, from: cfg[key], to: min, reason });
|
|
1485
|
+
cfg[key] = min;
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
raise("frontSetback", "frontSetback", zone.setbacks.front, "setback_front");
|
|
1489
|
+
if (!partyWalls.includes("left")) raise("marginLeft", "marginLeft", zone.setbacks.side, "setback_side");
|
|
1490
|
+
if (!partyWalls.includes("right")) raise("marginRight", "marginRight", zone.setbacks.side, "setback_side");
|
|
1491
|
+
raise("backyardDepth", "backyardDepth", zone.setbacks.back, "setback_back");
|
|
1492
|
+
}
|
|
1493
|
+
var ADJUSTABLE_ZONES = /* @__PURE__ */ new Set(["garden", "backyard", "side_yard"]);
|
|
1494
|
+
function enforcePermeability(lot, zones, zone, adjustments) {
|
|
1495
|
+
const target = [...registries().PERMEABLE_GROUNDS][0];
|
|
1496
|
+
if (!target) return;
|
|
1497
|
+
const [u0, v0, u1, v1] = lot.rect;
|
|
1498
|
+
const area = (u1 - u0) * (v1 - v0);
|
|
1499
|
+
const areaOfZone = (z) => z.parts.reduce((s, p) => s + (p[2] - p[0]) * (p[3] - p[1]), 0);
|
|
1500
|
+
let permeable = zones.filter((z) => z.ground != null && registries().PERMEABLE_GROUNDS.has(z.ground)).reduce((s, z) => s + areaOfZone(z), 0);
|
|
1501
|
+
if (permeable / area >= zone.permeableMin) return;
|
|
1502
|
+
const candidates = zones.filter((z) => ADJUSTABLE_ZONES.has(z.type) && !(z.ground != null && registries().PERMEABLE_GROUNDS.has(z.ground))).sort((a, b) => areaOfZone(b) - areaOfZone(a));
|
|
1503
|
+
for (const z of candidates) {
|
|
1504
|
+
if (permeable / area >= zone.permeableMin) break;
|
|
1505
|
+
adjustments.push({ field: "ground:" + z.id, from: z.ground ?? "", to: target, reason: "permeable_min" });
|
|
1506
|
+
z.ground = target;
|
|
1507
|
+
permeable += areaOfZone(z);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
var floorsOf = (tpl, op) => tpl.floorsFn ? tpl.floorsFn(op || {}) : tpl.floors;
|
|
1511
|
+
function clampSize(tpl, w, d, op) {
|
|
1512
|
+
const L = tpl.limitsFn ? tpl.limitsFn(op || {}) : tpl.limits;
|
|
1513
|
+
const P0 = tpl.defaultsFn ? tpl.defaultsFn(op || {}) : tpl.defaults;
|
|
1514
|
+
const ww = w == null ? P0.w : clamp(w, L.w[0], L.w[1]);
|
|
1515
|
+
const dd = d == null ? P0.d : clamp(d, L.d[0], L.d[1]);
|
|
1516
|
+
return { w: r2(ww), d: r2(dd) };
|
|
1517
|
+
}
|
|
1518
|
+
function buildPlan(tpl, wIn, dIn, opIn, bc = {}) {
|
|
1519
|
+
const op = opIn || {};
|
|
1520
|
+
const { w, d } = clampSize(tpl, wIn, dIn, op);
|
|
1521
|
+
const styleId = resolveStyleId(op.style || (typeof tpl.style === "function" ? tpl.style(op) : tpl.style) || "budget");
|
|
1522
|
+
const FLOOR_DEFS = floorsOf(tpl, op);
|
|
1523
|
+
const style = styleOf(styleId);
|
|
1524
|
+
const plan = {
|
|
1525
|
+
w,
|
|
1526
|
+
d,
|
|
1527
|
+
tpl,
|
|
1528
|
+
floorDefs: FLOOR_DEFS,
|
|
1529
|
+
style: styleId,
|
|
1530
|
+
floors: [],
|
|
1531
|
+
rooms: [],
|
|
1532
|
+
zones: [],
|
|
1533
|
+
doors: [],
|
|
1534
|
+
windows: [],
|
|
1535
|
+
walls: [],
|
|
1536
|
+
gates: [],
|
|
1537
|
+
paths: [],
|
|
1538
|
+
lot: null,
|
|
1539
|
+
openPairs: {},
|
|
1540
|
+
reservations: [],
|
|
1541
|
+
options: op,
|
|
1542
|
+
playerRadius: op.playerRadius ?? PLAYER_RADIUS,
|
|
1543
|
+
carClearance: Object.assign({}, CAR_CLEARANCE, op.carClearance)
|
|
1544
|
+
};
|
|
1545
|
+
FLOOR_DEFS.forEach((A, floorIndex) => {
|
|
1546
|
+
const floorNumber = tpl.singleFloor ? op.floor || 3 : floorIndex + 1;
|
|
1547
|
+
plan.floors.push({
|
|
1548
|
+
index: floorIndex,
|
|
1549
|
+
name: tpl.singleFloor ? "floor_n" : A.label,
|
|
1550
|
+
number: tpl.singleFloor ? floorNumber : void 0,
|
|
1551
|
+
y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : tpl.cumulativeCeilings ? FLOOR_DEFS.slice(0, floorIndex).reduce((t, q) => t + q.ceiling, 0) : floorIndex * A.ceiling,
|
|
1552
|
+
ceiling: A.ceiling
|
|
1553
|
+
});
|
|
1554
|
+
plan.openPairs[floorIndex] = A.openPairs || [];
|
|
1555
|
+
const list = A.rooms(w, d, op).map((c) => normalizeRoom(c, floorIndex));
|
|
1556
|
+
for (const c of list) {
|
|
1557
|
+
if (plan.rooms.some((x) => x.id === c.id)) throw new Error("Duplicate room: " + c.id);
|
|
1558
|
+
plan.rooms.push(c);
|
|
1559
|
+
}
|
|
1560
|
+
const byId2 = {};
|
|
1561
|
+
for (const c of list) byId2[c.id] = c;
|
|
1562
|
+
const floorDoors = [];
|
|
1563
|
+
(A.doors(w, d, op) || []).forEach((sp, i) => {
|
|
1564
|
+
const a = byId2[sp.a];
|
|
1565
|
+
if (!a) throw new Error("Door references a missing room: " + sp.a);
|
|
1566
|
+
let edge;
|
|
1567
|
+
if (sp.edge) edge = sp.edge;
|
|
1568
|
+
else if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
|
|
1569
|
+
else {
|
|
1570
|
+
const b = byId2[sp.b];
|
|
1571
|
+
if (!b) throw new Error("Door references a missing room: " + sp.b);
|
|
1572
|
+
edge = sharedEdge(a, b, sp.at);
|
|
1573
|
+
}
|
|
1574
|
+
if (!edge) throw new Error("Door without a wall: " + sp.a + " / " + sp.b);
|
|
1575
|
+
const width = sp.width;
|
|
1576
|
+
const margin = width / 2 + (sp.clearance ?? tpl.clearance?.door ?? DOOR_EDGE_CLEARANCE);
|
|
1577
|
+
if (edge.a1 - edge.a0 < width + 0.24) throw new Error("Wall too short for the door: " + sp.a + " / " + sp.b);
|
|
1578
|
+
const c = clamp(sp.at != null ? sp.at : (edge.a0 + edge.a1) / 2, edge.a0 + margin, edge.a1 - margin);
|
|
1579
|
+
const exterior = sp.b === "outside";
|
|
1580
|
+
const leaf = exterior && sp.steel ? 0 : width > 1.1 ? width / 2 : width;
|
|
1581
|
+
const u = edge.axis === "u" ? c : edge.pos;
|
|
1582
|
+
const v = edge.axis === "u" ? edge.pos : c;
|
|
1583
|
+
floorDoors.push({
|
|
1584
|
+
id: "d" + floorIndex + "_" + i,
|
|
1585
|
+
floor: floorIndex,
|
|
1586
|
+
u: r2(u),
|
|
1587
|
+
v: r2(v),
|
|
1588
|
+
axis: edge.axis,
|
|
1589
|
+
pos: edge.pos,
|
|
1590
|
+
at: r2(c),
|
|
1591
|
+
width,
|
|
1592
|
+
a: sp.a,
|
|
1593
|
+
b: sp.b,
|
|
1594
|
+
opensTo: sp.opensTo || sp.a,
|
|
1595
|
+
exterior,
|
|
1596
|
+
side: sp.side || null,
|
|
1597
|
+
leaves: exterior && sp.steel ? 0 : width > 1.1 ? 2 : 1,
|
|
1598
|
+
model: sp.model || (sp.steel ? "steel" : width > 1.1 ? "double" : "single"),
|
|
1599
|
+
type: sp.type || "door",
|
|
1600
|
+
steps: sp.steps || 0,
|
|
1601
|
+
hinge: "start",
|
|
1602
|
+
zones: openingZones(u, v, edge.axis, width, openingDepth(width, leaf))
|
|
1603
|
+
});
|
|
1604
|
+
});
|
|
1605
|
+
plan.doors.push(...floorDoors);
|
|
1606
|
+
const floorWalls = (tpl.noWalls ? [] : buildWalls(list, floorDoors, A.openPairs || [], floorIndex)).map((p) => Object.assign(p, { h: p.h || A.ceiling }));
|
|
1607
|
+
const windowsOut = placeWindows({ list, byId: byId2, floorWalls, windowDefs: A.windows(w, d, op) || [], floorIndex, ceiling: A.ceiling, w, d, clearance: tpl.clearance?.window ?? WINDOW_EDGE_CLEARANCE });
|
|
1608
|
+
plan.windows.push(...windowsOut);
|
|
1609
|
+
plan.walls.push(...floorWalls);
|
|
1610
|
+
});
|
|
1611
|
+
if (tpl.config) plan.config = tpl.config(op, w, d);
|
|
1612
|
+
if (tpl.reservations) tpl.reservations(plan);
|
|
1613
|
+
buildLot(plan, tpl, w, d, op, style, styleId, bc);
|
|
1614
|
+
return plan;
|
|
1615
|
+
}
|
|
1616
|
+
function placeWindows(params) {
|
|
1617
|
+
const { list, byId: byId2, floorWalls, windowDefs, floorIndex, ceiling, w, d, clearance } = params;
|
|
1618
|
+
const stretches = floorWalls.filter((p) => p.exterior && p.type === "wall");
|
|
1619
|
+
const found = [];
|
|
1620
|
+
const groups = [];
|
|
1621
|
+
let order = 0;
|
|
1622
|
+
for (const def of windowDefs) {
|
|
1623
|
+
const room = byId2[def.room];
|
|
1624
|
+
if (!room) throw new Error("Window references a missing room: " + def.room);
|
|
1625
|
+
const edge = exteriorEdge(room, def.side, w, d, def.at);
|
|
1626
|
+
if (!edge) continue;
|
|
1627
|
+
const centres = def.n ? Array.from({ length: def.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (def.n + 1)) : [def.at];
|
|
1628
|
+
for (const centre of centres) {
|
|
1629
|
+
const onEdge = stretches.filter((t) => t.axis === edge.axis && eq(edge.axis === "u" ? t.v0 : t.u0, edge.pos) && t.sides.includes(def.room));
|
|
1630
|
+
const start = (t) => edge.axis === "u" ? t.u0 : t.v0;
|
|
1631
|
+
const end = (t) => edge.axis === "u" ? t.u1 : t.v1;
|
|
1632
|
+
const gap = (t) => Math.min(Math.abs(centre - start(t)), Math.abs(centre - end(t)));
|
|
1633
|
+
const target = onEdge.find((t) => centre >= start(t) - TOL && centre <= end(t) + TOL) || onEdge.slice().sort((x, y) => gap(x) - gap(y))[0];
|
|
1634
|
+
if (!target) continue;
|
|
1635
|
+
let group = groups.find((x) => x.wall === target && x.def.room === def.room);
|
|
1636
|
+
if (!group) groups.push(group = { wall: target, edge, start: start(target), end: end(target), def, entries: [] });
|
|
1637
|
+
group.entries.push({ def, centre, order: order++ });
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
for (const g of groups) {
|
|
1641
|
+
const room = byId2[g.def.room];
|
|
1642
|
+
const bath = room.type === "bathroom";
|
|
1643
|
+
const length = g.end - g.start;
|
|
1644
|
+
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 }));
|
|
1645
|
+
const fits = (l) => l.reduce((t, x) => t + x.width, 0) + (l.length + 1) * clearance <= length + TOL;
|
|
1646
|
+
while (its.length && !fits(its)) {
|
|
1647
|
+
const k = its.reduce((m, x, i, arr) => x.width > arr[m].width ? i : m, 0);
|
|
1648
|
+
if (its[k].width > 0.6 + TOL) its[k] = Object.assign({}, its[k], { width: Math.max(0.6, r2(its[k].width - 0.1)) });
|
|
1649
|
+
else its.pop();
|
|
1650
|
+
}
|
|
1651
|
+
if (!its.length) continue;
|
|
1652
|
+
const space = (length - its.reduce((t, x) => t + x.width, 0)) / (its.length + 1);
|
|
1653
|
+
let pos = g.start + space;
|
|
1654
|
+
for (const x of its) {
|
|
1655
|
+
const c = pos + x.width / 2;
|
|
1656
|
+
pos += x.width + space;
|
|
1657
|
+
const u = g.edge.axis === "u" ? c : g.edge.pos;
|
|
1658
|
+
const v = g.edge.axis === "u" ? g.edge.pos : c;
|
|
1659
|
+
let y0 = x.def.y0 || 1;
|
|
1660
|
+
let y1 = x.def.y1 || 2.15;
|
|
1661
|
+
if (bath) {
|
|
1662
|
+
y0 = Math.max(y0, 2.1);
|
|
1663
|
+
y1 = Math.min(Math.max(y1, y0 + 0.4), ceiling - 0.25);
|
|
1664
|
+
}
|
|
1665
|
+
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 });
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
for (const room of list) {
|
|
1669
|
+
if (WINDOWLESS_ROOM_TYPES.has(room.type) || found.some((j) => j.room === room.id)) continue;
|
|
1670
|
+
let best = null;
|
|
1671
|
+
for (const t of stretches) {
|
|
1672
|
+
if (!t.sides.includes(room.id)) continue;
|
|
1673
|
+
const i0 = t.axis === "u" ? t.u0 : t.v0;
|
|
1674
|
+
const i1 = t.axis === "u" ? t.u1 : t.v1;
|
|
1675
|
+
const length = i1 - i0;
|
|
1676
|
+
const width = Math.min(room.type === "bathroom" ? 0.6 : 1.2, length - 2 * clearance);
|
|
1677
|
+
if (width < 0.6 - TOL || best && best.length >= length) continue;
|
|
1678
|
+
const side = t.axis === "u" ? t.sides[0] === "outside" ? "front" : "back" : t.sides[0] === "outside" ? "left" : "right";
|
|
1679
|
+
const c = (i0 + i1) / 2;
|
|
1680
|
+
const pos = t.axis === "u" ? t.v0 : t.u0;
|
|
1681
|
+
const bath = room.type === "bathroom";
|
|
1682
|
+
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 } };
|
|
1683
|
+
}
|
|
1684
|
+
if (best) found.push(best.window);
|
|
1685
|
+
}
|
|
1686
|
+
found.sort((x, y) => x._order - y._order);
|
|
1687
|
+
found.forEach((j, i) => {
|
|
1688
|
+
delete j._order;
|
|
1689
|
+
j.id = "w" + floorIndex + "_" + i;
|
|
1690
|
+
});
|
|
1691
|
+
return found;
|
|
1692
|
+
}
|
|
1693
|
+
function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
|
|
1694
|
+
const seed = op.seed == null ? 1 : op.seed;
|
|
1695
|
+
const rFacade = createRng(seed, STREAM.facade);
|
|
1696
|
+
const facadeStyle = style.facade;
|
|
1697
|
+
const palette2 = style.swatches;
|
|
1698
|
+
const color = palette2[Math.floor(rFacade() * palette2.length)];
|
|
1699
|
+
const extra = tpl.facade || {};
|
|
1700
|
+
plan.facade = {
|
|
1701
|
+
style: styleId,
|
|
1702
|
+
color: { name: color[0], hex: color[1] },
|
|
1703
|
+
finish: pickWeighted(rFacade, facadeStyle.finish),
|
|
1704
|
+
grilles: rFacade() < facadeStyle.grilles,
|
|
1705
|
+
roof: pickWeighted(rFacade, facadeStyle.roof),
|
|
1706
|
+
balcony: !!extra.balcony,
|
|
1707
|
+
veranda: !!extra.veranda,
|
|
1708
|
+
steelDoor: !!extra.steelDoor,
|
|
1709
|
+
floors: plan.floorDefs.length
|
|
1710
|
+
};
|
|
1711
|
+
if (tpl.facadeExtra) tpl.facadeExtra(plan.facade, rFacade, w, d, op);
|
|
1712
|
+
if (plan.floorDefs.length > 1 && plan.facade.roof === "fiber_cement") plan.facade.roof = "colonial";
|
|
1713
|
+
if (!tpl.lot) return;
|
|
1714
|
+
const cfg = tpl.lot(w, d, style, op);
|
|
1715
|
+
if (bc.zone) clampLotToZone(cfg, bc.zone, bc.adjustments || [], tpl.partyWalls);
|
|
1716
|
+
const rLot = createRng(seed, STREAM.lot);
|
|
1717
|
+
const setback = cfg.frontSetback;
|
|
1718
|
+
const marginL = cfg.marginLeft;
|
|
1719
|
+
const marginR = cfg.marginRight;
|
|
1720
|
+
const backDepth = cfg.backyardDepth;
|
|
1721
|
+
const lot = {
|
|
1722
|
+
rect: [-marginL, -setback, w + marginR, d + backDepth],
|
|
1723
|
+
setback,
|
|
1724
|
+
sideYard: { left: marginL, right: marginR },
|
|
1725
|
+
backyard: backDepth,
|
|
1726
|
+
style: styleId,
|
|
1727
|
+
zones: [],
|
|
1728
|
+
perimeter: [],
|
|
1729
|
+
gates: [],
|
|
1730
|
+
paths: [],
|
|
1731
|
+
pool: !!cfg.pool
|
|
1732
|
+
};
|
|
1733
|
+
const ZONES = [];
|
|
1734
|
+
const libs = Object.assign({ garden: "garden", backyard: "backyard", parking: "parking", sideYard: "side_yard" }, cfg.libraries || {});
|
|
1735
|
+
const addZone = (id, type, label, parts, library) => ZONES.push(Object.assign(normalizeRoom({ id, type, label, rect: parts[0], parts: parts.slice(1), library, outdoor: true }, -1), { floor: -1, outdoor: true }));
|
|
1736
|
+
if (setback > 0) {
|
|
1737
|
+
let parts = [[-marginL, -setback, w + marginR, 0]];
|
|
1738
|
+
if (cfg.parking) {
|
|
1739
|
+
const garageU = cfg.garage.u;
|
|
1740
|
+
const vw = cfg.parking.width;
|
|
1741
|
+
const x0 = Math.max(-marginL, garageU - vw / 2);
|
|
1742
|
+
const x1 = Math.min(w + marginR, garageU + vw / 2);
|
|
1743
|
+
parts = [];
|
|
1744
|
+
if (x0 + marginL > 0.8) parts.push([-marginL, -setback, x0, 0]);
|
|
1745
|
+
if (w + marginR - x1 > 0.8) parts.push([x1, -setback, w + marginR, 0]);
|
|
1746
|
+
addZone("parking", "parking", "parking", [[x0, -setback, x1, 0]], libs.parking);
|
|
1747
|
+
if (!parts.length) parts = [[x1, -setback, w + marginR, 0]];
|
|
1748
|
+
}
|
|
1749
|
+
parts.sort((a, b) => b[2] - b[0] - (a[2] - a[0]));
|
|
1750
|
+
addZone("garden", "garden", "garden", parts, libs.garden);
|
|
1751
|
+
}
|
|
1752
|
+
const yard = cfg.yardZones;
|
|
1753
|
+
if (backDepth > 0 && yard) {
|
|
1754
|
+
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);
|
|
1755
|
+
} else if (backDepth > 0) addZone("backyard", "backyard", "backyard", [[-marginL, d, w + marginR, d + backDepth]], libs.backyard);
|
|
1756
|
+
for (const q of cfg.extraZones || []) addZone(q.id, q.type, q.label || q.id, q.parts, q.library);
|
|
1757
|
+
if (marginL > 0.5) addZone("side_left", "side_yard", "side_left", [[-marginL, 0, 0, d]], libs.sideYard);
|
|
1758
|
+
if (marginR > 0.5) addZone("side_right", "side_yard", "side_right", [[w, 0, w + marginR, d]], libs.sideYard);
|
|
1759
|
+
const gates = [];
|
|
1760
|
+
const addGate = (type, model, u, v, width, side, toTarget) => {
|
|
1761
|
+
const g = {
|
|
1762
|
+
id: "g" + gates.length,
|
|
1763
|
+
type,
|
|
1764
|
+
model,
|
|
1765
|
+
u: r2(u),
|
|
1766
|
+
v: r2(v),
|
|
1767
|
+
axis: "u",
|
|
1768
|
+
width,
|
|
1769
|
+
height: type === "pedestrian" ? style.height : Math.min(style.height, 2.2),
|
|
1770
|
+
side,
|
|
1771
|
+
to: toTarget || null,
|
|
1772
|
+
material: "iron",
|
|
1773
|
+
zones: openingZones(u, v, "u", width, openingDepth(width, type === "pedestrian" ? width : 0))
|
|
1774
|
+
};
|
|
1775
|
+
gates.push(g);
|
|
1776
|
+
return g;
|
|
1777
|
+
};
|
|
1778
|
+
if (cfg.pedestrian && setback > 0) addGate("pedestrian", "pedestrian", cfg.pedestrian.u, -setback, 1, "front", "door");
|
|
1779
|
+
if (cfg.garage && setback > 0) addGate("garage", pickWeighted(rLot, style.gate.garage), cfg.garage.u, -setback, cfg.garage.width, "front", cfg.garage.to);
|
|
1780
|
+
if (cfg.cargo) addGate("cargo", "sliding", cfg.cargo.u, d + backDepth, cfg.cargo.width, "back", "stockroom");
|
|
1781
|
+
plan.gates = gates;
|
|
1782
|
+
lot.gates = gates;
|
|
1783
|
+
const paths = [];
|
|
1784
|
+
const target = (a, side) => plan.doors.find((p) => p.exterior && p.a === a && p.side === side);
|
|
1785
|
+
for (const g of gates) {
|
|
1786
|
+
if (g.type === "pedestrian" && cfg.doorTarget) {
|
|
1787
|
+
const pt = target(cfg.doorTarget.a, cfg.doorTarget.side);
|
|
1788
|
+
if (!pt) continue;
|
|
1789
|
+
const gateU = g.u;
|
|
1790
|
+
const dx = pt.u;
|
|
1791
|
+
const ym = -setback / 2;
|
|
1792
|
+
const L = 1;
|
|
1793
|
+
const h = L / 2;
|
|
1794
|
+
const rs = [];
|
|
1795
|
+
if (Math.abs(gateU - dx) < 0.05) rs.push([dx - h, -setback, dx + h, 0]);
|
|
1796
|
+
else {
|
|
1797
|
+
rs.push([gateU - h, -setback, gateU + h, ym + h]);
|
|
1798
|
+
rs.push([Math.min(gateU, dx) - h, ym - h, Math.max(gateU, dx) + h, ym + h]);
|
|
1799
|
+
rs.push([dx - h, ym - h, dx + h, 0]);
|
|
1800
|
+
}
|
|
1801
|
+
paths.push({ id: "pedestrian_path", type: "pedestrian", from: g.id, to: pt.id, width: L, rects: rs.map((r) => r.map(r2)), points: [[gateU, -setback], [gateU, ym], [dx, ym], [dx, 0]] });
|
|
1802
|
+
} else if (g.type === "garage" && g.to === "inner_garage") {
|
|
1803
|
+
const pt = plan.doors.find((p) => p.exterior && p.side === "front" && Math.abs(p.u - g.u) < 0.6 && p.width >= 2.5);
|
|
1804
|
+
if (!pt) continue;
|
|
1805
|
+
const hw = Math.max(g.width, pt.width) / 2;
|
|
1806
|
+
paths.push({ id: "garage_path", type: "vehicle", from: g.id, to: pt.id, width: hw * 2, rects: [[g.u - hw, -setback, g.u + hw, 0].map(r2)], points: [[g.u, -setback], [g.u, 0]] });
|
|
1807
|
+
} else if (g.type === "cargo") {
|
|
1808
|
+
const pt = target("stockroom", "back");
|
|
1809
|
+
if (!pt) continue;
|
|
1810
|
+
const hw = Math.max(g.width, pt.width) / 2;
|
|
1811
|
+
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]] });
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
if (tpl.paths) paths.push(...tpl.paths(plan, { gates, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, d }));
|
|
1815
|
+
else addExteriorDoorPaths(plan, { gates, paths, zones: ZONES, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, target: cfg.doorTarget });
|
|
1816
|
+
plan.paths = paths;
|
|
1817
|
+
lot.paths = paths;
|
|
1818
|
+
const boundary = (rule) => {
|
|
1819
|
+
const type = pickWeighted(rLot, rule.type);
|
|
1820
|
+
const top = type === "railing" ? "none" : pickWeighted(rLot, rule.top);
|
|
1821
|
+
const wallHeight = type === "railing" ? 1.2 : type === "fence" ? style.fenceHeight ?? 2 : style.height;
|
|
1822
|
+
return { type, material: type === "wall" ? "block" : type === "fence" ? "wire_mesh" : "iron", height: wallHeight, top, totalHeight: r2(wallHeight + registries().WALL_TOP_HEIGHTS[top]), thickness: WALL_THICKNESS };
|
|
1823
|
+
};
|
|
1824
|
+
const A = { front: boundary(style.front), left: boundary(style.sideYard), right: boundary(style.sideYard), back: boundary(style.back) };
|
|
1825
|
+
const seg = (side, u0, v0, u1, v1) => {
|
|
1826
|
+
if (Math.abs(u1 - u0) + Math.abs(v1 - v0) < 0.05) return;
|
|
1827
|
+
lot.perimeter.push(Object.assign({ id: "m" + lot.perimeter.length, side, u0: r2(u0), v0: r2(v0), u1: r2(u1), v1: r2(v1) }, A[side]));
|
|
1828
|
+
};
|
|
1829
|
+
const cutRun = (side, de, to, vfix, gs) => {
|
|
1830
|
+
let pieces = [[de, to]];
|
|
1831
|
+
for (const g of gs) {
|
|
1832
|
+
const g0 = g.u - g.width / 2;
|
|
1833
|
+
const g1 = g.u + g.width / 2;
|
|
1834
|
+
const n = [];
|
|
1835
|
+
for (const [x, y] of pieces) {
|
|
1836
|
+
if (g1 <= x || g0 >= y) {
|
|
1837
|
+
n.push([x, y]);
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
if (g0 > x) n.push([x, g0]);
|
|
1841
|
+
if (g1 < y) n.push([g1, y]);
|
|
1842
|
+
}
|
|
1843
|
+
pieces = n;
|
|
1844
|
+
}
|
|
1845
|
+
for (const [x, y] of pieces) seg(side, x, vfix, y, vfix);
|
|
1846
|
+
};
|
|
1847
|
+
if (setback > 0)
|
|
1848
|
+
cutRun("front", -marginL, w + marginR, -setback, gates.filter((g) => g.side === "front"));
|
|
1849
|
+
cutRun("back", -marginL, w + marginR, d + backDepth, gates.filter((g) => g.side === "back"));
|
|
1850
|
+
const vi = setback > 0 ? -setback : 0;
|
|
1851
|
+
if (marginL > 0.5) seg("left", -marginL, vi, -marginL, d + backDepth);
|
|
1852
|
+
else if (backDepth > 0) seg("left", 0, d, 0, d + backDepth);
|
|
1853
|
+
if (marginR > 0.5) seg("right", w + marginR, vi, w + marginR, d + backDepth);
|
|
1854
|
+
else if (backDepth > 0) seg("right", w, d, w, d + backDepth);
|
|
1855
|
+
for (const z of ZONES) z.ground = (cfg.grounds || {})[z.id] || (cfg.grounds || {})[z.type] || pickWeighted(rLot, style.grounds[z.type] || { concrete: 1 });
|
|
1856
|
+
if (bc.zone) enforcePermeability(lot, ZONES, bc.zone, bc.adjustments || []);
|
|
1857
|
+
plan.zones = ZONES;
|
|
1858
|
+
lot.zones = ZONES;
|
|
1859
|
+
plan.lot = lot;
|
|
1860
|
+
}
|
|
1861
|
+
var HIGH_SATURATION = 0.4;
|
|
1862
|
+
var MIN_WALL_FLOOR_CONTRAST = 20;
|
|
1863
|
+
var MIN_FABRIC_FLOOR_CONTRAST = 15;
|
|
1864
|
+
var SCHEMES = ["analogous", "complementary", "split_complementary", "triad"];
|
|
1865
|
+
var HARMONY_OFFSETS = { analogous: [-30, 30], complementary: [180, 165], split_complementary: [150, 210], triad: [120, 240] };
|
|
1866
|
+
var ROLE_FABRIC_MAIN = "fabric_main";
|
|
1867
|
+
var ROLE_FABRIC_ACCENT = "fabric_accent";
|
|
1868
|
+
var ROLE_FLOOR_REFERENCE = "floor_living";
|
|
1869
|
+
var hslToInt = (h, s, l) => {
|
|
1870
|
+
h = (h % 360 + 360) % 360;
|
|
1871
|
+
s = clamp(s, 0, 1);
|
|
1872
|
+
l = clamp(l, 0, 1);
|
|
1873
|
+
const a = s * Math.min(l, 1 - l);
|
|
1874
|
+
const f = (n) => {
|
|
1875
|
+
const k = (n + h / 30) % 12;
|
|
1876
|
+
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
1877
|
+
};
|
|
1878
|
+
return Math.round(f(0) * 255) << 16 | Math.round(f(8) * 255) << 8 | Math.round(f(4) * 255);
|
|
1879
|
+
};
|
|
1880
|
+
var intToHsl = (n) => {
|
|
1881
|
+
const r = (n >> 16 & 255) / 255;
|
|
1882
|
+
const g = (n >> 8 & 255) / 255;
|
|
1883
|
+
const b = (n & 255) / 255;
|
|
1884
|
+
const mx = Math.max(r, g, b);
|
|
1885
|
+
const mn = Math.min(r, g, b);
|
|
1886
|
+
const l = (mx + mn) / 2;
|
|
1887
|
+
const d = mx - mn;
|
|
1888
|
+
let h = 0;
|
|
1889
|
+
let s = 0;
|
|
1890
|
+
if (d > 1e-9) {
|
|
1891
|
+
s = d / (1 - Math.abs(2 * l - 1));
|
|
1892
|
+
h = mx === r ? (g - b) / d + (g < b ? 6 : 0) : mx === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
1893
|
+
h *= 60;
|
|
1894
|
+
}
|
|
1895
|
+
return { h, s, l };
|
|
1896
|
+
};
|
|
1897
|
+
var lstar = (n) => {
|
|
1898
|
+
const linear = (c) => (c /= 255) <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
1899
|
+
const Y = 0.2126 * linear(n >> 16 & 255) + 0.7152 * linear(n >> 8 & 255) + 0.0722 * linear(n & 255);
|
|
1900
|
+
return Y > 216 / 24389 ? 116 * Math.cbrt(Y) - 16 : Y * 24389 / 27;
|
|
1901
|
+
};
|
|
1902
|
+
var hexString = (n) => "#" + (n >>> 0).toString(16).padStart(6, "0");
|
|
1903
|
+
function palette(o) {
|
|
1904
|
+
const opt = o || {};
|
|
1905
|
+
let style = resolveStyleId(opt.style || "budget");
|
|
1906
|
+
if (!registries().PALETTE_STYLES[style]) style = registries().PALETTE_STYLES.mid ? "mid" : Object.keys(registries().PALETTE_STYLES)[0];
|
|
1907
|
+
const def = registries().PALETTE_STYLES[style];
|
|
1908
|
+
const state = opt.state || "normal";
|
|
1909
|
+
const seed = opt.seed == null ? 1 : opt.seed;
|
|
1910
|
+
const rng = createRng(seed, STREAM.palettePrefix + (def.seedKey ?? style));
|
|
1911
|
+
const table = def.facadeColors;
|
|
1912
|
+
const base = table[Math.floor(rng() * table.length)];
|
|
1913
|
+
const scheme = SCHEMES[Math.floor(rng() * 4)];
|
|
1914
|
+
const offsets = HARMONY_OFFSETS[scheme];
|
|
1915
|
+
const jit = (v, a) => v + (rng() - 0.5) * a;
|
|
1916
|
+
const hsl = (h, s, l) => ({ h: (h % 360 + 360) % 360, s: clamp(s, 0, 1), l: clamp(l, 0, 1) });
|
|
1917
|
+
const H0 = base[1];
|
|
1918
|
+
const H1 = H0 + offsets[0];
|
|
1919
|
+
const H2 = H0 + offsets[1];
|
|
1920
|
+
const draw = { rng, base, H0, H1, H2, jit, hsl };
|
|
1921
|
+
const early = def.early(draw);
|
|
1922
|
+
const { wallMode, sMax } = early;
|
|
1923
|
+
const R = Object.assign({}, early.roles);
|
|
1924
|
+
const ctx = Object.assign({ style, sMax, wallMode, generalFloor: early.generalFloor }, draw);
|
|
1925
|
+
for (const c of registries().ROOM_COLORS) {
|
|
1926
|
+
const [wh, wl] = c.wall(ctx);
|
|
1927
|
+
R["wall_" + c.type] = hsl(wh, c.neutral ? Math.min(0.16, sMax) : clamp(jit(sMax * 0.75, sMax * 0.5), 0.03, sMax), jit(wl, 0.03));
|
|
1928
|
+
const pi = c.floor || early.generalFloor;
|
|
1929
|
+
R["floor_" + c.type] = hsl(pi[0], Math.min(pi[1], wallMode ? 0.18 : 0.3), jit(pi[2], 0.04));
|
|
1930
|
+
}
|
|
1931
|
+
Object.assign(R, def.late(draw, early));
|
|
1932
|
+
registries().STATE_PLUGINS[state]?.adjustPalette?.(R);
|
|
1933
|
+
const adjust = (color, ref, min, preferredDir) => {
|
|
1934
|
+
for (let i = 0; i < 60; i++) {
|
|
1935
|
+
if (Math.abs(lstar(hslToInt(color.h, color.s, color.l)) - lstar(hslToInt(ref.h, ref.s, ref.l))) >= min) return;
|
|
1936
|
+
const refL = lstar(hslToInt(ref.h, ref.s, ref.l));
|
|
1937
|
+
let dir = preferredDir || (refL > 50 ? -1 : 1);
|
|
1938
|
+
if (dir < 0 && color.l <= 0.06 || dir > 0 && color.l >= 0.94) dir = -dir;
|
|
1939
|
+
color.l = clamp(color.l + dir * 0.015, 0.04, 0.96);
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
const types = registries().ROOM_COLORS.map((c) => c.type);
|
|
1943
|
+
for (const c of types) {
|
|
1944
|
+
const w = R["wall_" + c];
|
|
1945
|
+
const p = R["floor_" + c];
|
|
1946
|
+
if (w.s > 0.3) p.s = Math.min(p.s, 0.2);
|
|
1947
|
+
if (p.s > HIGH_SATURATION) p.s = HIGH_SATURATION - 0.05;
|
|
1948
|
+
adjust(p, w, MIN_WALL_FLOOR_CONTRAST + 2, lstar(hslToInt(w.h, w.s, w.l)) > 50 ? -1 : 1);
|
|
1949
|
+
}
|
|
1950
|
+
const floorRef = R[ROLE_FLOOR_REFERENCE] || R["floor_" + types[0]];
|
|
1951
|
+
const fabric = R[ROLE_FABRIC_MAIN];
|
|
1952
|
+
adjust(fabric, floorRef, MIN_FABRIC_FLOOR_CONTRAST + 3);
|
|
1953
|
+
adjust(R[ROLE_FABRIC_ACCENT], floorRef, MIN_FABRIC_FLOOR_CONTRAST + 3);
|
|
1954
|
+
for (const c of types) {
|
|
1955
|
+
const p = R["floor_" + c];
|
|
1956
|
+
if (Math.abs(lstar(hslToInt(fabric.h, fabric.s, fabric.l)) - lstar(hslToInt(p.h, p.s, p.l))) < MIN_FABRIC_FLOOR_CONTRAST)
|
|
1957
|
+
adjust(p, fabric, MIN_FABRIC_FLOOR_CONTRAST + 2, lstar(hslToInt(fabric.h, fabric.s, fabric.l)) > 50 ? -1 : 1);
|
|
1958
|
+
}
|
|
1959
|
+
const out = { meta: { seed, style, state, scheme, base: base[0], accentMode: wallMode ? "wall" : "object" } };
|
|
1960
|
+
for (const k of Object.keys(R)) out[k] = hslToInt(R[k].h, R[k].s, R[k].l);
|
|
1961
|
+
const num = (k) => out[k];
|
|
1962
|
+
const pairOk = (w, p) => Math.abs(lstar(w) - lstar(p)) >= MIN_WALL_FLOOR_CONTRAST + 1 && Math.abs(lstar(num(ROLE_FABRIC_MAIN)) - lstar(p)) >= MIN_FABRIC_FLOOR_CONTRAST + 1;
|
|
1963
|
+
const search = (color, ok) => {
|
|
1964
|
+
const h = intToHsl(color);
|
|
1965
|
+
let bestValue = null;
|
|
1966
|
+
let md = 9;
|
|
1967
|
+
for (let l = 0.02; l <= 0.98; l += 5e-3) {
|
|
1968
|
+
const n = hslToInt(h.h, h.s, l);
|
|
1969
|
+
if (ok(n) && Math.abs(l - h.l) < md) {
|
|
1970
|
+
md = Math.abs(l - h.l);
|
|
1971
|
+
bestValue = n;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return bestValue;
|
|
1975
|
+
};
|
|
1976
|
+
for (const c of types) {
|
|
1977
|
+
if (pairOk(num("wall_" + c), num("floor_" + c))) continue;
|
|
1978
|
+
const n = search(num("floor_" + c), (p) => pairOk(num("wall_" + c), p));
|
|
1979
|
+
if (n != null) {
|
|
1980
|
+
out["floor_" + c] = n;
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
const w = search(num("wall_" + c), (w2) => pairOk(w2, num("floor_" + c)));
|
|
1984
|
+
if (w != null) out["wall_" + c] = w;
|
|
1985
|
+
}
|
|
1986
|
+
return out;
|
|
1987
|
+
}
|
|
1988
|
+
var SIDE_NORMAL = { left: [1, 0], right: [-1, 0], front: [0, 1], back: [0, -1] };
|
|
1989
|
+
var FIXED_FACING = { "u+": [1, 0], "u-": [-1, 0], "v+": [0, 1], "v-": [0, -1] };
|
|
1990
|
+
var SIDE_OPPOSITE = { front: "back", back: "front", left: "right", right: "left" };
|
|
1991
|
+
var WINDOW_SIDE = { front: "front", back: "back", left: "left", right: "right" };
|
|
1992
|
+
function slotDefsOf(plan, room) {
|
|
1993
|
+
const name = room.library || room.type;
|
|
1994
|
+
const key = registries().LIBRARY_ALIAS[name] || name;
|
|
1995
|
+
const library = room.slots ? room.slots : registries().LIBRARIES[key];
|
|
1996
|
+
if (!library) return [];
|
|
1997
|
+
const R = inner(room.parts[0]);
|
|
1998
|
+
let defs = typeof library === "function" ? library({ w: R[2] - R[0], d: R[3] - R[1] }) : library.slice();
|
|
1999
|
+
defs = defs.map((x) => Object.assign({}, x));
|
|
2000
|
+
for (const id of room.without || []) defs = defs.filter((x) => x.id !== id);
|
|
2001
|
+
for (const [id, aj] of Object.entries(room.overrides || {})) {
|
|
2002
|
+
const i = defs.findIndex((x) => x.id === id);
|
|
2003
|
+
if (i < 0) continue;
|
|
2004
|
+
if (aj === null) defs.splice(i, 1);
|
|
2005
|
+
else defs[i] = Object.assign(defs[i], aj);
|
|
2006
|
+
}
|
|
2007
|
+
defs.push(...(room.extras || []).map((x) => Object.assign({}, x)));
|
|
2008
|
+
const out = [];
|
|
2009
|
+
for (const def of defs) {
|
|
2010
|
+
def.parent = def.on;
|
|
2011
|
+
if (def.anchor === "wall" && def.side === "any") for (const l of ["left", "right", "front", "back"]) out.push(Object.assign({}, def, { id: def.id + "@" + l, side: l, group: def.group || def.id }));
|
|
2012
|
+
else if (def.anchor === "corner" && def.corner === "any") for (const l of ["front-left", "front-right", "back-left", "back-right"]) out.push(Object.assign({}, def, { id: def.id + "@" + l, corner: l, group: def.group || def.id }));
|
|
2013
|
+
else if (def.anchor === "window" && def.n == null) {
|
|
2014
|
+
const windowList = windowsOfRoom(plan, room);
|
|
2015
|
+
windowList.forEach((_j, i) => out.push(Object.assign({}, def, { id: def.id + "@w" + i, n: i, group: def.group || def.id })));
|
|
2016
|
+
} else out.push(def);
|
|
2017
|
+
}
|
|
2018
|
+
return out;
|
|
2019
|
+
}
|
|
2020
|
+
function resolveSlots(plan) {
|
|
2021
|
+
const all = [];
|
|
2022
|
+
for (const room of plan.rooms.concat(plan.zones)) {
|
|
2023
|
+
const defs = slotDefsOf(plan, room);
|
|
2024
|
+
const byId2 = {};
|
|
2025
|
+
const zones = room.type === "stairs" ? plan.floors.flatMap((_, floorIndex) => zonesOfFloor(plan, floorIndex)) : zonesOfFloor(plan, room.floor);
|
|
2026
|
+
for (const def of defs) {
|
|
2027
|
+
const s = resolveSlot(plan, room, def, byId2, zones);
|
|
2028
|
+
byId2[def.id] = s;
|
|
2029
|
+
all.push(s);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
return all;
|
|
2033
|
+
}
|
|
2034
|
+
function resolveSlot(plan, room, def, byId2, zones) {
|
|
2035
|
+
const base = {
|
|
2036
|
+
id: def.id,
|
|
2037
|
+
room: room.id,
|
|
2038
|
+
floor: room.floor,
|
|
2039
|
+
outdoor: room.outdoor,
|
|
2040
|
+
anchor: def.anchor,
|
|
2041
|
+
side: def.side || def.corner || null,
|
|
2042
|
+
cats: def.cats,
|
|
2043
|
+
slide: def.slide,
|
|
2044
|
+
required: !!def.required,
|
|
2045
|
+
chance: def.required ? 1 : def.chance == null ? 1 : def.chance,
|
|
2046
|
+
clearance: def.clearance == null ? 0.5 : def.clearance,
|
|
2047
|
+
front: def.facing || "center",
|
|
2048
|
+
group: def.group || null,
|
|
2049
|
+
requires: def.requires || null,
|
|
2050
|
+
on: def.parent || null,
|
|
2051
|
+
target: def.target || null,
|
|
2052
|
+
valid: true,
|
|
2053
|
+
reason: null
|
|
2054
|
+
};
|
|
2055
|
+
const invalid = (m) => Object.assign(base, { valid: false, reason: m, x: 0, z: 0, ry: 0, w: 0, d: 0, frontU: 0, frontV: 1, aabb: [0, 0, 0, 0], _cell: null });
|
|
2056
|
+
if (def.anchor === "on" || def.anchor === "inside") {
|
|
2057
|
+
const parent = def.parent ? byId2[def.parent] : void 0;
|
|
2058
|
+
if (!parent || !parent.valid) return invalid("invalid_parent");
|
|
2059
|
+
return Object.assign(base, { x: parent.x, z: parent.z, ry: parent.ry, w: parent.w, d: parent.d, frontU: parent.frontU, frontV: parent.frontV, aabb: parent.aabb, _cell: parent._cell });
|
|
2060
|
+
}
|
|
2061
|
+
const size = def.size;
|
|
2062
|
+
const roomCenter = (() => {
|
|
2063
|
+
const R0 = inner(room.parts[0]);
|
|
2064
|
+
return [(R0[0] + R0[2]) / 2, (R0[1] + R0[3]) / 2];
|
|
2065
|
+
})();
|
|
2066
|
+
const openings = openingsOfFloor(plan, room.floor);
|
|
2067
|
+
let point = [0, 0];
|
|
2068
|
+
const targetPoint = (rule2) => {
|
|
2069
|
+
if (rule2 === "door") {
|
|
2070
|
+
let best = null;
|
|
2071
|
+
let bestDist = 1e9;
|
|
2072
|
+
for (const ab of openings) {
|
|
2073
|
+
const dist = Math.hypot(ab.u - point[0], ab.v - point[1]);
|
|
2074
|
+
if (dist < bestDist) {
|
|
2075
|
+
bestDist = dist;
|
|
2076
|
+
best = ab;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
return best ? [best.u, best.v] : roomCenter;
|
|
2080
|
+
}
|
|
2081
|
+
if (rule2 === "target") {
|
|
2082
|
+
const t = byId2[def.target];
|
|
2083
|
+
return t && t.valid ? [t.x, t.z] : roomCenter;
|
|
2084
|
+
}
|
|
2085
|
+
return roomCenter;
|
|
2086
|
+
};
|
|
2087
|
+
const free = (rect) => !!def.floorFree || !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
|
|
2088
|
+
const rule = def.facing || "center";
|
|
2089
|
+
let R = inner(room.parts[Math.min(def.part || 0, room.parts.length - 1)]);
|
|
2090
|
+
if (def.anchor === "wall" || def.anchor === "window") {
|
|
2091
|
+
let side = def.side;
|
|
2092
|
+
let a = null;
|
|
2093
|
+
let win = null;
|
|
2094
|
+
if (def.anchor === "window") {
|
|
2095
|
+
const windowList = windowsOfRoom(plan, room);
|
|
2096
|
+
win = windowList[def.n];
|
|
2097
|
+
if (!win) return invalid("no_window");
|
|
2098
|
+
const found2 = win;
|
|
2099
|
+
side = room.outdoor ? SIDE_OPPOSITE[win.side] : WINDOW_SIDE[win.side];
|
|
2100
|
+
const cand = room.parts.map(inner).find(
|
|
2101
|
+
(r) => (found2.axis === "u" ? found2.u >= r[0] - TOL && found2.u <= r[2] + TOL : found2.v >= r[1] - TOL && found2.v <= r[3] + TOL) && (side === "left" && eq(r[0], found2.pos + MARGIN) || side === "right" && eq(r[2], found2.pos - MARGIN) || side === "front" && eq(r[1], found2.pos + MARGIN) || side === "back" && eq(r[3], found2.pos - MARGIN))
|
|
2102
|
+
);
|
|
2103
|
+
if (!cand) return invalid("window_outside_room");
|
|
2104
|
+
R = cand;
|
|
2105
|
+
a = win.axis === "u" ? win.u : win.v;
|
|
2106
|
+
}
|
|
2107
|
+
const n = SIDE_NORMAL[side];
|
|
2108
|
+
if (!n) return invalid("invalid_side");
|
|
2109
|
+
const horizontal = side === "front" || side === "back";
|
|
2110
|
+
const a0 = horizontal ? R[0] : R[1];
|
|
2111
|
+
const a1 = horizontal ? R[2] : R[3];
|
|
2112
|
+
const len = a1 - a0;
|
|
2113
|
+
const depth = size[1];
|
|
2114
|
+
if (depth > (horizontal ? R[3] - R[1] : R[2] - R[0]) + EPS) return invalid("larger_than_room");
|
|
2115
|
+
const hw = Math.min(size[0], len) / 2;
|
|
2116
|
+
const cell = (ac) => side === "left" ? [R[0], ac - hw, R[0] + depth, ac + hw] : side === "right" ? [R[2] - depth, ac - hw, R[2], ac + hw] : side === "front" ? [ac - hw, R[1], ac + hw, R[1] + depth] : [ac - hw, R[3] - depth, ac + hw, R[3]];
|
|
2117
|
+
const nominal = a != null ? a : a0 + hw + (def.pos == null ? 0.5 : def.pos) * Math.max(0, len - 2 * hw) + (def.shift || 0);
|
|
2118
|
+
const wp = side === "left" ? R[0] - MARGIN : side === "right" ? R[2] + MARGIN : side === "front" ? R[1] - MARGIN : R[3] + MARGIN;
|
|
2119
|
+
const pa = clamp(nominal, a0 + hw, a1 - hw);
|
|
2120
|
+
let found = null;
|
|
2121
|
+
const steps = [0];
|
|
2122
|
+
if (def.anchor !== "window") for (let k = 1; k <= 60; k++) steps.push(k, -k);
|
|
2123
|
+
for (const k of steps) {
|
|
2124
|
+
const ac = pa + k * 0.05;
|
|
2125
|
+
if (ac - hw < a0 - EPS || ac + hw > a1 + EPS) continue;
|
|
2126
|
+
if (free(cell(ac))) {
|
|
2127
|
+
found = ac;
|
|
2128
|
+
break;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
if (found == null) return invalid("blocked_by_door");
|
|
2132
|
+
const sp = horizontal ? [found, wp - n[1] * 0.1] : [wp - n[0] * 0.1, found];
|
|
2133
|
+
if (isOpenSide(plan, room, sp[0], sp[1])) return invalid("open_wall");
|
|
2134
|
+
const rect = cell(found);
|
|
2135
|
+
return Object.assign(base, { side, x: r2((rect[0] + rect[2]) / 2), z: r2((rect[1] + rect[3]) / 2), frontU: n[0], frontV: n[1], ry: r2(Math.atan2(n[0], n[1])), w: size[0], d: size[1], aabb: rect.map(r2), _cell: { rect, f: n, enclosed: [side], R, size } });
|
|
2136
|
+
}
|
|
2137
|
+
if (def.anchor === "corner") {
|
|
2138
|
+
const [cornerV, cornerU] = def.corner.split("-");
|
|
2139
|
+
const normalV = SIDE_NORMAL[cornerV];
|
|
2140
|
+
const normalU = SIDE_NORMAL[cornerU];
|
|
2141
|
+
if (!normalV || !normalU) return invalid("invalid_corner");
|
|
2142
|
+
const kx = cornerU === "left" ? R[0] : R[2];
|
|
2143
|
+
const kz = cornerV === "front" ? R[1] : R[3];
|
|
2144
|
+
point = [kx, kz];
|
|
2145
|
+
let f;
|
|
2146
|
+
if (FIXED_FACING[rule]) {
|
|
2147
|
+
f = FIXED_FACING[rule];
|
|
2148
|
+
if (!(f[0] === normalU[0] && f[1] === normalU[1] || f[0] === normalV[0] && f[1] === normalV[1])) f = normalU;
|
|
2149
|
+
} else {
|
|
2150
|
+
const t = targetPoint(rule);
|
|
2151
|
+
f = Math.abs(t[0] - kx) >= Math.abs(t[1] - kz) ? normalU : normalV;
|
|
2152
|
+
}
|
|
2153
|
+
const [extentU, extentV] = extent(size, f);
|
|
2154
|
+
if (extentU > R[2] - R[0] + EPS || extentV > R[3] - R[1] + EPS) return invalid("larger_than_room");
|
|
2155
|
+
const rect = [cornerU === "left" ? R[0] : R[2] - extentU, cornerV === "front" ? R[1] : R[3] - extentV, cornerU === "left" ? R[0] + extentU : R[2], cornerV === "front" ? R[1] + extentV : R[3]];
|
|
2156
|
+
if (!free(rect)) return invalid("blocked_by_door");
|
|
2157
|
+
for (const sp of [[kx - normalU[0] * (MARGIN + 0.1), (rect[1] + rect[3]) / 2], [(rect[0] + rect[2]) / 2, kz - normalV[1] * (MARGIN + 0.1)]]) if (isOpenSide(plan, room, sp[0], sp[1])) return invalid("open_wall");
|
|
2158
|
+
return Object.assign(base, { x: r2((rect[0] + rect[2]) / 2), z: r2((rect[1] + rect[3]) / 2), frontU: f[0], frontV: f[1], ry: r2(Math.atan2(f[0], f[1])), w: size[0], d: size[1], aabb: rect.map(r2), _cell: { rect, f, enclosed: [cornerU, cornerV], R, size } });
|
|
2159
|
+
}
|
|
2160
|
+
if (def.anchor === "center") {
|
|
2161
|
+
let cx = R[0] + (def.fx == null ? 0.5 : def.fx) * (R[2] - R[0]) + (def.dx || 0);
|
|
2162
|
+
let cz = R[1] + (def.fz == null ? 0.5 : def.fz) * (R[3] - R[1]) + (def.dz || 0);
|
|
2163
|
+
point = [cx, cz];
|
|
2164
|
+
let f;
|
|
2165
|
+
if (FIXED_FACING[rule]) f = FIXED_FACING[rule];
|
|
2166
|
+
else {
|
|
2167
|
+
const t = targetPoint(rule);
|
|
2168
|
+
const dx = t[0] - cx;
|
|
2169
|
+
const dz = t[1] - cz;
|
|
2170
|
+
f = Math.hypot(dx, dz) < 0.05 ? [0, 1] : cardinal(dx, dz);
|
|
2171
|
+
}
|
|
2172
|
+
const [extentU, extentV] = extent(size, f);
|
|
2173
|
+
if (extentU > R[2] - R[0] + EPS || extentV > R[3] - R[1] + EPS) return invalid("larger_than_room");
|
|
2174
|
+
cx = clamp(cx, R[0] + extentU / 2, R[2] - extentU / 2);
|
|
2175
|
+
cz = clamp(cz, R[1] + extentV / 2, R[3] - extentV / 2);
|
|
2176
|
+
const rect = [cx - extentU / 2, cz - extentV / 2, cx + extentU / 2, cz + extentV / 2];
|
|
2177
|
+
if (!free(rect)) return invalid("blocked_by_door");
|
|
2178
|
+
return Object.assign(base, { x: r2(cx), z: r2(cz), frontU: f[0], frontV: f[1], ry: r2(Math.atan2(f[0], f[1])), w: size[0], d: size[1], aabb: rect.map(r2), _cell: { rect, f, enclosed: [], R, size } });
|
|
2179
|
+
}
|
|
2180
|
+
return invalid("unknown_anchor");
|
|
2181
|
+
}
|
|
2182
|
+
function templateOf(t) {
|
|
2183
|
+
if (typeof t === "string") {
|
|
2184
|
+
if (!registries().TEMPLATES[t]) throw new Error("Unknown template: " + t);
|
|
2185
|
+
return registries().TEMPLATES[t];
|
|
2186
|
+
}
|
|
2187
|
+
return t;
|
|
2188
|
+
}
|
|
2189
|
+
var cleanSlot = (s) => {
|
|
2190
|
+
const o = {};
|
|
2191
|
+
for (const k of Object.keys(s)) if (k[0] !== "_") o[k] = s[k];
|
|
2192
|
+
o.cats = Object.keys(s.cats || {});
|
|
2193
|
+
return o;
|
|
2194
|
+
};
|
|
2195
|
+
function slots(template, options) {
|
|
2196
|
+
const tpl = templateOf(template);
|
|
2197
|
+
const o = options || {};
|
|
2198
|
+
const plan = buildPlan(tpl, o.w, o.d, o);
|
|
2199
|
+
const locale = o.locale || "en";
|
|
2200
|
+
return resolveSlots(plan).map(cleanSlot).map((s) => s.reason ? Object.assign(s, { reason: translate("reasons", s.reason, locale) }) : s);
|
|
2201
|
+
}
|
|
2202
|
+
function pickState(requested, seed) {
|
|
2203
|
+
if (requested && registries().STATE_PLUGINS[requested]) return requested;
|
|
2204
|
+
const dist = registries().stateDistribution.weights;
|
|
2205
|
+
if (!dist) return "normal";
|
|
2206
|
+
const x = createRng(seed, registries().stateDistribution.seedKey)();
|
|
2207
|
+
let acc = 0;
|
|
2208
|
+
const keys = Object.keys(dist);
|
|
2209
|
+
for (const k of keys) {
|
|
2210
|
+
acc += dist[k];
|
|
2211
|
+
if (x < acc) return k;
|
|
2212
|
+
}
|
|
2213
|
+
return keys[keys.length - 1];
|
|
2214
|
+
}
|
|
2215
|
+
function colorOf(m, seed) {
|
|
2216
|
+
const spec = m.colorSpec || registries().CATALOG[m.type]?.color || { role: "wood" };
|
|
2217
|
+
if ("role" in spec) return spec.role;
|
|
2218
|
+
if ("hex" in spec) return spec.hex;
|
|
2219
|
+
return spec.pick[hashString(seed + "|" + (m._seedId ?? m.id)) % spec.pick.length];
|
|
2220
|
+
}
|
|
2221
|
+
function applyState(plan, items, ctx, seed, state) {
|
|
2222
|
+
const plugin = registries().STATE_PLUGINS[state];
|
|
2223
|
+
if (!plugin || state === "normal") return [];
|
|
2224
|
+
const c = {
|
|
2225
|
+
plan,
|
|
2226
|
+
items,
|
|
2227
|
+
seed,
|
|
2228
|
+
state,
|
|
2229
|
+
rng: createRng(seed, plugin.seedKey ?? "state:" + state),
|
|
2230
|
+
ctx,
|
|
2231
|
+
kit: placementKit,
|
|
2232
|
+
catalog: registries().CATALOG,
|
|
2233
|
+
wallTopHeights: registries().WALL_TOP_HEIGHTS
|
|
2234
|
+
};
|
|
2235
|
+
return plugin.apply(c);
|
|
2236
|
+
}
|
|
2237
|
+
var loc = (key, locale, ns = "labels", vars) => key ? translate(ns, key, locale, vars) : key;
|
|
2238
|
+
function generate(template, options) {
|
|
2239
|
+
const tpl = templateOf(template);
|
|
2240
|
+
const o = options || {};
|
|
2241
|
+
const seed = o.seed == null ? 1 : o.seed;
|
|
2242
|
+
const locale = o.locale || "en";
|
|
2243
|
+
let op = Object.assign({}, o, { seed });
|
|
2244
|
+
const zone = resolveZone(o.zone);
|
|
2245
|
+
const adjustments = [];
|
|
2246
|
+
if (zone) op = clampOptionsToZone(tpl, op, zone, adjustments);
|
|
2247
|
+
const plan = buildPlan(tpl, op.w, op.d, op, { zone, adjustments });
|
|
2248
|
+
const all = resolveSlots(plan);
|
|
2249
|
+
const items = [];
|
|
2250
|
+
const missing = [];
|
|
2251
|
+
for (const room of plan.rooms.concat(plan.zones)) {
|
|
2252
|
+
const sl = all.filter((s) => s.room === room.id && s.floor === room.floor);
|
|
2253
|
+
const r = fillRoom(plan, room, sl, createRng(seed, STREAM.roomPrefix + (room.seedKey ?? registries().SEED_KEYS.rooms[room.id] ?? room.id)));
|
|
2254
|
+
items.push(...r.items);
|
|
2255
|
+
missing.push(...r.missing);
|
|
2256
|
+
}
|
|
2257
|
+
const ctx = runPostProcess(plan, items, seed);
|
|
2258
|
+
const state = pickState(o.state, seed);
|
|
2259
|
+
const stateItemsAll = applyState(plan, items, ctx, seed, state);
|
|
2260
|
+
const colors2 = palette({ seed, style: plan.style, state });
|
|
2261
|
+
const strip = (m) => {
|
|
2262
|
+
const x = {};
|
|
2263
|
+
for (const k of Object.keys(m)) if (k[0] !== "_" && k !== "colorSpec") x[k] = m[k];
|
|
2264
|
+
return x;
|
|
2265
|
+
};
|
|
2266
|
+
const L = plan.lot;
|
|
2267
|
+
const lot = L ? {
|
|
2268
|
+
rect: L.rect.map(r2),
|
|
2269
|
+
setback: r2(L.setback),
|
|
2270
|
+
backyard: r2(L.backyard),
|
|
2271
|
+
sideYard: L.sideYard,
|
|
2272
|
+
style: L.style,
|
|
2273
|
+
pool: L.pool,
|
|
2274
|
+
zones: plan.zones.map((z) => ({ id: z.id, type: z.type, label: z.label, name: loc(z.label, locale), parts: z.parts, ground: z.ground })),
|
|
2275
|
+
perimeter: L.perimeter.map((s) => Object.assign({ color: "wall" }, s)),
|
|
2276
|
+
gates: plan.gates.map((g) => ({ id: g.id, type: g.type, model: g.model, u: g.u, v: g.v, axis: g.axis, width: g.width, height: g.height, side: g.side, to: g.to, material: g.material, open: !!g.open, color: "gate" })),
|
|
2277
|
+
paths: plan.paths.map((c) => ({ id: c.id, type: c.type, from: c.from, to: c.to, width: c.width, rects: c.rects, points: c.points }))
|
|
2278
|
+
} : null;
|
|
2279
|
+
const facade = Object.assign({}, plan.facade, {
|
|
2280
|
+
color: { name: loc(colors2.meta.base, locale, "facadeColors"), key: colors2.meta.base, hex: hexString(colors2.facade) },
|
|
2281
|
+
roles: { facade: "facade", detail: "facade_detail", roof: "roof", wall: "wall", gate: "gate" }
|
|
2282
|
+
});
|
|
2283
|
+
const itemsOut = items.map((m) => {
|
|
2284
|
+
const out = strip(m);
|
|
2285
|
+
out.color = colorOf(m, seed);
|
|
2286
|
+
return out;
|
|
2287
|
+
});
|
|
2288
|
+
const stateItemsOut = stateItemsAll.map((m) => {
|
|
2289
|
+
const out = strip(m);
|
|
2290
|
+
if (m.category) out.color = colorOf(m, seed);
|
|
2291
|
+
return out;
|
|
2292
|
+
});
|
|
2293
|
+
const base = {
|
|
2294
|
+
template: tpl.id,
|
|
2295
|
+
name: translate("templates", tpl.id, locale),
|
|
2296
|
+
use: useOf(tpl),
|
|
2297
|
+
seed,
|
|
2298
|
+
style: plan.style,
|
|
2299
|
+
state,
|
|
2300
|
+
w: plan.w,
|
|
2301
|
+
d: plan.d,
|
|
2302
|
+
floors: plan.floors.map((f) => ({ index: f.index, label: f.name, name: f.number != null ? translate("labels", "floor_n", locale, { n: f.number }) : loc(f.name, locale), number: f.number, y0: f.y0, ceiling: f.ceiling })),
|
|
2303
|
+
rooms: plan.rooms.map((c) => ({
|
|
2304
|
+
id: c.id,
|
|
2305
|
+
type: c.type,
|
|
2306
|
+
label: c.label,
|
|
2307
|
+
name: loc(c.label, locale),
|
|
2308
|
+
floor: c.floor,
|
|
2309
|
+
u0: c.parts[0][0],
|
|
2310
|
+
v0: c.parts[0][1],
|
|
2311
|
+
u1: c.parts[0][2],
|
|
2312
|
+
v1: c.parts[0][3],
|
|
2313
|
+
parts: c.parts,
|
|
2314
|
+
link: c.link,
|
|
2315
|
+
ceiling: c.ceiling || plan.floors[c.floor].ceiling,
|
|
2316
|
+
color: { wall: "wall_" + c.type, floor: "floor_" + c.type }
|
|
2317
|
+
})),
|
|
2318
|
+
walls: plan.walls,
|
|
2319
|
+
doors: plan.doors,
|
|
2320
|
+
windows: plan.windows,
|
|
2321
|
+
openPairs: [].concat(...Object.values(plan.openPairs)),
|
|
2322
|
+
facade,
|
|
2323
|
+
lot,
|
|
2324
|
+
items: itemsOut,
|
|
2325
|
+
stateItems: stateItemsOut,
|
|
2326
|
+
colors: colors2,
|
|
2327
|
+
building: tpl.building ? tpl.building(plan.w, plan.d, op) : null,
|
|
2328
|
+
missing
|
|
2329
|
+
};
|
|
2330
|
+
if (tpl.singleFloor) base.floorNumber = op.floor || 3;
|
|
2331
|
+
const result = Object.assign(base, { partyWalls: tpl.partyWalls || [] });
|
|
2332
|
+
if (plan.config) result.config = plan.config;
|
|
2333
|
+
runResultExtensions(plan, result, seed, op);
|
|
2334
|
+
if (zone) {
|
|
2335
|
+
const report = { zone: zone.id, adjustments, violations: checkLot(zone, lot, Object.assign({}, result, { rooms: plan.rooms })) };
|
|
2336
|
+
result.zoning = report;
|
|
2337
|
+
}
|
|
2338
|
+
return result;
|
|
2339
|
+
}
|
|
2340
|
+
var isSolid = (m) => !m.flat && !m.on && (!m.decor || m.mount === "floor");
|
|
2341
|
+
function validate(result, options) {
|
|
2342
|
+
const locale = options?.locale || result.locale || "en";
|
|
2343
|
+
const t = (key, vars) => translate("messages", key, locale, vars);
|
|
2344
|
+
const errors = [];
|
|
2345
|
+
const solids = result.items.filter(isSolid);
|
|
2346
|
+
for (let i = 0; i < solids.length; i++)
|
|
2347
|
+
for (let j = i + 1; j < solids.length; j++)
|
|
2348
|
+
if (solids[i].floor === solids[j].floor && !solids[i].barricade && !solids[j].barricade && overlaps(solids[i].aabb, solids[j].aabb, 0.011)) errors.push(t("overlap", { a: solids[i].id, b: solids[j].id }));
|
|
2349
|
+
for (const m of solids) {
|
|
2350
|
+
if (m.barricade) continue;
|
|
2351
|
+
for (const p of result.doors) if (p.floor === (m.outdoor ? 0 : m.floor) && p.zones.some((z) => overlaps(m.aabb, z, 0.011))) errors.push(t("door_blocked", { id: m.id }));
|
|
2352
|
+
if (result.lot) {
|
|
2353
|
+
for (const g of result.lot.gates) if (m.outdoor && openingZones(g.u, g.v, "u", g.width, DOOR_CLEARANCE).some((z) => overlaps(m.aabb, z, 0.011))) errors.push(t("gate_blocked", { id: m.id }));
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
for (const f of result.missing) errors.push(t("missing_required", { room: f.room, slot: f.slot }));
|
|
2357
|
+
if (result.zoning) for (const v of result.zoning.violations) errors.push(t("zone_violation", { zone: result.zoning.zone, rule: v.rule, actual: v.actual, limit: v.limit }));
|
|
2358
|
+
return errors;
|
|
2359
|
+
}
|
|
2360
|
+
function limitsOf(template, options) {
|
|
2361
|
+
const tpl = templateOf(template);
|
|
2362
|
+
const op = options || {};
|
|
2363
|
+
return { limits: tpl.limitsFn ? tpl.limitsFn(op) : tpl.limits, defaults: tpl.defaultsFn ? tpl.defaultsFn(op) : tpl.defaults };
|
|
2364
|
+
}
|
|
2365
|
+
function listTemplates(locale = "en") {
|
|
2366
|
+
return Object.values(registries().TEMPLATES).map((t) => {
|
|
2367
|
+
const a = floorsOf(t, {});
|
|
2368
|
+
return {
|
|
2369
|
+
id: t.id,
|
|
2370
|
+
name: translate("templates", t.id, locale),
|
|
2371
|
+
use: useOf(t),
|
|
2372
|
+
style: typeof t.style === "function" ? t.style({}) : t.style,
|
|
2373
|
+
limits: t.limits,
|
|
2374
|
+
defaults: t.defaults,
|
|
2375
|
+
floors: a.length,
|
|
2376
|
+
floorRange: t.floorRange || null,
|
|
2377
|
+
lot: !!t.lot,
|
|
2378
|
+
options: t.options || {}
|
|
2379
|
+
};
|
|
2380
|
+
});
|
|
119
2381
|
}
|
|
120
2382
|
function registerGroundItems() {
|
|
121
2383
|
const flat = { category: "ground_cover", w: 1, d: 1, h: 0, decor: true, mount: "ground", flat: true };
|
|
@@ -127,6 +2389,14 @@ var GameUrban = (() => {
|
|
|
127
2389
|
{ type: "dirt", ...flat, color: { role: "dirt" } }
|
|
128
2390
|
]);
|
|
129
2391
|
}
|
|
2392
|
+
var slotKit = {
|
|
2393
|
+
wall: (id, side, pos, size, cats, o) => Object.assign({ id, anchor: "wall", side, pos, size, cats }, o),
|
|
2394
|
+
corner: (id, corner, size, cats, o) => Object.assign({ id, anchor: "corner", corner, size, cats }, o),
|
|
2395
|
+
center: (id, fx, fz, size, cats, o) => Object.assign({ id, anchor: "center", fx, fz, size, cats }, o),
|
|
2396
|
+
windowSlot: (id, size, cats, o) => Object.assign({ id, anchor: "window", size, cats }, o),
|
|
2397
|
+
on: (id, parent, cats, o) => Object.assign({ id, anchor: "on", on: parent, cats }, o),
|
|
2398
|
+
inside: (id, parent, cats, o) => Object.assign({ id, anchor: "inside", on: parent, cats }, o)
|
|
2399
|
+
};
|
|
130
2400
|
var en_default = {
|
|
131
2401
|
labels: {
|
|
132
2402
|
floor_n: "Floor {n}",
|
|
@@ -321,6 +2591,82 @@ var GameUrban = (() => {
|
|
|
321
2591
|
reason_permeable_min: "\xC1rea perme\xE1vel da zona"
|
|
322
2592
|
}
|
|
323
2593
|
};
|
|
2594
|
+
var version = "0.1.0";
|
|
2595
|
+
function buildEngine(scope) {
|
|
2596
|
+
const scoped = (fn) => ((...args) => withRegistries(scope, () => fn(...args)));
|
|
2597
|
+
return {
|
|
2598
|
+
version,
|
|
2599
|
+
generate: scoped(generate),
|
|
2600
|
+
slots: scoped(slots),
|
|
2601
|
+
validate: scoped(validate),
|
|
2602
|
+
palette: scoped(palette),
|
|
2603
|
+
limitsOf: scoped(limitsOf),
|
|
2604
|
+
listTemplates: scoped(listTemplates),
|
|
2605
|
+
listStates: scoped(listStates),
|
|
2606
|
+
// content registration
|
|
2607
|
+
registerItem: scoped(registerItem),
|
|
2608
|
+
registerItems: scoped(registerItems),
|
|
2609
|
+
registerLibrary: scoped(registerLibrary),
|
|
2610
|
+
registerLibraryAlias: scoped(registerLibraryAlias),
|
|
2611
|
+
registerTemplate: scoped(registerTemplate),
|
|
2612
|
+
registerStyle: scoped(registerStyle),
|
|
2613
|
+
registerPaletteStyle: scoped(registerPaletteStyle),
|
|
2614
|
+
registerRoomColors: scoped(registerRoomColors),
|
|
2615
|
+
registerDecorProfile: scoped(registerDecorProfile),
|
|
2616
|
+
registerPass: scoped(registerPass),
|
|
2617
|
+
registerGroundOverlay: scoped(registerGroundOverlay),
|
|
2618
|
+
registerWallTop: scoped(registerWallTop),
|
|
2619
|
+
registerSeedKeys: scoped(registerSeedKeys),
|
|
2620
|
+
registerState: scoped(registerState),
|
|
2621
|
+
setStateDistribution: scoped(setStateDistribution),
|
|
2622
|
+
registerLocale: scoped(registerLocale),
|
|
2623
|
+
registerResultExtension: scoped(registerResultExtension),
|
|
2624
|
+
registerFindsProvider: scoped(registerFindsProvider),
|
|
2625
|
+
findsFor: scoped(findsFor),
|
|
2626
|
+
findsAnswer: scoped(findsAnswer),
|
|
2627
|
+
registerViewerLayer: scoped(registerViewerLayer),
|
|
2628
|
+
viewerLayers: scope.VIEWER_LAYERS,
|
|
2629
|
+
blocksWindow: scoped(blocksWindow),
|
|
2630
|
+
// zoning
|
|
2631
|
+
defineZone: scoped(defineZone),
|
|
2632
|
+
getZone: scoped(getZone),
|
|
2633
|
+
listZones: scoped(listZones),
|
|
2634
|
+
checkLot: scoped(checkLot),
|
|
2635
|
+
registerPermeableGround: scoped(registerPermeableGround),
|
|
2636
|
+
// kits and data
|
|
2637
|
+
slotKit,
|
|
2638
|
+
translate: scoped(translate),
|
|
2639
|
+
listLocales: scoped(listLocales),
|
|
2640
|
+
templates: scope.TEMPLATES,
|
|
2641
|
+
catalog: scope.CATALOG,
|
|
2642
|
+
libraries: scope.LIBRARIES,
|
|
2643
|
+
styles: scope.STYLES,
|
|
2644
|
+
paletteStyles: scope.PALETTE_STYLES,
|
|
2645
|
+
createRng,
|
|
2646
|
+
mulberry32,
|
|
2647
|
+
hashString,
|
|
2648
|
+
overlaps,
|
|
2649
|
+
windowZone,
|
|
2650
|
+
openingZones,
|
|
2651
|
+
color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
|
|
2652
|
+
constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
function seedBase(scope) {
|
|
2656
|
+
withRegistries(scope, () => {
|
|
2657
|
+
registerGroundItems();
|
|
2658
|
+
registerPermeableGround("lawn", "dirt");
|
|
2659
|
+
registerLocale("en", en_default);
|
|
2660
|
+
registerLocale("pt-BR", pt_BR_default);
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
var defaultEngine;
|
|
2664
|
+
function defaultFloorplan() {
|
|
2665
|
+
if (defaultEngine) return defaultEngine;
|
|
2666
|
+
seedBase(getDefaultRegistries());
|
|
2667
|
+
defaultEngine = buildEngine(getDefaultRegistries());
|
|
2668
|
+
return defaultEngine;
|
|
2669
|
+
}
|
|
324
2670
|
function createSilhouetteRandom(seed) {
|
|
325
2671
|
let state = seed;
|
|
326
2672
|
return () => {
|
|
@@ -521,10 +2867,7 @@ var GameUrban = (() => {
|
|
|
521
2867
|
}
|
|
522
2868
|
return b.finish({ x: p.x, z: p.z, width: w + 0.5, depth: d + 0.5, height: 4.6 });
|
|
523
2869
|
}
|
|
524
|
-
|
|
525
|
-
registerPermeableGround("lawn", "dirt");
|
|
526
|
-
registerLocale("en", en_default);
|
|
527
|
-
registerLocale("pt-BR", pt_BR_default);
|
|
2870
|
+
var engine = defaultFloorplan();
|
|
528
2871
|
|
|
529
2872
|
// src/scene.ts
|
|
530
2873
|
var ROOT2 = -1;
|
|
@@ -537,7 +2880,7 @@ var GameUrban = (() => {
|
|
|
537
2880
|
const parts = [];
|
|
538
2881
|
const items = [];
|
|
539
2882
|
const windows = { lit: [], dark: [] };
|
|
540
|
-
const lights = options.budget ??
|
|
2883
|
+
const lights = options.budget ?? createLightAllowance(Number.POSITIVE_INFINITY);
|
|
541
2884
|
const pick = (list) => list[Math.floor(random() * list.length)];
|
|
542
2885
|
const group = (x, y, z, ry = 0, parent = ROOT2) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
|
|
543
2886
|
const box = (x, y, z, width, height, depth, color, o = {}) => {
|
|
@@ -838,7 +3181,7 @@ var GameUrban = (() => {
|
|
|
838
3181
|
var lerp = (a, b, t) => a + (b - a) * t;
|
|
839
3182
|
function fillBlock(b, cx, cz, size, minHeight, maxHeight, options = {}) {
|
|
840
3183
|
const placed = [];
|
|
841
|
-
const
|
|
3184
|
+
const palette2 = options.palette ?? DEFAULT_FACADES;
|
|
842
3185
|
const half = (size - STREET_MARGIN) / 2;
|
|
843
3186
|
const k = options.k || DEFAULT_PARTITIONS;
|
|
844
3187
|
const lot = (size - STREET_MARGIN) / k;
|
|
@@ -864,7 +3207,7 @@ var GameUrban = (() => {
|
|
|
864
3207
|
}
|
|
865
3208
|
const x = cx - half + lot * (i + 0.5);
|
|
866
3209
|
const z = cz - half + lot * (j + 0.5);
|
|
867
|
-
const color = b.pick(
|
|
3210
|
+
const color = b.pick(palette2);
|
|
868
3211
|
placed.push((options.draw ?? drawBuilding)(b, x, z, width, depth, height, color, extra ? { ...options, ...extra } : options));
|
|
869
3212
|
}
|
|
870
3213
|
}
|
|
@@ -894,16 +3237,16 @@ var GameUrban = (() => {
|
|
|
894
3237
|
b.prism(x, 0, z, w, w * 0.7, d, color ?? colors(c).tent, -1, ry);
|
|
895
3238
|
}
|
|
896
3239
|
function drawPine(b, x, z, scale = 1, y = 0, c) {
|
|
897
|
-
const
|
|
898
|
-
b.cylinder(x, y, z, 0.12 * scale, 0.16 * scale, 1 * scale,
|
|
899
|
-
const green = b.pick(
|
|
3240
|
+
const palette2 = colors(c);
|
|
3241
|
+
b.cylinder(x, y, z, 0.12 * scale, 0.16 * scale, 1 * scale, palette2.trunk);
|
|
3242
|
+
const green = b.pick(palette2.pines);
|
|
900
3243
|
b.cone(x, y + 0.8 * scale, z, 1.1 * scale, 2.2 * scale, green);
|
|
901
3244
|
b.cone(x, y + 2.1 * scale, z, 0.8 * scale, 1.8 * scale, green);
|
|
902
3245
|
}
|
|
903
3246
|
function drawTree(b, x, z, scale = 1, c) {
|
|
904
|
-
const
|
|
905
|
-
b.cylinder(x, 0, z, 0.12 * scale, 0.18 * scale, 1.8 * scale,
|
|
906
|
-
b.crown(x, 2.6 * scale, z, 1.3 * scale, b.pick(
|
|
3247
|
+
const palette2 = colors(c);
|
|
3248
|
+
b.cylinder(x, 0, z, 0.12 * scale, 0.18 * scale, 1.8 * scale, palette2.trunk);
|
|
3249
|
+
b.crown(x, 2.6 * scale, z, 1.3 * scale, b.pick(palette2.crowns));
|
|
907
3250
|
}
|
|
908
3251
|
function drawContainer(b, x, y, z, ry, color) {
|
|
909
3252
|
const group = b.group(x, y, z, ry);
|
|
@@ -911,11 +3254,11 @@ var GameUrban = (() => {
|
|
|
911
3254
|
for (let k = -2; k <= 2; k++) b.box(k * 0.5, 0, 3.04, 0.06, 2.6, 0.04, 1710618, { parent: group });
|
|
912
3255
|
}
|
|
913
3256
|
function drawWatchtower(b, x, z, searchlight = false, c) {
|
|
914
|
-
const
|
|
915
|
-
for (const a of [-1, 1]) for (const d of [-1, 1]) b.box(x + a * 1.2, 0, z + d * 1.2, 0.25, 7, 0.25,
|
|
916
|
-
b.box(x, 7, z, 3.2, 1.2, 3.2,
|
|
917
|
-
b.box(x, 8.2, z, 3.6, 0.15, 3.6,
|
|
918
|
-
for (const side of [-1.5, 1.5]) b.box(x + side, 8.2, z, 0.15, 1.6, 0.15,
|
|
3257
|
+
const palette2 = colors(c).watchtower;
|
|
3258
|
+
for (const a of [-1, 1]) for (const d of [-1, 1]) b.box(x + a * 1.2, 0, z + d * 1.2, 0.25, 7, 0.25, palette2.leg);
|
|
3259
|
+
b.box(x, 7, z, 3.2, 1.2, 3.2, palette2.cabin);
|
|
3260
|
+
b.box(x, 8.2, z, 3.6, 0.15, 3.6, palette2.deck);
|
|
3261
|
+
for (const side of [-1.5, 1.5]) b.box(x + side, 8.2, z, 0.15, 1.6, 0.15, palette2.deck);
|
|
919
3262
|
if (searchlight) b.spot({ x, y: 9, z, color: 15134463, intensity: 2.4, distance: 70, decay: 1.2, angle: 0.35, penumbra: 0.5, target: { x: x + 20, y: 0, z: z + 20 }, sweep: { speed: 0.5, radius: 25 } });
|
|
920
3263
|
}
|
|
921
3264
|
function drawSandbags(b, x, z, length, along, rows = 3, c) {
|
|
@@ -935,34 +3278,6 @@ var GameUrban = (() => {
|
|
|
935
3278
|
b.line(points.map(([px, pz]) => [px, y, pz]), color ?? colors(c).wire);
|
|
936
3279
|
}
|
|
937
3280
|
|
|
938
|
-
// ../game-contracts/dist/index.js
|
|
939
|
-
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
940
|
-
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
941
|
-
var deepMerge = (base, patch) => {
|
|
942
|
-
if (!patch) return base;
|
|
943
|
-
const result = { ...base };
|
|
944
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
945
|
-
if (value === void 0 || UNSAFE_KEYS.has(key)) continue;
|
|
946
|
-
const current = result[key];
|
|
947
|
-
result[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value;
|
|
948
|
-
}
|
|
949
|
-
return result;
|
|
950
|
-
};
|
|
951
|
-
var mergeById = (shipped, extra) => {
|
|
952
|
-
const entries = /* @__PURE__ */ new Map();
|
|
953
|
-
for (const entry of [...shipped, ...extra ?? []]) entries.set(entry.id, entry);
|
|
954
|
-
return entries;
|
|
955
|
-
};
|
|
956
|
-
var applyPatches = (entries, patches) => {
|
|
957
|
-
for (const [id, patch] of Object.entries(patches ?? {})) {
|
|
958
|
-
const entry = entries.get(id);
|
|
959
|
-
if (entry) entries.set(id, deepMerge(entry, patch));
|
|
960
|
-
}
|
|
961
|
-
};
|
|
962
|
-
var removeIds = (entries, ids) => {
|
|
963
|
-
for (const id of ids ?? []) entries.delete(id);
|
|
964
|
-
};
|
|
965
|
-
|
|
966
3281
|
// src/neighbourhoods/houses.ts
|
|
967
3282
|
var DEFAULT_WIDTH = 8;
|
|
968
3283
|
var DEFAULT_DEPTH = 10;
|
|
@@ -1701,7 +4016,7 @@ var GameUrban = (() => {
|
|
|
1701
4016
|
const plan = typeof planOrId === "string" ? plans.resolve(planOrId) : resolvePlan(planOrId);
|
|
1702
4017
|
const locale = options.locale ?? DEFAULT_LOCALE2;
|
|
1703
4018
|
const cap = Math.max(0, options.maxFloors ?? maxFloors);
|
|
1704
|
-
const
|
|
4019
|
+
const clamp3 = (n) => Math.min(cap, Math.max(0, n));
|
|
1705
4020
|
const districts = plan.districts.map((d) => {
|
|
1706
4021
|
const kind = kinds.get(d.kind);
|
|
1707
4022
|
const kindText = text(kind.text, locale);
|
|
@@ -1715,7 +4030,7 @@ var GameUrban = (() => {
|
|
|
1715
4030
|
color: kind.color,
|
|
1716
4031
|
zone: kind.zone,
|
|
1717
4032
|
pattern: kind.pattern,
|
|
1718
|
-
floors: [
|
|
4033
|
+
floors: [clamp3(lo), clamp3(hi)],
|
|
1719
4034
|
uses: d.uses ?? kind.uses,
|
|
1720
4035
|
block: kind.block
|
|
1721
4036
|
};
|
|
@@ -1798,10 +4113,10 @@ var GameUrban = (() => {
|
|
|
1798
4113
|
}
|
|
1799
4114
|
|
|
1800
4115
|
// src/terrain/math.ts
|
|
1801
|
-
var
|
|
4116
|
+
var clamp2 = (v, a, b) => v < a ? a : v > b ? b : v;
|
|
1802
4117
|
var lerp2 = (a, b, t) => a + (b - a) * t;
|
|
1803
4118
|
var smoothstep = (a, b, x) => {
|
|
1804
|
-
const t =
|
|
4119
|
+
const t = clamp2((x - a) / (b - a), 0, 1);
|
|
1805
4120
|
return t * t * (3 - 2 * t);
|
|
1806
4121
|
};
|
|
1807
4122
|
var hash2 = (a, b) => {
|
|
@@ -1815,7 +4130,7 @@ var GameUrban = (() => {
|
|
|
1815
4130
|
const [bx, by] = points[index + 1];
|
|
1816
4131
|
const dx = bx - ax;
|
|
1817
4132
|
const dy = by - ay;
|
|
1818
|
-
const t =
|
|
4133
|
+
const t = clamp2(((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy), 0, 1);
|
|
1819
4134
|
const d = Math.hypot(px - ax - t * dx, py - ay - t * dy);
|
|
1820
4135
|
if (d < best) best = d;
|
|
1821
4136
|
}
|
|
@@ -1924,7 +4239,8 @@ var GameUrban = (() => {
|
|
|
1924
4239
|
}
|
|
1925
4240
|
|
|
1926
4241
|
// src/kit.ts
|
|
1927
|
-
var
|
|
4242
|
+
var DEFAULT_LIGHT_ALLOWANCE = 16;
|
|
4243
|
+
var DEFAULT_LIGHT_BUDGET = DEFAULT_LIGHT_ALLOWANCE;
|
|
1928
4244
|
var DEFAULT_URBAN_SEED = 7;
|
|
1929
4245
|
function createUrban(options = {}) {
|
|
1930
4246
|
const entries = mergeById(DEFAULT_POLES, options.packs?.poles);
|
|
@@ -1935,7 +4251,7 @@ var GameUrban = (() => {
|
|
|
1935
4251
|
applyPatches(patterns, options.patch?.neighbourhoods);
|
|
1936
4252
|
removeIds(patterns, options.remove?.neighbourhoods);
|
|
1937
4253
|
const composers = { ...COMPOSERS, ...options.composers };
|
|
1938
|
-
const lightBudget = options.lights?.budget ??
|
|
4254
|
+
const lightBudget = options.lights?.budget ?? DEFAULT_LIGHT_ALLOWANCE;
|
|
1939
4255
|
const facades = options.facades ?? DEFAULT_FACADES;
|
|
1940
4256
|
const propColors = { ...DEFAULT_PROP_COLORS, ...options.props };
|
|
1941
4257
|
const towers = options.towers ? options.towers === true ? {} : options.towers : null;
|
|
@@ -1977,7 +4293,7 @@ var GameUrban = (() => {
|
|
|
1977
4293
|
wires: (b, points, y, color) => drawWires(b, points, y, color, propColors)
|
|
1978
4294
|
}
|
|
1979
4295
|
};
|
|
1980
|
-
const builder = (o = {}) => createUrbanBuilder(o.random ?? createSilhouetteRandom(o.seed ?? DEFAULT_URBAN_SEED), { budget:
|
|
4296
|
+
const builder = (o = {}) => createUrbanBuilder(o.random ?? createSilhouetteRandom(o.seed ?? DEFAULT_URBAN_SEED), { budget: createLightAllowance(o.budget ?? lightBudget) });
|
|
1981
4297
|
const neighbourhoods = {
|
|
1982
4298
|
ids: () => [...patterns.keys()],
|
|
1983
4299
|
get(id) {
|
|
@@ -6898,7 +9214,7 @@ var GameUrban = (() => {
|
|
|
6898
9214
|
const THREE = kit.THREE;
|
|
6899
9215
|
const trl = fx.t;
|
|
6900
9216
|
const lbl = fx.label;
|
|
6901
|
-
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G = kit.geo, profX = kit.profX, halfAt = kit.halfAt,
|
|
9217
|
+
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G = kit.geo, profX = kit.profX, halfAt = kit.halfAt, clamp3 = kit.clamp, smooth = kit.smooth;
|
|
6902
9218
|
const GLOSS = CM("#07080a", { roughness: 0.18, metalness: 0.5 });
|
|
6903
9219
|
const ALLOY = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 });
|
|
6904
9220
|
const ALLOYD = CM("#3a3e42", { metalness: 0.6, roughness: 0.35 });
|
|
@@ -6912,7 +9228,7 @@ var GameUrban = (() => {
|
|
|
6912
9228
|
const a = (y - o.ys) / (o.belt - o.ys);
|
|
6913
9229
|
return o.kt * a * a;
|
|
6914
9230
|
}
|
|
6915
|
-
const b =
|
|
9231
|
+
const b = clamp3((o.ys - y) / (o.ys - o.yb), 0, 1.25);
|
|
6916
9232
|
return o.kb * b * b;
|
|
6917
9233
|
}
|
|
6918
9234
|
const f = function(x, y, z) {
|
|
@@ -6992,13 +9308,13 @@ var GameUrban = (() => {
|
|
|
6992
9308
|
});
|
|
6993
9309
|
}
|
|
6994
9310
|
function roofAt(gh, x) {
|
|
6995
|
-
const rR = gh.roofR || gh.roof, t =
|
|
9311
|
+
const rR = gh.roofR || gh.roof, t = clamp3((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
|
|
6996
9312
|
return rR + (gh.roof - rR) * t + (gh.crown || 0) * 4 * t * (1 - t);
|
|
6997
9313
|
}
|
|
6998
9314
|
function windows(gh) {
|
|
6999
9315
|
const inn = 0.07, rise = gh.rise || 0;
|
|
7000
9316
|
function ybAt(x) {
|
|
7001
|
-
return gh.belt + 0.035 + rise *
|
|
9317
|
+
return gh.belt + 0.035 + rise * clamp3((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
|
|
7002
9318
|
}
|
|
7003
9319
|
function lx(x0, x1, y, top) {
|
|
7004
9320
|
return x0 + (x1 - x0) * (y - gh.belt) / (top - gh.belt);
|
|
@@ -7201,7 +9517,7 @@ var GameUrban = (() => {
|
|
|
7201
9517
|
}
|
|
7202
9518
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: tot * (full ? 2 : 1), E, sd };
|
|
7203
9519
|
G2.at = function(u, y) {
|
|
7204
|
-
const fi =
|
|
9520
|
+
const fi = clamp3(u, 0, 1) * nu, fj = clamp3((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
|
|
7205
9521
|
function L(M) {
|
|
7206
9522
|
return M[i0][j0].clone().multiplyScalar((1 - a) * (1 - b)).addScaledVector(M[i0 + 1][j0], a * (1 - b)).addScaledVector(M[i0][j0 + 1], (1 - a) * b).addScaledVector(M[i0 + 1][j0 + 1], a * b);
|
|
7207
9523
|
}
|
|
@@ -7338,10 +9654,10 @@ var GameUrban = (() => {
|
|
|
7338
9654
|
m.rotation.order = "XYZ";
|
|
7339
9655
|
return m;
|
|
7340
9656
|
}
|
|
7341
|
-
function regLight(c, kind, label,
|
|
7342
|
-
const L = { group: null, inner, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
9657
|
+
function regLight(c, kind, label, inner2, lens, pos) {
|
|
9658
|
+
const L = { group: null, inner: inner2, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
7343
9659
|
c.lights.push(L);
|
|
7344
|
-
const pt = c.part(kind, label,
|
|
9660
|
+
const pt = c.part(kind, label, inner2[0], pos, 0.8);
|
|
7345
9661
|
pt.light = L;
|
|
7346
9662
|
return L;
|
|
7347
9663
|
}
|
|
@@ -7373,7 +9689,7 @@ var GameUrban = (() => {
|
|
|
7373
9689
|
du: 0.018,
|
|
7374
9690
|
dv: 0.012
|
|
7375
9691
|
});
|
|
7376
|
-
const pu = pad / Gd.len,
|
|
9692
|
+
const pu = pad / Gd.len, inner2 = [], lens = [];
|
|
7377
9693
|
const yb = function(u) {
|
|
7378
9694
|
return sh(u)[0];
|
|
7379
9695
|
}, yt = function(u) {
|
|
@@ -7398,13 +9714,13 @@ var GameUrban = (() => {
|
|
|
7398
9714
|
ring.castShadow = false;
|
|
7399
9715
|
const b = onSurf(c.sphere(rad, MT.head, 0, 0, 0, 1, 0.55, 1), s, 0.01);
|
|
7400
9716
|
b.castShadow = false;
|
|
7401
|
-
|
|
9717
|
+
inner2.push(b);
|
|
7402
9718
|
});
|
|
7403
9719
|
(o.drl || []).forEach(function(d) {
|
|
7404
|
-
|
|
9720
|
+
inner2.push(skin(c, Gd, d.mat || DRL, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 95e-4, nv: 2 }));
|
|
7405
9721
|
});
|
|
7406
9722
|
(o.amber || []).forEach(function(d) {
|
|
7407
|
-
|
|
9723
|
+
inner2.push(skin(c, Gd, MT.amber, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 9e-3, nv: 2 }));
|
|
7408
9724
|
});
|
|
7409
9725
|
const ln = skin(c, Gd, MT.lens, {
|
|
7410
9726
|
yb: function(u) {
|
|
@@ -7418,9 +9734,9 @@ var GameUrban = (() => {
|
|
|
7418
9734
|
order: 3
|
|
7419
9735
|
});
|
|
7420
9736
|
lens.push(ln);
|
|
7421
|
-
if (!
|
|
9737
|
+
if (!inner2.length) inner2.push(ln);
|
|
7422
9738
|
const cs = Gd.at(0.5, (rg[0] + rg[1]) / 2).p;
|
|
7423
|
-
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")),
|
|
9739
|
+
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")), inner2, lens, cs);
|
|
7424
9740
|
return Gd;
|
|
7425
9741
|
};
|
|
7426
9742
|
K.tail = function(sd, o) {
|
|
@@ -7434,7 +9750,7 @@ var GameUrban = (() => {
|
|
|
7434
9750
|
du: 0.018,
|
|
7435
9751
|
dv: 0.012
|
|
7436
9752
|
});
|
|
7437
|
-
const pu = pad / Gd.len,
|
|
9753
|
+
const pu = pad / Gd.len, inner2 = [], lens = [], yb = function(u) {
|
|
7438
9754
|
return sh(u)[0];
|
|
7439
9755
|
}, yt = function(u) {
|
|
7440
9756
|
return sh(u)[1];
|
|
@@ -7449,15 +9765,15 @@ var GameUrban = (() => {
|
|
|
7449
9765
|
off: 3e-3,
|
|
7450
9766
|
nv: 4
|
|
7451
9767
|
});
|
|
7452
|
-
|
|
9768
|
+
inner2.push(skin(c, Gd, o.main || TAILD, { u0: pu, u1: 1 - pu, yb, yt, off: 6e-3, nv: 4 }));
|
|
7453
9769
|
(o.hi || []).forEach(function(d) {
|
|
7454
|
-
|
|
9770
|
+
inner2.push(skin(c, Gd, d.mat || TAILHI, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 9e-3, nv: 2 }));
|
|
7455
9771
|
});
|
|
7456
9772
|
(o.rev || []).forEach(function(d) {
|
|
7457
|
-
|
|
9773
|
+
inner2.push(skin(c, Gd, MT.rev, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 85e-4, nv: 2 }));
|
|
7458
9774
|
});
|
|
7459
9775
|
(o.amber || []).forEach(function(d) {
|
|
7460
|
-
|
|
9776
|
+
inner2.push(skin(c, Gd, MT.amber, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 85e-4, nv: 2 }));
|
|
7461
9777
|
});
|
|
7462
9778
|
lens.push(
|
|
7463
9779
|
skin(c, Gd, o.lens || MT.lensRed, {
|
|
@@ -7476,7 +9792,7 @@ var GameUrban = (() => {
|
|
|
7476
9792
|
c,
|
|
7477
9793
|
"taillight",
|
|
7478
9794
|
o.label || (sd > 0 ? trl("labels.right_taillight") : trl("labels.left_taillight")),
|
|
7479
|
-
|
|
9795
|
+
inner2,
|
|
7480
9796
|
lens,
|
|
7481
9797
|
Gd.at(0.4, (rg[0] + rg[1]) / 2).p
|
|
7482
9798
|
);
|
|
@@ -7781,7 +10097,7 @@ var GameUrban = (() => {
|
|
|
7781
10097
|
c.roundBox(x1 - x0, 0.08, 0.035, 0.015, MT.trim, (x0 + x1) / 2, by + 0.05, sd * (hw - 8e-3));
|
|
7782
10098
|
if (s.clad)
|
|
7783
10099
|
s.arches.forEach(function(a) {
|
|
7784
|
-
const a0 = Math.asin(
|
|
10100
|
+
const a0 = Math.asin(clamp3((by - a[1]) / a[2], -1, 1)), tor = c.add(new THREE.TorusGeometry(a[2] + 0.02, 0.035, 6, 26, PI - 2 * a0), MT.trim, a[0], a[1], sd * (hw - 0.01));
|
|
7785
10101
|
tor.rotation.z = a0;
|
|
7786
10102
|
tor.scale.z = 0.8;
|
|
7787
10103
|
});
|
|
@@ -7912,7 +10228,7 @@ var GameUrban = (() => {
|
|
|
7912
10228
|
c.cylinder(0.06, 0.06, 0.012, MT.head, x + 0.055, y, z, "x");
|
|
7913
10229
|
}
|
|
7914
10230
|
function sm(t) {
|
|
7915
|
-
t =
|
|
10231
|
+
t = clamp3(t, 0, 1);
|
|
7916
10232
|
return t * t * (3 - 2 * t);
|
|
7917
10233
|
}
|
|
7918
10234
|
function sideGrid(S, sd, o) {
|
|
@@ -7929,7 +10245,7 @@ var GameUrban = (() => {
|
|
|
7929
10245
|
}
|
|
7930
10246
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: Math.abs(o.x1 - o.x0) };
|
|
7931
10247
|
G2.at = function(u, y) {
|
|
7932
|
-
const fi =
|
|
10248
|
+
const fi = clamp3(u, 0, 1) * nu, fj = clamp3((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
|
|
7933
10249
|
function L(M) {
|
|
7934
10250
|
return M[i0][j0].clone().multiplyScalar((1 - a) * (1 - b)).addScaledVector(M[i0 + 1][j0], a * (1 - b)).addScaledVector(M[i0][j0 + 1], (1 - a) * b).addScaledVector(M[i0 + 1][j0 + 1], a * b);
|
|
7935
10251
|
}
|
|
@@ -9657,7 +11973,7 @@ var GameUrban = (() => {
|
|
|
9657
11973
|
const g = K.grid(1, 0, { z1: 0.66, y0: 0.27, y1: 0.61, du: 0.03, dv: 0.03 }), zt = 0.6, zb = 0.43, yB0 = 0.3, yT = 0.585;
|
|
9658
11974
|
const gyb = function(u) {
|
|
9659
11975
|
const z = Math.abs(g.zu(u));
|
|
9660
|
-
return z <= zb ? yB0 : yB0 + (yT - 0.02 - yB0) *
|
|
11976
|
+
return z <= zb ? yB0 : yB0 + (yT - 0.02 - yB0) * clamp3((z - zb) / (zt - zb), 0, 1);
|
|
9661
11977
|
};
|
|
9662
11978
|
K.skin(g, grilleMat("mesh"), { u0: g.uz(-zt), u1: g.uz(zt), yb: gyb, yt: yT, off: 0.022, tile: 0.05, nv: 6 });
|
|
9663
11979
|
outline(c2, g, { u0: g.uz(-zt), u1: g.uz(zt), yb: gyb, yt: yT }, 0.012, LH, 0.024);
|
|
@@ -11220,6 +13536,7 @@ var GameUrban = (() => {
|
|
|
11220
13536
|
hood: true
|
|
11221
13537
|
},
|
|
11222
13538
|
function(c) {
|
|
13539
|
+
c.meta.armor = "III";
|
|
11223
13540
|
const f = 3.75, W = 2.32, len = 1.85, ax = 2.5, wr = 0.506, ww = 0.28, col = "#56616b";
|
|
11224
13541
|
c.meta.bullet = true;
|
|
11225
13542
|
c.meta.cabLength = len + 0.15;
|
|
@@ -14607,8 +16924,8 @@ var GameUrban = (() => {
|
|
|
14607
16924
|
kit.steeringWheel(c, 1.5, 1.42, -0.62, { r: 0.2, tilt: 0.9, col: 0.35 });
|
|
14608
16925
|
c.roundBox(0.35, 0.3, 0.7, 0.04, CM("#25261f"), 1.85, 1.45, -0.6);
|
|
14609
16926
|
kit.seat(c, 0.15, fl, 0, { w: 0.5, h: 0.45, back: 0.62, lean: 0.12, mat: CM("#2f3226", { roughness: 0.9 }) });
|
|
14610
|
-
for (let
|
|
14611
|
-
c.roundBox(0.25, 0.32, 0.25, 0.03, CM("#3f4a2c", { roughness: 0.6 }), -0.7 -
|
|
16927
|
+
for (let r22 = 0; r22 < 3; r22++)
|
|
16928
|
+
c.roundBox(0.25, 0.32, 0.25, 0.03, CM("#3f4a2c", { roughness: 0.6 }), -0.7 - r22 * 0.6, roofY - 0.35, 0.68);
|
|
14612
16929
|
const ramp2 = new THREE.Group(), RL = 1.36, rmat = paintMat(c, col);
|
|
14613
16930
|
ramp2.position.set(R - 0.05, 0.62, 0);
|
|
14614
16931
|
ramp2.rotation.z = -PI / 2;
|
|
@@ -15815,7 +18132,7 @@ var GameUrban = (() => {
|
|
|
15815
18132
|
const THREE = kit.THREE;
|
|
15816
18133
|
const trl = fx.t;
|
|
15817
18134
|
const lbl = fx.label;
|
|
15818
|
-
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G = kit.geo, profX = kit.profX, halfAt = kit.halfAt,
|
|
18135
|
+
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G = kit.geo, profX = kit.profX, halfAt = kit.halfAt, clamp3 = kit.clamp, smooth = kit.smooth;
|
|
15819
18136
|
const GLOSS = CM("#07080a", { roughness: 0.18, metalness: 0.5 });
|
|
15820
18137
|
const ALLOY = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 });
|
|
15821
18138
|
const ALLOYD = CM("#3a3e42", { metalness: 0.6, roughness: 0.35 });
|
|
@@ -15834,7 +18151,7 @@ var GameUrban = (() => {
|
|
|
15834
18151
|
const a = (y - o.ys) / (o.belt - o.ys);
|
|
15835
18152
|
return o.kt * a * a;
|
|
15836
18153
|
}
|
|
15837
|
-
const b =
|
|
18154
|
+
const b = clamp3((o.ys - y) / (o.ys - o.yb), 0, 1.25);
|
|
15838
18155
|
return o.kb * b * b;
|
|
15839
18156
|
}
|
|
15840
18157
|
const f = function(x, y, z) {
|
|
@@ -16014,13 +18331,13 @@ var GameUrban = (() => {
|
|
|
16014
18331
|
return L;
|
|
16015
18332
|
}
|
|
16016
18333
|
function roofAt(gh, x) {
|
|
16017
|
-
const rR = gh.roofR || gh.roof, t =
|
|
18334
|
+
const rR = gh.roofR || gh.roof, t = clamp3((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
|
|
16018
18335
|
return rR + (gh.roof - rR) * t + (gh.crown || 0) * 4 * t * (1 - t);
|
|
16019
18336
|
}
|
|
16020
18337
|
function windows(gh) {
|
|
16021
18338
|
const inn = 0.07, rise = gh.rise || 0;
|
|
16022
18339
|
function ybAt(x) {
|
|
16023
|
-
return gh.belt + 0.035 + rise *
|
|
18340
|
+
return gh.belt + 0.035 + rise * clamp3((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
|
|
16024
18341
|
}
|
|
16025
18342
|
function lx(x0, x1, y, top) {
|
|
16026
18343
|
return x0 + (x1 - x0) * (y - gh.belt) / (top - gh.belt);
|
|
@@ -16231,7 +18548,7 @@ var GameUrban = (() => {
|
|
|
16231
18548
|
}
|
|
16232
18549
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: tot * (full ? 2 : 1), E, sd };
|
|
16233
18550
|
G2.at = function(u, y) {
|
|
16234
|
-
const fi =
|
|
18551
|
+
const fi = clamp3(u, 0, 1) * nu, fj = clamp3((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
|
|
16235
18552
|
function L(M) {
|
|
16236
18553
|
return M[i0][j0].clone().multiplyScalar((1 - a) * (1 - b)).addScaledVector(M[i0 + 1][j0], a * (1 - b)).addScaledVector(M[i0][j0 + 1], (1 - a) * b).addScaledVector(M[i0 + 1][j0 + 1], a * b);
|
|
16237
18554
|
}
|
|
@@ -16368,10 +18685,10 @@ var GameUrban = (() => {
|
|
|
16368
18685
|
m.rotation.order = "XYZ";
|
|
16369
18686
|
return m;
|
|
16370
18687
|
}
|
|
16371
|
-
function regLight(c, kind, label,
|
|
16372
|
-
const L = { group: null, inner, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
18688
|
+
function regLight(c, kind, label, inner2, lens, pos) {
|
|
18689
|
+
const L = { group: null, inner: inner2, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
16373
18690
|
c.lights.push(L);
|
|
16374
|
-
const pt = c.part(kind, label,
|
|
18691
|
+
const pt = c.part(kind, label, inner2[0], pos, 0.8);
|
|
16375
18692
|
pt.light = L;
|
|
16376
18693
|
return L;
|
|
16377
18694
|
}
|
|
@@ -16403,7 +18720,7 @@ var GameUrban = (() => {
|
|
|
16403
18720
|
du: 0.018,
|
|
16404
18721
|
dv: 0.012
|
|
16405
18722
|
});
|
|
16406
|
-
const pu = pad / Gd.len,
|
|
18723
|
+
const pu = pad / Gd.len, inner2 = [], lens = [];
|
|
16407
18724
|
const yb = function(u) {
|
|
16408
18725
|
return sh(u)[0];
|
|
16409
18726
|
}, yt = function(u) {
|
|
@@ -16428,13 +18745,13 @@ var GameUrban = (() => {
|
|
|
16428
18745
|
ring.castShadow = false;
|
|
16429
18746
|
const b = onSurf(c.sphere(rad, MT.head, 0, 0, 0, 1, 0.55, 1), s, 0.01);
|
|
16430
18747
|
b.castShadow = false;
|
|
16431
|
-
|
|
18748
|
+
inner2.push(b);
|
|
16432
18749
|
});
|
|
16433
18750
|
(o.drl || []).forEach(function(d) {
|
|
16434
|
-
|
|
18751
|
+
inner2.push(skin(c, Gd, d.mat || DRL, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 95e-4, nv: 2 }));
|
|
16435
18752
|
});
|
|
16436
18753
|
(o.amber || []).forEach(function(d) {
|
|
16437
|
-
|
|
18754
|
+
inner2.push(skin(c, Gd, MT.amber, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 9e-3, nv: 2 }));
|
|
16438
18755
|
});
|
|
16439
18756
|
const ln = skin(c, Gd, MT.lens, {
|
|
16440
18757
|
yb: function(u) {
|
|
@@ -16448,9 +18765,9 @@ var GameUrban = (() => {
|
|
|
16448
18765
|
order: 3
|
|
16449
18766
|
});
|
|
16450
18767
|
lens.push(ln);
|
|
16451
|
-
if (!
|
|
18768
|
+
if (!inner2.length) inner2.push(ln);
|
|
16452
18769
|
const cs = Gd.at(0.5, (rg[0] + rg[1]) / 2).p;
|
|
16453
|
-
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")),
|
|
18770
|
+
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")), inner2, lens, cs);
|
|
16454
18771
|
return Gd;
|
|
16455
18772
|
};
|
|
16456
18773
|
K.tail = function(sd, o) {
|
|
@@ -16464,7 +18781,7 @@ var GameUrban = (() => {
|
|
|
16464
18781
|
du: 0.018,
|
|
16465
18782
|
dv: 0.012
|
|
16466
18783
|
});
|
|
16467
|
-
const pu = pad / Gd.len,
|
|
18784
|
+
const pu = pad / Gd.len, inner2 = [], lens = [], yb = function(u) {
|
|
16468
18785
|
return sh(u)[0];
|
|
16469
18786
|
}, yt = function(u) {
|
|
16470
18787
|
return sh(u)[1];
|
|
@@ -16479,15 +18796,15 @@ var GameUrban = (() => {
|
|
|
16479
18796
|
off: 3e-3,
|
|
16480
18797
|
nv: 4
|
|
16481
18798
|
});
|
|
16482
|
-
|
|
18799
|
+
inner2.push(skin(c, Gd, o.main || TAILD, { u0: pu, u1: 1 - pu, yb, yt, off: 6e-3, nv: 4 }));
|
|
16483
18800
|
(o.hi || []).forEach(function(d) {
|
|
16484
|
-
|
|
18801
|
+
inner2.push(skin(c, Gd, d.mat || TAILHI, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 9e-3, nv: 2 }));
|
|
16485
18802
|
});
|
|
16486
18803
|
(o.rev || []).forEach(function(d) {
|
|
16487
|
-
|
|
18804
|
+
inner2.push(skin(c, Gd, MT.rev, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 85e-4, nv: 2 }));
|
|
16488
18805
|
});
|
|
16489
18806
|
(o.amber || []).forEach(function(d) {
|
|
16490
|
-
|
|
18807
|
+
inner2.push(skin(c, Gd, MT.amber, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 85e-4, nv: 2 }));
|
|
16491
18808
|
});
|
|
16492
18809
|
lens.push(
|
|
16493
18810
|
skin(c, Gd, o.lens || MT.lensRed, {
|
|
@@ -16506,7 +18823,7 @@ var GameUrban = (() => {
|
|
|
16506
18823
|
c,
|
|
16507
18824
|
"taillight",
|
|
16508
18825
|
o.label || (sd > 0 ? trl("labels.right_taillight") : trl("labels.left_taillight")),
|
|
16509
|
-
|
|
18826
|
+
inner2,
|
|
16510
18827
|
lens,
|
|
16511
18828
|
Gd.at(0.4, (rg[0] + rg[1]) / 2).p
|
|
16512
18829
|
);
|
|
@@ -16792,7 +19109,7 @@ var GameUrban = (() => {
|
|
|
16792
19109
|
c.roundBox(x1 - x0, 0.08, 0.035, 0.015, MT.trim, (x0 + x1) / 2, by + 0.05, sd * (hw - 8e-3));
|
|
16793
19110
|
if (s.clad)
|
|
16794
19111
|
s.arches.forEach(function(a) {
|
|
16795
|
-
const a0 = Math.asin(
|
|
19112
|
+
const a0 = Math.asin(clamp3((by - a[1]) / a[2], -1, 1)), tor = c.add(new THREE.TorusGeometry(a[2] + 0.02, 0.035, 6, 26, PI - 2 * a0), MT.trim, a[0], a[1], sd * (hw - 0.01));
|
|
16796
19113
|
tor.rotation.z = a0;
|
|
16797
19114
|
tor.scale.z = 0.8;
|
|
16798
19115
|
});
|
|
@@ -16913,7 +19230,7 @@ var GameUrban = (() => {
|
|
|
16913
19230
|
c.cylinder(0.06, 0.06, 0.012, MT.head, x + 0.055, y, z, "x");
|
|
16914
19231
|
}
|
|
16915
19232
|
function sm(t) {
|
|
16916
|
-
t =
|
|
19233
|
+
t = clamp3(t, 0, 1);
|
|
16917
19234
|
return t * t * (3 - 2 * t);
|
|
16918
19235
|
}
|
|
16919
19236
|
function sideGrid(S, sd, o) {
|
|
@@ -16930,7 +19247,7 @@ var GameUrban = (() => {
|
|
|
16930
19247
|
}
|
|
16931
19248
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: Math.abs(o.x1 - o.x0) };
|
|
16932
19249
|
G2.at = function(u, y) {
|
|
16933
|
-
const fi =
|
|
19250
|
+
const fi = clamp3(u, 0, 1) * nu, fj = clamp3((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
|
|
16934
19251
|
function L(M) {
|
|
16935
19252
|
return M[i0][j0].clone().multiplyScalar((1 - a) * (1 - b)).addScaledVector(M[i0 + 1][j0], a * (1 - b)).addScaledVector(M[i0][j0 + 1], (1 - a) * b).addScaledVector(M[i0 + 1][j0 + 1], a * b);
|
|
16936
19253
|
}
|
|
@@ -17089,7 +19406,7 @@ var GameUrban = (() => {
|
|
|
17089
19406
|
return function(c, I) {
|
|
17090
19407
|
const gh = I.gh, col = c.meta.color;
|
|
17091
19408
|
function yb(x) {
|
|
17092
|
-
return gh.belt + 0.035 + (gh.rise || 0) *
|
|
19409
|
+
return gh.belt + 0.035 + (gh.rise || 0) * clamp3((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
|
|
17093
19410
|
}
|
|
17094
19411
|
;
|
|
17095
19412
|
[1, -1].forEach(function(sd) {
|
|
@@ -19960,7 +22277,7 @@ var GameUrban = (() => {
|
|
|
19960
22277
|
return n.y > 0.3 && ce.x > 0.9 && ce.y > 0.6;
|
|
19961
22278
|
},
|
|
19962
22279
|
sdf: function(x, y, z) {
|
|
19963
|
-
const t =
|
|
22280
|
+
const t = clamp3((x - 1.17) / 0.5, 0, 1);
|
|
19964
22281
|
return Math.max(0.94 - x, x - 1.69, Math.abs(z) - (0.44 - 0.16 * t * t));
|
|
19965
22282
|
}
|
|
19966
22283
|
});
|
|
@@ -23958,7 +26275,7 @@ var GameUrban = (() => {
|
|
|
23958
26275
|
const THREE = kit.THREE;
|
|
23959
26276
|
const trl = fx.t;
|
|
23960
26277
|
const lbl = fx.label;
|
|
23961
|
-
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial,
|
|
26278
|
+
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, clamp3 = kit.clamp, smooth = kit.smooth;
|
|
23962
26279
|
const CLAD = CM("#1a1b1d", { roughness: 0.82 }), LINER = CM("#1c1d1f", { roughness: 0.92 }), RIMDARK = CM("#2a2d30", { metalness: 0.6, roughness: 0.4 });
|
|
23963
26280
|
const RIMSIL = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 }), RIMGUN = CM("#55595d", { metalness: 0.7, roughness: 0.35 });
|
|
23964
26281
|
function axles(WB, oF, L, dF, dR) {
|
|
@@ -23989,16 +26306,16 @@ var GameUrban = (() => {
|
|
|
23989
26306
|
const m = c.meta, W = m.width, by = o.bottom != null ? o.bottom : m.clearance, top = o.top != null ? o.top : m.belt;
|
|
23990
26307
|
const Wg = m.glasshouse && m.glasshouse.Wg || W, front = m.front, rear = m.rear;
|
|
23991
26308
|
const tuck = o.tuck == null ? 0.045 : o.tuck, peak = o.peak || 0.45;
|
|
23992
|
-
const B = o.sh != null ? o.sh :
|
|
26309
|
+
const B = o.sh != null ? o.sh : clamp3(1 - (Wg + 0.06) / W, 0, 0.06);
|
|
23993
26310
|
const Rf = o.Rf || 0.55, Rr = o.Rr || 0.42, pf = o.pf == null ? 0.08 : o.pf, pr = o.pr == null ? 0.05 : o.pr;
|
|
23994
26311
|
const bowF = o.bowF == null ? 0.07 : o.bowF, bowR = o.bowR == null ? 0.04 : o.bowR, Rb = o.Rb || 0.35, hw = W / 2;
|
|
23995
26312
|
const crown = o.crown == null ? 0.045 : o.crown, xc = o.crownX != null ? o.crownX : m.glasshouse ? m.glasshouse.fb : front - 1, y0 = by + 0.55 * (top - by);
|
|
23996
26313
|
function sc(y) {
|
|
23997
|
-
const t =
|
|
26314
|
+
const t = clamp3((y - by) / (top - by), 0, 1);
|
|
23998
26315
|
return t < peak ? 1 - tuck * Math.pow(1 - t / peak, 2) : 1 - B * Math.pow((t - peak) / (1 - peak), 2);
|
|
23999
26316
|
}
|
|
24000
26317
|
function F(v2) {
|
|
24001
|
-
const a = smooth((v2.x - (front - Rf)) / Rf), b = smooth((rear + Rr - v2.x) / Rr), zz =
|
|
26318
|
+
const a = smooth((v2.x - (front - Rf)) / Rf), b = smooth((rear + Rr - v2.x) / Rr), zz = clamp3(v2.z / hw, -1, 1) * clamp3(v2.z / hw, -1, 1);
|
|
24002
26319
|
const bf = smooth((v2.x - (front - Rb)) / Rb), br = smooth((rear + Rb - v2.x) / Rb);
|
|
24003
26320
|
const cr = crown * (1 - zz) * smooth((v2.y - y0) / (top - y0)) * smooth((v2.x - xc) / 0.3);
|
|
24004
26321
|
v2.x += -bowF * zz * bf + bowR * zz * br;
|
|
@@ -24075,7 +26392,7 @@ var GameUrban = (() => {
|
|
|
24075
26392
|
o = o || {};
|
|
24076
26393
|
const wd = o.w || 0.075, dp = o.d || 0.045, mat = o.mat || CLAD, out = [];
|
|
24077
26394
|
arches.forEach(function(a) {
|
|
24078
|
-
const a0 = Math.asin(
|
|
26395
|
+
const a0 = Math.asin(clamp3((by - a[1]) / a[2], -1, 1)) - 0.02, r0 = a[2] - (o.inner == null ? 0.055 : o.inner), r1 = a[2] + wd;
|
|
24079
26396
|
const sh = new THREE.Shape();
|
|
24080
26397
|
if (o.trap) {
|
|
24081
26398
|
const yb0 = Math.sin(a0) * r1, xb0 = Math.cos(a0) * r1, top = r1 * 0.93, xt = r1 * 0.52;
|
|
@@ -24424,7 +26741,7 @@ var GameUrban = (() => {
|
|
|
24424
26741
|
function roof(c, drop, x0, x1) {
|
|
24425
26742
|
const m = c.meta, b = m.glasshouse.belt, h = m.top - b;
|
|
24426
26743
|
function dy(x, y) {
|
|
24427
|
-
const t =
|
|
26744
|
+
const t = clamp3((y - b) / h, 0, 1);
|
|
24428
26745
|
return t <= 0 ? 0 : drop * smooth((x0 - x) / (x0 - x1)) * t;
|
|
24429
26746
|
}
|
|
24430
26747
|
c.root.updateMatrixWorld(true);
|
|
@@ -25618,8 +27935,8 @@ var GameUrban = (() => {
|
|
|
25618
27935
|
setOnHit(pt, function(c2, p0) {
|
|
25619
27936
|
const o = p0.obj;
|
|
25620
27937
|
if (o && o.userData.hung && o.parent && o.parent !== c2.vehicle) {
|
|
25621
|
-
o.parent.rotation.x =
|
|
25622
|
-
o.parent.rotation.y =
|
|
27938
|
+
o.parent.rotation.x = clamp3(o.parent.rotation.x, -0.22, 0.22);
|
|
27939
|
+
o.parent.rotation.y = clamp3(o.parent.rotation.y, -0.08, 0.08);
|
|
25623
27940
|
}
|
|
25624
27941
|
});
|
|
25625
27942
|
});
|
|
@@ -26225,7 +28542,7 @@ var GameUrban = (() => {
|
|
|
26225
28542
|
c2.cylinder(0.03, 0.04, 0.08, MT.chrome, sx + 2.35, fl + 0.99, -0.75);
|
|
26226
28543
|
c2.part("fuel_tank", trl("labels.oxygen_cylinder"), null, new V3(sx + 2.35, fl + 0.5, -0.75), 0.5);
|
|
26227
28544
|
c2.roundBox(0.04, 0.26, 0.34, 0.02, MT.screen, 0.1, 1.7, 0.5);
|
|
26228
|
-
const
|
|
28545
|
+
const lightPanel = c2.roundBox(
|
|
26229
28546
|
2.3,
|
|
26230
28547
|
0.02,
|
|
26231
28548
|
W - 0.3,
|
|
@@ -26235,7 +28552,7 @@ var GameUrban = (() => {
|
|
|
26235
28552
|
m.top - 0.06,
|
|
26236
28553
|
0
|
|
26237
28554
|
);
|
|
26238
|
-
|
|
28555
|
+
lightPanel.castShadow = false;
|
|
26239
28556
|
m.roofItems = beaconBar;
|
|
26240
28557
|
}
|
|
26241
28558
|
});
|
|
@@ -26270,7 +28587,7 @@ var GameUrban = (() => {
|
|
|
26270
28587
|
const shared = fx.shared;
|
|
26271
28588
|
const trl = fx.t;
|
|
26272
28589
|
const lbl = fx.label;
|
|
26273
|
-
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, _paintCol = kit.paintColor, G = kit.geo, DS = THREE.DoubleSide,
|
|
28590
|
+
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, _paintCol = kit.paintColor, G = kit.geo, DS = THREE.DoubleSide, clamp3 = kit.clamp;
|
|
26274
28591
|
function colorize(geo, fn) {
|
|
26275
28592
|
const p = geo.attributes.position, col = new Float32Array(p.count * 3);
|
|
26276
28593
|
for (let i = 0; i < p.count; i++) {
|
|
@@ -26286,7 +28603,7 @@ var GameUrban = (() => {
|
|
|
26286
28603
|
return a + (b - a) * t;
|
|
26287
28604
|
}
|
|
26288
28605
|
function sstep(a, b, x) {
|
|
26289
|
-
const t =
|
|
28606
|
+
const t = clamp3((x - a) / (b - a), 0, 1);
|
|
26290
28607
|
return t * t * (3 - 2 * t);
|
|
26291
28608
|
}
|
|
26292
28609
|
function tab(T) {
|
|
@@ -26363,7 +28680,7 @@ var GameUrban = (() => {
|
|
|
26363
28680
|
o,
|
|
26364
28681
|
sec,
|
|
26365
28682
|
t: function(x) {
|
|
26366
|
-
return
|
|
28683
|
+
return clamp3((x - o.x0) / o.L, 0, 1);
|
|
26367
28684
|
},
|
|
26368
28685
|
/* half beam of the hull at height y (inside, so floors and decks do not leak) */
|
|
26369
28686
|
half: function(x, y) {
|
|
@@ -26537,7 +28854,7 @@ var GameUrban = (() => {
|
|
|
26537
28854
|
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
26538
28855
|
const a = poly[i], b = poly[j];
|
|
26539
28856
|
if (a[1] > y !== b[1] > y && x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0]) inside = !inside;
|
|
26540
|
-
const vx = b[0] - a[0], vy = b[1] - a[1], wx = x - a[0], wy = y - a[1], t =
|
|
28857
|
+
const vx = b[0] - a[0], vy = b[1] - a[1], wx = x - a[0], wy = y - a[1], t = clamp3((wx * vx + wy * vy) / (vx * vx + vy * vy), 0, 1);
|
|
26541
28858
|
d = Math.min(d, Math.hypot(wx - vx * t, wy - vy * t));
|
|
26542
28859
|
}
|
|
26543
28860
|
return inside ? -d : d;
|
|
@@ -27583,7 +29900,7 @@ var GameUrban = (() => {
|
|
|
27583
29900
|
c.roundBox(2.4, 0.1, 2.5, 0.04, blue, hx, hy0 + hh + 0.05, 0);
|
|
27584
29901
|
const door = c.roundBox(0.05, 1.5, 0.66, 0.02, CM("#5a3e26"), hx - 1.3, hy0 + 0.8, 0.68, { rot: [0, 1.3, 0] });
|
|
27585
29902
|
void door;
|
|
27586
|
-
const
|
|
29903
|
+
const inner2 = CM("#b9a888", { roughness: 0.9 });
|
|
27587
29904
|
c.roundBox(0.4, 0.95, 1.9, 0.03, wood, hx + 0.75, hy0 + 0.48, 0);
|
|
27588
29905
|
c.roundBox(0.42, 0.03, 1.9, 0.01, CM("#2a2b2c"), hx + 0.75, hy0 + 0.97, 0);
|
|
27589
29906
|
[-0.45, 0.45].forEach(function(z) {
|
|
@@ -27602,10 +29919,10 @@ var GameUrban = (() => {
|
|
|
27602
29919
|
c.rod(new V3(0, 0, 0), new V3(0, Math.cos(a8) * 0.36, Math.sin(a8) * 0.36), 0.014, MT.wood, { parent: whl });
|
|
27603
29920
|
}
|
|
27604
29921
|
c.cylinder(0.05, 0.05, 0.14, MT.wood, 0.06, 0, 0, "x", { parent: whl });
|
|
27605
|
-
kit.seat(c, hx - 0.05, hy0, 0, { w: 0.5, h: 0.55, back: 0.45, mat:
|
|
29922
|
+
kit.seat(c, hx - 0.05, hy0, 0, { w: 0.5, h: 0.55, back: 0.45, mat: inner2, headrest: false });
|
|
27606
29923
|
c.roundBox(1.8, 0.12, 0.65, 0.04, CM("#7c5a3b"), hx - 0.2, hy0 + 0.5, -0.72);
|
|
27607
29924
|
c.roundBox(1.7, 0.08, 0.55, 0.04, CM("#6b7c8f", { roughness: 1 }), hx - 0.2, hy0 + 0.6, -0.72);
|
|
27608
|
-
c.roundBox(1.9, 0.02, 2.1, 0.01,
|
|
29925
|
+
c.roundBox(1.9, 0.02, 2.1, 0.01, inner2, hx, hy0 + hh - 0.06, 0).castShadow = false;
|
|
27609
29926
|
c.cylinder(0.1, 0.1, 1.2, MT.trim, hx - 0.75, hy0 + hh + 0.6, 0.65);
|
|
27610
29927
|
c.cylinder(0.13, 0.1, 0.1, MT.trim, hx - 0.75, hy0 + hh + 1.22, 0.65);
|
|
27611
29928
|
c.rod(new V3(hx + 0.4, hy0 + hh + 0.1, 0), new V3(hx + 0.4, hy0 + hh + 1.6, 0), 0.04, wood);
|
|
@@ -31797,7 +34114,7 @@ var GameUrban = (() => {
|
|
|
31797
34114
|
return (request, b) => {
|
|
31798
34115
|
const context = contexts[request.kind];
|
|
31799
34116
|
const key = `${seed}:${request.kind}:${request.x}:${request.z}`;
|
|
31800
|
-
const id = options.pick ? options.pick(request, context, key) : pickForContext(region, context, key);
|
|
34117
|
+
const id = options.pick ? options.pick(request, context, key) : (options.pickSpecies ?? ((table, spot) => pickForContext(region, table, spot)))(context, key);
|
|
31801
34118
|
b.place("plant", id, request.x, request.z, { scale: request.scale, seed: key });
|
|
31802
34119
|
};
|
|
31803
34120
|
}
|