@adatechnology/game-urban-three 0.2.9 → 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/dist/chunk-XUITKOBO.js +998 -0
- package/dist/chunk-XUITKOBO.js.map +1 -0
- package/dist/index.d.ts +34 -138
- package/dist/index.global.js +2550 -179
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +15 -940
- package/dist/index.js.map +1 -1
- package/dist/testing-ZS5dhCJM.d.ts +145 -0
- package/dist/testing.d.ts +6 -0
- package/dist/testing.js +19 -0
- package/dist/testing.js.map +1 -0
- package/package.json +13 -8
package/dist/index.global.js
CHANGED
|
@@ -37,6 +37,39 @@ var GameUrbanThree = (() => {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
// ../game-contracts/dist/index.js
|
|
40
|
+
var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
|
|
41
|
+
var DEFERRED_QUEUE_ID = {
|
|
42
|
+
DESTRUCTION_EVENTS: "destruction.events",
|
|
43
|
+
BREACH_PAINT: "floorplan.breach-paint",
|
|
44
|
+
AUDIO_VOICES: "audio.voices",
|
|
45
|
+
VEHICLE_CREASE: "vehicles.crease"
|
|
46
|
+
};
|
|
47
|
+
var DEFERRED_PRIORITY = {
|
|
48
|
+
[DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
|
|
49
|
+
[DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
|
|
50
|
+
[DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
|
|
51
|
+
[DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
|
|
52
|
+
};
|
|
53
|
+
var WARNED_KEY = /* @__PURE__ */ Symbol.for("@adatechnology/game-contracts.warned");
|
|
54
|
+
var warnedKeys = () => {
|
|
55
|
+
const holder = globalThis;
|
|
56
|
+
return holder[WARNED_KEY] ?? (holder[WARNED_KEY] = /* @__PURE__ */ new Set());
|
|
57
|
+
};
|
|
58
|
+
var isProduction = () => {
|
|
59
|
+
try {
|
|
60
|
+
return process.env.NODE_ENV === "production";
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var isQuiet = () => globalThis.__GAME_KIT_QUIET__ === true;
|
|
66
|
+
var warnOnce = (key, message) => {
|
|
67
|
+
if (isQuiet() || isProduction()) return;
|
|
68
|
+
const seen = warnedKeys();
|
|
69
|
+
if (seen.has(key)) return;
|
|
70
|
+
seen.add(key);
|
|
71
|
+
console.warn(`[game-kit] ${message}`);
|
|
72
|
+
};
|
|
40
73
|
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
41
74
|
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
42
75
|
var deepMerge = (base, patch) => {
|
|
@@ -58,10 +91,13 @@ var GameUrbanThree = (() => {
|
|
|
58
91
|
for (const [id, patch] of Object.entries(patches ?? {})) {
|
|
59
92
|
const entry = entries.get(id);
|
|
60
93
|
if (entry) entries.set(id, deepMerge(entry, patch));
|
|
94
|
+
else warnOnce(`registry.patch.${id}`, `patch for "${id}" ignored: no entry has that id`);
|
|
61
95
|
}
|
|
62
96
|
};
|
|
63
97
|
var removeIds = (entries, ids) => {
|
|
64
|
-
for (const id of ids ?? [])
|
|
98
|
+
for (const id of ids ?? []) {
|
|
99
|
+
if (!entries.delete(id)) warnOnce(`registry.remove.${id}`, `remove of "${id}" ignored: no entry has that id`);
|
|
100
|
+
}
|
|
65
101
|
};
|
|
66
102
|
var KIT_ORIGIN_KEY = "kitOrigin";
|
|
67
103
|
function markKitOrigin(object2, origin) {
|
|
@@ -70,8 +106,63 @@ var GameUrbanThree = (() => {
|
|
|
70
106
|
node.userData[KIT_ORIGIN_KEY] = { ...origin };
|
|
71
107
|
return object2;
|
|
72
108
|
}
|
|
109
|
+
var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
|
|
110
|
+
var GAIT_IDS = ["walk", "run"];
|
|
111
|
+
var LANDING_IDS = ["light", "heavy"];
|
|
112
|
+
var BODY_SOUND_IDS = [
|
|
113
|
+
"jump",
|
|
114
|
+
"cloth_rustle",
|
|
115
|
+
"body_fall",
|
|
116
|
+
"fall_damage",
|
|
117
|
+
"player_hurt",
|
|
118
|
+
"player_breath_run",
|
|
119
|
+
"player_death",
|
|
120
|
+
"bite",
|
|
121
|
+
"grab",
|
|
122
|
+
"punch_swing",
|
|
123
|
+
"punch_hit_body",
|
|
124
|
+
"punch_hit_face",
|
|
125
|
+
"punch_hit_object",
|
|
126
|
+
"kick_swing",
|
|
127
|
+
"kick_hit",
|
|
128
|
+
"kick_hit_object",
|
|
129
|
+
"shove_two_hands",
|
|
130
|
+
"knife_swing",
|
|
131
|
+
"knife_stab",
|
|
132
|
+
"knife_slash",
|
|
133
|
+
"knife_hit_object",
|
|
134
|
+
"blunt_swing",
|
|
135
|
+
"blunt_hit",
|
|
136
|
+
"blunt_hit_object",
|
|
137
|
+
"axe_swing",
|
|
138
|
+
"axe_chop",
|
|
139
|
+
"axe_hit_object"
|
|
140
|
+
];
|
|
141
|
+
var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
|
|
142
|
+
var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
|
|
143
|
+
var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing) => `land_${surface}_${landing}`));
|
|
144
|
+
var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
|
|
145
|
+
|
|
146
|
+
// ../game-render-three/dist/index.js
|
|
147
|
+
var DEFAULT_ANGLE = Math.PI / 3;
|
|
148
|
+
var LIGHT_PRIORITY = {
|
|
149
|
+
blast: 900,
|
|
150
|
+
rocket: 800,
|
|
151
|
+
muzzle: 750,
|
|
152
|
+
fire: 600,
|
|
153
|
+
flare: 500,
|
|
154
|
+
headlight: 400,
|
|
155
|
+
weaponLight: 400,
|
|
156
|
+
lamp: 200,
|
|
157
|
+
street: 150
|
|
158
|
+
};
|
|
159
|
+
function isLightBudget(value) {
|
|
160
|
+
const candidate = value;
|
|
161
|
+
return typeof candidate === "object" && candidate !== null && typeof candidate.acquire === "function" && typeof candidate.addSource === "function" && typeof candidate.lease === "function" && typeof candidate.present === "function";
|
|
162
|
+
}
|
|
163
|
+
var EFFECT_STEP_SECONDS = 1 / 50;
|
|
73
164
|
|
|
74
|
-
// ../game-shapes/dist/chunk-
|
|
165
|
+
// ../game-shapes/dist/chunk-M5N2SHNL.js
|
|
75
166
|
function canonicalJson(value) {
|
|
76
167
|
if (value === null) return "null";
|
|
77
168
|
if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
|
|
@@ -1318,8 +1409,8 @@ var GameUrbanThree = (() => {
|
|
|
1318
1409
|
groups: []
|
|
1319
1410
|
};
|
|
1320
1411
|
}
|
|
1321
|
-
function make(
|
|
1322
|
-
switch (kind) {
|
|
1412
|
+
function make(p) {
|
|
1413
|
+
switch (p.kind) {
|
|
1323
1414
|
case "box": {
|
|
1324
1415
|
const size = p.size ?? [1, 1, 1];
|
|
1325
1416
|
const segments = p.segments ?? [1, 1, 1];
|
|
@@ -1348,7 +1439,7 @@ var GameUrbanThree = (() => {
|
|
|
1348
1439
|
case "mesh":
|
|
1349
1440
|
return mesh(p);
|
|
1350
1441
|
default:
|
|
1351
|
-
throw new Error(`game-shapes: unknown primitive kind "${String(kind)}"`);
|
|
1442
|
+
throw new Error(`game-shapes: unknown primitive kind "${String(p.kind)}"`);
|
|
1352
1443
|
}
|
|
1353
1444
|
}
|
|
1354
1445
|
var templates = /* @__PURE__ */ new Map();
|
|
@@ -1356,7 +1447,7 @@ var GameUrbanThree = (() => {
|
|
|
1356
1447
|
var stats = { hits: 0, misses: 0 };
|
|
1357
1448
|
function buildPrimitive(node, options = {}) {
|
|
1358
1449
|
const parameters = primitiveParameters(node, options);
|
|
1359
|
-
if (node.kind === "mesh" || templateLimit === 0) return make(node
|
|
1450
|
+
if (node.kind === "mesh" || templateLimit === 0) return make({ ...node, ...parameters });
|
|
1360
1451
|
const key = `${node.kind}:${canonicalJson(parameters)}`;
|
|
1361
1452
|
const cached = templates.get(key);
|
|
1362
1453
|
if (cached) {
|
|
@@ -1366,7 +1457,7 @@ var GameUrbanThree = (() => {
|
|
|
1366
1457
|
return cached;
|
|
1367
1458
|
}
|
|
1368
1459
|
stats.misses++;
|
|
1369
|
-
const built = make(node
|
|
1460
|
+
const built = make({ ...node, ...parameters });
|
|
1370
1461
|
templates.set(key, built);
|
|
1371
1462
|
if (templates.size > templateLimit) templates.delete(templates.keys().next().value);
|
|
1372
1463
|
return built;
|
|
@@ -1725,6 +1816,7 @@ var GameUrbanThree = (() => {
|
|
|
1725
1816
|
canvas.width = o.width;
|
|
1726
1817
|
canvas.height = o.height;
|
|
1727
1818
|
const g = canvas.getContext("2d");
|
|
1819
|
+
if (!g) throw new Error("game-urban-three: the canvas has no 2D context");
|
|
1728
1820
|
g.fillStyle = o.background;
|
|
1729
1821
|
g.fillRect(0, 0, o.width, o.height);
|
|
1730
1822
|
g.fillStyle = o.foreground;
|
|
@@ -1803,6 +1895,27 @@ var GameUrbanThree = (() => {
|
|
|
1803
1895
|
disposables.push(mesh2.geometry, mesh2.material);
|
|
1804
1896
|
return mesh2;
|
|
1805
1897
|
};
|
|
1898
|
+
const forward = new THREE.Vector3(0, 0, -1);
|
|
1899
|
+
const aimAt = (node, from, to) => {
|
|
1900
|
+
node.quaternion.setFromUnitVectors(forward, new THREE.Vector3(to.x - from.x, to.y - from.y, to.z - from.z).normalize());
|
|
1901
|
+
};
|
|
1902
|
+
const sourceOf = (part, bulb, extra) => options.lights.addSource({ owner: "urban.light", priority: LIGHT_PRIORITY.street, color: part.color, intensity: part.intensity, distance: part.distance, decay: part.decay, position: [0, 0, 0], follow: bulb, ...extra });
|
|
1903
|
+
const drawBudgetedLight = (part, animators, lights) => {
|
|
1904
|
+
const bulb = new THREE.Object3D();
|
|
1905
|
+
bulb.position.set(part.x, part.y, part.z);
|
|
1906
|
+
if (part.kind === "point") {
|
|
1907
|
+
const source2 = sourceOf(part, bulb, {});
|
|
1908
|
+
lights.push({ bulb, part, source: source2 });
|
|
1909
|
+
if (part.flicker) animators.push((t) => source2.update({ intensity: part.intensity * (Math.sin(t * 23 + part.x) > 0.3 || Math.sin(t * 3.1) > 0.85 ? 0.15 : 1) }));
|
|
1910
|
+
return [bulb];
|
|
1911
|
+
}
|
|
1912
|
+
aimAt(bulb, part, part.target);
|
|
1913
|
+
const source = sourceOf(part, bulb, { kind: "spot", angle: part.angle, penumbra: part.penumbra });
|
|
1914
|
+
lights.push({ bulb, part, source });
|
|
1915
|
+
const { sweep } = part;
|
|
1916
|
+
if (sweep) animators.push((t) => aimAt(bulb, part, { x: part.x + Math.sin(t * sweep.speed + part.x) * sweep.radius, y: 0, z: part.z + Math.cos(t * sweep.speed + part.x) * sweep.radius }));
|
|
1917
|
+
return [bulb];
|
|
1918
|
+
};
|
|
1806
1919
|
const drawPart = (part, animators, lights) => {
|
|
1807
1920
|
switch (part.shape) {
|
|
1808
1921
|
case "group": {
|
|
@@ -1916,6 +2029,7 @@ var GameUrbanThree = (() => {
|
|
|
1916
2029
|
return [holder];
|
|
1917
2030
|
}
|
|
1918
2031
|
case "light": {
|
|
2032
|
+
if (options.lights) return drawBudgetedLight(part, animators, lights);
|
|
1919
2033
|
if (part.kind === "point") {
|
|
1920
2034
|
const light2 = new THREE.PointLight(part.color, part.intensity, part.distance, part.decay);
|
|
1921
2035
|
light2.position.set(part.x, part.y, part.z);
|
|
@@ -1996,6 +2110,9 @@ var GameUrbanThree = (() => {
|
|
|
1996
2110
|
lights,
|
|
1997
2111
|
animate: (t) => animators.forEach((step) => step(t)),
|
|
1998
2112
|
dispose: () => {
|
|
2113
|
+
if (options.lights) {
|
|
2114
|
+
for (const lamp of lights) if ("source" in lamp) lamp.source.remove();
|
|
2115
|
+
}
|
|
1999
2116
|
root.parent?.remove(root);
|
|
2000
2117
|
for (const item of disposables.splice(0)) item.dispose?.();
|
|
2001
2118
|
for (const geometry of geometries.values()) geometry.dispose();
|
|
@@ -2008,7 +2125,7 @@ var GameUrbanThree = (() => {
|
|
|
2008
2125
|
return { THREE, draw };
|
|
2009
2126
|
}
|
|
2010
2127
|
|
|
2011
|
-
// ../game-vegetation/dist/chunk-
|
|
2128
|
+
// ../game-vegetation/dist/chunk-CJRLKDWB.js
|
|
2012
2129
|
var __defProp2 = Object.defineProperty;
|
|
2013
2130
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
2014
2131
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -3149,8 +3266,8 @@ var GameUrbanThree = (() => {
|
|
|
3149
3266
|
const n = o.lod === 2 ? 4 : 6;
|
|
3150
3267
|
for (let i = 0; i < n; i++) {
|
|
3151
3268
|
const c = jitter(col(i), rng, 4e-3, 0.03, 0.02);
|
|
3152
|
-
const
|
|
3153
|
-
petal(b, f, { angle: i / n * TAU + (
|
|
3269
|
+
const inner2 = i % 2;
|
|
3270
|
+
petal(b, f, { angle: i / n * TAU + (inner2 ? 0.5 : 0), r0: s * (inner2 ? 0.2 : 0.35), elev: inner2 ? 1.35 : 1.15, length: s * (inner2 ? 1.7 : 1.9), width: s * 1.05, c0: shade(c, 0.78), c1: lighten(c, 0.22), curl: 0.28, face: "radial", rows: o.lod === 0 ? 3 : 2, swayK: sw, peak: 0.45 });
|
|
3154
3271
|
}
|
|
3155
3272
|
break;
|
|
3156
3273
|
}
|
|
@@ -3719,7 +3836,7 @@ var GameUrbanThree = (() => {
|
|
|
3719
3836
|
const bloom = spec.bloom;
|
|
3720
3837
|
const bloomOn = !!bloom && inSeason(ctx, bloom.seasons);
|
|
3721
3838
|
const replaces = bloomOn && !!bloom?.replacesLeaves;
|
|
3722
|
-
const
|
|
3839
|
+
const palette2 = (spec.leafColor ?? DEFAULT_LEAF).map(rgb);
|
|
3723
3840
|
const amount = leafAmount(ctx, deciduous && !replaces);
|
|
3724
3841
|
const baseKeep = spec.density ?? 1;
|
|
3725
3842
|
const keepThreshold = clamp(amount * baseKeep, 0, 1.3);
|
|
@@ -3743,7 +3860,7 @@ var GameUrbanThree = (() => {
|
|
|
3743
3860
|
const anchors = drawTreeWood(ctx, tree, (br, i) => drawBranch(ctx.wood, br, bark, ctx.lod, i < 0 ? 0 : i * 0.37));
|
|
3744
3861
|
ctx.piece("crown", "crown", "trunk");
|
|
3745
3862
|
const colorFor = (tip) => {
|
|
3746
|
-
const pc =
|
|
3863
|
+
const pc = palette2[Math.floor(tip.roll * palette2.length) % palette2.length];
|
|
3747
3864
|
let base = leafTint(ctx, pc, deciduous, tip.roll);
|
|
3748
3865
|
if (bloomOn && bloom) {
|
|
3749
3866
|
const bc = rgb(bloom.colors[Math.floor((tip.seed >>> 8) % 1e3 / 1e3 * bloom.colors.length)]);
|
|
@@ -3769,12 +3886,12 @@ var GameUrbanThree = (() => {
|
|
|
3769
3886
|
for (let bi = 0; bi < tree.branches.length; bi++) {
|
|
3770
3887
|
const br = tree.branches[bi];
|
|
3771
3888
|
if (br.level !== 1) continue;
|
|
3772
|
-
const
|
|
3889
|
+
const r22 = new Rng(Math.floor(br.radii[0] * 1e6) ^ bi * 7919);
|
|
3773
3890
|
const mid = br.pts[Math.floor(br.pts.length * 0.55)];
|
|
3774
|
-
const roll =
|
|
3891
|
+
const roll = r22.next();
|
|
3775
3892
|
if (roll > keepThreshold) continue;
|
|
3776
3893
|
const r = crownRadius * rK * 0.95 * grow;
|
|
3777
|
-
anchors2.push({ p: add(mid, [0, r * 0.35, 0]), r, tip: { p: mid, dir: [0, 1, 0], size: 1, level: 1, seed:
|
|
3894
|
+
anchors2.push({ p: add(mid, [0, r * 0.35, 0]), r, tip: { p: mid, dir: [0, 1, 0], size: 1, level: 1, seed: r22.seed(), keep: roll, roll: r22.next() } });
|
|
3778
3895
|
}
|
|
3779
3896
|
}
|
|
3780
3897
|
const crown = tree.crown;
|
|
@@ -3812,17 +3929,17 @@ var GameUrbanThree = (() => {
|
|
|
3812
3929
|
}
|
|
3813
3930
|
}
|
|
3814
3931
|
if (ctx.condition === "overgrown" && ctx.lod < 2) {
|
|
3815
|
-
const
|
|
3816
|
-
clump(ctx.foliage, { ...o, center: add(g.p, [
|
|
3932
|
+
const r22 = new Rng(t0.seed ^ 4660);
|
|
3933
|
+
clump(ctx.foliage, { ...o, center: add(g.p, [r22.range(-0.4, 0.4) * g.r, g.r * r22.range(0.2, 0.5), r22.range(-0.4, 0.4) * g.r]), radius: g.r * 0.7, seed: t0.seed ^ 119, leaves: void 0, detail: 1 });
|
|
3817
3934
|
}
|
|
3818
3935
|
if ((shape === "weeping" || sparse) && ctx.lod < 2) {
|
|
3819
|
-
const
|
|
3936
|
+
const r22 = new Rng(t0.seed ^ 20817);
|
|
3820
3937
|
const n = ctx.lod === 0 ? sparse ? 7 : 10 : sparse ? 3 : 4;
|
|
3821
3938
|
for (let i = 0; i < n; i++) {
|
|
3822
|
-
const ang =
|
|
3823
|
-
const rr = g.r *
|
|
3939
|
+
const ang = r22.range(0, TAU);
|
|
3940
|
+
const rr = g.r * r22.range(0.3, 0.95);
|
|
3824
3941
|
const top = [g.p[0] + Math.cos(ang) * rr, g.p[1] - g.r * 0.2, g.p[2] + Math.sin(ang) * rr];
|
|
3825
|
-
const hang = g.r *
|
|
3942
|
+
const hang = g.r * r22.range(1.4, 2.4) * (sparse ? 1.3 : 1);
|
|
3826
3943
|
const g0 = shade(base, 0.75);
|
|
3827
3944
|
const g1 = mix(base, [0.78, 0.82, 0.4], 0.35);
|
|
3828
3945
|
leaf(ctx.foliage, top, [0.1 * Math.cos(ang), -1, 0.1 * Math.sin(ang)], [Math.cos(ang), 0.2, Math.sin(ang)], hang, g.r * 0.3, g0, g1, { rows: 2, curl: 0.05, fold: 0.3, swayBase: 0.4, swayTip: 1, peak: 0.25 });
|
|
@@ -3831,17 +3948,17 @@ var GameUrbanThree = (() => {
|
|
|
3831
3948
|
}
|
|
3832
3949
|
}
|
|
3833
3950
|
if (bloomOn && bloom && ctx.lod === 0) {
|
|
3834
|
-
const
|
|
3951
|
+
const r22 = new Rng(Math.floor(height * 1e3) + 77);
|
|
3835
3952
|
for (const a of groups) {
|
|
3836
3953
|
const n = replaces ? 6 : 5;
|
|
3837
3954
|
for (let i = 0; i < n; i++) {
|
|
3838
|
-
const ang =
|
|
3839
|
-
const y =
|
|
3955
|
+
const ang = r22.range(0, TAU);
|
|
3956
|
+
const y = r22.range(0.1, 1);
|
|
3840
3957
|
const rr = Math.sqrt(1 - y * y);
|
|
3841
3958
|
const d = [Math.cos(ang) * rr, y, Math.sin(ang) * rr];
|
|
3842
3959
|
const p = [a.p[0] + d[0] * a.r * 1, a.p[1] + d[1] * a.r * (a.sq ?? squash) * 1, a.p[2] + d[2] * a.r * 1];
|
|
3843
|
-
const bc = rgb(
|
|
3844
|
-
blossom(ctx.flowers, p, norm(d), a.r * 0.2, shade(bc, 1.05), jitter(bc,
|
|
3960
|
+
const bc = rgb(r22.pick(bloom.colors));
|
|
3961
|
+
blossom(ctx.flowers, p, norm(d), a.r * 0.2, shade(bc, 1.05), jitter(bc, r22, 0.01, 0.05), 0.5, 4, r22.range(0, TAU));
|
|
3845
3962
|
}
|
|
3846
3963
|
}
|
|
3847
3964
|
}
|
|
@@ -3849,41 +3966,41 @@ var GameUrbanThree = (() => {
|
|
|
3849
3966
|
if (spec.fruit && ctx.condition !== "dry") {
|
|
3850
3967
|
const f = spec.fruit;
|
|
3851
3968
|
const count = Math.round(f.count * ctx.pick(1, 0.3, 0));
|
|
3852
|
-
const
|
|
3969
|
+
const r22 = new Rng(Math.floor(height * 733) + 5);
|
|
3853
3970
|
const fr = f.radius ?? 0.07;
|
|
3854
3971
|
if (f.onTrunk) {
|
|
3855
3972
|
const mains = tree.branches.filter((b2) => b2.level === 1);
|
|
3856
3973
|
for (let i = 0; i < count; i++) {
|
|
3857
|
-
const onBranch = mains.length > 0 &&
|
|
3858
|
-
const br = onBranch ? mains[
|
|
3974
|
+
const onBranch = mains.length > 0 && r22.next() > 0.45;
|
|
3975
|
+
const br = onBranch ? mains[r22.int(0, mains.length - 1)] : tree.trunk;
|
|
3859
3976
|
const tpts = br.pts;
|
|
3860
|
-
const u = onBranch ?
|
|
3977
|
+
const u = onBranch ? r22.range(0.05, 0.75) : r22.range(0.03, Math.min(0.7, trunkFraction * 1.05 / 0.93));
|
|
3861
3978
|
const idx = Math.min(tpts.length - 2, Math.floor(u * (tpts.length - 1)));
|
|
3862
3979
|
const k = u * (tpts.length - 1) - idx;
|
|
3863
3980
|
const pos = [lerp(tpts[idx][0], tpts[idx + 1][0], k), lerp(tpts[idx][1], tpts[idx + 1][1], k), lerp(tpts[idx][2], tpts[idx + 1][2], k)];
|
|
3864
3981
|
const rad = lerp(br.radii[idx], br.radii[idx + 1], k);
|
|
3865
|
-
const ang =
|
|
3866
|
-
const c = rgb(
|
|
3867
|
-
const out = [Math.cos(ang),
|
|
3982
|
+
const ang = r22.range(0, TAU);
|
|
3983
|
+
const c = rgb(r22.pick(f.colors));
|
|
3984
|
+
const out = [Math.cos(ang), r22.range(-0.3, 0.5), Math.sin(ang)];
|
|
3868
3985
|
const center = add(pos, mul(norm(out), rad + fr * 0.4));
|
|
3869
|
-
const rr = fr *
|
|
3986
|
+
const rr = fr * r22.range(0.8, 1.2);
|
|
3870
3987
|
ctx.fruitItem(() => octa(ctx.fruit, center, [rr, rr, rr], (h) => shade(c, 0.75 + h * 0.6), 0));
|
|
3871
3988
|
}
|
|
3872
3989
|
} else {
|
|
3873
3990
|
for (let i = 0; i < count; i++) {
|
|
3874
3991
|
const a = groups[i % groups.length];
|
|
3875
|
-
const ang =
|
|
3876
|
-
const rr = a.r *
|
|
3992
|
+
const ang = r22.range(0, TAU);
|
|
3993
|
+
const rr = a.r * r22.range(0.45, 0.9);
|
|
3877
3994
|
const depth = Math.sqrt(Math.max(0, 1 - (rr / a.r) ** 2)) * 0.8;
|
|
3878
3995
|
const top = [a.p[0] + Math.cos(ang) * rr, a.p[1] - a.r * (a.sq ?? squash) * depth * 0.9, a.p[2] + Math.sin(ang) * rr];
|
|
3879
|
-
const c = rgb(
|
|
3880
|
-
const s = fr *
|
|
3996
|
+
const c = rgb(r22.pick(f.colors));
|
|
3997
|
+
const s = fr * r22.range(0.9, 1.15);
|
|
3881
3998
|
const drop = s * 3.2;
|
|
3882
3999
|
const centre = [top[0], top[1] - drop - s * 0.6, top[2]];
|
|
3883
4000
|
ctx.fruitItem(() => {
|
|
3884
4001
|
tube(ctx.wood, [top, [top[0], top[1] - drop * 0.8, top[2]]], { sides: 3, radii: [8e-3, 8e-3], color: () => [0.3, 0.4, 0.2], sway: () => 0.5, end: "none" });
|
|
3885
4002
|
if (s < 0.06) octa(ctx.fruit, centre, [s * 0.85, s * 1.2, s * 0.85], (h) => shade(c, 0.7 + h * 0.45), 0.4);
|
|
3886
|
-
else icoShell(ctx.fruit, { center: centre, radii: [s * 0.85, s * 1.2, s * 0.85], detail: 0, rng:
|
|
4003
|
+
else icoShell(ctx.fruit, { center: centre, radii: [s * 0.85, s * 1.2, s * 0.85], detail: 0, rng: r22, noise: 0.04, color: (v) => shade(c, 0.7 + v.h * 0.45), sway: () => 0.4 });
|
|
3887
4004
|
});
|
|
3888
4005
|
}
|
|
3889
4006
|
}
|
|
@@ -5217,7 +5334,7 @@ var GameUrbanThree = (() => {
|
|
|
5217
5334
|
const R = (spec.radius ?? 0.6) * (over ? 1.2 : 1);
|
|
5218
5335
|
const H = (spec.height ?? (shape === "hedge" ? 1.1 : R * 1.5)) * (over ? 1.15 : 1);
|
|
5219
5336
|
const density = (spec.density ?? 1) * leafAmount(ctx, false);
|
|
5220
|
-
const
|
|
5337
|
+
const palette2 = (spec.leafColor ?? DEFAULT_LEAF2).map((c) => tintGreen(rgb(c), ctx.condition));
|
|
5221
5338
|
const stemC = rgb(spec.stemColor ?? 6966324);
|
|
5222
5339
|
const flowers = spec.flowers && !dead && !(ctx.season === "winter" && rng.fork("w").next() < 0.5);
|
|
5223
5340
|
const variegation = spec.variegation?.map(rgb);
|
|
@@ -5251,7 +5368,7 @@ var GameUrbanThree = (() => {
|
|
|
5251
5368
|
const sections = Math.max(1, Math.round(length / (width * 1.1)));
|
|
5252
5369
|
const secLen = length / sections;
|
|
5253
5370
|
const grid = ctx.lod === 0 ? [6, 4, 4] : ctx.lod === 1 ? [4, 3, 3] : [3, 2, 2];
|
|
5254
|
-
const base = dead ? mix(
|
|
5371
|
+
const base = dead ? mix(palette2[0], [0.42, 0.33, 0.22], 0.65) : ctx.condition === "dry" ? mix(palette2[0], [0.5, 0.45, 0.22], 0.35) : palette2[0];
|
|
5255
5372
|
for (let i = 0; i < sections; i++) {
|
|
5256
5373
|
const sr = rng.fork(i * 17 + 3);
|
|
5257
5374
|
const cx = -length / 2 + secLen * (i + 0.5);
|
|
@@ -5311,7 +5428,7 @@ var GameUrbanThree = (() => {
|
|
|
5311
5428
|
}
|
|
5312
5429
|
anchors.forEach((a, idx) => {
|
|
5313
5430
|
const crn = idx % 2 === 0;
|
|
5314
|
-
const base = variegation && crn ? variegation[idx % variegation.length] :
|
|
5431
|
+
const base = variegation && crn ? variegation[idx % variegation.length] : palette2[idx % palette2.length];
|
|
5315
5432
|
const o = {
|
|
5316
5433
|
center: a.p,
|
|
5317
5434
|
radius: a.r,
|
|
@@ -5584,29 +5701,29 @@ var GameUrbanThree = (() => {
|
|
|
5584
5701
|
const a1 = (i + 1) / sides * TAU;
|
|
5585
5702
|
ctx.wood.flatTri([0, soilY, 0], [Math.cos(a1) * rim, soilY, Math.sin(a1) * rim], [Math.cos(a0) * rim, soilY, Math.sin(a0) * rim], shade(soil, 0.9), soil, soil, 0, 0, 0, [0, 1, 0]);
|
|
5586
5703
|
}
|
|
5587
|
-
const
|
|
5704
|
+
const inner2 = generate2(spec.plant, { seed: ctx.rng.fork("plant").seed(), lod: ctx.lod, condition: ctx.condition, season: ctx.season });
|
|
5588
5705
|
const yOff = soilY - 0.02;
|
|
5589
5706
|
const tagsOf = (part) => {
|
|
5590
|
-
const mesh2 =
|
|
5707
|
+
const mesh2 = inner2.parts[part];
|
|
5591
5708
|
if (!mesh2) return void 0;
|
|
5592
5709
|
const tags = new Array(mesh2.triangleCount).fill("");
|
|
5593
|
-
for (const pc of
|
|
5710
|
+
for (const pc of inner2.pieces) {
|
|
5594
5711
|
if (pc.part !== part) continue;
|
|
5595
5712
|
if (!ctx.pieceMeta.has(pc.id)) ctx.pieceMeta.set(pc.id, { kind: pc.kind, parent: pc.parent, pivot: [pc.pivot[0], pc.pivot[1] + yOff, pc.pivot[2]], ...pc.radius === void 0 || pc.length === void 0 || !pc.axis || !pc.base ? {} : { geom: { radius: pc.radius, length: pc.length, axis: pc.axis, base: [pc.base[0], pc.base[1] + yOff, pc.base[2]] } } });
|
|
5596
5713
|
for (let t = pc.indexStart / 3; t < (pc.indexStart + pc.indexCount) / 3; t++) tags[t] = pc.id;
|
|
5597
5714
|
}
|
|
5598
5715
|
return (t) => tags[t];
|
|
5599
5716
|
};
|
|
5600
|
-
if (
|
|
5601
|
-
if (
|
|
5602
|
-
if (
|
|
5603
|
-
if (
|
|
5604
|
-
if (
|
|
5717
|
+
if (inner2.parts.wood) ctx.wood.appendMesh(inner2.parts.wood, 0, yOff, 0, 0, 1, tagsOf("wood"));
|
|
5718
|
+
if (inner2.parts.spines) ctx.spines.appendMesh(inner2.parts.spines, 0, yOff, 0, 0, 1, tagsOf("spines"));
|
|
5719
|
+
if (inner2.parts.foliage) ctx.foliage.appendMesh(inner2.parts.foliage, 0, yOff, 0, 0, 1, tagsOf("foliage"));
|
|
5720
|
+
if (inner2.parts.flowers) ctx.flowers.appendMesh(inner2.parts.flowers, 0, yOff, 0, 0, 1, tagsOf("flowers"));
|
|
5721
|
+
if (inner2.parts.fruit) ctx.fruit.appendMesh(inner2.parts.fruit, 0, yOff, 0, 0, 1, tagsOf("fruit"));
|
|
5605
5722
|
return {
|
|
5606
5723
|
collider: { kind: "cylinder", radius: Math.max(R * 1.1, 0.1), height: h },
|
|
5607
|
-
bendable:
|
|
5608
|
-
thorny:
|
|
5609
|
-
foliageMaterial:
|
|
5724
|
+
bendable: inner2.bendable,
|
|
5725
|
+
thorny: inner2.thorny,
|
|
5726
|
+
foliageMaterial: inner2.parts.foliage?.material
|
|
5610
5727
|
};
|
|
5611
5728
|
}
|
|
5612
5729
|
var GENERATORS = {
|
|
@@ -8745,7 +8862,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
8745
8862
|
const key = `${part.id}|${part.seed}`;
|
|
8746
8863
|
if (!made.has(key)) {
|
|
8747
8864
|
try {
|
|
8748
|
-
const mesh2 = generate2(toSpec(part.id), { seed: part.seed, lod: options.lod ?? 1, condition: options.condition ?? "healthy", season: options.season ?? "spring", species: part.id });
|
|
8865
|
+
const mesh2 = generate2((options.toSpec ?? toSpec)(part.id), { seed: part.seed, lod: options.lod ?? 1, condition: options.condition ?? "healthy", season: options.season ?? "spring", species: part.id });
|
|
8749
8866
|
made.set(key, createPlantObject(THREE, mesh2, materials));
|
|
8750
8867
|
} catch {
|
|
8751
8868
|
made.set(key, null);
|
|
@@ -8763,26 +8880,2185 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
8763
8880
|
}
|
|
8764
8881
|
|
|
8765
8882
|
// ../game-floorplan/dist/index.js
|
|
8766
|
-
var
|
|
8767
|
-
|
|
8768
|
-
|
|
8883
|
+
var createRegistries = () => ({
|
|
8884
|
+
CATALOG: {},
|
|
8885
|
+
LIBRARIES: {},
|
|
8886
|
+
LIBRARY_ALIAS: {},
|
|
8887
|
+
TEMPLATES: {},
|
|
8888
|
+
STYLES: {},
|
|
8889
|
+
STYLE_ALIAS: {},
|
|
8890
|
+
PALETTE_STYLES: {},
|
|
8891
|
+
ROOM_COLORS: [],
|
|
8892
|
+
PASSES: [],
|
|
8893
|
+
DECOR_PROFILES: {},
|
|
8894
|
+
GROUND_OVERLAYS: [],
|
|
8895
|
+
STATE_PLUGINS: { normal: { apply: () => [] } },
|
|
8896
|
+
WALL_TOP_HEIGHTS: { none: 0 },
|
|
8897
|
+
SEED_KEYS: { rooms: {}, items: {} },
|
|
8898
|
+
stateDistribution: { weights: null, seedKey: "state" },
|
|
8899
|
+
RESULT_EXTENSIONS: [],
|
|
8900
|
+
FINDS_PROVIDERS: [],
|
|
8901
|
+
VIEWER_LAYERS: [],
|
|
8902
|
+
LOCALES: {},
|
|
8903
|
+
ZONES: {},
|
|
8904
|
+
PERMEABLE_GROUNDS: /* @__PURE__ */ new Set()
|
|
8905
|
+
});
|
|
8906
|
+
var defaultRegistries;
|
|
8907
|
+
var activeRegistries;
|
|
8908
|
+
var getDefaultRegistries = () => defaultRegistries ?? (defaultRegistries = createRegistries());
|
|
8909
|
+
var registries = () => activeRegistries ?? getDefaultRegistries();
|
|
8910
|
+
function withRegistries(scope, run) {
|
|
8911
|
+
const previous = activeRegistries;
|
|
8912
|
+
activeRegistries = scope;
|
|
8913
|
+
try {
|
|
8914
|
+
return run();
|
|
8915
|
+
} finally {
|
|
8916
|
+
activeRegistries = previous;
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8769
8919
|
function registerItem(def) {
|
|
8770
8920
|
const h = def.h;
|
|
8771
8921
|
const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
|
|
8772
|
-
CATALOG[def.type] = item;
|
|
8922
|
+
registries().CATALOG[def.type] = item;
|
|
8773
8923
|
return item;
|
|
8774
8924
|
}
|
|
8775
8925
|
function registerItems(list2) {
|
|
8776
8926
|
for (const d of list2) registerItem(d);
|
|
8777
8927
|
}
|
|
8778
|
-
|
|
8928
|
+
function registerLibrary(id, library) {
|
|
8929
|
+
registries().LIBRARIES[id] = library;
|
|
8930
|
+
}
|
|
8931
|
+
function registerLibraryAlias(alias, id) {
|
|
8932
|
+
registries().LIBRARY_ALIAS[alias] = id;
|
|
8933
|
+
}
|
|
8934
|
+
function registerTemplate(t) {
|
|
8935
|
+
registries().TEMPLATES[t.id] = t;
|
|
8936
|
+
return t;
|
|
8937
|
+
}
|
|
8938
|
+
function registerStyle(style) {
|
|
8939
|
+
registries().STYLES[style.id] = style;
|
|
8940
|
+
for (const a of style.aliases || []) registries().STYLE_ALIAS[a] = style.id;
|
|
8941
|
+
}
|
|
8942
|
+
function registerPaletteStyle(id, def) {
|
|
8943
|
+
registries().PALETTE_STYLES[id] = def;
|
|
8944
|
+
}
|
|
8945
|
+
function registerRoomColors(list2) {
|
|
8946
|
+
for (const d of list2) {
|
|
8947
|
+
const i = registries().ROOM_COLORS.findIndex((x) => x.type === d.type);
|
|
8948
|
+
if (i >= 0) registries().ROOM_COLORS.splice(i, 1);
|
|
8949
|
+
registries().ROOM_COLORS.push(d);
|
|
8950
|
+
}
|
|
8951
|
+
registries().ROOM_COLORS.sort((a, b) => a.order - b.order);
|
|
8952
|
+
}
|
|
8953
|
+
function registerDecorProfile(styleId, profile) {
|
|
8954
|
+
registries().DECOR_PROFILES[styleId] = profile;
|
|
8955
|
+
}
|
|
8956
|
+
function registerPass(name, run) {
|
|
8957
|
+
const i = registries().PASSES.findIndex((p) => p.name === name);
|
|
8958
|
+
if (i >= 0) registries().PASSES[i] = { name, run };
|
|
8959
|
+
else registries().PASSES.push({ name, run });
|
|
8960
|
+
}
|
|
8961
|
+
function registerGroundOverlay(rule) {
|
|
8962
|
+
registries().GROUND_OVERLAYS.push(rule);
|
|
8963
|
+
}
|
|
8964
|
+
function registerWallTop(id, height) {
|
|
8965
|
+
registries().WALL_TOP_HEIGHTS[id] = height;
|
|
8966
|
+
}
|
|
8967
|
+
function registerSeedKeys(keys) {
|
|
8968
|
+
Object.assign(registries().SEED_KEYS.rooms, keys.rooms);
|
|
8969
|
+
Object.assign(registries().SEED_KEYS.items, keys.items);
|
|
8970
|
+
}
|
|
8971
|
+
function registerState(name, plugin) {
|
|
8972
|
+
registries().STATE_PLUGINS[name] = plugin;
|
|
8973
|
+
}
|
|
8974
|
+
function setStateDistribution(weights, seedKey = "state") {
|
|
8975
|
+
registries().stateDistribution.weights = weights;
|
|
8976
|
+
registries().stateDistribution.seedKey = seedKey;
|
|
8977
|
+
}
|
|
8978
|
+
var listStates = () => Object.keys(registries().STATE_PLUGINS);
|
|
8979
|
+
function resolveStyleId(id) {
|
|
8980
|
+
const s = id ?? "";
|
|
8981
|
+
return registries().STYLE_ALIAS[s] ?? s;
|
|
8982
|
+
}
|
|
8983
|
+
function styleOf(id) {
|
|
8984
|
+
return registries().STYLES[id] || registries().STYLES.budget || Object.values(registries().STYLES)[0];
|
|
8985
|
+
}
|
|
8986
|
+
function registerResultExtension(name, run, options = {}) {
|
|
8987
|
+
const entry = { name, run, after: options.after || [], seedKey: options.seedKey ?? name };
|
|
8988
|
+
const i = registries().RESULT_EXTENSIONS.findIndex((x) => x.name === name);
|
|
8989
|
+
if (i >= 0) registries().RESULT_EXTENSIONS[i] = entry;
|
|
8990
|
+
else registries().RESULT_EXTENSIONS.push(entry);
|
|
8991
|
+
}
|
|
8992
|
+
function registerFindsProvider(provider) {
|
|
8993
|
+
registries().FINDS_PROVIDERS.push(provider);
|
|
8994
|
+
}
|
|
8995
|
+
function registerViewerLayer(layer) {
|
|
8996
|
+
const i = registries().VIEWER_LAYERS.findIndex((x) => x.id === layer.id);
|
|
8997
|
+
if (i >= 0) registries().VIEWER_LAYERS[i] = layer;
|
|
8998
|
+
else registries().VIEWER_LAYERS.push(layer);
|
|
8999
|
+
}
|
|
9000
|
+
function mulberry322(a) {
|
|
9001
|
+
return function() {
|
|
9002
|
+
a |= 0;
|
|
9003
|
+
a = a + 1831565813 | 0;
|
|
9004
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
9005
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
9006
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
9007
|
+
};
|
|
9008
|
+
}
|
|
9009
|
+
function hashString2(input) {
|
|
9010
|
+
const s = String(input);
|
|
9011
|
+
let h = 1779033703 ^ s.length;
|
|
9012
|
+
for (let i = 0; i < s.length; i++) {
|
|
9013
|
+
h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
|
|
9014
|
+
h = h << 13 | h >>> 19;
|
|
9015
|
+
}
|
|
9016
|
+
h = Math.imul(h ^ h >>> 16, 2246822507);
|
|
9017
|
+
h = Math.imul(h ^ h >>> 13, 3266489909);
|
|
9018
|
+
return (h ^ h >>> 16) >>> 0;
|
|
9019
|
+
}
|
|
9020
|
+
var createRng2 = (seed, key) => mulberry322(hashString2(seed + "|" + key));
|
|
9021
|
+
function pickWeighted(rng, weights) {
|
|
9022
|
+
const keys = Object.keys(weights);
|
|
9023
|
+
let total = 0;
|
|
9024
|
+
for (const k of keys) total += weights[k];
|
|
9025
|
+
let x = rng() * total;
|
|
9026
|
+
for (const k of keys) {
|
|
9027
|
+
x -= weights[k];
|
|
9028
|
+
if (x < 0) return k;
|
|
9029
|
+
}
|
|
9030
|
+
return keys[keys.length - 1];
|
|
9031
|
+
}
|
|
9032
|
+
function weightedOrder(candidates, rng) {
|
|
9033
|
+
const arr = candidates.slice();
|
|
9034
|
+
const out = [];
|
|
9035
|
+
while (arr.length) {
|
|
9036
|
+
let total = 0;
|
|
9037
|
+
for (const c of arr) total += c.weight;
|
|
9038
|
+
let x = rng() * total;
|
|
9039
|
+
let i = 0;
|
|
9040
|
+
for (; i < arr.length - 1; i++) {
|
|
9041
|
+
x -= arr[i].weight;
|
|
9042
|
+
if (x < 0) break;
|
|
9043
|
+
}
|
|
9044
|
+
out.push(arr.splice(i, 1)[0]);
|
|
9045
|
+
}
|
|
9046
|
+
return out;
|
|
9047
|
+
}
|
|
9048
|
+
function shuffle(arr, rng) {
|
|
9049
|
+
const a = arr.slice();
|
|
9050
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
9051
|
+
const j = Math.floor(rng() * (i + 1));
|
|
9052
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
9053
|
+
}
|
|
9054
|
+
return a;
|
|
9055
|
+
}
|
|
9056
|
+
function orderOf(extensions) {
|
|
9057
|
+
const ordered = [];
|
|
9058
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
9059
|
+
const visit = (extension) => {
|
|
9060
|
+
if (ordered.includes(extension)) return;
|
|
9061
|
+
if (visiting.has(extension.name)) throw new Error("Result extensions depend on each other: " + extension.name);
|
|
9062
|
+
visiting.add(extension.name);
|
|
9063
|
+
for (const name of extension.after) {
|
|
9064
|
+
const before = extensions.find((x) => x.name === name);
|
|
9065
|
+
if (before) visit(before);
|
|
9066
|
+
}
|
|
9067
|
+
visiting.delete(extension.name);
|
|
9068
|
+
ordered.push(extension);
|
|
9069
|
+
};
|
|
9070
|
+
extensions.forEach(visit);
|
|
9071
|
+
return ordered;
|
|
9072
|
+
}
|
|
9073
|
+
function runResultExtensions(plan, result, seed, options) {
|
|
9074
|
+
for (const extension of orderOf(registries().RESULT_EXTENSIONS)) {
|
|
9075
|
+
result[extension.name] = extension.run({ plan, result, seed, rng: createRng2(seed, extension.seedKey), options });
|
|
9076
|
+
}
|
|
9077
|
+
}
|
|
9078
|
+
function findsFor(query) {
|
|
9079
|
+
return answerOf(query) ?? [];
|
|
9080
|
+
}
|
|
9081
|
+
function findsAnswer(query) {
|
|
9082
|
+
return answerOf(query);
|
|
9083
|
+
}
|
|
9084
|
+
function answerOf(query) {
|
|
9085
|
+
for (const provider of registries().FINDS_PROVIDERS) {
|
|
9086
|
+
const stacks = provider.findsFor(query);
|
|
9087
|
+
if (stacks !== null) return stacks;
|
|
9088
|
+
}
|
|
9089
|
+
return null;
|
|
9090
|
+
}
|
|
9091
|
+
var WALL_THICKNESS = 0.15;
|
|
9092
|
+
var MARGIN = WALL_THICKNESS / 2;
|
|
9093
|
+
var EPS = 1e-6;
|
|
9094
|
+
var TOL = 1e-3;
|
|
9095
|
+
var DOOR_CLEARANCE = 0.9;
|
|
9096
|
+
var PLAYER_RADIUS = 0.24;
|
|
9097
|
+
var DOOR_EDGE_CLEARANCE = 0.12;
|
|
9098
|
+
var WINDOW_EDGE_CLEARANCE = 0.3;
|
|
9099
|
+
var OVERHEAD_TOP = 2.15;
|
|
9100
|
+
var WINDOWLESS_ROOM_TYPES = /* @__PURE__ */ new Set([
|
|
9101
|
+
"stockroom",
|
|
9102
|
+
"kitchen",
|
|
9103
|
+
"laundry",
|
|
9104
|
+
"bathroom",
|
|
9105
|
+
"void",
|
|
9106
|
+
"elevator",
|
|
9107
|
+
"stairs",
|
|
9108
|
+
"pantry",
|
|
9109
|
+
"closet",
|
|
9110
|
+
"corridor",
|
|
9111
|
+
"hall",
|
|
9112
|
+
"balcony",
|
|
9113
|
+
"terrace",
|
|
9114
|
+
"garage",
|
|
9115
|
+
"storage",
|
|
9116
|
+
"porch",
|
|
9117
|
+
"tent",
|
|
9118
|
+
"outhouse"
|
|
9119
|
+
]);
|
|
9120
|
+
var CAR_CLEARANCE = { driver: 0.8, side: 0.6, end: 0.6 };
|
|
9121
|
+
var r2 = (x) => Math.round(x * 100) / 100;
|
|
9122
|
+
var clamp3 = (x, a, b) => Math.max(a, Math.min(b, x));
|
|
9123
|
+
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;
|
|
9124
|
+
var inner = (p) => [p[0] + MARGIN, p[1] + MARGIN, p[2] - MARGIN, p[3] - MARGIN];
|
|
9125
|
+
var eq = (a, b) => Math.abs(a - b) < TOL;
|
|
9126
|
+
var inflate2 = (r, e) => [r[0] - e, r[1] - e, r[2] + e, r[3] + e];
|
|
9127
|
+
var areaOf = (r) => (r[2] - r[0]) * (r[3] - r[1]);
|
|
9128
|
+
var topOf = (m) => (m.y || 0) + m.h;
|
|
9129
|
+
var extent = (size, f) => Math.abs(f[1]) === 1 ? [size[0], size[1]] : [size[1], size[0]];
|
|
9130
|
+
var cardinal = (dx, dz) => Math.abs(dx) > Math.abs(dz) ? [Math.sign(dx), 0] : [0, Math.sign(dz) || 1];
|
|
9131
|
+
function clearRect(r, f, clearance) {
|
|
9132
|
+
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]];
|
|
9133
|
+
}
|
|
9134
|
+
function insideUnion(r, inners) {
|
|
9135
|
+
const e = 1e-4;
|
|
9136
|
+
const points = [
|
|
9137
|
+
[r[0] + e, r[1] + e],
|
|
9138
|
+
[r[2] - e, r[1] + e],
|
|
9139
|
+
[r[0] + e, r[3] - e],
|
|
9140
|
+
[r[2] - e, r[3] - e],
|
|
9141
|
+
[(r[0] + r[2]) / 2, (r[1] + r[3]) / 2]
|
|
9142
|
+
];
|
|
9143
|
+
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));
|
|
9144
|
+
}
|
|
9145
|
+
function cutIntervals(intervals, a, b) {
|
|
9146
|
+
const out = [];
|
|
9147
|
+
for (const [x, y] of intervals) {
|
|
9148
|
+
if (b <= x || a >= y) {
|
|
9149
|
+
out.push([x, y]);
|
|
9150
|
+
continue;
|
|
9151
|
+
}
|
|
9152
|
+
if (a > x) out.push([x, a]);
|
|
9153
|
+
if (b < y) out.push([b, y]);
|
|
9154
|
+
}
|
|
9155
|
+
return out;
|
|
9156
|
+
}
|
|
9157
|
+
function exteriorEdge(room, side, w, d, c) {
|
|
9158
|
+
const cand = [];
|
|
9159
|
+
for (const p of room.parts) {
|
|
9160
|
+
if (side === "front" && eq(p[1], 0)) cand.push({ axis: "u", pos: 0, a0: p[0], a1: p[2] });
|
|
9161
|
+
else if (side === "back" && eq(p[3], d)) cand.push({ axis: "u", pos: d, a0: p[0], a1: p[2] });
|
|
9162
|
+
else if (side === "left" && eq(p[0], 0)) cand.push({ axis: "v", pos: 0, a0: p[1], a1: p[3] });
|
|
9163
|
+
else if (side === "right" && eq(p[2], w)) cand.push({ axis: "v", pos: w, a0: p[1], a1: p[3] });
|
|
9164
|
+
}
|
|
9165
|
+
if (!cand.length) return null;
|
|
9166
|
+
if (c != null) {
|
|
9167
|
+
const k = cand.find((x) => c >= x.a0 - TOL && c <= x.a1 + TOL);
|
|
9168
|
+
if (k) return k;
|
|
9169
|
+
}
|
|
9170
|
+
return cand.sort((x, y) => y.a1 - y.a0 - (x.a1 - x.a0))[0];
|
|
9171
|
+
}
|
|
9172
|
+
function sharedEdge(A, B, c) {
|
|
9173
|
+
const cand = [];
|
|
9174
|
+
for (const pa of A.parts)
|
|
9175
|
+
for (const pb of B.parts) {
|
|
9176
|
+
let o;
|
|
9177
|
+
if (eq(pa[2], pb[0]) || eq(pa[0], pb[2])) {
|
|
9178
|
+
o = [Math.max(pa[1], pb[1]), Math.min(pa[3], pb[3])];
|
|
9179
|
+
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] });
|
|
9180
|
+
}
|
|
9181
|
+
if (eq(pa[3], pb[1]) || eq(pa[1], pb[3])) {
|
|
9182
|
+
o = [Math.max(pa[0], pb[0]), Math.min(pa[2], pb[2])];
|
|
9183
|
+
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] });
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
if (!cand.length) return null;
|
|
9187
|
+
if (c != null) {
|
|
9188
|
+
const k = cand.find((x) => c >= x.a0 - TOL && c <= x.a1 + TOL);
|
|
9189
|
+
if (k) return k;
|
|
9190
|
+
}
|
|
9191
|
+
return cand.sort((x, y) => y.a1 - y.a0 - (x.a1 - x.a0))[0];
|
|
9192
|
+
}
|
|
9193
|
+
function openingZones(u, v, axis, width, depth) {
|
|
9194
|
+
const h = width / 2;
|
|
9195
|
+
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]];
|
|
9196
|
+
}
|
|
9197
|
+
var openingDepth = (width, leaf2) => Math.max(0.9, leaf2) + MARGIN;
|
|
9198
|
+
function entryPoints(ab) {
|
|
9199
|
+
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]];
|
|
9200
|
+
}
|
|
9201
|
+
function normalizeRoom(c, floorIndex) {
|
|
9202
|
+
const parts = [c.rect].concat(c.parts || []).map((p) => p.map(r2));
|
|
9203
|
+
return {
|
|
9204
|
+
id: c.id,
|
|
9205
|
+
type: c.type,
|
|
9206
|
+
label: c.label,
|
|
9207
|
+
floor: floorIndex,
|
|
9208
|
+
parts,
|
|
9209
|
+
library: c.library || null,
|
|
9210
|
+
without: c.without || [],
|
|
9211
|
+
overrides: c.overrides || {},
|
|
9212
|
+
extras: c.extras || [],
|
|
9213
|
+
slots: c.slots || null,
|
|
9214
|
+
link: c.link || null,
|
|
9215
|
+
outdoor: !!c.outdoor,
|
|
9216
|
+
ceiling: c.ceiling || null,
|
|
9217
|
+
seedKey: c.seedKey
|
|
9218
|
+
};
|
|
9219
|
+
}
|
|
9220
|
+
function usableParts(room, extra) {
|
|
9221
|
+
const m = MARGIN + (extra || 0);
|
|
9222
|
+
const out = [];
|
|
9223
|
+
for (const p of room.parts) {
|
|
9224
|
+
out.push([p[0] + m, p[1] + m, p[2] - m, p[3] - m]);
|
|
9225
|
+
for (let k = 0; k < 4; k++) {
|
|
9226
|
+
const spans = [];
|
|
9227
|
+
for (const q of room.parts) {
|
|
9228
|
+
if (q === p) continue;
|
|
9229
|
+
if (k === 0 && eq(q[2], p[0])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
9230
|
+
else if (k === 2 && eq(q[0], p[2])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
9231
|
+
else if (k === 1 && eq(q[3], p[1])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
9232
|
+
else if (k === 3 && eq(q[1], p[3])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
9233
|
+
}
|
|
9234
|
+
spans.sort((x, y) => x[0] - y[0]);
|
|
9235
|
+
const merged = [];
|
|
9236
|
+
for (const span of spans) {
|
|
9237
|
+
const last = merged[merged.length - 1];
|
|
9238
|
+
if (last && span[0] <= last[1] + TOL) last[1] = Math.max(last[1], span[1]);
|
|
9239
|
+
else merged.push([span[0], span[1]]);
|
|
9240
|
+
}
|
|
9241
|
+
for (const [a, b] of merged) {
|
|
9242
|
+
if (b - a <= 0.3) continue;
|
|
9243
|
+
const x0 = a + m;
|
|
9244
|
+
const x1 = b - m;
|
|
9245
|
+
if (x1 <= x0) continue;
|
|
9246
|
+
if (k === 0) out.push([p[0], x0, p[0] + m, x1]);
|
|
9247
|
+
else if (k === 2) out.push([p[2] - m, x0, p[2], x1]);
|
|
9248
|
+
else if (k === 1) out.push([x0, p[1], x1, p[1] + m]);
|
|
9249
|
+
else out.push([x0, p[3] - m, x1, p[3]]);
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
}
|
|
9253
|
+
return out;
|
|
9254
|
+
}
|
|
9255
|
+
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);
|
|
9256
|
+
function buildWalls(rooms, doors, openPairs, floorIndex) {
|
|
9257
|
+
const groups = {};
|
|
9258
|
+
const add2 = (axis, pos, a, b, id, side) => {
|
|
9259
|
+
const k = axis + "|" + pos.toFixed(2);
|
|
9260
|
+
(groups[k] = groups[k] || { axis, pos, es: [] }).es.push({ a, b, id, side });
|
|
9261
|
+
};
|
|
9262
|
+
for (const c of rooms)
|
|
9263
|
+
for (const p of c.parts) {
|
|
9264
|
+
add2("u", p[1], p[0], p[2], c.id, 1);
|
|
9265
|
+
add2("u", p[3], p[0], p[2], c.id, -1);
|
|
9266
|
+
add2("v", p[0], p[1], p[3], c.id, 1);
|
|
9267
|
+
add2("v", p[2], p[1], p[3], c.id, -1);
|
|
9268
|
+
}
|
|
9269
|
+
const par = (x, y) => openPairs.some((o) => o[0] === x && o[1] === y || o[0] === y && o[1] === x);
|
|
9270
|
+
const typeOf = {};
|
|
9271
|
+
for (const c of rooms) typeOf[c.id] = c.type;
|
|
9272
|
+
const out = [];
|
|
9273
|
+
for (const g of Object.values(groups)) {
|
|
9274
|
+
const breaks = [...new Set(g.es.flatMap((e) => [r2(e.a), r2(e.b)]))].sort((x, y) => x - y);
|
|
9275
|
+
const segs = [];
|
|
9276
|
+
for (let i = 0; i < breaks.length - 1; i++) {
|
|
9277
|
+
const p = breaks[i];
|
|
9278
|
+
const q = breaks[i + 1];
|
|
9279
|
+
const m = (p + q) / 2;
|
|
9280
|
+
const after = g.es.find((e) => e.side === 1 && m > e.a && m < e.b);
|
|
9281
|
+
const before = g.es.find((e) => e.side === -1 && m > e.a && m < e.b);
|
|
9282
|
+
if (!after && !before) continue;
|
|
9283
|
+
const A = before ? before.id : "outside";
|
|
9284
|
+
const B = after ? after.id : "outside";
|
|
9285
|
+
if (A === B || A !== "outside" && B !== "outside" && par(A, B)) continue;
|
|
9286
|
+
const last = segs[segs.length - 1];
|
|
9287
|
+
if (last && last.A === A && last.B === B && eq(last.q, p)) last.q = q;
|
|
9288
|
+
else segs.push({ A, B, p, q });
|
|
9289
|
+
}
|
|
9290
|
+
for (const s of segs) {
|
|
9291
|
+
let pieces = [[s.p, s.q]];
|
|
9292
|
+
for (const pt of doors) {
|
|
9293
|
+
if (pt.axis !== g.axis || !eq(pt.pos, g.pos)) continue;
|
|
9294
|
+
const c0 = pt.at - pt.width / 2;
|
|
9295
|
+
const c1 = pt.at + pt.width / 2;
|
|
9296
|
+
const n = [];
|
|
9297
|
+
for (const [x, y] of pieces) {
|
|
9298
|
+
if (c1 <= x + TOL || c0 >= y - TOL) {
|
|
9299
|
+
n.push([x, y]);
|
|
9300
|
+
continue;
|
|
9301
|
+
}
|
|
9302
|
+
if (c0 > x + TOL) n.push([x, c0]);
|
|
9303
|
+
if (c1 < y - TOL) n.push([c1, y]);
|
|
9304
|
+
}
|
|
9305
|
+
pieces = n;
|
|
9306
|
+
}
|
|
9307
|
+
for (const [x, y] of pieces) {
|
|
9308
|
+
const isOpen = (t) => t === "balcony" || t === "terrace" || t === "porch";
|
|
9309
|
+
const isVoid = typeOf[s.A] === "void" || typeOf[s.B] === "void";
|
|
9310
|
+
if (isVoid && (s.A === "outside" || s.B === "outside")) continue;
|
|
9311
|
+
const railing = isVoid || isOpen(typeOf[s.A]) && s.B === "outside" || isOpen(typeOf[s.B]) && s.A === "outside";
|
|
9312
|
+
out.push({
|
|
9313
|
+
floor: floorIndex,
|
|
9314
|
+
axis: g.axis,
|
|
9315
|
+
u0: g.axis === "u" ? x : g.pos,
|
|
9316
|
+
v0: g.axis === "u" ? g.pos : x,
|
|
9317
|
+
u1: g.axis === "u" ? y : g.pos,
|
|
9318
|
+
v1: g.axis === "u" ? g.pos : y,
|
|
9319
|
+
thickness: WALL_THICKNESS,
|
|
9320
|
+
sides: [s.A, s.B],
|
|
9321
|
+
exterior: s.A === "outside" || s.B === "outside",
|
|
9322
|
+
type: railing ? "railing" : "wall",
|
|
9323
|
+
h: railing ? 1.1 : 0
|
|
9324
|
+
});
|
|
9325
|
+
}
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
return out;
|
|
9329
|
+
}
|
|
9330
|
+
var STEP = 0.05;
|
|
9331
|
+
var MAX_CANDIDATES = 80;
|
|
9332
|
+
function carCandidates(slot, item, car) {
|
|
9333
|
+
const front = [slot.frontU, slot.frontV];
|
|
9334
|
+
if (!slot._cell) throw new Error("Slot without a cell: " + slot.id);
|
|
9335
|
+
const zone = slot._cell.R;
|
|
9336
|
+
const alongV = Math.abs(front[1]) === 1;
|
|
9337
|
+
const [extentU, extentV] = extent([item.w, item.d], front);
|
|
9338
|
+
const across = alongV ? extentU : extentV;
|
|
9339
|
+
const along = alongV ? extentV : extentU;
|
|
9340
|
+
const a = alongV ? 0 : 1;
|
|
9341
|
+
const b = alongV ? 1 : 0;
|
|
9342
|
+
const driverSign = alongV ? front[1] : -front[0];
|
|
9343
|
+
const frontSign = alongV ? front[1] : front[0];
|
|
9344
|
+
const lowClear = driverSign < 0 ? car.driver : car.side;
|
|
9345
|
+
const highClear = driverSign < 0 ? car.side : car.driver;
|
|
9346
|
+
const lo = zone[a] + lowClear;
|
|
9347
|
+
const hi = zone[a + 2] - highClear - across;
|
|
9348
|
+
const alongLo = zone[b] + car.end;
|
|
9349
|
+
const alongHi = zone[b + 2] - car.end - along;
|
|
9350
|
+
if (hi < lo - 1e-6 || alongHi < alongLo - 1e-6) return [];
|
|
9351
|
+
const preferred = (alongV ? slot.x : slot.z) - across / 2;
|
|
9352
|
+
const alongStart = frontSign < 0 ? alongHi : alongLo;
|
|
9353
|
+
const positions = [];
|
|
9354
|
+
for (let p = lo; p <= hi + 1e-6; p += STEP) positions.push(Math.min(p, hi));
|
|
9355
|
+
positions.sort((x, y) => Math.abs(x - preferred) - Math.abs(y - preferred));
|
|
9356
|
+
return positions.slice(0, MAX_CANDIDATES).map((p) => {
|
|
9357
|
+
const rect = alongV ? [p, alongStart, p + across, alongStart + along] : [alongStart, p, alongStart + along, p + across];
|
|
9358
|
+
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];
|
|
9359
|
+
return { rect, clear };
|
|
9360
|
+
});
|
|
9361
|
+
}
|
|
9362
|
+
function zonesOfFloor(plan, floorIndex) {
|
|
9363
|
+
const out = [];
|
|
9364
|
+
if (floorIndex >= 0) {
|
|
9365
|
+
for (const p of plan.doors) if (p.floor === floorIndex) out.push(...p.zones);
|
|
9366
|
+
} else {
|
|
9367
|
+
for (const p of plan.doors) if (p.floor === 0 && p.exterior) out.push(...p.zones);
|
|
9368
|
+
for (const g of plan.gates) out.push(...g.zones);
|
|
9369
|
+
for (const c of plan.paths) out.push(...c.rects);
|
|
9370
|
+
out.push(...plan.reservations);
|
|
9371
|
+
}
|
|
9372
|
+
return out;
|
|
9373
|
+
}
|
|
9374
|
+
function openingsOfFloor(plan, floorIndex) {
|
|
9375
|
+
if (floorIndex >= 0) return plan.doors.filter((p) => p.floor === floorIndex);
|
|
9376
|
+
return plan.doors.filter((p) => p.floor === 0 && p.exterior).concat(plan.gates);
|
|
9377
|
+
}
|
|
9378
|
+
function windowZone(j, outside) {
|
|
9379
|
+
const h = j.width / 2 + 0.1;
|
|
9380
|
+
const depth = 0.5;
|
|
9381
|
+
let n;
|
|
9382
|
+
if (j.side === "front") n = [0, 1];
|
|
9383
|
+
else if (j.side === "back") n = [0, -1];
|
|
9384
|
+
else if (j.side === "left") n = [1, 0];
|
|
9385
|
+
else n = [-1, 0];
|
|
9386
|
+
if (outside) n = [-n[0], -n[1]];
|
|
9387
|
+
const u = j.u;
|
|
9388
|
+
const v = j.v;
|
|
9389
|
+
if (j.axis === "u") return n[1] > 0 ? [u - h, v - MARGIN, u + h, v + depth] : [u - h, v - depth, u + h, v + MARGIN];
|
|
9390
|
+
return n[0] > 0 ? [u - MARGIN, v - h, u + depth, v + h] : [u - depth, v - h, u + MARGIN, v + h];
|
|
9391
|
+
}
|
|
9392
|
+
function blocksWindow(it, win, overheadOff) {
|
|
9393
|
+
const height = Math.max(it.h, it.windowHeight || 0, it.overhead && !overheadOff ? OVERHEAD_TOP : 0);
|
|
9394
|
+
return !it.window || it.h > 1 + EPS || height > win.y0 - 0.05 + EPS;
|
|
9395
|
+
}
|
|
9396
|
+
function windowsOfRoom(plan, room) {
|
|
9397
|
+
if (!room.outdoor) return plan.windows.filter((j) => j.room === room.id);
|
|
9398
|
+
return plan.windows.filter(
|
|
9399
|
+
(j) => j.floor === 0 && room.parts.some((p) => {
|
|
9400
|
+
if (j.side === "front") return eq(p[3], j.pos) && j.u >= p[0] && j.u <= p[2];
|
|
9401
|
+
if (j.side === "back") return eq(p[1], j.pos) && j.u >= p[0] && j.u <= p[2];
|
|
9402
|
+
if (j.side === "left") return eq(p[2], j.pos) && j.v >= p[1] && j.v <= p[3];
|
|
9403
|
+
return eq(p[0], j.pos) && j.v >= p[1] && j.v <= p[3];
|
|
9404
|
+
})
|
|
9405
|
+
);
|
|
9406
|
+
}
|
|
9407
|
+
function roomAt(plan, u, v, floorIndex) {
|
|
9408
|
+
const list2 = floorIndex < 0 ? plan.zones : plan.rooms;
|
|
9409
|
+
return list2.find((c) => c.floor === floorIndex && inRect(c, u, v)) || null;
|
|
9410
|
+
}
|
|
9411
|
+
function isOpenSide(plan, room, pu, pv) {
|
|
9412
|
+
const o = roomAt(plan, pu, pv, room.floor);
|
|
9413
|
+
if (!o) return false;
|
|
9414
|
+
if (o.id === room.id) return true;
|
|
9415
|
+
if (room.outdoor) return true;
|
|
9416
|
+
return (plan.openPairs[room.floor] || []).some((x) => x[0] === room.id && x[1] === o.id || x[1] === room.id && x[0] === o.id);
|
|
9417
|
+
}
|
|
9418
|
+
function wallBodies(plan, floor) {
|
|
9419
|
+
const cache2 = plan.wallBodies = plan.wallBodies || {};
|
|
9420
|
+
if (cache2[floor]) return cache2[floor];
|
|
9421
|
+
const doors = plan.doors.filter((p) => p.floor === floor);
|
|
9422
|
+
return cache2[floor] = plan.walls.filter((p) => p.floor === floor && (p.type === "wall" || p.type === "railing")).map((p) => {
|
|
9423
|
+
const a0 = p.axis === "u" ? p.u0 : p.v0;
|
|
9424
|
+
const a1 = p.axis === "u" ? p.u1 : p.v1;
|
|
9425
|
+
const pos = p.axis === "u" ? p.v0 : p.u0;
|
|
9426
|
+
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)));
|
|
9427
|
+
const e0 = atDoor(a0) ? 0 : MARGIN;
|
|
9428
|
+
const e1 = atDoor(a1) ? 0 : MARGIN;
|
|
9429
|
+
return p.axis === "u" ? [a0 - e0, pos - MARGIN, a1 + e1, pos + MARGIN] : [pos - MARGIN, a0 - e0, pos + MARGIN, a1 + e1];
|
|
9430
|
+
});
|
|
9431
|
+
}
|
|
9432
|
+
function createGrid(plan, room, ignored, radius = plan.playerRadius) {
|
|
9433
|
+
const gs = room.outdoor ? 0.3 : 0.1;
|
|
9434
|
+
let u0 = 1e9;
|
|
9435
|
+
let v0 = 1e9;
|
|
9436
|
+
let u1 = -1e9;
|
|
9437
|
+
let v1 = -1e9;
|
|
9438
|
+
for (const p of room.parts) {
|
|
9439
|
+
u0 = Math.min(u0, p[0]);
|
|
9440
|
+
v0 = Math.min(v0, p[1]);
|
|
9441
|
+
u1 = Math.max(u1, p[2]);
|
|
9442
|
+
v1 = Math.max(v1, p[3]);
|
|
9443
|
+
}
|
|
9444
|
+
const nx = Math.ceil((u1 - u0) / gs);
|
|
9445
|
+
const nz = Math.ceil((v1 - v0) / gs);
|
|
9446
|
+
const insideCells = new Uint8Array(nx * nz);
|
|
9447
|
+
const insets = room.outdoor ? usableParts(room, 0.08) : room.parts;
|
|
9448
|
+
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);
|
|
9449
|
+
const tooClose = (u, v) => bodies.some((r) => {
|
|
9450
|
+
const dx = Math.max(r[0] - u, 0, u - r[2]);
|
|
9451
|
+
const dz = Math.max(r[1] - v, 0, v - r[3]);
|
|
9452
|
+
return dx * dx + dz * dz < radius * radius - 1e-9;
|
|
9453
|
+
});
|
|
9454
|
+
for (let j = 0; j < nz; j++)
|
|
9455
|
+
for (let i = 0; i < nx; i++) {
|
|
9456
|
+
const u = u0 + (i + 0.5) * gs;
|
|
9457
|
+
const v = v0 + (j + 0.5) * gs;
|
|
9458
|
+
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;
|
|
9459
|
+
}
|
|
9460
|
+
const entries = [];
|
|
9461
|
+
for (const ab of openingsOfFloor(plan, room.floor))
|
|
9462
|
+
if (!(ignored && ignored.has(ab.id)))
|
|
9463
|
+
for (const pt of entryPoints(ab)) {
|
|
9464
|
+
if (!inRect(room, pt[0], pt[1])) continue;
|
|
9465
|
+
const i = Math.floor((pt[0] - u0) / gs);
|
|
9466
|
+
const j = Math.floor((pt[1] - v0) / gs);
|
|
9467
|
+
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
9468
|
+
}
|
|
9469
|
+
if (!room.outdoor)
|
|
9470
|
+
for (const pair of plan.openPairs[room.floor] || []) {
|
|
9471
|
+
const otherId = pair[0] === room.id ? pair[1] : pair[1] === room.id ? pair[0] : null;
|
|
9472
|
+
const other = otherId && plan.rooms.find((c) => c.id === otherId && c.floor === room.floor);
|
|
9473
|
+
if (!other) continue;
|
|
9474
|
+
const edge = sharedEdge(room, other, null);
|
|
9475
|
+
if (!edge || edge.a1 - edge.a0 > 2) continue;
|
|
9476
|
+
const mid = (edge.a0 + edge.a1) / 2;
|
|
9477
|
+
for (const offset of [-0.4, 0.4]) {
|
|
9478
|
+
const pt = edge.axis === "u" ? [mid, edge.pos + offset] : [edge.pos + offset, mid];
|
|
9479
|
+
if (!inRect(room, pt[0], pt[1])) continue;
|
|
9480
|
+
const i = Math.floor((pt[0] - u0) / gs);
|
|
9481
|
+
const j = Math.floor((pt[1] - v0) / gs);
|
|
9482
|
+
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
9483
|
+
}
|
|
9484
|
+
}
|
|
9485
|
+
const R = inner(room.parts[0]);
|
|
9486
|
+
return { gs, u0, v0, nx, nz, inside: insideCells, entries, center: [(R[0] + R[2]) / 2, (R[1] + R[3]) / 2] };
|
|
9487
|
+
}
|
|
9488
|
+
function reachable(g, solids, inflation) {
|
|
9489
|
+
const { gs, u0, v0, nx, nz, inside } = g;
|
|
9490
|
+
const blocked = new Uint8Array(nx * nz);
|
|
9491
|
+
for (const r of solids) {
|
|
9492
|
+
const i0 = Math.max(0, Math.floor((r[0] - inflation - u0) / gs));
|
|
9493
|
+
const i1 = Math.min(nx - 1, Math.floor((r[2] + inflation - u0) / gs));
|
|
9494
|
+
const j0 = Math.max(0, Math.floor((r[1] - inflation - v0) / gs));
|
|
9495
|
+
const j1 = Math.min(nz - 1, Math.floor((r[3] + inflation - v0) / gs));
|
|
9496
|
+
for (let j = j0; j <= j1; j++)
|
|
9497
|
+
for (let i = i0; i <= i1; i++) {
|
|
9498
|
+
const u = u0 + (i + 0.5) * gs;
|
|
9499
|
+
const v = v0 + (j + 0.5) * gs;
|
|
9500
|
+
if (u > r[0] - inflation && u < r[2] + inflation && v > r[1] - inflation && v < r[3] + inflation) blocked[j * nx + i] = 1;
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
9503
|
+
let best = -1;
|
|
9504
|
+
let bestDist = 1e9;
|
|
9505
|
+
for (let j = 0; j < nz; j++)
|
|
9506
|
+
for (let i = 0; i < nx; i++) {
|
|
9507
|
+
const k = j * nx + i;
|
|
9508
|
+
if (!inside[k] || blocked[k]) continue;
|
|
9509
|
+
const dist2 = Math.hypot(u0 + (i + 0.5) * gs - g.center[0], v0 + (j + 0.5) * gs - g.center[1]);
|
|
9510
|
+
if (dist2 < bestDist) {
|
|
9511
|
+
bestDist = dist2;
|
|
9512
|
+
best = k;
|
|
9513
|
+
}
|
|
9514
|
+
}
|
|
9515
|
+
if (best < 0) return false;
|
|
9516
|
+
if (!g.entries.length) return true;
|
|
9517
|
+
for (const [i, j] of g.entries) if (blocked[j * nx + i]) return false;
|
|
9518
|
+
const visited = new Uint8Array(nx * nz);
|
|
9519
|
+
const queue = new Int32Array(nx * nz);
|
|
9520
|
+
let h = 0;
|
|
9521
|
+
let t = 0;
|
|
9522
|
+
const s = g.entries[0][1] * nx + g.entries[0][0];
|
|
9523
|
+
visited[s] = 1;
|
|
9524
|
+
queue[t++] = s;
|
|
9525
|
+
while (h < t) {
|
|
9526
|
+
const k = queue[h++];
|
|
9527
|
+
const i = k % nx;
|
|
9528
|
+
const j = (k - i) / nx;
|
|
9529
|
+
for (const [di, dj] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
9530
|
+
const a = i + di;
|
|
9531
|
+
const b = j + dj;
|
|
9532
|
+
if (a < 0 || b < 0 || a >= nx || b >= nz) continue;
|
|
9533
|
+
const q = b * nx + a;
|
|
9534
|
+
if (visited[q] || !inside[q] || blocked[q]) continue;
|
|
9535
|
+
visited[q] = 1;
|
|
9536
|
+
queue[t++] = q;
|
|
9537
|
+
}
|
|
9538
|
+
}
|
|
9539
|
+
if (!visited[best]) return false;
|
|
9540
|
+
for (const [i, j] of g.entries) if (!visited[j * nx + i]) return false;
|
|
9541
|
+
return true;
|
|
9542
|
+
}
|
|
9543
|
+
function candidatesOf(cats) {
|
|
9544
|
+
const out = [];
|
|
9545
|
+
for (const [cat, weight] of Object.entries(cats)) {
|
|
9546
|
+
const exact = cat[0] === "=";
|
|
9547
|
+
const its = Object.values(registries().CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
|
|
9548
|
+
for (const it of its) out.push({ it, weight: weight / its.length });
|
|
9549
|
+
}
|
|
9550
|
+
return out;
|
|
9551
|
+
}
|
|
9552
|
+
function itemRect(slot, it, shift) {
|
|
9553
|
+
const c = slot._cell;
|
|
9554
|
+
if (!c) throw new Error("Slot without a cell: " + slot.id);
|
|
9555
|
+
const f = c.f;
|
|
9556
|
+
const [extentU, extentV] = extent([it.w, it.d], f);
|
|
9557
|
+
const R = c.R;
|
|
9558
|
+
let u0 = (c.rect ? (c.rect[0] + c.rect[2]) / 2 : slot.x) - extentU / 2;
|
|
9559
|
+
let v0 = (c.rect ? (c.rect[1] + c.rect[3]) / 2 : slot.z) - extentV / 2;
|
|
9560
|
+
if (shift) {
|
|
9561
|
+
if (slot.side === "left" || slot.side === "right") v0 += shift;
|
|
9562
|
+
else u0 += shift;
|
|
9563
|
+
}
|
|
9564
|
+
for (const s of c.enclosed) {
|
|
9565
|
+
if (s === "left") u0 = R[0];
|
|
9566
|
+
else if (s === "right") u0 = R[2] - extentU;
|
|
9567
|
+
else if (s === "front") v0 = R[1];
|
|
9568
|
+
else if (s === "back") v0 = R[3] - extentV;
|
|
9569
|
+
}
|
|
9570
|
+
return [u0, v0, u0 + extentU, v0 + extentV];
|
|
9571
|
+
}
|
|
9572
|
+
var RELAXED_RADIUS = 0.12;
|
|
9573
|
+
var MESH_FRONT = { "+z": [0, 1], "-z": [0, -1], "+x": [1, 0], "-x": [-1, 0] };
|
|
9574
|
+
function rotationFor(f, front) {
|
|
9575
|
+
const meshFront = MESH_FRONT[front] || [0, 1];
|
|
9576
|
+
let ry = Math.atan2(f[0], f[1]) - Math.atan2(meshFront[0], meshFront[1]);
|
|
9577
|
+
if (ry > Math.PI + EPS) ry -= 2 * Math.PI;
|
|
9578
|
+
if (ry < -Math.PI - EPS) ry += 2 * Math.PI;
|
|
9579
|
+
return r2(ry);
|
|
9580
|
+
}
|
|
9581
|
+
function fillRoom(plan, room, slots2, rng) {
|
|
9582
|
+
const items = [];
|
|
9583
|
+
const missing = [];
|
|
9584
|
+
const state = {};
|
|
9585
|
+
const solids = [];
|
|
9586
|
+
const reserves = [];
|
|
9587
|
+
const rims = [];
|
|
9588
|
+
const inners = usableParts(room, 0);
|
|
9589
|
+
const grid = createGrid(plan, room);
|
|
9590
|
+
const relaxedGrid = room.outdoor ? grid : createGrid(plan, room, null, RELAXED_RADIUS);
|
|
9591
|
+
const zones = zonesOfFloor(plan, room.floor);
|
|
9592
|
+
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
9593
|
+
const inflation = room.outdoor ? 0.15 : plan.playerRadius;
|
|
9594
|
+
const units = [];
|
|
9595
|
+
const seen = {};
|
|
9596
|
+
for (const s of slots2) {
|
|
9597
|
+
if (s.group) {
|
|
9598
|
+
if (seen[s.group]) {
|
|
9599
|
+
seen[s.group].members.push(s);
|
|
9600
|
+
continue;
|
|
9601
|
+
}
|
|
9602
|
+
const u = { group: s.group, members: [s] };
|
|
9603
|
+
seen[s.group] = u;
|
|
9604
|
+
units.push(u);
|
|
9605
|
+
} else units.push({ group: null, members: [s] });
|
|
9606
|
+
}
|
|
9607
|
+
for (const u of units) u.required = u.members.some((m) => m.required);
|
|
9608
|
+
const queue = units.filter((u) => u.required).concat(units.filter((u) => !u.required));
|
|
9609
|
+
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)));
|
|
9610
|
+
const done = /* @__PURE__ */ new Set();
|
|
9611
|
+
const ready = (u) => dependencies(u).every((id) => done.has(id));
|
|
9612
|
+
const make2 = (s, it, r, parent, overheadOff) => {
|
|
9613
|
+
const f = [s.frontU, s.frontV];
|
|
9614
|
+
const id = room.id + "." + s.id;
|
|
9615
|
+
const m = {
|
|
9616
|
+
id,
|
|
9617
|
+
type: it.type,
|
|
9618
|
+
category: it.category,
|
|
9619
|
+
x: r2((r[0] + r[2]) / 2),
|
|
9620
|
+
z: r2((r[1] + r[3]) / 2),
|
|
9621
|
+
ry: rotationFor(f, it.front),
|
|
9622
|
+
w: it.w,
|
|
9623
|
+
d: it.d,
|
|
9624
|
+
h: it.h,
|
|
9625
|
+
y: parent ? r2((parent.y || 0) + parent.h) : 0,
|
|
9626
|
+
frontU: f[0],
|
|
9627
|
+
frontV: f[1],
|
|
9628
|
+
room: room.id,
|
|
9629
|
+
floor: room.floor,
|
|
9630
|
+
outdoor: !!room.outdoor,
|
|
9631
|
+
slot: s.id,
|
|
9632
|
+
on: parent ? s.on : null,
|
|
9633
|
+
mode: parent ? s.anchor === "inside" ? "inside" : "on" : null,
|
|
9634
|
+
clearance: s.clearance,
|
|
9635
|
+
flat: it.flat,
|
|
9636
|
+
low: it.low,
|
|
9637
|
+
aabb: r.map(r2),
|
|
9638
|
+
_r: r,
|
|
9639
|
+
_seedId: registries().SEED_KEYS.items[id] ?? id
|
|
9640
|
+
};
|
|
9641
|
+
if (it.overhead) m.overhead = !overheadOff;
|
|
9642
|
+
if (it.category === "car") {
|
|
9643
|
+
m.driver = [f[1], -f[0]];
|
|
9644
|
+
m.carClearance = plan.carClearance;
|
|
9645
|
+
}
|
|
9646
|
+
if (parent && s.anchor === "inside") m.y = r2(parent.y || 0);
|
|
9647
|
+
if (room.link) m.link = { from: room.link[0], to: room.link[1] };
|
|
9648
|
+
state[s.id] = m;
|
|
9649
|
+
items.push(m);
|
|
9650
|
+
return m;
|
|
9651
|
+
};
|
|
9652
|
+
const tryFit = (s, relax, overheadOff) => {
|
|
9653
|
+
const cands = weightedOrder(candidatesOf(s.cats), rng);
|
|
9654
|
+
if (s.anchor === "on" || s.anchor === "inside") {
|
|
9655
|
+
const parent = state[s.on];
|
|
9656
|
+
if (!parent) return null;
|
|
9657
|
+
for (const { it } of cands) {
|
|
9658
|
+
if (it.w > parent.w + EPS || it.d > parent.d + EPS) continue;
|
|
9659
|
+
const f = [parent.frontU ?? 0, parent.frontV ?? 1];
|
|
9660
|
+
const r = parent.aabb;
|
|
9661
|
+
const [extentU, extentV] = extent([it.w, it.d], f);
|
|
9662
|
+
const cu = (r[0] + r[2]) / 2 - f[0] * (parent.d / 2 - it.d / 2);
|
|
9663
|
+
const cv = (r[1] + r[3]) / 2 - f[1] * (parent.d / 2 - it.d / 2);
|
|
9664
|
+
return make2(s, it, [cu - extentU / 2, cv - extentV / 2, cu + extentU / 2, cv + extentV / 2], parent);
|
|
9665
|
+
}
|
|
9666
|
+
return null;
|
|
9667
|
+
}
|
|
9668
|
+
for (const { it } of cands) {
|
|
9669
|
+
if (it.w > s.w + EPS || it.d > s.d + EPS) continue;
|
|
9670
|
+
const f = [s.frontU, s.frontV];
|
|
9671
|
+
const slide = s.anchor === "wall" ? s.slide == null ? 2 : s.slide : 0;
|
|
9672
|
+
const shifts = [0];
|
|
9673
|
+
for (let k = 1; k * 0.1 <= slide + 1e-9; k++) shifts.push(k * 0.1, -k * 0.1);
|
|
9674
|
+
let found = null;
|
|
9675
|
+
const car = it.category === "car" && !relax;
|
|
9676
|
+
const tries = car ? carCandidates(s, it, plan.carClearance) : shifts.map((shift) => ({ rect: itemRect(s, it, shift) }));
|
|
9677
|
+
for (const attempt of tries) {
|
|
9678
|
+
const r3 = attempt.rect;
|
|
9679
|
+
if (!insideUnion(r3, inners)) continue;
|
|
9680
|
+
if (car && (!insideUnion(attempt.clear, inners) || solids.some((o) => overlaps(attempt.clear, o)))) continue;
|
|
9681
|
+
if (it.flat) {
|
|
9682
|
+
found = { rect: r3 };
|
|
9683
|
+
break;
|
|
9684
|
+
}
|
|
9685
|
+
if (zones.some((z) => overlaps(r3, z))) continue;
|
|
9686
|
+
if (windowZones.some(({ z, j }) => blocksWindow(it, j, overheadOff) && overlaps(r3, z))) continue;
|
|
9687
|
+
if (solids.some((o) => overlaps(r3, o))) continue;
|
|
9688
|
+
if (!it.deck && rims.some((a) => overlaps(r3, a))) continue;
|
|
9689
|
+
let cl2 = car ? attempt.clear : null;
|
|
9690
|
+
if (!car && !relax && s.clearance > 0 && !it.low) {
|
|
9691
|
+
cl2 = clearRect(r3, f, s.clearance);
|
|
9692
|
+
if (!insideUnion(cl2, inners)) continue;
|
|
9693
|
+
if (items.some((m2) => !m2.low && !m2.flat && !m2.on && overlaps(cl2, m2._r ?? m2.aabb))) continue;
|
|
9694
|
+
}
|
|
9695
|
+
if (!it.low && reserves.some((q) => overlaps(r3, q))) continue;
|
|
9696
|
+
let rimRect2 = null;
|
|
9697
|
+
if (it.rim) {
|
|
9698
|
+
rimRect2 = [r3[0] - it.rim, r3[1] - it.rim, r3[2] + it.rim, r3[3] + it.rim];
|
|
9699
|
+
if (!insideUnion(rimRect2, inners) || solids.some((o) => overlaps(rimRect2, o)) || zones.some((z) => overlaps(rimRect2, z))) continue;
|
|
9700
|
+
}
|
|
9701
|
+
if (!(relax ? reachable(relaxedGrid, solids.concat([r3]), RELAXED_RADIUS) : reachable(grid, solids.concat([r3]), inflation))) continue;
|
|
9702
|
+
found = { rect: r3, cl: cl2, rimRect: rimRect2 };
|
|
9703
|
+
break;
|
|
9704
|
+
}
|
|
9705
|
+
if (!found) continue;
|
|
9706
|
+
const { rect: r, cl, rimRect } = found;
|
|
9707
|
+
if (it.flat) return make2(s, it, r, null);
|
|
9708
|
+
const m = make2(s, it, r, null, overheadOff);
|
|
9709
|
+
if (cl) reserves.push(cl);
|
|
9710
|
+
if (rimRect) rims.push(rimRect);
|
|
9711
|
+
solids.push(r);
|
|
9712
|
+
return m;
|
|
9713
|
+
}
|
|
9714
|
+
return null;
|
|
9715
|
+
};
|
|
9716
|
+
const process2 = (u) => {
|
|
9717
|
+
const pr = u.members[0].chance;
|
|
9718
|
+
const x = rng();
|
|
9719
|
+
const arr = u.members.slice();
|
|
9720
|
+
const members = [];
|
|
9721
|
+
while (arr.length) members.push(arr.splice(arr.length > 1 ? Math.floor(rng() * arr.length) : 0, 1)[0]);
|
|
9722
|
+
const applicable = members.filter((m) => m.valid && (!m.requires || state[m.requires]) && (!m.on || state[m.on]));
|
|
9723
|
+
if (!u.required && x >= pr) return;
|
|
9724
|
+
let ok = false;
|
|
9725
|
+
for (const m of applicable) {
|
|
9726
|
+
if (tryFit(m, false)) {
|
|
9727
|
+
ok = true;
|
|
9728
|
+
break;
|
|
9729
|
+
}
|
|
9730
|
+
}
|
|
9731
|
+
if (!ok)
|
|
9732
|
+
for (const m of applicable) {
|
|
9733
|
+
if (tryFit(m, false, true)) {
|
|
9734
|
+
ok = true;
|
|
9735
|
+
break;
|
|
9736
|
+
}
|
|
9737
|
+
}
|
|
9738
|
+
if (!ok && u.required)
|
|
9739
|
+
for (const m of applicable) {
|
|
9740
|
+
if (tryFit(m, true, true)) {
|
|
9741
|
+
ok = true;
|
|
9742
|
+
break;
|
|
9743
|
+
}
|
|
9744
|
+
}
|
|
9745
|
+
if (!ok && u.required && (applicable.length || !u.members.some((m) => m.requires))) missing.push({ room: room.id, slot: u.group || u.members[0].id });
|
|
9746
|
+
};
|
|
9747
|
+
const pending = queue.slice();
|
|
9748
|
+
while (pending.length) {
|
|
9749
|
+
let i = pending.findIndex(ready);
|
|
9750
|
+
if (i < 0) i = 0;
|
|
9751
|
+
const u = pending.splice(i, 1)[0];
|
|
9752
|
+
process2(u);
|
|
9753
|
+
for (const m of u.members) done.add(m.id);
|
|
9754
|
+
}
|
|
9755
|
+
return { items, missing };
|
|
9756
|
+
}
|
|
9757
|
+
var STREAM = {
|
|
9758
|
+
facade: "fachada",
|
|
9759
|
+
lot: "lote",
|
|
9760
|
+
decor: "decor",
|
|
9761
|
+
roomPrefix: "c:",
|
|
9762
|
+
palettePrefix: "paleta:"
|
|
9763
|
+
};
|
|
9764
|
+
var placementKit = {
|
|
9765
|
+
MARGIN,
|
|
9766
|
+
EPS,
|
|
9767
|
+
r2,
|
|
9768
|
+
clamp: clamp3,
|
|
9769
|
+
overlaps,
|
|
9770
|
+
inner,
|
|
9771
|
+
inflate: inflate2,
|
|
9772
|
+
areaOf,
|
|
9773
|
+
topOf,
|
|
9774
|
+
extent,
|
|
9775
|
+
insideUnion,
|
|
9776
|
+
usableParts,
|
|
9777
|
+
zonesOfFloor,
|
|
9778
|
+
windowsOfRoom,
|
|
9779
|
+
windowZone,
|
|
9780
|
+
roomAt,
|
|
9781
|
+
reachable,
|
|
9782
|
+
createGrid,
|
|
9783
|
+
clearRect,
|
|
9784
|
+
pickWeighted,
|
|
9785
|
+
createRng: createRng2,
|
|
9786
|
+
roomSeedKey: (room) => room.seedKey ?? registries().SEED_KEYS.rooms[room.id] ?? room.id,
|
|
9787
|
+
shuffle,
|
|
9788
|
+
facingRotation: (f, type) => rotationFor(f, registries().CATALOG[type].front),
|
|
9789
|
+
cutIntervals,
|
|
9790
|
+
blocksWindow
|
|
9791
|
+
};
|
|
9792
|
+
function createPlacementContext(plan, items) {
|
|
9793
|
+
const added = [];
|
|
9794
|
+
const counters = {};
|
|
9795
|
+
const make2 = (room, type, r, f, o) => {
|
|
9796
|
+
const it = registries().CATALOG[type];
|
|
9797
|
+
const n = counters[room.id + type] = (counters[room.id + type] || 0) + 1;
|
|
9798
|
+
return Object.assign(
|
|
9799
|
+
{
|
|
9800
|
+
id: room.id + ".dec_" + type + n,
|
|
9801
|
+
type,
|
|
9802
|
+
category: it.category,
|
|
9803
|
+
x: r2((r[0] + r[2]) / 2),
|
|
9804
|
+
z: r2((r[1] + r[3]) / 2),
|
|
9805
|
+
ry: r2(Math.atan2(f[0], f[1])),
|
|
9806
|
+
w: it.w,
|
|
9807
|
+
d: it.d,
|
|
9808
|
+
h: it.h,
|
|
9809
|
+
y: 0,
|
|
9810
|
+
frontU: f[0],
|
|
9811
|
+
frontV: f[1],
|
|
9812
|
+
room: room.id,
|
|
9813
|
+
floor: room.floor,
|
|
9814
|
+
outdoor: !!room.outdoor,
|
|
9815
|
+
slot: "dec_" + type + n,
|
|
9816
|
+
on: null,
|
|
9817
|
+
mode: null,
|
|
9818
|
+
clearance: 0,
|
|
9819
|
+
flat: !!it.flat,
|
|
9820
|
+
low: it.low,
|
|
9821
|
+
aabb: r.map(r2),
|
|
9822
|
+
_r: r,
|
|
9823
|
+
decor: true,
|
|
9824
|
+
mount: it.mount
|
|
9825
|
+
},
|
|
9826
|
+
o || {}
|
|
9827
|
+
);
|
|
9828
|
+
};
|
|
9829
|
+
const add2 = (m) => {
|
|
9830
|
+
added.push(m);
|
|
9831
|
+
items.push(m);
|
|
9832
|
+
return m;
|
|
9833
|
+
};
|
|
9834
|
+
const itemsOfRoom = (c) => items.filter((m) => m.room === c.id && m.floor === c.floor);
|
|
9835
|
+
const extraSolids = {};
|
|
9836
|
+
const solidsOfRoom = (c) => itemsOfRoom(c).filter((m) => !m.flat && !m.on && (!m.decor || m.mount === "floor")).map((m) => m._r).concat(extraSolids[c.id] || []);
|
|
9837
|
+
const gridCache = {};
|
|
9838
|
+
let ignored = null;
|
|
9839
|
+
const gridOf = (c) => gridCache[c.id] || (gridCache[c.id] = createGrid(plan, c, ignored));
|
|
9840
|
+
function placeOnFloor(room, type, rng2, optsIn) {
|
|
9841
|
+
const opts = Object.assign({}, optsIn || {});
|
|
9842
|
+
const it0 = registries().CATALOG[type];
|
|
9843
|
+
if (it0.flat) opts.flat = true;
|
|
9844
|
+
const it = registries().CATALOG[type];
|
|
9845
|
+
const inners = usableParts(room, 0);
|
|
9846
|
+
const solidList = solidsOfRoom(room);
|
|
9847
|
+
const zones = zonesOfFloor(plan, room.floor);
|
|
9848
|
+
const occupied = itemsOfRoom(room);
|
|
9849
|
+
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
9850
|
+
const landings = it0.flat ? [] : items.filter((m) => m.type === "stairs" && m.floor === room.floor && m.aabb).map((m) => {
|
|
9851
|
+
const r = m.aabb;
|
|
9852
|
+
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]];
|
|
9853
|
+
});
|
|
9854
|
+
const cand = opts.cands ? opts.cands.slice() : [];
|
|
9855
|
+
if (!opts.cands)
|
|
9856
|
+
for (const p of inners) {
|
|
9857
|
+
const extentU = opts.dim ? opts.dim[0] : it.w;
|
|
9858
|
+
const extentV = opts.dim ? opts.dim[1] : it.d;
|
|
9859
|
+
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]);
|
|
9860
|
+
for (let k = 0; k < 6; k++) {
|
|
9861
|
+
const t = rng2();
|
|
9862
|
+
const side = Math.floor(rng2() * 4);
|
|
9863
|
+
const x = side < 2 ? p[0] + t * Math.max(0, p[2] - p[0] - extentU) : side === 2 ? p[0] : p[2] - extentU;
|
|
9864
|
+
const z = side >= 2 ? p[1] + t * Math.max(0, p[3] - p[1] - extentV) : side === 0 ? p[1] : p[3] - extentV;
|
|
9865
|
+
cand.push([x, z, x + extentU, z + extentV]);
|
|
9866
|
+
}
|
|
9867
|
+
}
|
|
9868
|
+
for (const r of opts.ordered ? cand : shuffle(cand, rng2)) {
|
|
9869
|
+
if (!insideUnion(r, inners)) continue;
|
|
9870
|
+
if (zones.some((z) => overlaps(r, z)) || landings.some((z) => overlaps(r, z))) continue;
|
|
9871
|
+
if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r, z))) continue;
|
|
9872
|
+
if (solidList.some((o) => overlaps(r, o))) continue;
|
|
9873
|
+
if (opts.flat) {
|
|
9874
|
+
if (!(opts.without && opts.without(r))) return r;
|
|
9875
|
+
continue;
|
|
9876
|
+
}
|
|
9877
|
+
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;
|
|
9878
|
+
if (opts.without && opts.without(r)) continue;
|
|
9879
|
+
if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), room.outdoor ? 0.16 : plan.playerRadius)) continue;
|
|
9880
|
+
return r;
|
|
9881
|
+
}
|
|
9882
|
+
return null;
|
|
9883
|
+
}
|
|
9884
|
+
const roomById = (id) => plan.rooms.find((c) => c.id === id) || plan.zones.find((c) => c.id === id);
|
|
9885
|
+
return {
|
|
9886
|
+
make: make2,
|
|
9887
|
+
add: add2,
|
|
9888
|
+
placeOnFloor,
|
|
9889
|
+
itemsOfRoom,
|
|
9890
|
+
solidsOfRoom,
|
|
9891
|
+
gridOf,
|
|
9892
|
+
extraSolids,
|
|
9893
|
+
roomById,
|
|
9894
|
+
setIgnored: (s) => {
|
|
9895
|
+
ignored = s;
|
|
9896
|
+
for (const k of Object.keys(gridCache)) delete gridCache[k];
|
|
9897
|
+
},
|
|
9898
|
+
added
|
|
9899
|
+
};
|
|
9900
|
+
}
|
|
9901
|
+
function groundCover(plan, items, ctx) {
|
|
9902
|
+
if (!plan.lot) return;
|
|
9903
|
+
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])]);
|
|
9904
|
+
const groundPiece = (z, type, r, layer, i) => {
|
|
9905
|
+
const m = ctx.make(z, type, r, [0, 1], {
|
|
9906
|
+
id: z.id + ".ground" + (layer ? "_overlay" : "") + i,
|
|
9907
|
+
slot: "ground" + (layer ? "_overlay" : "") + i,
|
|
9908
|
+
w: r2(r[2] - r[0]),
|
|
9909
|
+
d: r2(r[3] - r[1]),
|
|
9910
|
+
h: 0,
|
|
9911
|
+
polygon: polygonOf(r),
|
|
9912
|
+
layer,
|
|
9913
|
+
zone: z.id,
|
|
9914
|
+
outdoor: true,
|
|
9915
|
+
floor: -1
|
|
9916
|
+
});
|
|
9917
|
+
return ctx.add(m);
|
|
9918
|
+
};
|
|
9919
|
+
for (const z of plan.zones) {
|
|
9920
|
+
z.parts.forEach((p, i) => groundPiece(z, z.ground, p, 0, i));
|
|
9921
|
+
let k = 0;
|
|
9922
|
+
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];
|
|
9923
|
+
for (const rule of registries().GROUND_OVERLAYS) {
|
|
9924
|
+
const triggers = items.filter((m) => m.room === z.id && m.category === rule.category);
|
|
9925
|
+
if (rule.merge) {
|
|
9926
|
+
if (triggers.length && z.ground !== rule.unlessGround) {
|
|
9927
|
+
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]);
|
|
9928
|
+
const q = clip(inflate2(u, rule.inflate));
|
|
9929
|
+
if (q) groundPiece(z, rule.ground, q, 1, k++);
|
|
9930
|
+
}
|
|
9931
|
+
} else {
|
|
9932
|
+
for (const m of triggers) {
|
|
9933
|
+
const q = clip(inflate2(m._r, rule.inflate));
|
|
9934
|
+
if (q) groundPiece(z, rule.ground, q, 1, k++);
|
|
9935
|
+
}
|
|
9936
|
+
}
|
|
9937
|
+
}
|
|
9938
|
+
}
|
|
9939
|
+
}
|
|
9940
|
+
function runPostProcess(plan, items, seed) {
|
|
9941
|
+
const ctx = createPlacementContext(plan, items);
|
|
9942
|
+
const rng = createRng2(seed, STREAM.decor);
|
|
9943
|
+
for (const pass of registries().PASSES) pass.run({ plan, items, seed, rng, ctx, kit: placementKit, catalog: registries().CATALOG, profiles: registries().DECOR_PROFILES });
|
|
9944
|
+
groundCover(plan, items, ctx);
|
|
9945
|
+
return ctx;
|
|
9946
|
+
}
|
|
8779
9947
|
function registerLocale(code, table) {
|
|
9948
|
+
const { LOCALES: locales } = registries();
|
|
8780
9949
|
const cur = locales[code] = locales[code] || {};
|
|
8781
9950
|
for (const ns of Object.keys(table)) cur[ns] = Object.assign(cur[ns] || {}, table[ns]);
|
|
8782
9951
|
}
|
|
8783
|
-
var
|
|
9952
|
+
var listLocales = () => Object.keys(registries().LOCALES);
|
|
9953
|
+
function translate(ns, key, locale = "en", vars) {
|
|
9954
|
+
const { LOCALES: locales } = registries();
|
|
9955
|
+
const raw = locales[locale]?.[ns]?.[key] ?? locales.en?.[ns]?.[key] ?? key;
|
|
9956
|
+
return vars ? raw.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m) : raw;
|
|
9957
|
+
}
|
|
9958
|
+
var CLOSE = 1e-6;
|
|
9959
|
+
var rounded = (r) => r.map(r2);
|
|
9960
|
+
var touches = (a, r) => a[0] <= r[2] + CLOSE && a[2] >= r[0] - CLOSE && a[1] <= r[3] + CLOSE && a[3] >= r[1] - CLOSE;
|
|
9961
|
+
function addExteriorDoorPaths(plan, c) {
|
|
9962
|
+
const { gates, paths, zones, setback, marginLeft, marginRight, backyard, w } = c;
|
|
9963
|
+
const social = gates.find((g) => g.type === "pedestrian");
|
|
9964
|
+
const socialPath = social ? paths.find((p) => p.from === social.id && p.type === "pedestrian") : null;
|
|
9965
|
+
const parkingZone = zones.find((z) => z.id === "parking");
|
|
9966
|
+
const parking = parkingZone ? parkingZone.parts[0] : null;
|
|
9967
|
+
const midY = setback > 0 ? -setback / 2 : 0;
|
|
9968
|
+
const joinToSocial = (x, width, rects, points) => {
|
|
9969
|
+
if (!socialPath) return;
|
|
9970
|
+
const h = width / 2;
|
|
9971
|
+
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];
|
|
9972
|
+
const targetX = x < horizontal[0] ? horizontal[0] + h : x > horizontal[2] ? horizontal[2] - h : x;
|
|
9973
|
+
if (Math.abs(targetX - x) > CLOSE) {
|
|
9974
|
+
rects.push([Math.min(x, targetX) - h, midY - h, Math.max(x, targetX) + h, midY + h]);
|
|
9975
|
+
points.push([targetX, midY]);
|
|
9976
|
+
}
|
|
9977
|
+
};
|
|
9978
|
+
for (const door of plan.doors.filter((q) => q.floor === 0 && q.exterior)) {
|
|
9979
|
+
if (paths.some((p) => p.to === door.id)) continue;
|
|
9980
|
+
const half = Math.max(1, Math.min(door.width, 2)) / 2;
|
|
9981
|
+
const rects = [];
|
|
9982
|
+
const points = [];
|
|
9983
|
+
const isTarget = c.target && door.a === c.target.a && door.side === c.target.side;
|
|
9984
|
+
let fromId = social ? social.id : null;
|
|
9985
|
+
let width = 1;
|
|
9986
|
+
const id = isTarget ? "entrance_path" : "path_" + door.id;
|
|
9987
|
+
if (door.side === "front") {
|
|
9988
|
+
if (setback > 0) {
|
|
9989
|
+
const stub = Math.min(1, setback);
|
|
9990
|
+
rects.push([door.u - half, -stub, door.u + half, 0]);
|
|
9991
|
+
points.push([door.u, 0], [door.u, -stub]);
|
|
9992
|
+
if (-stub > midY) {
|
|
9993
|
+
rects.push([door.u - width / 2, midY - width / 2, door.u + width / 2, -stub]);
|
|
9994
|
+
points.push([door.u, midY]);
|
|
9995
|
+
}
|
|
9996
|
+
joinToSocial(door.u, width, rects, points);
|
|
9997
|
+
} else {
|
|
9998
|
+
rects.push([door.u - half, -1.2, door.u + half, 0]);
|
|
9999
|
+
points.push([door.u, 0], [door.u, -1.2]);
|
|
10000
|
+
}
|
|
10001
|
+
} else {
|
|
10002
|
+
const lanes = [];
|
|
10003
|
+
if (marginLeft > 0.5) lanes.push({ x: -marginLeft / 2, width: marginLeft, side: "left" });
|
|
10004
|
+
if (marginRight > 0.5) lanes.push({ x: w + marginRight / 2, width: marginRight, side: "right" });
|
|
10005
|
+
const targets = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
10006
|
+
let best = null;
|
|
10007
|
+
for (const lane of lanes) {
|
|
10008
|
+
if (door.side === "left" && lane.side !== "left" || door.side === "right" && lane.side !== "right") continue;
|
|
10009
|
+
const laneWidth = Math.min(0.9, lane.width - 0.1);
|
|
10010
|
+
const h = laneWidth / 2;
|
|
10011
|
+
const rearStrip = Math.min(0.8, backyard);
|
|
10012
|
+
const rearY = door.v + rearStrip + laneWidth / 2;
|
|
10013
|
+
const overlapsParking = !!parking && lane.x + h > parking[0] && lane.x - h < parking[2];
|
|
10014
|
+
const frontY = overlapsParking || setback <= 0 ? 0 : -0.6;
|
|
10015
|
+
let frontStub = null;
|
|
10016
|
+
if (!overlapsParking && setback > 0) {
|
|
10017
|
+
const dir = social && social.u < lane.x ? -1 : 1;
|
|
10018
|
+
const band = [-0.6 - h, -0.6 + h];
|
|
10019
|
+
let nearestX = social ? social.u : lane.x;
|
|
10020
|
+
let nearestGap = Infinity;
|
|
10021
|
+
for (const r of targets) {
|
|
10022
|
+
if (!(r[1] <= band[1] && r[3] >= band[0])) continue;
|
|
10023
|
+
const edge = dir < 0 ? r[2] : r[0];
|
|
10024
|
+
const gap = Math.abs(edge - lane.x);
|
|
10025
|
+
if ((dir < 0 ? edge <= lane.x : edge >= lane.x) && gap < nearestGap) {
|
|
10026
|
+
nearestGap = gap;
|
|
10027
|
+
nearestX = edge;
|
|
10028
|
+
}
|
|
10029
|
+
}
|
|
10030
|
+
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];
|
|
10031
|
+
}
|
|
10032
|
+
const vertical = [lane.x - h, frontY - (frontY < 0 ? h : 0), lane.x + h, (door.side === "back" ? rearY : door.v) + laneWidth / 2];
|
|
10033
|
+
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;
|
|
10034
|
+
const cost = (horizontal ? horizontal[2] - horizontal[0] : 0) + (frontStub ? frontStub[2] - frontStub[0] : 0);
|
|
10035
|
+
if (!best || cost < best.cost) best = { lane, laneWidth, half: h, vertical, horizontal, frontStub, cost, rearStrip, rearY, frontY, overlapsParking };
|
|
10036
|
+
}
|
|
10037
|
+
if (best) {
|
|
10038
|
+
const { lane, laneWidth, half: h, vertical, horizontal, frontStub, rearStrip, rearY, frontY, overlapsParking } = best;
|
|
10039
|
+
width = laneWidth;
|
|
10040
|
+
if (overlapsParking) fromId = (gates.find((g) => g.type === "garage") || social || {}).id || null;
|
|
10041
|
+
if (door.side === "back") {
|
|
10042
|
+
rects.push([door.u - half, door.v, door.u + half, door.v + rearStrip + laneWidth], horizontal, vertical);
|
|
10043
|
+
points.push([door.u, door.v], [door.u, rearY], [lane.x, rearY], [lane.x, frontY]);
|
|
10044
|
+
} else {
|
|
10045
|
+
const x0 = door.side === "left" ? lane.x - h : w;
|
|
10046
|
+
const x1 = door.side === "left" ? 0 : lane.x + h;
|
|
10047
|
+
rects.push([Math.min(x0, x1), door.v - half, Math.max(x0, x1), door.v + half], vertical);
|
|
10048
|
+
points.push([door.u, door.v], [lane.x, door.v], [lane.x, frontY]);
|
|
10049
|
+
}
|
|
10050
|
+
if (frontStub) {
|
|
10051
|
+
rects.push(frontStub);
|
|
10052
|
+
points.push([frontStub[0] < lane.x ? frontStub[0] : frontStub[2], -0.6]);
|
|
10053
|
+
}
|
|
10054
|
+
} else {
|
|
10055
|
+
const stub = 1;
|
|
10056
|
+
if (door.side === "back") rects.push([door.u - half, door.v, door.u + half, door.v + Math.min(stub, backyard)]);
|
|
10057
|
+
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]);
|
|
10058
|
+
points.push([door.u, door.v]);
|
|
10059
|
+
}
|
|
10060
|
+
}
|
|
10061
|
+
const reached = rects.slice();
|
|
10062
|
+
const pool = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
10063
|
+
let grows = true;
|
|
10064
|
+
while (grows) {
|
|
10065
|
+
grows = false;
|
|
10066
|
+
for (const r of pool) {
|
|
10067
|
+
if (!reached.includes(r) && reached.some((a) => touches(a, r))) {
|
|
10068
|
+
reached.push(r);
|
|
10069
|
+
grows = true;
|
|
10070
|
+
}
|
|
10071
|
+
}
|
|
10072
|
+
}
|
|
10073
|
+
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);
|
|
10074
|
+
const gate = gates.find((q) => q.type === "pedestrian" && inside(q)) || gates.find((q) => q.type !== "cargo" && inside(q));
|
|
10075
|
+
if (gate) fromId = gate.id;
|
|
10076
|
+
paths.push({ id, type: "pedestrian", from: fromId, to: door.id, width, rects: rects.map(rounded), points: points.map((q) => q.map(r2)) });
|
|
10077
|
+
}
|
|
10078
|
+
}
|
|
10079
|
+
function defineZone(zone) {
|
|
10080
|
+
if (!zone || !zone.id) throw new Error("defineZone: id is required");
|
|
10081
|
+
registries().ZONES[zone.id] = zone;
|
|
10082
|
+
return zone;
|
|
10083
|
+
}
|
|
10084
|
+
var getZone = (id) => registries().ZONES[id];
|
|
10085
|
+
var listZones = () => Object.values(registries().ZONES);
|
|
8784
10086
|
function registerPermeableGround(...types) {
|
|
8785
|
-
for (const t of types) PERMEABLE_GROUNDS.add(t);
|
|
10087
|
+
for (const t of types) registries().PERMEABLE_GROUNDS.add(t);
|
|
10088
|
+
}
|
|
10089
|
+
function resolveZone(z) {
|
|
10090
|
+
if (!z) return null;
|
|
10091
|
+
if (typeof z === "string") {
|
|
10092
|
+
const found = registries().ZONES[z];
|
|
10093
|
+
if (!found) throw new Error("Unknown zone: " + z);
|
|
10094
|
+
return found;
|
|
10095
|
+
}
|
|
10096
|
+
return z;
|
|
10097
|
+
}
|
|
10098
|
+
var floorsOfTemplate = (tpl, op) => (tpl.floorsFn ? tpl.floorsFn(op) : tpl.floors || []).length;
|
|
10099
|
+
var useOf = (tpl) => tpl.use || "residential_single";
|
|
10100
|
+
function measure(plan) {
|
|
10101
|
+
const floors = plan.floors;
|
|
10102
|
+
const top = floors.reduce((m, f) => Math.max(m, f.y0 + f.ceiling), 0);
|
|
10103
|
+
const number = plan.floorNumber != null ? plan.floorNumber : floors.length;
|
|
10104
|
+
const rooms = (plan.rooms || []).filter((r) => r.floor >= 0);
|
|
10105
|
+
const area3 = (r) => r.parts.reduce((s, p) => s + (p[2] - p[0]) * (p[3] - p[1]), 0);
|
|
10106
|
+
const footprint = plan.w * plan.d;
|
|
10107
|
+
const built = rooms.reduce((s, r) => s + area3(r), 0);
|
|
10108
|
+
return { storeys: number, height: r2(top), footprint, built };
|
|
10109
|
+
}
|
|
10110
|
+
function checkLot(zoneIn, lot, plan) {
|
|
10111
|
+
const zone = resolveZone(zoneIn);
|
|
10112
|
+
const out = [];
|
|
10113
|
+
const m = measure(plan);
|
|
10114
|
+
const use = plan.use || "residential_single";
|
|
10115
|
+
if (!zone.allowedUses.includes(use)) out.push({ rule: "use", actual: use, limit: zone.allowedUses.join(",") });
|
|
10116
|
+
if (m.storeys > zone.maxFloors) out.push({ rule: "floors", actual: m.storeys, limit: zone.maxFloors });
|
|
10117
|
+
if (m.height > zone.maxHeight + 1e-9) out.push({ rule: "height", actual: m.height, limit: zone.maxHeight });
|
|
10118
|
+
if (!lot) return out;
|
|
10119
|
+
const [u0, v0, u1, v1] = lot.rect;
|
|
10120
|
+
const width = r2(u1 - u0);
|
|
10121
|
+
const depth = r2(v1 - v0);
|
|
10122
|
+
const area3 = width * depth;
|
|
10123
|
+
const party = plan.partyWalls || [];
|
|
10124
|
+
const sides = [party.includes("left") ? Infinity : lot.sideYard.left, party.includes("right") ? Infinity : lot.sideYard.right];
|
|
10125
|
+
const left = sides[0];
|
|
10126
|
+
const right = sides[1];
|
|
10127
|
+
if (lot.setback < zone.setbacks.front - 1e-9) out.push({ rule: "setback_front", actual: lot.setback, limit: zone.setbacks.front });
|
|
10128
|
+
if (Math.min(left, right) < zone.setbacks.side - 1e-9) out.push({ rule: "setback_side", actual: Math.min(left, right), limit: zone.setbacks.side });
|
|
10129
|
+
if (lot.backyard < zone.setbacks.back - 1e-9) out.push({ rule: "setback_back", actual: lot.backyard, limit: zone.setbacks.back });
|
|
10130
|
+
if (area3 < zone.minLot - 1e-9) out.push({ rule: "min_lot", actual: r2(area3), limit: zone.minLot });
|
|
10131
|
+
if (width < zone.minFrontage - 1e-9) out.push({ rule: "min_frontage", actual: width, limit: zone.minFrontage });
|
|
10132
|
+
const ground = m.footprint;
|
|
10133
|
+
const coverage = ground / area3;
|
|
10134
|
+
if (coverage > zone.maxCoverage + 1e-9) out.push({ rule: "coverage", actual: r2(coverage), limit: zone.maxCoverage });
|
|
10135
|
+
const far = m.built / area3;
|
|
10136
|
+
if (far > zone.maxFar + 1e-9) out.push({ rule: "far", actual: r2(far), limit: zone.maxFar });
|
|
10137
|
+
let permeable = 0;
|
|
10138
|
+
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]);
|
|
10139
|
+
const permeability = permeable / area3;
|
|
10140
|
+
if (permeability < zone.permeableMin - 1e-9) out.push({ rule: "permeable", actual: r2(permeability), limit: zone.permeableMin });
|
|
10141
|
+
return out;
|
|
10142
|
+
}
|
|
10143
|
+
function clampOptionsToZone(tpl, op, zone, adjustments) {
|
|
10144
|
+
if (tpl.singleFloor) {
|
|
10145
|
+
const ceiling2 = (tpl.floors || tpl.floorsFn?.(op) || [])[0]?.ceiling ?? 2.8;
|
|
10146
|
+
const current2 = op.floor || 3;
|
|
10147
|
+
const limit2 = Math.max(1, Math.min(zone.maxFloors, Math.floor(zone.maxHeight / ceiling2 + 1e-9)));
|
|
10148
|
+
if (current2 <= limit2) return op;
|
|
10149
|
+
adjustments.push({ field: "floor", from: current2, to: limit2, reason: current2 > zone.maxFloors ? "max_floors" : "max_height" });
|
|
10150
|
+
return Object.assign({}, op, { floor: limit2 });
|
|
10151
|
+
}
|
|
10152
|
+
if (!tpl.floorRange || !tpl.floorsFn) return op;
|
|
10153
|
+
const current = floorsOfTemplate(tpl, op);
|
|
10154
|
+
const floors = tpl.floorsFn(op);
|
|
10155
|
+
const ceiling = floors.reduce((m, f) => Math.max(m, f.ceiling), 0) || 2.8;
|
|
10156
|
+
const byHeight = Math.max(1, Math.floor(zone.maxHeight / ceiling + 1e-9));
|
|
10157
|
+
const limit = Math.max(tpl.floorRange[0], Math.min(zone.maxFloors, byHeight, tpl.floorRange[1]));
|
|
10158
|
+
if (current <= limit) return op;
|
|
10159
|
+
adjustments.push({ field: "floors", from: current, to: limit, reason: current > zone.maxFloors ? "max_floors" : "max_height" });
|
|
10160
|
+
return Object.assign({}, op, { floors: limit });
|
|
10161
|
+
}
|
|
10162
|
+
function clampLotToZone(cfg, zone, adjustments, partyWalls = []) {
|
|
10163
|
+
const raise = (field, key, min, reason) => {
|
|
10164
|
+
if (cfg[key] < min) {
|
|
10165
|
+
adjustments.push({ field, from: cfg[key], to: min, reason });
|
|
10166
|
+
cfg[key] = min;
|
|
10167
|
+
}
|
|
10168
|
+
};
|
|
10169
|
+
raise("frontSetback", "frontSetback", zone.setbacks.front, "setback_front");
|
|
10170
|
+
if (!partyWalls.includes("left")) raise("marginLeft", "marginLeft", zone.setbacks.side, "setback_side");
|
|
10171
|
+
if (!partyWalls.includes("right")) raise("marginRight", "marginRight", zone.setbacks.side, "setback_side");
|
|
10172
|
+
raise("backyardDepth", "backyardDepth", zone.setbacks.back, "setback_back");
|
|
10173
|
+
}
|
|
10174
|
+
var ADJUSTABLE_ZONES = /* @__PURE__ */ new Set(["garden", "backyard", "side_yard"]);
|
|
10175
|
+
function enforcePermeability(lot, zones, zone, adjustments) {
|
|
10176
|
+
const target = [...registries().PERMEABLE_GROUNDS][0];
|
|
10177
|
+
if (!target) return;
|
|
10178
|
+
const [u0, v0, u1, v1] = lot.rect;
|
|
10179
|
+
const area3 = (u1 - u0) * (v1 - v0);
|
|
10180
|
+
const areaOfZone = (z) => z.parts.reduce((s, p) => s + (p[2] - p[0]) * (p[3] - p[1]), 0);
|
|
10181
|
+
let permeable = zones.filter((z) => z.ground != null && registries().PERMEABLE_GROUNDS.has(z.ground)).reduce((s, z) => s + areaOfZone(z), 0);
|
|
10182
|
+
if (permeable / area3 >= zone.permeableMin) return;
|
|
10183
|
+
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));
|
|
10184
|
+
for (const z of candidates) {
|
|
10185
|
+
if (permeable / area3 >= zone.permeableMin) break;
|
|
10186
|
+
adjustments.push({ field: "ground:" + z.id, from: z.ground ?? "", to: target, reason: "permeable_min" });
|
|
10187
|
+
z.ground = target;
|
|
10188
|
+
permeable += areaOfZone(z);
|
|
10189
|
+
}
|
|
10190
|
+
}
|
|
10191
|
+
var floorsOf = (tpl, op) => tpl.floorsFn ? tpl.floorsFn(op || {}) : tpl.floors;
|
|
10192
|
+
function clampSize(tpl, w, d, op) {
|
|
10193
|
+
const L = tpl.limitsFn ? tpl.limitsFn(op || {}) : tpl.limits;
|
|
10194
|
+
const P0 = tpl.defaultsFn ? tpl.defaultsFn(op || {}) : tpl.defaults;
|
|
10195
|
+
const ww = w == null ? P0.w : clamp3(w, L.w[0], L.w[1]);
|
|
10196
|
+
const dd = d == null ? P0.d : clamp3(d, L.d[0], L.d[1]);
|
|
10197
|
+
return { w: r2(ww), d: r2(dd) };
|
|
10198
|
+
}
|
|
10199
|
+
function buildPlan(tpl, wIn, dIn, opIn, bc = {}) {
|
|
10200
|
+
const op = opIn || {};
|
|
10201
|
+
const { w, d } = clampSize(tpl, wIn, dIn, op);
|
|
10202
|
+
const styleId = resolveStyleId(op.style || (typeof tpl.style === "function" ? tpl.style(op) : tpl.style) || "budget");
|
|
10203
|
+
const FLOOR_DEFS = floorsOf(tpl, op);
|
|
10204
|
+
const style = styleOf(styleId);
|
|
10205
|
+
const plan = {
|
|
10206
|
+
w,
|
|
10207
|
+
d,
|
|
10208
|
+
tpl,
|
|
10209
|
+
floorDefs: FLOOR_DEFS,
|
|
10210
|
+
style: styleId,
|
|
10211
|
+
floors: [],
|
|
10212
|
+
rooms: [],
|
|
10213
|
+
zones: [],
|
|
10214
|
+
doors: [],
|
|
10215
|
+
windows: [],
|
|
10216
|
+
walls: [],
|
|
10217
|
+
gates: [],
|
|
10218
|
+
paths: [],
|
|
10219
|
+
lot: null,
|
|
10220
|
+
openPairs: {},
|
|
10221
|
+
reservations: [],
|
|
10222
|
+
options: op,
|
|
10223
|
+
playerRadius: op.playerRadius ?? PLAYER_RADIUS,
|
|
10224
|
+
carClearance: Object.assign({}, CAR_CLEARANCE, op.carClearance)
|
|
10225
|
+
};
|
|
10226
|
+
FLOOR_DEFS.forEach((A, floorIndex) => {
|
|
10227
|
+
const floorNumber = tpl.singleFloor ? op.floor || 3 : floorIndex + 1;
|
|
10228
|
+
plan.floors.push({
|
|
10229
|
+
index: floorIndex,
|
|
10230
|
+
name: tpl.singleFloor ? "floor_n" : A.label,
|
|
10231
|
+
number: tpl.singleFloor ? floorNumber : void 0,
|
|
10232
|
+
y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : tpl.cumulativeCeilings ? FLOOR_DEFS.slice(0, floorIndex).reduce((t, q) => t + q.ceiling, 0) : floorIndex * A.ceiling,
|
|
10233
|
+
ceiling: A.ceiling
|
|
10234
|
+
});
|
|
10235
|
+
plan.openPairs[floorIndex] = A.openPairs || [];
|
|
10236
|
+
const list2 = A.rooms(w, d, op).map((c) => normalizeRoom(c, floorIndex));
|
|
10237
|
+
for (const c of list2) {
|
|
10238
|
+
if (plan.rooms.some((x) => x.id === c.id)) throw new Error("Duplicate room: " + c.id);
|
|
10239
|
+
plan.rooms.push(c);
|
|
10240
|
+
}
|
|
10241
|
+
const byId2 = {};
|
|
10242
|
+
for (const c of list2) byId2[c.id] = c;
|
|
10243
|
+
const floorDoors = [];
|
|
10244
|
+
(A.doors(w, d, op) || []).forEach((sp, i) => {
|
|
10245
|
+
const a = byId2[sp.a];
|
|
10246
|
+
if (!a) throw new Error("Door references a missing room: " + sp.a);
|
|
10247
|
+
let edge;
|
|
10248
|
+
if (sp.edge) edge = sp.edge;
|
|
10249
|
+
else if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
|
|
10250
|
+
else {
|
|
10251
|
+
const b = byId2[sp.b];
|
|
10252
|
+
if (!b) throw new Error("Door references a missing room: " + sp.b);
|
|
10253
|
+
edge = sharedEdge(a, b, sp.at);
|
|
10254
|
+
}
|
|
10255
|
+
if (!edge) throw new Error("Door without a wall: " + sp.a + " / " + sp.b);
|
|
10256
|
+
const width = sp.width;
|
|
10257
|
+
const margin = width / 2 + (sp.clearance ?? tpl.clearance?.door ?? DOOR_EDGE_CLEARANCE);
|
|
10258
|
+
if (edge.a1 - edge.a0 < width + 0.24) throw new Error("Wall too short for the door: " + sp.a + " / " + sp.b);
|
|
10259
|
+
const c = clamp3(sp.at != null ? sp.at : (edge.a0 + edge.a1) / 2, edge.a0 + margin, edge.a1 - margin);
|
|
10260
|
+
const exterior = sp.b === "outside";
|
|
10261
|
+
const leaf2 = exterior && sp.steel ? 0 : width > 1.1 ? width / 2 : width;
|
|
10262
|
+
const u = edge.axis === "u" ? c : edge.pos;
|
|
10263
|
+
const v = edge.axis === "u" ? edge.pos : c;
|
|
10264
|
+
floorDoors.push({
|
|
10265
|
+
id: "d" + floorIndex + "_" + i,
|
|
10266
|
+
floor: floorIndex,
|
|
10267
|
+
u: r2(u),
|
|
10268
|
+
v: r2(v),
|
|
10269
|
+
axis: edge.axis,
|
|
10270
|
+
pos: edge.pos,
|
|
10271
|
+
at: r2(c),
|
|
10272
|
+
width,
|
|
10273
|
+
a: sp.a,
|
|
10274
|
+
b: sp.b,
|
|
10275
|
+
opensTo: sp.opensTo || sp.a,
|
|
10276
|
+
exterior,
|
|
10277
|
+
side: sp.side || null,
|
|
10278
|
+
leaves: exterior && sp.steel ? 0 : width > 1.1 ? 2 : 1,
|
|
10279
|
+
model: sp.model || (sp.steel ? "steel" : width > 1.1 ? "double" : "single"),
|
|
10280
|
+
type: sp.type || "door",
|
|
10281
|
+
steps: sp.steps || 0,
|
|
10282
|
+
hinge: "start",
|
|
10283
|
+
zones: openingZones(u, v, edge.axis, width, openingDepth(width, leaf2))
|
|
10284
|
+
});
|
|
10285
|
+
});
|
|
10286
|
+
plan.doors.push(...floorDoors);
|
|
10287
|
+
const floorWalls = (tpl.noWalls ? [] : buildWalls(list2, floorDoors, A.openPairs || [], floorIndex)).map((p) => Object.assign(p, { h: p.h || A.ceiling }));
|
|
10288
|
+
const windowsOut = placeWindows({ list: list2, byId: byId2, floorWalls, windowDefs: A.windows(w, d, op) || [], floorIndex, ceiling: A.ceiling, w, d, clearance: tpl.clearance?.window ?? WINDOW_EDGE_CLEARANCE });
|
|
10289
|
+
plan.windows.push(...windowsOut);
|
|
10290
|
+
plan.walls.push(...floorWalls);
|
|
10291
|
+
});
|
|
10292
|
+
if (tpl.config) plan.config = tpl.config(op, w, d);
|
|
10293
|
+
if (tpl.reservations) tpl.reservations(plan);
|
|
10294
|
+
buildLot2(plan, tpl, w, d, op, style, styleId, bc);
|
|
10295
|
+
return plan;
|
|
10296
|
+
}
|
|
10297
|
+
function placeWindows(params) {
|
|
10298
|
+
const { list: list2, byId: byId2, floorWalls, windowDefs, floorIndex, ceiling, w, d, clearance } = params;
|
|
10299
|
+
const stretches = floorWalls.filter((p) => p.exterior && p.type === "wall");
|
|
10300
|
+
const found = [];
|
|
10301
|
+
const groups = [];
|
|
10302
|
+
let order = 0;
|
|
10303
|
+
for (const def of windowDefs) {
|
|
10304
|
+
const room = byId2[def.room];
|
|
10305
|
+
if (!room) throw new Error("Window references a missing room: " + def.room);
|
|
10306
|
+
const edge = exteriorEdge(room, def.side, w, d, def.at);
|
|
10307
|
+
if (!edge) continue;
|
|
10308
|
+
const centres = def.n ? Array.from({ length: def.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (def.n + 1)) : [def.at];
|
|
10309
|
+
for (const centre of centres) {
|
|
10310
|
+
const onEdge = stretches.filter((t) => t.axis === edge.axis && eq(edge.axis === "u" ? t.v0 : t.u0, edge.pos) && t.sides.includes(def.room));
|
|
10311
|
+
const start = (t) => edge.axis === "u" ? t.u0 : t.v0;
|
|
10312
|
+
const end = (t) => edge.axis === "u" ? t.u1 : t.v1;
|
|
10313
|
+
const gap = (t) => Math.min(Math.abs(centre - start(t)), Math.abs(centre - end(t)));
|
|
10314
|
+
const target = onEdge.find((t) => centre >= start(t) - TOL && centre <= end(t) + TOL) || onEdge.slice().sort((x, y) => gap(x) - gap(y))[0];
|
|
10315
|
+
if (!target) continue;
|
|
10316
|
+
let group = groups.find((x) => x.wall === target && x.def.room === def.room);
|
|
10317
|
+
if (!group) groups.push(group = { wall: target, edge, start: start(target), end: end(target), def, entries: [] });
|
|
10318
|
+
group.entries.push({ def, centre, order: order++ });
|
|
10319
|
+
}
|
|
10320
|
+
}
|
|
10321
|
+
for (const g of groups) {
|
|
10322
|
+
const room = byId2[g.def.room];
|
|
10323
|
+
const bath = room.type === "bathroom";
|
|
10324
|
+
const length = g.end - g.start;
|
|
10325
|
+
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 }));
|
|
10326
|
+
const fits = (l) => l.reduce((t, x) => t + x.width, 0) + (l.length + 1) * clearance <= length + TOL;
|
|
10327
|
+
while (its.length && !fits(its)) {
|
|
10328
|
+
const k = its.reduce((m, x, i, arr) => x.width > arr[m].width ? i : m, 0);
|
|
10329
|
+
if (its[k].width > 0.6 + TOL) its[k] = Object.assign({}, its[k], { width: Math.max(0.6, r2(its[k].width - 0.1)) });
|
|
10330
|
+
else its.pop();
|
|
10331
|
+
}
|
|
10332
|
+
if (!its.length) continue;
|
|
10333
|
+
const space = (length - its.reduce((t, x) => t + x.width, 0)) / (its.length + 1);
|
|
10334
|
+
let pos = g.start + space;
|
|
10335
|
+
for (const x of its) {
|
|
10336
|
+
const c = pos + x.width / 2;
|
|
10337
|
+
pos += x.width + space;
|
|
10338
|
+
const u = g.edge.axis === "u" ? c : g.edge.pos;
|
|
10339
|
+
const v = g.edge.axis === "u" ? g.edge.pos : c;
|
|
10340
|
+
let y0 = x.def.y0 || 1;
|
|
10341
|
+
let y1 = x.def.y1 || 2.15;
|
|
10342
|
+
if (bath) {
|
|
10343
|
+
y0 = Math.max(y0, 2.1);
|
|
10344
|
+
y1 = Math.min(Math.max(y1, y0 + 0.4), ceiling - 0.25);
|
|
10345
|
+
}
|
|
10346
|
+
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 });
|
|
10347
|
+
}
|
|
10348
|
+
}
|
|
10349
|
+
for (const room of list2) {
|
|
10350
|
+
if (WINDOWLESS_ROOM_TYPES.has(room.type) || found.some((j) => j.room === room.id)) continue;
|
|
10351
|
+
let best = null;
|
|
10352
|
+
for (const t of stretches) {
|
|
10353
|
+
if (!t.sides.includes(room.id)) continue;
|
|
10354
|
+
const i0 = t.axis === "u" ? t.u0 : t.v0;
|
|
10355
|
+
const i1 = t.axis === "u" ? t.u1 : t.v1;
|
|
10356
|
+
const length = i1 - i0;
|
|
10357
|
+
const width = Math.min(room.type === "bathroom" ? 0.6 : 1.2, length - 2 * clearance);
|
|
10358
|
+
if (width < 0.6 - TOL || best && best.length >= length) continue;
|
|
10359
|
+
const side = t.axis === "u" ? t.sides[0] === "outside" ? "front" : "back" : t.sides[0] === "outside" ? "left" : "right";
|
|
10360
|
+
const c = (i0 + i1) / 2;
|
|
10361
|
+
const pos = t.axis === "u" ? t.v0 : t.u0;
|
|
10362
|
+
const bath = room.type === "bathroom";
|
|
10363
|
+
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 } };
|
|
10364
|
+
}
|
|
10365
|
+
if (best) found.push(best.window);
|
|
10366
|
+
}
|
|
10367
|
+
found.sort((x, y) => x._order - y._order);
|
|
10368
|
+
found.forEach((j, i) => {
|
|
10369
|
+
delete j._order;
|
|
10370
|
+
j.id = "w" + floorIndex + "_" + i;
|
|
10371
|
+
});
|
|
10372
|
+
return found;
|
|
10373
|
+
}
|
|
10374
|
+
function buildLot2(plan, tpl, w, d, op, style, styleId, bc) {
|
|
10375
|
+
const seed = op.seed == null ? 1 : op.seed;
|
|
10376
|
+
const rFacade = createRng2(seed, STREAM.facade);
|
|
10377
|
+
const facadeStyle = style.facade;
|
|
10378
|
+
const palette2 = style.swatches;
|
|
10379
|
+
const color2 = palette2[Math.floor(rFacade() * palette2.length)];
|
|
10380
|
+
const extra = tpl.facade || {};
|
|
10381
|
+
plan.facade = {
|
|
10382
|
+
style: styleId,
|
|
10383
|
+
color: { name: color2[0], hex: color2[1] },
|
|
10384
|
+
finish: pickWeighted(rFacade, facadeStyle.finish),
|
|
10385
|
+
grilles: rFacade() < facadeStyle.grilles,
|
|
10386
|
+
roof: pickWeighted(rFacade, facadeStyle.roof),
|
|
10387
|
+
balcony: !!extra.balcony,
|
|
10388
|
+
veranda: !!extra.veranda,
|
|
10389
|
+
steelDoor: !!extra.steelDoor,
|
|
10390
|
+
floors: plan.floorDefs.length
|
|
10391
|
+
};
|
|
10392
|
+
if (tpl.facadeExtra) tpl.facadeExtra(plan.facade, rFacade, w, d, op);
|
|
10393
|
+
if (plan.floorDefs.length > 1 && plan.facade.roof === "fiber_cement") plan.facade.roof = "colonial";
|
|
10394
|
+
if (!tpl.lot) return;
|
|
10395
|
+
const cfg = tpl.lot(w, d, style, op);
|
|
10396
|
+
if (bc.zone) clampLotToZone(cfg, bc.zone, bc.adjustments || [], tpl.partyWalls);
|
|
10397
|
+
const rLot = createRng2(seed, STREAM.lot);
|
|
10398
|
+
const setback = cfg.frontSetback;
|
|
10399
|
+
const marginL = cfg.marginLeft;
|
|
10400
|
+
const marginR = cfg.marginRight;
|
|
10401
|
+
const backDepth = cfg.backyardDepth;
|
|
10402
|
+
const lot = {
|
|
10403
|
+
rect: [-marginL, -setback, w + marginR, d + backDepth],
|
|
10404
|
+
setback,
|
|
10405
|
+
sideYard: { left: marginL, right: marginR },
|
|
10406
|
+
backyard: backDepth,
|
|
10407
|
+
style: styleId,
|
|
10408
|
+
zones: [],
|
|
10409
|
+
perimeter: [],
|
|
10410
|
+
gates: [],
|
|
10411
|
+
paths: [],
|
|
10412
|
+
pool: !!cfg.pool
|
|
10413
|
+
};
|
|
10414
|
+
const ZONES = [];
|
|
10415
|
+
const libs = Object.assign({ garden: "garden", backyard: "backyard", parking: "parking", sideYard: "side_yard" }, cfg.libraries || {});
|
|
10416
|
+
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 }));
|
|
10417
|
+
if (setback > 0) {
|
|
10418
|
+
let parts = [[-marginL, -setback, w + marginR, 0]];
|
|
10419
|
+
if (cfg.parking) {
|
|
10420
|
+
const garageU = cfg.garage.u;
|
|
10421
|
+
const vw = cfg.parking.width;
|
|
10422
|
+
const x0 = Math.max(-marginL, garageU - vw / 2);
|
|
10423
|
+
const x1 = Math.min(w + marginR, garageU + vw / 2);
|
|
10424
|
+
parts = [];
|
|
10425
|
+
if (x0 + marginL > 0.8) parts.push([-marginL, -setback, x0, 0]);
|
|
10426
|
+
if (w + marginR - x1 > 0.8) parts.push([x1, -setback, w + marginR, 0]);
|
|
10427
|
+
addZone("parking", "parking", "parking", [[x0, -setback, x1, 0]], libs.parking);
|
|
10428
|
+
if (!parts.length) parts = [[x1, -setback, w + marginR, 0]];
|
|
10429
|
+
}
|
|
10430
|
+
parts.sort((a, b) => b[2] - b[0] - (a[2] - a[0]));
|
|
10431
|
+
addZone("garden", "garden", "garden", parts, libs.garden);
|
|
10432
|
+
}
|
|
10433
|
+
const yard = cfg.yardZones;
|
|
10434
|
+
if (backDepth > 0 && yard) {
|
|
10435
|
+
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);
|
|
10436
|
+
} else if (backDepth > 0) addZone("backyard", "backyard", "backyard", [[-marginL, d, w + marginR, d + backDepth]], libs.backyard);
|
|
10437
|
+
for (const q of cfg.extraZones || []) addZone(q.id, q.type, q.label || q.id, q.parts, q.library);
|
|
10438
|
+
if (marginL > 0.5) addZone("side_left", "side_yard", "side_left", [[-marginL, 0, 0, d]], libs.sideYard);
|
|
10439
|
+
if (marginR > 0.5) addZone("side_right", "side_yard", "side_right", [[w, 0, w + marginR, d]], libs.sideYard);
|
|
10440
|
+
const gates = [];
|
|
10441
|
+
const addGate = (type, model, u, v, width, side, toTarget) => {
|
|
10442
|
+
const g = {
|
|
10443
|
+
id: "g" + gates.length,
|
|
10444
|
+
type,
|
|
10445
|
+
model,
|
|
10446
|
+
u: r2(u),
|
|
10447
|
+
v: r2(v),
|
|
10448
|
+
axis: "u",
|
|
10449
|
+
width,
|
|
10450
|
+
height: type === "pedestrian" ? style.height : Math.min(style.height, 2.2),
|
|
10451
|
+
side,
|
|
10452
|
+
to: toTarget || null,
|
|
10453
|
+
material: "iron",
|
|
10454
|
+
zones: openingZones(u, v, "u", width, openingDepth(width, type === "pedestrian" ? width : 0))
|
|
10455
|
+
};
|
|
10456
|
+
gates.push(g);
|
|
10457
|
+
return g;
|
|
10458
|
+
};
|
|
10459
|
+
if (cfg.pedestrian && setback > 0) addGate("pedestrian", "pedestrian", cfg.pedestrian.u, -setback, 1, "front", "door");
|
|
10460
|
+
if (cfg.garage && setback > 0) addGate("garage", pickWeighted(rLot, style.gate.garage), cfg.garage.u, -setback, cfg.garage.width, "front", cfg.garage.to);
|
|
10461
|
+
if (cfg.cargo) addGate("cargo", "sliding", cfg.cargo.u, d + backDepth, cfg.cargo.width, "back", "stockroom");
|
|
10462
|
+
plan.gates = gates;
|
|
10463
|
+
lot.gates = gates;
|
|
10464
|
+
const paths = [];
|
|
10465
|
+
const target = (a, side) => plan.doors.find((p) => p.exterior && p.a === a && p.side === side);
|
|
10466
|
+
for (const g of gates) {
|
|
10467
|
+
if (g.type === "pedestrian" && cfg.doorTarget) {
|
|
10468
|
+
const pt = target(cfg.doorTarget.a, cfg.doorTarget.side);
|
|
10469
|
+
if (!pt) continue;
|
|
10470
|
+
const gateU = g.u;
|
|
10471
|
+
const dx = pt.u;
|
|
10472
|
+
const ym = -setback / 2;
|
|
10473
|
+
const L = 1;
|
|
10474
|
+
const h = L / 2;
|
|
10475
|
+
const rs = [];
|
|
10476
|
+
if (Math.abs(gateU - dx) < 0.05) rs.push([dx - h, -setback, dx + h, 0]);
|
|
10477
|
+
else {
|
|
10478
|
+
rs.push([gateU - h, -setback, gateU + h, ym + h]);
|
|
10479
|
+
rs.push([Math.min(gateU, dx) - h, ym - h, Math.max(gateU, dx) + h, ym + h]);
|
|
10480
|
+
rs.push([dx - h, ym - h, dx + h, 0]);
|
|
10481
|
+
}
|
|
10482
|
+
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]] });
|
|
10483
|
+
} else if (g.type === "garage" && g.to === "inner_garage") {
|
|
10484
|
+
const pt = plan.doors.find((p) => p.exterior && p.side === "front" && Math.abs(p.u - g.u) < 0.6 && p.width >= 2.5);
|
|
10485
|
+
if (!pt) continue;
|
|
10486
|
+
const hw = Math.max(g.width, pt.width) / 2;
|
|
10487
|
+
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]] });
|
|
10488
|
+
} else if (g.type === "cargo") {
|
|
10489
|
+
const pt = target("stockroom", "back");
|
|
10490
|
+
if (!pt) continue;
|
|
10491
|
+
const hw = Math.max(g.width, pt.width) / 2;
|
|
10492
|
+
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]] });
|
|
10493
|
+
}
|
|
10494
|
+
}
|
|
10495
|
+
if (tpl.paths) paths.push(...tpl.paths(plan, { gates, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, d }));
|
|
10496
|
+
else addExteriorDoorPaths(plan, { gates, paths, zones: ZONES, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, target: cfg.doorTarget });
|
|
10497
|
+
plan.paths = paths;
|
|
10498
|
+
lot.paths = paths;
|
|
10499
|
+
const boundary = (rule) => {
|
|
10500
|
+
const type = pickWeighted(rLot, rule.type);
|
|
10501
|
+
const top = type === "railing" ? "none" : pickWeighted(rLot, rule.top);
|
|
10502
|
+
const wallHeight = type === "railing" ? 1.2 : type === "fence" ? style.fenceHeight ?? 2 : style.height;
|
|
10503
|
+
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 };
|
|
10504
|
+
};
|
|
10505
|
+
const A = { front: boundary(style.front), left: boundary(style.sideYard), right: boundary(style.sideYard), back: boundary(style.back) };
|
|
10506
|
+
const seg = (side, u0, v0, u1, v1) => {
|
|
10507
|
+
if (Math.abs(u1 - u0) + Math.abs(v1 - v0) < 0.05) return;
|
|
10508
|
+
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]));
|
|
10509
|
+
};
|
|
10510
|
+
const cutRun = (side, de, to, vfix, gs) => {
|
|
10511
|
+
let pieces = [[de, to]];
|
|
10512
|
+
for (const g of gs) {
|
|
10513
|
+
const g0 = g.u - g.width / 2;
|
|
10514
|
+
const g1 = g.u + g.width / 2;
|
|
10515
|
+
const n = [];
|
|
10516
|
+
for (const [x, y] of pieces) {
|
|
10517
|
+
if (g1 <= x || g0 >= y) {
|
|
10518
|
+
n.push([x, y]);
|
|
10519
|
+
continue;
|
|
10520
|
+
}
|
|
10521
|
+
if (g0 > x) n.push([x, g0]);
|
|
10522
|
+
if (g1 < y) n.push([g1, y]);
|
|
10523
|
+
}
|
|
10524
|
+
pieces = n;
|
|
10525
|
+
}
|
|
10526
|
+
for (const [x, y] of pieces) seg(side, x, vfix, y, vfix);
|
|
10527
|
+
};
|
|
10528
|
+
if (setback > 0)
|
|
10529
|
+
cutRun("front", -marginL, w + marginR, -setback, gates.filter((g) => g.side === "front"));
|
|
10530
|
+
cutRun("back", -marginL, w + marginR, d + backDepth, gates.filter((g) => g.side === "back"));
|
|
10531
|
+
const vi = setback > 0 ? -setback : 0;
|
|
10532
|
+
if (marginL > 0.5) seg("left", -marginL, vi, -marginL, d + backDepth);
|
|
10533
|
+
else if (backDepth > 0) seg("left", 0, d, 0, d + backDepth);
|
|
10534
|
+
if (marginR > 0.5) seg("right", w + marginR, vi, w + marginR, d + backDepth);
|
|
10535
|
+
else if (backDepth > 0) seg("right", w, d, w, d + backDepth);
|
|
10536
|
+
for (const z of ZONES) z.ground = (cfg.grounds || {})[z.id] || (cfg.grounds || {})[z.type] || pickWeighted(rLot, style.grounds[z.type] || { concrete: 1 });
|
|
10537
|
+
if (bc.zone) enforcePermeability(lot, ZONES, bc.zone, bc.adjustments || []);
|
|
10538
|
+
plan.zones = ZONES;
|
|
10539
|
+
lot.zones = ZONES;
|
|
10540
|
+
plan.lot = lot;
|
|
10541
|
+
}
|
|
10542
|
+
var HIGH_SATURATION = 0.4;
|
|
10543
|
+
var MIN_WALL_FLOOR_CONTRAST = 20;
|
|
10544
|
+
var MIN_FABRIC_FLOOR_CONTRAST = 15;
|
|
10545
|
+
var SCHEMES = ["analogous", "complementary", "split_complementary", "triad"];
|
|
10546
|
+
var HARMONY_OFFSETS = { analogous: [-30, 30], complementary: [180, 165], split_complementary: [150, 210], triad: [120, 240] };
|
|
10547
|
+
var ROLE_FABRIC_MAIN = "fabric_main";
|
|
10548
|
+
var ROLE_FABRIC_ACCENT = "fabric_accent";
|
|
10549
|
+
var ROLE_FLOOR_REFERENCE = "floor_living";
|
|
10550
|
+
var hslToInt = (h, s, l) => {
|
|
10551
|
+
h = (h % 360 + 360) % 360;
|
|
10552
|
+
s = clamp3(s, 0, 1);
|
|
10553
|
+
l = clamp3(l, 0, 1);
|
|
10554
|
+
const a = s * Math.min(l, 1 - l);
|
|
10555
|
+
const f = (n) => {
|
|
10556
|
+
const k = (n + h / 30) % 12;
|
|
10557
|
+
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
10558
|
+
};
|
|
10559
|
+
return Math.round(f(0) * 255) << 16 | Math.round(f(8) * 255) << 8 | Math.round(f(4) * 255);
|
|
10560
|
+
};
|
|
10561
|
+
var intToHsl = (n) => {
|
|
10562
|
+
const r = (n >> 16 & 255) / 255;
|
|
10563
|
+
const g = (n >> 8 & 255) / 255;
|
|
10564
|
+
const b = (n & 255) / 255;
|
|
10565
|
+
const mx = Math.max(r, g, b);
|
|
10566
|
+
const mn = Math.min(r, g, b);
|
|
10567
|
+
const l = (mx + mn) / 2;
|
|
10568
|
+
const d = mx - mn;
|
|
10569
|
+
let h = 0;
|
|
10570
|
+
let s = 0;
|
|
10571
|
+
if (d > 1e-9) {
|
|
10572
|
+
s = d / (1 - Math.abs(2 * l - 1));
|
|
10573
|
+
h = mx === r ? (g - b) / d + (g < b ? 6 : 0) : mx === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
10574
|
+
h *= 60;
|
|
10575
|
+
}
|
|
10576
|
+
return { h, s, l };
|
|
10577
|
+
};
|
|
10578
|
+
var lstar = (n) => {
|
|
10579
|
+
const linear = (c) => (c /= 255) <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
10580
|
+
const Y = 0.2126 * linear(n >> 16 & 255) + 0.7152 * linear(n >> 8 & 255) + 0.0722 * linear(n & 255);
|
|
10581
|
+
return Y > 216 / 24389 ? 116 * Math.cbrt(Y) - 16 : Y * 24389 / 27;
|
|
10582
|
+
};
|
|
10583
|
+
var hexString = (n) => "#" + (n >>> 0).toString(16).padStart(6, "0");
|
|
10584
|
+
function palette(o) {
|
|
10585
|
+
const opt = o || {};
|
|
10586
|
+
let style = resolveStyleId(opt.style || "budget");
|
|
10587
|
+
if (!registries().PALETTE_STYLES[style]) style = registries().PALETTE_STYLES.mid ? "mid" : Object.keys(registries().PALETTE_STYLES)[0];
|
|
10588
|
+
const def = registries().PALETTE_STYLES[style];
|
|
10589
|
+
const state = opt.state || "normal";
|
|
10590
|
+
const seed = opt.seed == null ? 1 : opt.seed;
|
|
10591
|
+
const rng = createRng2(seed, STREAM.palettePrefix + (def.seedKey ?? style));
|
|
10592
|
+
const table = def.facadeColors;
|
|
10593
|
+
const base = table[Math.floor(rng() * table.length)];
|
|
10594
|
+
const scheme = SCHEMES[Math.floor(rng() * 4)];
|
|
10595
|
+
const offsets = HARMONY_OFFSETS[scheme];
|
|
10596
|
+
const jit = (v, a) => v + (rng() - 0.5) * a;
|
|
10597
|
+
const hsl = (h, s, l) => ({ h: (h % 360 + 360) % 360, s: clamp3(s, 0, 1), l: clamp3(l, 0, 1) });
|
|
10598
|
+
const H0 = base[1];
|
|
10599
|
+
const H1 = H0 + offsets[0];
|
|
10600
|
+
const H2 = H0 + offsets[1];
|
|
10601
|
+
const draw = { rng, base, H0, H1, H2, jit, hsl };
|
|
10602
|
+
const early = def.early(draw);
|
|
10603
|
+
const { wallMode, sMax } = early;
|
|
10604
|
+
const R = Object.assign({}, early.roles);
|
|
10605
|
+
const ctx = Object.assign({ style, sMax, wallMode, generalFloor: early.generalFloor }, draw);
|
|
10606
|
+
for (const c of registries().ROOM_COLORS) {
|
|
10607
|
+
const [wh, wl] = c.wall(ctx);
|
|
10608
|
+
R["wall_" + c.type] = hsl(wh, c.neutral ? Math.min(0.16, sMax) : clamp3(jit(sMax * 0.75, sMax * 0.5), 0.03, sMax), jit(wl, 0.03));
|
|
10609
|
+
const pi = c.floor || early.generalFloor;
|
|
10610
|
+
R["floor_" + c.type] = hsl(pi[0], Math.min(pi[1], wallMode ? 0.18 : 0.3), jit(pi[2], 0.04));
|
|
10611
|
+
}
|
|
10612
|
+
Object.assign(R, def.late(draw, early));
|
|
10613
|
+
registries().STATE_PLUGINS[state]?.adjustPalette?.(R);
|
|
10614
|
+
const adjust = (color2, ref, min, preferredDir) => {
|
|
10615
|
+
for (let i = 0; i < 60; i++) {
|
|
10616
|
+
if (Math.abs(lstar(hslToInt(color2.h, color2.s, color2.l)) - lstar(hslToInt(ref.h, ref.s, ref.l))) >= min) return;
|
|
10617
|
+
const refL = lstar(hslToInt(ref.h, ref.s, ref.l));
|
|
10618
|
+
let dir = preferredDir || (refL > 50 ? -1 : 1);
|
|
10619
|
+
if (dir < 0 && color2.l <= 0.06 || dir > 0 && color2.l >= 0.94) dir = -dir;
|
|
10620
|
+
color2.l = clamp3(color2.l + dir * 0.015, 0.04, 0.96);
|
|
10621
|
+
}
|
|
10622
|
+
};
|
|
10623
|
+
const types = registries().ROOM_COLORS.map((c) => c.type);
|
|
10624
|
+
for (const c of types) {
|
|
10625
|
+
const w = R["wall_" + c];
|
|
10626
|
+
const p = R["floor_" + c];
|
|
10627
|
+
if (w.s > 0.3) p.s = Math.min(p.s, 0.2);
|
|
10628
|
+
if (p.s > HIGH_SATURATION) p.s = HIGH_SATURATION - 0.05;
|
|
10629
|
+
adjust(p, w, MIN_WALL_FLOOR_CONTRAST + 2, lstar(hslToInt(w.h, w.s, w.l)) > 50 ? -1 : 1);
|
|
10630
|
+
}
|
|
10631
|
+
const floorRef = R[ROLE_FLOOR_REFERENCE] || R["floor_" + types[0]];
|
|
10632
|
+
const fabric = R[ROLE_FABRIC_MAIN];
|
|
10633
|
+
adjust(fabric, floorRef, MIN_FABRIC_FLOOR_CONTRAST + 3);
|
|
10634
|
+
adjust(R[ROLE_FABRIC_ACCENT], floorRef, MIN_FABRIC_FLOOR_CONTRAST + 3);
|
|
10635
|
+
for (const c of types) {
|
|
10636
|
+
const p = R["floor_" + c];
|
|
10637
|
+
if (Math.abs(lstar(hslToInt(fabric.h, fabric.s, fabric.l)) - lstar(hslToInt(p.h, p.s, p.l))) < MIN_FABRIC_FLOOR_CONTRAST)
|
|
10638
|
+
adjust(p, fabric, MIN_FABRIC_FLOOR_CONTRAST + 2, lstar(hslToInt(fabric.h, fabric.s, fabric.l)) > 50 ? -1 : 1);
|
|
10639
|
+
}
|
|
10640
|
+
const out = { meta: { seed, style, state, scheme, base: base[0], accentMode: wallMode ? "wall" : "object" } };
|
|
10641
|
+
for (const k of Object.keys(R)) out[k] = hslToInt(R[k].h, R[k].s, R[k].l);
|
|
10642
|
+
const num2 = (k) => out[k];
|
|
10643
|
+
const pairOk = (w, p) => Math.abs(lstar(w) - lstar(p)) >= MIN_WALL_FLOOR_CONTRAST + 1 && Math.abs(lstar(num2(ROLE_FABRIC_MAIN)) - lstar(p)) >= MIN_FABRIC_FLOOR_CONTRAST + 1;
|
|
10644
|
+
const search = (color2, ok) => {
|
|
10645
|
+
const h = intToHsl(color2);
|
|
10646
|
+
let bestValue = null;
|
|
10647
|
+
let md = 9;
|
|
10648
|
+
for (let l = 0.02; l <= 0.98; l += 5e-3) {
|
|
10649
|
+
const n = hslToInt(h.h, h.s, l);
|
|
10650
|
+
if (ok(n) && Math.abs(l - h.l) < md) {
|
|
10651
|
+
md = Math.abs(l - h.l);
|
|
10652
|
+
bestValue = n;
|
|
10653
|
+
}
|
|
10654
|
+
}
|
|
10655
|
+
return bestValue;
|
|
10656
|
+
};
|
|
10657
|
+
for (const c of types) {
|
|
10658
|
+
if (pairOk(num2("wall_" + c), num2("floor_" + c))) continue;
|
|
10659
|
+
const n = search(num2("floor_" + c), (p) => pairOk(num2("wall_" + c), p));
|
|
10660
|
+
if (n != null) {
|
|
10661
|
+
out["floor_" + c] = n;
|
|
10662
|
+
continue;
|
|
10663
|
+
}
|
|
10664
|
+
const w = search(num2("wall_" + c), (w2) => pairOk(w2, num2("floor_" + c)));
|
|
10665
|
+
if (w != null) out["wall_" + c] = w;
|
|
10666
|
+
}
|
|
10667
|
+
return out;
|
|
10668
|
+
}
|
|
10669
|
+
var SIDE_NORMAL = { left: [1, 0], right: [-1, 0], front: [0, 1], back: [0, -1] };
|
|
10670
|
+
var FIXED_FACING = { "u+": [1, 0], "u-": [-1, 0], "v+": [0, 1], "v-": [0, -1] };
|
|
10671
|
+
var SIDE_OPPOSITE = { front: "back", back: "front", left: "right", right: "left" };
|
|
10672
|
+
var WINDOW_SIDE = { front: "front", back: "back", left: "left", right: "right" };
|
|
10673
|
+
function slotDefsOf(plan, room) {
|
|
10674
|
+
const name = room.library || room.type;
|
|
10675
|
+
const key = registries().LIBRARY_ALIAS[name] || name;
|
|
10676
|
+
const library = room.slots ? room.slots : registries().LIBRARIES[key];
|
|
10677
|
+
if (!library) return [];
|
|
10678
|
+
const R = inner(room.parts[0]);
|
|
10679
|
+
let defs = typeof library === "function" ? library({ w: R[2] - R[0], d: R[3] - R[1] }) : library.slice();
|
|
10680
|
+
defs = defs.map((x) => Object.assign({}, x));
|
|
10681
|
+
for (const id of room.without || []) defs = defs.filter((x) => x.id !== id);
|
|
10682
|
+
for (const [id, aj] of Object.entries(room.overrides || {})) {
|
|
10683
|
+
const i = defs.findIndex((x) => x.id === id);
|
|
10684
|
+
if (i < 0) continue;
|
|
10685
|
+
if (aj === null) defs.splice(i, 1);
|
|
10686
|
+
else defs[i] = Object.assign(defs[i], aj);
|
|
10687
|
+
}
|
|
10688
|
+
defs.push(...(room.extras || []).map((x) => Object.assign({}, x)));
|
|
10689
|
+
const out = [];
|
|
10690
|
+
for (const def of defs) {
|
|
10691
|
+
def.parent = def.on;
|
|
10692
|
+
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 }));
|
|
10693
|
+
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 }));
|
|
10694
|
+
else if (def.anchor === "window" && def.n == null) {
|
|
10695
|
+
const windowList = windowsOfRoom(plan, room);
|
|
10696
|
+
windowList.forEach((_j, i) => out.push(Object.assign({}, def, { id: def.id + "@w" + i, n: i, group: def.group || def.id })));
|
|
10697
|
+
} else out.push(def);
|
|
10698
|
+
}
|
|
10699
|
+
return out;
|
|
10700
|
+
}
|
|
10701
|
+
function resolveSlots(plan) {
|
|
10702
|
+
const all = [];
|
|
10703
|
+
for (const room of plan.rooms.concat(plan.zones)) {
|
|
10704
|
+
const defs = slotDefsOf(plan, room);
|
|
10705
|
+
const byId2 = {};
|
|
10706
|
+
const zones = room.type === "stairs" ? plan.floors.flatMap((_, floorIndex) => zonesOfFloor(plan, floorIndex)) : zonesOfFloor(plan, room.floor);
|
|
10707
|
+
for (const def of defs) {
|
|
10708
|
+
const s = resolveSlot(plan, room, def, byId2, zones);
|
|
10709
|
+
byId2[def.id] = s;
|
|
10710
|
+
all.push(s);
|
|
10711
|
+
}
|
|
10712
|
+
}
|
|
10713
|
+
return all;
|
|
10714
|
+
}
|
|
10715
|
+
function resolveSlot(plan, room, def, byId2, zones) {
|
|
10716
|
+
const base = {
|
|
10717
|
+
id: def.id,
|
|
10718
|
+
room: room.id,
|
|
10719
|
+
floor: room.floor,
|
|
10720
|
+
outdoor: room.outdoor,
|
|
10721
|
+
anchor: def.anchor,
|
|
10722
|
+
side: def.side || def.corner || null,
|
|
10723
|
+
cats: def.cats,
|
|
10724
|
+
slide: def.slide,
|
|
10725
|
+
required: !!def.required,
|
|
10726
|
+
chance: def.required ? 1 : def.chance == null ? 1 : def.chance,
|
|
10727
|
+
clearance: def.clearance == null ? 0.5 : def.clearance,
|
|
10728
|
+
front: def.facing || "center",
|
|
10729
|
+
group: def.group || null,
|
|
10730
|
+
requires: def.requires || null,
|
|
10731
|
+
on: def.parent || null,
|
|
10732
|
+
target: def.target || null,
|
|
10733
|
+
valid: true,
|
|
10734
|
+
reason: null
|
|
10735
|
+
};
|
|
10736
|
+
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 });
|
|
10737
|
+
if (def.anchor === "on" || def.anchor === "inside") {
|
|
10738
|
+
const parent = def.parent ? byId2[def.parent] : void 0;
|
|
10739
|
+
if (!parent || !parent.valid) return invalid("invalid_parent");
|
|
10740
|
+
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 });
|
|
10741
|
+
}
|
|
10742
|
+
const size = def.size;
|
|
10743
|
+
const roomCenter = (() => {
|
|
10744
|
+
const R0 = inner(room.parts[0]);
|
|
10745
|
+
return [(R0[0] + R0[2]) / 2, (R0[1] + R0[3]) / 2];
|
|
10746
|
+
})();
|
|
10747
|
+
const openings = openingsOfFloor(plan, room.floor);
|
|
10748
|
+
let point = [0, 0];
|
|
10749
|
+
const targetPoint = (rule2) => {
|
|
10750
|
+
if (rule2 === "door") {
|
|
10751
|
+
let best = null;
|
|
10752
|
+
let bestDist = 1e9;
|
|
10753
|
+
for (const ab of openings) {
|
|
10754
|
+
const dist2 = Math.hypot(ab.u - point[0], ab.v - point[1]);
|
|
10755
|
+
if (dist2 < bestDist) {
|
|
10756
|
+
bestDist = dist2;
|
|
10757
|
+
best = ab;
|
|
10758
|
+
}
|
|
10759
|
+
}
|
|
10760
|
+
return best ? [best.u, best.v] : roomCenter;
|
|
10761
|
+
}
|
|
10762
|
+
if (rule2 === "target") {
|
|
10763
|
+
const t = byId2[def.target];
|
|
10764
|
+
return t && t.valid ? [t.x, t.z] : roomCenter;
|
|
10765
|
+
}
|
|
10766
|
+
return roomCenter;
|
|
10767
|
+
};
|
|
10768
|
+
const free = (rect) => !!def.floorFree || !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
|
|
10769
|
+
const rule = def.facing || "center";
|
|
10770
|
+
let R = inner(room.parts[Math.min(def.part || 0, room.parts.length - 1)]);
|
|
10771
|
+
if (def.anchor === "wall" || def.anchor === "window") {
|
|
10772
|
+
let side = def.side;
|
|
10773
|
+
let a = null;
|
|
10774
|
+
let win = null;
|
|
10775
|
+
if (def.anchor === "window") {
|
|
10776
|
+
const windowList = windowsOfRoom(plan, room);
|
|
10777
|
+
win = windowList[def.n];
|
|
10778
|
+
if (!win) return invalid("no_window");
|
|
10779
|
+
const found2 = win;
|
|
10780
|
+
side = room.outdoor ? SIDE_OPPOSITE[win.side] : WINDOW_SIDE[win.side];
|
|
10781
|
+
const cand = room.parts.map(inner).find(
|
|
10782
|
+
(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))
|
|
10783
|
+
);
|
|
10784
|
+
if (!cand) return invalid("window_outside_room");
|
|
10785
|
+
R = cand;
|
|
10786
|
+
a = win.axis === "u" ? win.u : win.v;
|
|
10787
|
+
}
|
|
10788
|
+
const n = SIDE_NORMAL[side];
|
|
10789
|
+
if (!n) return invalid("invalid_side");
|
|
10790
|
+
const horizontal = side === "front" || side === "back";
|
|
10791
|
+
const a0 = horizontal ? R[0] : R[1];
|
|
10792
|
+
const a1 = horizontal ? R[2] : R[3];
|
|
10793
|
+
const len2 = a1 - a0;
|
|
10794
|
+
const depth = size[1];
|
|
10795
|
+
if (depth > (horizontal ? R[3] - R[1] : R[2] - R[0]) + EPS) return invalid("larger_than_room");
|
|
10796
|
+
const hw = Math.min(size[0], len2) / 2;
|
|
10797
|
+
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]];
|
|
10798
|
+
const nominal = a != null ? a : a0 + hw + (def.pos == null ? 0.5 : def.pos) * Math.max(0, len2 - 2 * hw) + (def.shift || 0);
|
|
10799
|
+
const wp = side === "left" ? R[0] - MARGIN : side === "right" ? R[2] + MARGIN : side === "front" ? R[1] - MARGIN : R[3] + MARGIN;
|
|
10800
|
+
const pa = clamp3(nominal, a0 + hw, a1 - hw);
|
|
10801
|
+
let found = null;
|
|
10802
|
+
const steps = [0];
|
|
10803
|
+
if (def.anchor !== "window") for (let k = 1; k <= 60; k++) steps.push(k, -k);
|
|
10804
|
+
for (const k of steps) {
|
|
10805
|
+
const ac = pa + k * 0.05;
|
|
10806
|
+
if (ac - hw < a0 - EPS || ac + hw > a1 + EPS) continue;
|
|
10807
|
+
if (free(cell(ac))) {
|
|
10808
|
+
found = ac;
|
|
10809
|
+
break;
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
10812
|
+
if (found == null) return invalid("blocked_by_door");
|
|
10813
|
+
const sp = horizontal ? [found, wp - n[1] * 0.1] : [wp - n[0] * 0.1, found];
|
|
10814
|
+
if (isOpenSide(plan, room, sp[0], sp[1])) return invalid("open_wall");
|
|
10815
|
+
const rect = cell(found);
|
|
10816
|
+
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 } });
|
|
10817
|
+
}
|
|
10818
|
+
if (def.anchor === "corner") {
|
|
10819
|
+
const [cornerV, cornerU] = def.corner.split("-");
|
|
10820
|
+
const normalV = SIDE_NORMAL[cornerV];
|
|
10821
|
+
const normalU = SIDE_NORMAL[cornerU];
|
|
10822
|
+
if (!normalV || !normalU) return invalid("invalid_corner");
|
|
10823
|
+
const kx = cornerU === "left" ? R[0] : R[2];
|
|
10824
|
+
const kz = cornerV === "front" ? R[1] : R[3];
|
|
10825
|
+
point = [kx, kz];
|
|
10826
|
+
let f;
|
|
10827
|
+
if (FIXED_FACING[rule]) {
|
|
10828
|
+
f = FIXED_FACING[rule];
|
|
10829
|
+
if (!(f[0] === normalU[0] && f[1] === normalU[1] || f[0] === normalV[0] && f[1] === normalV[1])) f = normalU;
|
|
10830
|
+
} else {
|
|
10831
|
+
const t = targetPoint(rule);
|
|
10832
|
+
f = Math.abs(t[0] - kx) >= Math.abs(t[1] - kz) ? normalU : normalV;
|
|
10833
|
+
}
|
|
10834
|
+
const [extentU, extentV] = extent(size, f);
|
|
10835
|
+
if (extentU > R[2] - R[0] + EPS || extentV > R[3] - R[1] + EPS) return invalid("larger_than_room");
|
|
10836
|
+
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]];
|
|
10837
|
+
if (!free(rect)) return invalid("blocked_by_door");
|
|
10838
|
+
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");
|
|
10839
|
+
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 } });
|
|
10840
|
+
}
|
|
10841
|
+
if (def.anchor === "center") {
|
|
10842
|
+
let cx = R[0] + (def.fx == null ? 0.5 : def.fx) * (R[2] - R[0]) + (def.dx || 0);
|
|
10843
|
+
let cz = R[1] + (def.fz == null ? 0.5 : def.fz) * (R[3] - R[1]) + (def.dz || 0);
|
|
10844
|
+
point = [cx, cz];
|
|
10845
|
+
let f;
|
|
10846
|
+
if (FIXED_FACING[rule]) f = FIXED_FACING[rule];
|
|
10847
|
+
else {
|
|
10848
|
+
const t = targetPoint(rule);
|
|
10849
|
+
const dx = t[0] - cx;
|
|
10850
|
+
const dz = t[1] - cz;
|
|
10851
|
+
f = Math.hypot(dx, dz) < 0.05 ? [0, 1] : cardinal(dx, dz);
|
|
10852
|
+
}
|
|
10853
|
+
const [extentU, extentV] = extent(size, f);
|
|
10854
|
+
if (extentU > R[2] - R[0] + EPS || extentV > R[3] - R[1] + EPS) return invalid("larger_than_room");
|
|
10855
|
+
cx = clamp3(cx, R[0] + extentU / 2, R[2] - extentU / 2);
|
|
10856
|
+
cz = clamp3(cz, R[1] + extentV / 2, R[3] - extentV / 2);
|
|
10857
|
+
const rect = [cx - extentU / 2, cz - extentV / 2, cx + extentU / 2, cz + extentV / 2];
|
|
10858
|
+
if (!free(rect)) return invalid("blocked_by_door");
|
|
10859
|
+
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 } });
|
|
10860
|
+
}
|
|
10861
|
+
return invalid("unknown_anchor");
|
|
10862
|
+
}
|
|
10863
|
+
function templateOf(t) {
|
|
10864
|
+
if (typeof t === "string") {
|
|
10865
|
+
if (!registries().TEMPLATES[t]) throw new Error("Unknown template: " + t);
|
|
10866
|
+
return registries().TEMPLATES[t];
|
|
10867
|
+
}
|
|
10868
|
+
return t;
|
|
10869
|
+
}
|
|
10870
|
+
var cleanSlot = (s) => {
|
|
10871
|
+
const o = {};
|
|
10872
|
+
for (const k of Object.keys(s)) if (k[0] !== "_") o[k] = s[k];
|
|
10873
|
+
o.cats = Object.keys(s.cats || {});
|
|
10874
|
+
return o;
|
|
10875
|
+
};
|
|
10876
|
+
function slots(template, options) {
|
|
10877
|
+
const tpl = templateOf(template);
|
|
10878
|
+
const o = options || {};
|
|
10879
|
+
const plan = buildPlan(tpl, o.w, o.d, o);
|
|
10880
|
+
const locale = o.locale || "en";
|
|
10881
|
+
return resolveSlots(plan).map(cleanSlot).map((s) => s.reason ? Object.assign(s, { reason: translate("reasons", s.reason, locale) }) : s);
|
|
10882
|
+
}
|
|
10883
|
+
function pickState(requested, seed) {
|
|
10884
|
+
if (requested && registries().STATE_PLUGINS[requested]) return requested;
|
|
10885
|
+
const dist2 = registries().stateDistribution.weights;
|
|
10886
|
+
if (!dist2) return "normal";
|
|
10887
|
+
const x = createRng2(seed, registries().stateDistribution.seedKey)();
|
|
10888
|
+
let acc = 0;
|
|
10889
|
+
const keys = Object.keys(dist2);
|
|
10890
|
+
for (const k of keys) {
|
|
10891
|
+
acc += dist2[k];
|
|
10892
|
+
if (x < acc) return k;
|
|
10893
|
+
}
|
|
10894
|
+
return keys[keys.length - 1];
|
|
10895
|
+
}
|
|
10896
|
+
function colorOf(m, seed) {
|
|
10897
|
+
const spec = m.colorSpec || registries().CATALOG[m.type]?.color || { role: "wood" };
|
|
10898
|
+
if ("role" in spec) return spec.role;
|
|
10899
|
+
if ("hex" in spec) return spec.hex;
|
|
10900
|
+
return spec.pick[hashString2(seed + "|" + (m._seedId ?? m.id)) % spec.pick.length];
|
|
10901
|
+
}
|
|
10902
|
+
function applyState(plan, items, ctx, seed, state) {
|
|
10903
|
+
const plugin = registries().STATE_PLUGINS[state];
|
|
10904
|
+
if (!plugin || state === "normal") return [];
|
|
10905
|
+
const c = {
|
|
10906
|
+
plan,
|
|
10907
|
+
items,
|
|
10908
|
+
seed,
|
|
10909
|
+
state,
|
|
10910
|
+
rng: createRng2(seed, plugin.seedKey ?? "state:" + state),
|
|
10911
|
+
ctx,
|
|
10912
|
+
kit: placementKit,
|
|
10913
|
+
catalog: registries().CATALOG,
|
|
10914
|
+
wallTopHeights: registries().WALL_TOP_HEIGHTS
|
|
10915
|
+
};
|
|
10916
|
+
return plugin.apply(c);
|
|
10917
|
+
}
|
|
10918
|
+
var loc = (key, locale, ns = "labels", vars) => key ? translate(ns, key, locale, vars) : key;
|
|
10919
|
+
function generate3(template, options) {
|
|
10920
|
+
const tpl = templateOf(template);
|
|
10921
|
+
const o = options || {};
|
|
10922
|
+
const seed = o.seed == null ? 1 : o.seed;
|
|
10923
|
+
const locale = o.locale || "en";
|
|
10924
|
+
let op = Object.assign({}, o, { seed });
|
|
10925
|
+
const zone = resolveZone(o.zone);
|
|
10926
|
+
const adjustments = [];
|
|
10927
|
+
if (zone) op = clampOptionsToZone(tpl, op, zone, adjustments);
|
|
10928
|
+
const plan = buildPlan(tpl, op.w, op.d, op, { zone, adjustments });
|
|
10929
|
+
const all = resolveSlots(plan);
|
|
10930
|
+
const items = [];
|
|
10931
|
+
const missing = [];
|
|
10932
|
+
for (const room of plan.rooms.concat(plan.zones)) {
|
|
10933
|
+
const sl = all.filter((s) => s.room === room.id && s.floor === room.floor);
|
|
10934
|
+
const r = fillRoom(plan, room, sl, createRng2(seed, STREAM.roomPrefix + (room.seedKey ?? registries().SEED_KEYS.rooms[room.id] ?? room.id)));
|
|
10935
|
+
items.push(...r.items);
|
|
10936
|
+
missing.push(...r.missing);
|
|
10937
|
+
}
|
|
10938
|
+
const ctx = runPostProcess(plan, items, seed);
|
|
10939
|
+
const state = pickState(o.state, seed);
|
|
10940
|
+
const stateItemsAll = applyState(plan, items, ctx, seed, state);
|
|
10941
|
+
const colors2 = palette({ seed, style: plan.style, state });
|
|
10942
|
+
const strip = (m) => {
|
|
10943
|
+
const x = {};
|
|
10944
|
+
for (const k of Object.keys(m)) if (k[0] !== "_" && k !== "colorSpec") x[k] = m[k];
|
|
10945
|
+
return x;
|
|
10946
|
+
};
|
|
10947
|
+
const L = plan.lot;
|
|
10948
|
+
const lot = L ? {
|
|
10949
|
+
rect: L.rect.map(r2),
|
|
10950
|
+
setback: r2(L.setback),
|
|
10951
|
+
backyard: r2(L.backyard),
|
|
10952
|
+
sideYard: L.sideYard,
|
|
10953
|
+
style: L.style,
|
|
10954
|
+
pool: L.pool,
|
|
10955
|
+
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 })),
|
|
10956
|
+
perimeter: L.perimeter.map((s) => Object.assign({ color: "wall" }, s)),
|
|
10957
|
+
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" })),
|
|
10958
|
+
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 }))
|
|
10959
|
+
} : null;
|
|
10960
|
+
const facade = Object.assign({}, plan.facade, {
|
|
10961
|
+
color: { name: loc(colors2.meta.base, locale, "facadeColors"), key: colors2.meta.base, hex: hexString(colors2.facade) },
|
|
10962
|
+
roles: { facade: "facade", detail: "facade_detail", roof: "roof", wall: "wall", gate: "gate" }
|
|
10963
|
+
});
|
|
10964
|
+
const itemsOut = items.map((m) => {
|
|
10965
|
+
const out = strip(m);
|
|
10966
|
+
out.color = colorOf(m, seed);
|
|
10967
|
+
return out;
|
|
10968
|
+
});
|
|
10969
|
+
const stateItemsOut = stateItemsAll.map((m) => {
|
|
10970
|
+
const out = strip(m);
|
|
10971
|
+
if (m.category) out.color = colorOf(m, seed);
|
|
10972
|
+
return out;
|
|
10973
|
+
});
|
|
10974
|
+
const base = {
|
|
10975
|
+
template: tpl.id,
|
|
10976
|
+
name: translate("templates", tpl.id, locale),
|
|
10977
|
+
use: useOf(tpl),
|
|
10978
|
+
seed,
|
|
10979
|
+
style: plan.style,
|
|
10980
|
+
state,
|
|
10981
|
+
w: plan.w,
|
|
10982
|
+
d: plan.d,
|
|
10983
|
+
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 })),
|
|
10984
|
+
rooms: plan.rooms.map((c) => ({
|
|
10985
|
+
id: c.id,
|
|
10986
|
+
type: c.type,
|
|
10987
|
+
label: c.label,
|
|
10988
|
+
name: loc(c.label, locale),
|
|
10989
|
+
floor: c.floor,
|
|
10990
|
+
u0: c.parts[0][0],
|
|
10991
|
+
v0: c.parts[0][1],
|
|
10992
|
+
u1: c.parts[0][2],
|
|
10993
|
+
v1: c.parts[0][3],
|
|
10994
|
+
parts: c.parts,
|
|
10995
|
+
link: c.link,
|
|
10996
|
+
ceiling: c.ceiling || plan.floors[c.floor].ceiling,
|
|
10997
|
+
color: { wall: "wall_" + c.type, floor: "floor_" + c.type }
|
|
10998
|
+
})),
|
|
10999
|
+
walls: plan.walls,
|
|
11000
|
+
doors: plan.doors,
|
|
11001
|
+
windows: plan.windows,
|
|
11002
|
+
openPairs: [].concat(...Object.values(plan.openPairs)),
|
|
11003
|
+
facade,
|
|
11004
|
+
lot,
|
|
11005
|
+
items: itemsOut,
|
|
11006
|
+
stateItems: stateItemsOut,
|
|
11007
|
+
colors: colors2,
|
|
11008
|
+
building: tpl.building ? tpl.building(plan.w, plan.d, op) : null,
|
|
11009
|
+
missing
|
|
11010
|
+
};
|
|
11011
|
+
if (tpl.singleFloor) base.floorNumber = op.floor || 3;
|
|
11012
|
+
const result = Object.assign(base, { partyWalls: tpl.partyWalls || [] });
|
|
11013
|
+
if (plan.config) result.config = plan.config;
|
|
11014
|
+
runResultExtensions(plan, result, seed, op);
|
|
11015
|
+
if (zone) {
|
|
11016
|
+
const report = { zone: zone.id, adjustments, violations: checkLot(zone, lot, Object.assign({}, result, { rooms: plan.rooms })) };
|
|
11017
|
+
result.zoning = report;
|
|
11018
|
+
}
|
|
11019
|
+
return result;
|
|
11020
|
+
}
|
|
11021
|
+
var isSolid = (m) => !m.flat && !m.on && (!m.decor || m.mount === "floor");
|
|
11022
|
+
function validate(result, options) {
|
|
11023
|
+
const locale = options?.locale || result.locale || "en";
|
|
11024
|
+
const t = (key, vars) => translate("messages", key, locale, vars);
|
|
11025
|
+
const errors = [];
|
|
11026
|
+
const solids = result.items.filter(isSolid);
|
|
11027
|
+
for (let i = 0; i < solids.length; i++)
|
|
11028
|
+
for (let j = i + 1; j < solids.length; j++)
|
|
11029
|
+
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 }));
|
|
11030
|
+
for (const m of solids) {
|
|
11031
|
+
if (m.barricade) continue;
|
|
11032
|
+
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 }));
|
|
11033
|
+
if (result.lot) {
|
|
11034
|
+
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 }));
|
|
11035
|
+
}
|
|
11036
|
+
}
|
|
11037
|
+
for (const f of result.missing) errors.push(t("missing_required", { room: f.room, slot: f.slot }));
|
|
11038
|
+
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 }));
|
|
11039
|
+
return errors;
|
|
11040
|
+
}
|
|
11041
|
+
function limitsOf(template, options) {
|
|
11042
|
+
const tpl = templateOf(template);
|
|
11043
|
+
const op = options || {};
|
|
11044
|
+
return { limits: tpl.limitsFn ? tpl.limitsFn(op) : tpl.limits, defaults: tpl.defaultsFn ? tpl.defaultsFn(op) : tpl.defaults };
|
|
11045
|
+
}
|
|
11046
|
+
function listTemplates(locale = "en") {
|
|
11047
|
+
return Object.values(registries().TEMPLATES).map((t) => {
|
|
11048
|
+
const a = floorsOf(t, {});
|
|
11049
|
+
return {
|
|
11050
|
+
id: t.id,
|
|
11051
|
+
name: translate("templates", t.id, locale),
|
|
11052
|
+
use: useOf(t),
|
|
11053
|
+
style: typeof t.style === "function" ? t.style({}) : t.style,
|
|
11054
|
+
limits: t.limits,
|
|
11055
|
+
defaults: t.defaults,
|
|
11056
|
+
floors: a.length,
|
|
11057
|
+
floorRange: t.floorRange || null,
|
|
11058
|
+
lot: !!t.lot,
|
|
11059
|
+
options: t.options || {}
|
|
11060
|
+
};
|
|
11061
|
+
});
|
|
8786
11062
|
}
|
|
8787
11063
|
function registerGroundItems() {
|
|
8788
11064
|
const flat = { category: "ground_cover", w: 1, d: 1, h: 0, decor: true, mount: "ground", flat: true };
|
|
@@ -8794,6 +11070,14 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
8794
11070
|
{ type: "dirt", ...flat, color: { role: "dirt" } }
|
|
8795
11071
|
]);
|
|
8796
11072
|
}
|
|
11073
|
+
var slotKit = {
|
|
11074
|
+
wall: (id, side, pos, size, cats, o) => Object.assign({ id, anchor: "wall", side, pos, size, cats }, o),
|
|
11075
|
+
corner: (id, corner, size, cats, o) => Object.assign({ id, anchor: "corner", corner, size, cats }, o),
|
|
11076
|
+
center: (id, fx, fz, size, cats, o) => Object.assign({ id, anchor: "center", fx, fz, size, cats }, o),
|
|
11077
|
+
windowSlot: (id, size, cats, o) => Object.assign({ id, anchor: "window", size, cats }, o),
|
|
11078
|
+
on: (id, parent, cats, o) => Object.assign({ id, anchor: "on", on: parent, cats }, o),
|
|
11079
|
+
inside: (id, parent, cats, o) => Object.assign({ id, anchor: "inside", on: parent, cats }, o)
|
|
11080
|
+
};
|
|
8797
11081
|
var en_default = {
|
|
8798
11082
|
labels: {
|
|
8799
11083
|
floor_n: "Floor {n}",
|
|
@@ -8988,6 +11272,82 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
8988
11272
|
reason_permeable_min: "\xC1rea perme\xE1vel da zona"
|
|
8989
11273
|
}
|
|
8990
11274
|
};
|
|
11275
|
+
var version = "0.1.0";
|
|
11276
|
+
function buildEngine(scope) {
|
|
11277
|
+
const scoped = (fn) => ((...args) => withRegistries(scope, () => fn(...args)));
|
|
11278
|
+
return {
|
|
11279
|
+
version,
|
|
11280
|
+
generate: scoped(generate3),
|
|
11281
|
+
slots: scoped(slots),
|
|
11282
|
+
validate: scoped(validate),
|
|
11283
|
+
palette: scoped(palette),
|
|
11284
|
+
limitsOf: scoped(limitsOf),
|
|
11285
|
+
listTemplates: scoped(listTemplates),
|
|
11286
|
+
listStates: scoped(listStates),
|
|
11287
|
+
// content registration
|
|
11288
|
+
registerItem: scoped(registerItem),
|
|
11289
|
+
registerItems: scoped(registerItems),
|
|
11290
|
+
registerLibrary: scoped(registerLibrary),
|
|
11291
|
+
registerLibraryAlias: scoped(registerLibraryAlias),
|
|
11292
|
+
registerTemplate: scoped(registerTemplate),
|
|
11293
|
+
registerStyle: scoped(registerStyle),
|
|
11294
|
+
registerPaletteStyle: scoped(registerPaletteStyle),
|
|
11295
|
+
registerRoomColors: scoped(registerRoomColors),
|
|
11296
|
+
registerDecorProfile: scoped(registerDecorProfile),
|
|
11297
|
+
registerPass: scoped(registerPass),
|
|
11298
|
+
registerGroundOverlay: scoped(registerGroundOverlay),
|
|
11299
|
+
registerWallTop: scoped(registerWallTop),
|
|
11300
|
+
registerSeedKeys: scoped(registerSeedKeys),
|
|
11301
|
+
registerState: scoped(registerState),
|
|
11302
|
+
setStateDistribution: scoped(setStateDistribution),
|
|
11303
|
+
registerLocale: scoped(registerLocale),
|
|
11304
|
+
registerResultExtension: scoped(registerResultExtension),
|
|
11305
|
+
registerFindsProvider: scoped(registerFindsProvider),
|
|
11306
|
+
findsFor: scoped(findsFor),
|
|
11307
|
+
findsAnswer: scoped(findsAnswer),
|
|
11308
|
+
registerViewerLayer: scoped(registerViewerLayer),
|
|
11309
|
+
viewerLayers: scope.VIEWER_LAYERS,
|
|
11310
|
+
blocksWindow: scoped(blocksWindow),
|
|
11311
|
+
// zoning
|
|
11312
|
+
defineZone: scoped(defineZone),
|
|
11313
|
+
getZone: scoped(getZone),
|
|
11314
|
+
listZones: scoped(listZones),
|
|
11315
|
+
checkLot: scoped(checkLot),
|
|
11316
|
+
registerPermeableGround: scoped(registerPermeableGround),
|
|
11317
|
+
// kits and data
|
|
11318
|
+
slotKit,
|
|
11319
|
+
translate: scoped(translate),
|
|
11320
|
+
listLocales: scoped(listLocales),
|
|
11321
|
+
templates: scope.TEMPLATES,
|
|
11322
|
+
catalog: scope.CATALOG,
|
|
11323
|
+
libraries: scope.LIBRARIES,
|
|
11324
|
+
styles: scope.STYLES,
|
|
11325
|
+
paletteStyles: scope.PALETTE_STYLES,
|
|
11326
|
+
createRng: createRng2,
|
|
11327
|
+
mulberry32: mulberry322,
|
|
11328
|
+
hashString: hashString2,
|
|
11329
|
+
overlaps,
|
|
11330
|
+
windowZone,
|
|
11331
|
+
openingZones,
|
|
11332
|
+
color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
|
|
11333
|
+
constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
|
|
11334
|
+
};
|
|
11335
|
+
}
|
|
11336
|
+
function seedBase(scope) {
|
|
11337
|
+
withRegistries(scope, () => {
|
|
11338
|
+
registerGroundItems();
|
|
11339
|
+
registerPermeableGround("lawn", "dirt");
|
|
11340
|
+
registerLocale("en", en_default);
|
|
11341
|
+
registerLocale("pt-BR", pt_BR_default);
|
|
11342
|
+
});
|
|
11343
|
+
}
|
|
11344
|
+
var defaultEngine;
|
|
11345
|
+
function defaultFloorplan() {
|
|
11346
|
+
if (defaultEngine) return defaultEngine;
|
|
11347
|
+
seedBase(getDefaultRegistries());
|
|
11348
|
+
defaultEngine = buildEngine(getDefaultRegistries());
|
|
11349
|
+
return defaultEngine;
|
|
11350
|
+
}
|
|
8991
11351
|
function createSilhouetteRandom(seed) {
|
|
8992
11352
|
let state = seed;
|
|
8993
11353
|
return () => {
|
|
@@ -9188,10 +11548,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
9188
11548
|
}
|
|
9189
11549
|
return b.finish({ x: p.x, z: p.z, width: w + 0.5, depth: d + 0.5, height: 4.6 });
|
|
9190
11550
|
}
|
|
9191
|
-
|
|
9192
|
-
registerPermeableGround("lawn", "dirt");
|
|
9193
|
-
registerLocale("en", en_default);
|
|
9194
|
-
registerLocale("pt-BR", pt_BR_default);
|
|
11551
|
+
var engine = defaultFloorplan();
|
|
9195
11552
|
|
|
9196
11553
|
// ../game-vehicles-fleet/dist/index.js
|
|
9197
11554
|
var cars = [
|
|
@@ -14074,7 +16431,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14074
16431
|
const THREE = kit.THREE;
|
|
14075
16432
|
const trl = fx.t;
|
|
14076
16433
|
const lbl = fx.label;
|
|
14077
|
-
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G = kit.geo, profX = kit.profX, halfAt = kit.halfAt,
|
|
16434
|
+
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G = kit.geo, profX = kit.profX, halfAt = kit.halfAt, clamp5 = kit.clamp, smooth2 = kit.smooth;
|
|
14078
16435
|
const GLOSS = CM("#07080a", { roughness: 0.18, metalness: 0.5 });
|
|
14079
16436
|
const ALLOY = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 });
|
|
14080
16437
|
const ALLOYD = CM("#3a3e42", { metalness: 0.6, roughness: 0.35 });
|
|
@@ -14088,7 +16445,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14088
16445
|
const a = (y - o.ys) / (o.belt - o.ys);
|
|
14089
16446
|
return o.kt * a * a;
|
|
14090
16447
|
}
|
|
14091
|
-
const b =
|
|
16448
|
+
const b = clamp5((o.ys - y) / (o.ys - o.yb), 0, 1.25);
|
|
14092
16449
|
return o.kb * b * b;
|
|
14093
16450
|
}
|
|
14094
16451
|
const f = function(x, y, z) {
|
|
@@ -14168,13 +16525,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14168
16525
|
});
|
|
14169
16526
|
}
|
|
14170
16527
|
function roofAt(gh, x) {
|
|
14171
|
-
const rR = gh.roofR || gh.roof, t =
|
|
16528
|
+
const rR = gh.roofR || gh.roof, t = clamp5((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
|
|
14172
16529
|
return rR + (gh.roof - rR) * t + (gh.crown || 0) * 4 * t * (1 - t);
|
|
14173
16530
|
}
|
|
14174
16531
|
function windows(gh) {
|
|
14175
16532
|
const inn = 0.07, rise = gh.rise || 0;
|
|
14176
16533
|
function ybAt(x) {
|
|
14177
|
-
return gh.belt + 0.035 + rise *
|
|
16534
|
+
return gh.belt + 0.035 + rise * clamp5((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
|
|
14178
16535
|
}
|
|
14179
16536
|
function lx(x0, x1, y, top) {
|
|
14180
16537
|
return x0 + (x1 - x0) * (y - gh.belt) / (top - gh.belt);
|
|
@@ -14377,7 +16734,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14377
16734
|
}
|
|
14378
16735
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: tot * (full ? 2 : 1), E, sd };
|
|
14379
16736
|
G2.at = function(u, y) {
|
|
14380
|
-
const fi =
|
|
16737
|
+
const fi = clamp5(u, 0, 1) * nu, fj = clamp5((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;
|
|
14381
16738
|
function L(M) {
|
|
14382
16739
|
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);
|
|
14383
16740
|
}
|
|
@@ -14514,10 +16871,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14514
16871
|
m.rotation.order = "XYZ";
|
|
14515
16872
|
return m;
|
|
14516
16873
|
}
|
|
14517
|
-
function regLight(c, kind, label,
|
|
14518
|
-
const L = { group: null, inner, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
16874
|
+
function regLight(c, kind, label, inner2, lens, pos) {
|
|
16875
|
+
const L = { group: null, inner: inner2, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
14519
16876
|
c.lights.push(L);
|
|
14520
|
-
const pt = c.part(kind, label,
|
|
16877
|
+
const pt = c.part(kind, label, inner2[0], pos, 0.8);
|
|
14521
16878
|
pt.light = L;
|
|
14522
16879
|
return L;
|
|
14523
16880
|
}
|
|
@@ -14549,7 +16906,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14549
16906
|
du: 0.018,
|
|
14550
16907
|
dv: 0.012
|
|
14551
16908
|
});
|
|
14552
|
-
const pu = pad / Gd.len,
|
|
16909
|
+
const pu = pad / Gd.len, inner2 = [], lens = [];
|
|
14553
16910
|
const yb = function(u) {
|
|
14554
16911
|
return sh(u)[0];
|
|
14555
16912
|
}, yt = function(u) {
|
|
@@ -14574,13 +16931,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14574
16931
|
ring2.castShadow = false;
|
|
14575
16932
|
const b = onSurf(c.sphere(rad, MT.head, 0, 0, 0, 1, 0.55, 1), s, 0.01);
|
|
14576
16933
|
b.castShadow = false;
|
|
14577
|
-
|
|
16934
|
+
inner2.push(b);
|
|
14578
16935
|
});
|
|
14579
16936
|
(o.drl || []).forEach(function(d) {
|
|
14580
|
-
|
|
16937
|
+
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 }));
|
|
14581
16938
|
});
|
|
14582
16939
|
(o.amber || []).forEach(function(d) {
|
|
14583
|
-
|
|
16940
|
+
inner2.push(skin(c, Gd, MT.amber, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 9e-3, nv: 2 }));
|
|
14584
16941
|
});
|
|
14585
16942
|
const ln = skin(c, Gd, MT.lens, {
|
|
14586
16943
|
yb: function(u) {
|
|
@@ -14594,9 +16951,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14594
16951
|
order: 3
|
|
14595
16952
|
});
|
|
14596
16953
|
lens.push(ln);
|
|
14597
|
-
if (!
|
|
16954
|
+
if (!inner2.length) inner2.push(ln);
|
|
14598
16955
|
const cs = Gd.at(0.5, (rg[0] + rg[1]) / 2).p;
|
|
14599
|
-
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")),
|
|
16956
|
+
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")), inner2, lens, cs);
|
|
14600
16957
|
return Gd;
|
|
14601
16958
|
};
|
|
14602
16959
|
K.tail = function(sd, o) {
|
|
@@ -14610,7 +16967,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14610
16967
|
du: 0.018,
|
|
14611
16968
|
dv: 0.012
|
|
14612
16969
|
});
|
|
14613
|
-
const pu = pad / Gd.len,
|
|
16970
|
+
const pu = pad / Gd.len, inner2 = [], lens = [], yb = function(u) {
|
|
14614
16971
|
return sh(u)[0];
|
|
14615
16972
|
}, yt = function(u) {
|
|
14616
16973
|
return sh(u)[1];
|
|
@@ -14625,15 +16982,15 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14625
16982
|
off: 3e-3,
|
|
14626
16983
|
nv: 4
|
|
14627
16984
|
});
|
|
14628
|
-
|
|
16985
|
+
inner2.push(skin(c, Gd, o.main || TAILD, { u0: pu, u1: 1 - pu, yb, yt, off: 6e-3, nv: 4 }));
|
|
14629
16986
|
(o.hi || []).forEach(function(d) {
|
|
14630
|
-
|
|
16987
|
+
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 }));
|
|
14631
16988
|
});
|
|
14632
16989
|
(o.rev || []).forEach(function(d) {
|
|
14633
|
-
|
|
16990
|
+
inner2.push(skin(c, Gd, MT.rev, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 85e-4, nv: 2 }));
|
|
14634
16991
|
});
|
|
14635
16992
|
(o.amber || []).forEach(function(d) {
|
|
14636
|
-
|
|
16993
|
+
inner2.push(skin(c, Gd, MT.amber, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 85e-4, nv: 2 }));
|
|
14637
16994
|
});
|
|
14638
16995
|
lens.push(
|
|
14639
16996
|
skin(c, Gd, o.lens || MT.lensRed, {
|
|
@@ -14652,7 +17009,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14652
17009
|
c,
|
|
14653
17010
|
"taillight",
|
|
14654
17011
|
o.label || (sd > 0 ? trl("labels.right_taillight") : trl("labels.left_taillight")),
|
|
14655
|
-
|
|
17012
|
+
inner2,
|
|
14656
17013
|
lens,
|
|
14657
17014
|
Gd.at(0.4, (rg[0] + rg[1]) / 2).p
|
|
14658
17015
|
);
|
|
@@ -14957,7 +17314,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14957
17314
|
c.roundBox(x1 - x0, 0.08, 0.035, 0.015, MT.trim, (x0 + x1) / 2, by + 0.05, sd * (hw - 8e-3));
|
|
14958
17315
|
if (s.clad)
|
|
14959
17316
|
s.arches.forEach(function(a) {
|
|
14960
|
-
const a0 = Math.asin(
|
|
17317
|
+
const a0 = Math.asin(clamp5((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));
|
|
14961
17318
|
tor.rotation.z = a0;
|
|
14962
17319
|
tor.scale.z = 0.8;
|
|
14963
17320
|
});
|
|
@@ -15088,7 +17445,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
15088
17445
|
c.cylinder(0.06, 0.06, 0.012, MT.head, x + 0.055, y, z, "x");
|
|
15089
17446
|
}
|
|
15090
17447
|
function sm(t) {
|
|
15091
|
-
t =
|
|
17448
|
+
t = clamp5(t, 0, 1);
|
|
15092
17449
|
return t * t * (3 - 2 * t);
|
|
15093
17450
|
}
|
|
15094
17451
|
function sideGrid(S, sd, o) {
|
|
@@ -15105,7 +17462,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
15105
17462
|
}
|
|
15106
17463
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: Math.abs(o.x1 - o.x0) };
|
|
15107
17464
|
G2.at = function(u, y) {
|
|
15108
|
-
const fi =
|
|
17465
|
+
const fi = clamp5(u, 0, 1) * nu, fj = clamp5((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;
|
|
15109
17466
|
function L(M) {
|
|
15110
17467
|
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);
|
|
15111
17468
|
}
|
|
@@ -16833,7 +19190,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
16833
19190
|
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;
|
|
16834
19191
|
const gyb = function(u) {
|
|
16835
19192
|
const z = Math.abs(g.zu(u));
|
|
16836
|
-
return z <= zb ? yB0 : yB0 + (yT - 0.02 - yB0) *
|
|
19193
|
+
return z <= zb ? yB0 : yB0 + (yT - 0.02 - yB0) * clamp5((z - zb) / (zt - zb), 0, 1);
|
|
16837
19194
|
};
|
|
16838
19195
|
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 });
|
|
16839
19196
|
outline(c2, g, { u0: g.uz(-zt), u1: g.uz(zt), yb: gyb, yt: yT }, 0.012, LH, 0.024);
|
|
@@ -18396,6 +20753,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
18396
20753
|
hood: true
|
|
18397
20754
|
},
|
|
18398
20755
|
function(c) {
|
|
20756
|
+
c.meta.armor = "III";
|
|
18399
20757
|
const f = 3.75, W = 2.32, len2 = 1.85, ax = 2.5, wr = 0.506, ww = 0.28, col = "#56616b";
|
|
18400
20758
|
c.meta.bullet = true;
|
|
18401
20759
|
c.meta.cabLength = len2 + 0.15;
|
|
@@ -21783,8 +24141,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
21783
24141
|
kit.steeringWheel(c, 1.5, 1.42, -0.62, { r: 0.2, tilt: 0.9, col: 0.35 });
|
|
21784
24142
|
c.roundBox(0.35, 0.3, 0.7, 0.04, CM("#25261f"), 1.85, 1.45, -0.6);
|
|
21785
24143
|
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 }) });
|
|
21786
|
-
for (let
|
|
21787
|
-
c.roundBox(0.25, 0.32, 0.25, 0.03, CM("#3f4a2c", { roughness: 0.6 }), -0.7 -
|
|
24144
|
+
for (let r22 = 0; r22 < 3; r22++)
|
|
24145
|
+
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);
|
|
21788
24146
|
const ramp2 = new THREE.Group(), RL = 1.36, rmat = paintMat(c, col);
|
|
21789
24147
|
ramp2.position.set(R - 0.05, 0.62, 0);
|
|
21790
24148
|
ramp2.rotation.z = -PI / 2;
|
|
@@ -22991,7 +25349,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
22991
25349
|
const THREE = kit.THREE;
|
|
22992
25350
|
const trl = fx.t;
|
|
22993
25351
|
const lbl = fx.label;
|
|
22994
|
-
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G = kit.geo, profX = kit.profX, halfAt = kit.halfAt,
|
|
25352
|
+
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G = kit.geo, profX = kit.profX, halfAt = kit.halfAt, clamp5 = kit.clamp, smooth2 = kit.smooth;
|
|
22995
25353
|
const GLOSS = CM("#07080a", { roughness: 0.18, metalness: 0.5 });
|
|
22996
25354
|
const ALLOY = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 });
|
|
22997
25355
|
const ALLOYD = CM("#3a3e42", { metalness: 0.6, roughness: 0.35 });
|
|
@@ -23010,7 +25368,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23010
25368
|
const a = (y - o.ys) / (o.belt - o.ys);
|
|
23011
25369
|
return o.kt * a * a;
|
|
23012
25370
|
}
|
|
23013
|
-
const b =
|
|
25371
|
+
const b = clamp5((o.ys - y) / (o.ys - o.yb), 0, 1.25);
|
|
23014
25372
|
return o.kb * b * b;
|
|
23015
25373
|
}
|
|
23016
25374
|
const f = function(x, y, z) {
|
|
@@ -23190,13 +25548,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23190
25548
|
return L;
|
|
23191
25549
|
}
|
|
23192
25550
|
function roofAt(gh, x) {
|
|
23193
|
-
const rR = gh.roofR || gh.roof, t =
|
|
25551
|
+
const rR = gh.roofR || gh.roof, t = clamp5((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
|
|
23194
25552
|
return rR + (gh.roof - rR) * t + (gh.crown || 0) * 4 * t * (1 - t);
|
|
23195
25553
|
}
|
|
23196
25554
|
function windows(gh) {
|
|
23197
25555
|
const inn = 0.07, rise = gh.rise || 0;
|
|
23198
25556
|
function ybAt(x) {
|
|
23199
|
-
return gh.belt + 0.035 + rise *
|
|
25557
|
+
return gh.belt + 0.035 + rise * clamp5((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
|
|
23200
25558
|
}
|
|
23201
25559
|
function lx(x0, x1, y, top) {
|
|
23202
25560
|
return x0 + (x1 - x0) * (y - gh.belt) / (top - gh.belt);
|
|
@@ -23407,7 +25765,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23407
25765
|
}
|
|
23408
25766
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: tot * (full ? 2 : 1), E, sd };
|
|
23409
25767
|
G2.at = function(u, y) {
|
|
23410
|
-
const fi =
|
|
25768
|
+
const fi = clamp5(u, 0, 1) * nu, fj = clamp5((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;
|
|
23411
25769
|
function L(M) {
|
|
23412
25770
|
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);
|
|
23413
25771
|
}
|
|
@@ -23544,10 +25902,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23544
25902
|
m.rotation.order = "XYZ";
|
|
23545
25903
|
return m;
|
|
23546
25904
|
}
|
|
23547
|
-
function regLight(c, kind, label,
|
|
23548
|
-
const L = { group: null, inner, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
25905
|
+
function regLight(c, kind, label, inner2, lens, pos) {
|
|
25906
|
+
const L = { group: null, inner: inner2, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
23549
25907
|
c.lights.push(L);
|
|
23550
|
-
const pt = c.part(kind, label,
|
|
25908
|
+
const pt = c.part(kind, label, inner2[0], pos, 0.8);
|
|
23551
25909
|
pt.light = L;
|
|
23552
25910
|
return L;
|
|
23553
25911
|
}
|
|
@@ -23579,7 +25937,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23579
25937
|
du: 0.018,
|
|
23580
25938
|
dv: 0.012
|
|
23581
25939
|
});
|
|
23582
|
-
const pu = pad / Gd.len,
|
|
25940
|
+
const pu = pad / Gd.len, inner2 = [], lens = [];
|
|
23583
25941
|
const yb = function(u) {
|
|
23584
25942
|
return sh(u)[0];
|
|
23585
25943
|
}, yt = function(u) {
|
|
@@ -23604,13 +25962,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23604
25962
|
ring2.castShadow = false;
|
|
23605
25963
|
const b = onSurf(c.sphere(rad, MT.head, 0, 0, 0, 1, 0.55, 1), s, 0.01);
|
|
23606
25964
|
b.castShadow = false;
|
|
23607
|
-
|
|
25965
|
+
inner2.push(b);
|
|
23608
25966
|
});
|
|
23609
25967
|
(o.drl || []).forEach(function(d) {
|
|
23610
|
-
|
|
25968
|
+
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 }));
|
|
23611
25969
|
});
|
|
23612
25970
|
(o.amber || []).forEach(function(d) {
|
|
23613
|
-
|
|
25971
|
+
inner2.push(skin(c, Gd, MT.amber, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 9e-3, nv: 2 }));
|
|
23614
25972
|
});
|
|
23615
25973
|
const ln = skin(c, Gd, MT.lens, {
|
|
23616
25974
|
yb: function(u) {
|
|
@@ -23624,9 +25982,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23624
25982
|
order: 3
|
|
23625
25983
|
});
|
|
23626
25984
|
lens.push(ln);
|
|
23627
|
-
if (!
|
|
25985
|
+
if (!inner2.length) inner2.push(ln);
|
|
23628
25986
|
const cs = Gd.at(0.5, (rg[0] + rg[1]) / 2).p;
|
|
23629
|
-
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")),
|
|
25987
|
+
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")), inner2, lens, cs);
|
|
23630
25988
|
return Gd;
|
|
23631
25989
|
};
|
|
23632
25990
|
K.tail = function(sd, o) {
|
|
@@ -23640,7 +25998,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23640
25998
|
du: 0.018,
|
|
23641
25999
|
dv: 0.012
|
|
23642
26000
|
});
|
|
23643
|
-
const pu = pad / Gd.len,
|
|
26001
|
+
const pu = pad / Gd.len, inner2 = [], lens = [], yb = function(u) {
|
|
23644
26002
|
return sh(u)[0];
|
|
23645
26003
|
}, yt = function(u) {
|
|
23646
26004
|
return sh(u)[1];
|
|
@@ -23655,15 +26013,15 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23655
26013
|
off: 3e-3,
|
|
23656
26014
|
nv: 4
|
|
23657
26015
|
});
|
|
23658
|
-
|
|
26016
|
+
inner2.push(skin(c, Gd, o.main || TAILD, { u0: pu, u1: 1 - pu, yb, yt, off: 6e-3, nv: 4 }));
|
|
23659
26017
|
(o.hi || []).forEach(function(d) {
|
|
23660
|
-
|
|
26018
|
+
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 }));
|
|
23661
26019
|
});
|
|
23662
26020
|
(o.rev || []).forEach(function(d) {
|
|
23663
|
-
|
|
26021
|
+
inner2.push(skin(c, Gd, MT.rev, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 85e-4, nv: 2 }));
|
|
23664
26022
|
});
|
|
23665
26023
|
(o.amber || []).forEach(function(d) {
|
|
23666
|
-
|
|
26024
|
+
inner2.push(skin(c, Gd, MT.amber, { u0: d.u0, u1: d.u1, yb: d.yb, yt: d.yt, off: 85e-4, nv: 2 }));
|
|
23667
26025
|
});
|
|
23668
26026
|
lens.push(
|
|
23669
26027
|
skin(c, Gd, o.lens || MT.lensRed, {
|
|
@@ -23682,7 +26040,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23682
26040
|
c,
|
|
23683
26041
|
"taillight",
|
|
23684
26042
|
o.label || (sd > 0 ? trl("labels.right_taillight") : trl("labels.left_taillight")),
|
|
23685
|
-
|
|
26043
|
+
inner2,
|
|
23686
26044
|
lens,
|
|
23687
26045
|
Gd.at(0.4, (rg[0] + rg[1]) / 2).p
|
|
23688
26046
|
);
|
|
@@ -23968,7 +26326,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23968
26326
|
c.roundBox(x1 - x0, 0.08, 0.035, 0.015, MT.trim, (x0 + x1) / 2, by + 0.05, sd * (hw - 8e-3));
|
|
23969
26327
|
if (s.clad)
|
|
23970
26328
|
s.arches.forEach(function(a) {
|
|
23971
|
-
const a0 = Math.asin(
|
|
26329
|
+
const a0 = Math.asin(clamp5((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));
|
|
23972
26330
|
tor.rotation.z = a0;
|
|
23973
26331
|
tor.scale.z = 0.8;
|
|
23974
26332
|
});
|
|
@@ -24089,7 +26447,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
24089
26447
|
c.cylinder(0.06, 0.06, 0.012, MT.head, x + 0.055, y, z, "x");
|
|
24090
26448
|
}
|
|
24091
26449
|
function sm(t) {
|
|
24092
|
-
t =
|
|
26450
|
+
t = clamp5(t, 0, 1);
|
|
24093
26451
|
return t * t * (3 - 2 * t);
|
|
24094
26452
|
}
|
|
24095
26453
|
function sideGrid(S, sd, o) {
|
|
@@ -24106,7 +26464,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
24106
26464
|
}
|
|
24107
26465
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: Math.abs(o.x1 - o.x0) };
|
|
24108
26466
|
G2.at = function(u, y) {
|
|
24109
|
-
const fi =
|
|
26467
|
+
const fi = clamp5(u, 0, 1) * nu, fj = clamp5((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;
|
|
24110
26468
|
function L(M) {
|
|
24111
26469
|
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);
|
|
24112
26470
|
}
|
|
@@ -24265,7 +26623,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
24265
26623
|
return function(c, I) {
|
|
24266
26624
|
const gh = I.gh, col = c.meta.color;
|
|
24267
26625
|
function yb(x) {
|
|
24268
|
-
return gh.belt + 0.035 + (gh.rise || 0) *
|
|
26626
|
+
return gh.belt + 0.035 + (gh.rise || 0) * clamp5((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
|
|
24269
26627
|
}
|
|
24270
26628
|
;
|
|
24271
26629
|
[1, -1].forEach(function(sd) {
|
|
@@ -27136,7 +29494,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
27136
29494
|
return n.y > 0.3 && ce.x > 0.9 && ce.y > 0.6;
|
|
27137
29495
|
},
|
|
27138
29496
|
sdf: function(x, y, z) {
|
|
27139
|
-
const t =
|
|
29497
|
+
const t = clamp5((x - 1.17) / 0.5, 0, 1);
|
|
27140
29498
|
return Math.max(0.94 - x, x - 1.69, Math.abs(z) - (0.44 - 0.16 * t * t));
|
|
27141
29499
|
}
|
|
27142
29500
|
});
|
|
@@ -31134,7 +33492,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31134
33492
|
const THREE = kit.THREE;
|
|
31135
33493
|
const trl = fx.t;
|
|
31136
33494
|
const lbl = fx.label;
|
|
31137
|
-
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial,
|
|
33495
|
+
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, clamp5 = kit.clamp, smooth2 = kit.smooth;
|
|
31138
33496
|
const CLAD = CM("#1a1b1d", { roughness: 0.82 }), LINER = CM("#1c1d1f", { roughness: 0.92 }), RIMDARK = CM("#2a2d30", { metalness: 0.6, roughness: 0.4 });
|
|
31139
33497
|
const RIMSIL = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 }), RIMGUN = CM("#55595d", { metalness: 0.7, roughness: 0.35 });
|
|
31140
33498
|
function axles(WB, oF, L, dF, dR) {
|
|
@@ -31165,16 +33523,16 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31165
33523
|
const m = c.meta, W = m.width, by = o.bottom != null ? o.bottom : m.clearance, top = o.top != null ? o.top : m.belt;
|
|
31166
33524
|
const Wg = m.glasshouse && m.glasshouse.Wg || W, front = m.front, rear = m.rear;
|
|
31167
33525
|
const tuck = o.tuck == null ? 0.045 : o.tuck, peak = o.peak || 0.45;
|
|
31168
|
-
const B = o.sh != null ? o.sh :
|
|
33526
|
+
const B = o.sh != null ? o.sh : clamp5(1 - (Wg + 0.06) / W, 0, 0.06);
|
|
31169
33527
|
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;
|
|
31170
33528
|
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;
|
|
31171
33529
|
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);
|
|
31172
33530
|
function sc(y) {
|
|
31173
|
-
const t =
|
|
33531
|
+
const t = clamp5((y - by) / (top - by), 0, 1);
|
|
31174
33532
|
return t < peak ? 1 - tuck * Math.pow(1 - t / peak, 2) : 1 - B * Math.pow((t - peak) / (1 - peak), 2);
|
|
31175
33533
|
}
|
|
31176
33534
|
function F(v2) {
|
|
31177
|
-
const a = smooth2((v2.x - (front - Rf)) / Rf), b = smooth2((rear + Rr - v2.x) / Rr), zz =
|
|
33535
|
+
const a = smooth2((v2.x - (front - Rf)) / Rf), b = smooth2((rear + Rr - v2.x) / Rr), zz = clamp5(v2.z / hw, -1, 1) * clamp5(v2.z / hw, -1, 1);
|
|
31178
33536
|
const bf = smooth2((v2.x - (front - Rb)) / Rb), br = smooth2((rear + Rb - v2.x) / Rb);
|
|
31179
33537
|
const cr = crown * (1 - zz) * smooth2((v2.y - y0) / (top - y0)) * smooth2((v2.x - xc) / 0.3);
|
|
31180
33538
|
v2.x += -bowF * zz * bf + bowR * zz * br;
|
|
@@ -31251,7 +33609,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31251
33609
|
o = o || {};
|
|
31252
33610
|
const wd = o.w || 0.075, dp = o.d || 0.045, mat = o.mat || CLAD, out = [];
|
|
31253
33611
|
arches.forEach(function(a) {
|
|
31254
|
-
const a0 = Math.asin(
|
|
33612
|
+
const a0 = Math.asin(clamp5((by - a[1]) / a[2], -1, 1)) - 0.02, r0 = a[2] - (o.inner == null ? 0.055 : o.inner), r1 = a[2] + wd;
|
|
31255
33613
|
const sh = new THREE.Shape();
|
|
31256
33614
|
if (o.trap) {
|
|
31257
33615
|
const yb0 = Math.sin(a0) * r1, xb0 = Math.cos(a0) * r1, top = r1 * 0.93, xt = r1 * 0.52;
|
|
@@ -31600,7 +33958,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31600
33958
|
function roof(c, drop, x0, x1) {
|
|
31601
33959
|
const m = c.meta, b = m.glasshouse.belt, h = m.top - b;
|
|
31602
33960
|
function dy(x, y) {
|
|
31603
|
-
const t =
|
|
33961
|
+
const t = clamp5((y - b) / h, 0, 1);
|
|
31604
33962
|
return t <= 0 ? 0 : drop * smooth2((x0 - x) / (x0 - x1)) * t;
|
|
31605
33963
|
}
|
|
31606
33964
|
c.root.updateMatrixWorld(true);
|
|
@@ -32794,8 +35152,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
32794
35152
|
setOnHit(pt, function(c2, p0) {
|
|
32795
35153
|
const o = p0.obj;
|
|
32796
35154
|
if (o && o.userData.hung && o.parent && o.parent !== c2.vehicle) {
|
|
32797
|
-
o.parent.rotation.x =
|
|
32798
|
-
o.parent.rotation.y =
|
|
35155
|
+
o.parent.rotation.x = clamp5(o.parent.rotation.x, -0.22, 0.22);
|
|
35156
|
+
o.parent.rotation.y = clamp5(o.parent.rotation.y, -0.08, 0.08);
|
|
32799
35157
|
}
|
|
32800
35158
|
});
|
|
32801
35159
|
});
|
|
@@ -33401,7 +35759,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33401
35759
|
c2.cylinder(0.03, 0.04, 0.08, MT.chrome, sx + 2.35, fl + 0.99, -0.75);
|
|
33402
35760
|
c2.part("fuel_tank", trl("labels.oxygen_cylinder"), null, new V3(sx + 2.35, fl + 0.5, -0.75), 0.5);
|
|
33403
35761
|
c2.roundBox(0.04, 0.26, 0.34, 0.02, MT.screen, 0.1, 1.7, 0.5);
|
|
33404
|
-
const
|
|
35762
|
+
const lightPanel = c2.roundBox(
|
|
33405
35763
|
2.3,
|
|
33406
35764
|
0.02,
|
|
33407
35765
|
W - 0.3,
|
|
@@ -33411,7 +35769,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33411
35769
|
m.top - 0.06,
|
|
33412
35770
|
0
|
|
33413
35771
|
);
|
|
33414
|
-
|
|
35772
|
+
lightPanel.castShadow = false;
|
|
33415
35773
|
m.roofItems = beaconBar;
|
|
33416
35774
|
}
|
|
33417
35775
|
});
|
|
@@ -33446,7 +35804,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33446
35804
|
const shared2 = fx.shared;
|
|
33447
35805
|
const trl = fx.t;
|
|
33448
35806
|
const lbl = fx.label;
|
|
33449
|
-
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, _paintCol = kit.paintColor, G = kit.geo, DS = THREE.DoubleSide,
|
|
35807
|
+
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, _paintCol = kit.paintColor, G = kit.geo, DS = THREE.DoubleSide, clamp5 = kit.clamp;
|
|
33450
35808
|
function colorize(geo, fn) {
|
|
33451
35809
|
const p = geo.attributes.position, col = new Float32Array(p.count * 3);
|
|
33452
35810
|
for (let i = 0; i < p.count; i++) {
|
|
@@ -33462,7 +35820,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33462
35820
|
return a + (b - a) * t;
|
|
33463
35821
|
}
|
|
33464
35822
|
function sstep(a, b, x) {
|
|
33465
|
-
const t =
|
|
35823
|
+
const t = clamp5((x - a) / (b - a), 0, 1);
|
|
33466
35824
|
return t * t * (3 - 2 * t);
|
|
33467
35825
|
}
|
|
33468
35826
|
function tab(T) {
|
|
@@ -33539,7 +35897,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33539
35897
|
o,
|
|
33540
35898
|
sec,
|
|
33541
35899
|
t: function(x) {
|
|
33542
|
-
return
|
|
35900
|
+
return clamp5((x - o.x0) / o.L, 0, 1);
|
|
33543
35901
|
},
|
|
33544
35902
|
/* half beam of the hull at height y (inside, so floors and decks do not leak) */
|
|
33545
35903
|
half: function(x, y) {
|
|
@@ -33713,7 +36071,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33713
36071
|
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
33714
36072
|
const a = poly[i], b = poly[j];
|
|
33715
36073
|
if (a[1] > y !== b[1] > y && x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0]) inside = !inside;
|
|
33716
|
-
const vx = b[0] - a[0], vy = b[1] - a[1], wx = x - a[0], wy = y - a[1], t =
|
|
36074
|
+
const vx = b[0] - a[0], vy = b[1] - a[1], wx = x - a[0], wy = y - a[1], t = clamp5((wx * vx + wy * vy) / (vx * vx + vy * vy), 0, 1);
|
|
33717
36075
|
d = Math.min(d, Math.hypot(wx - vx * t, wy - vy * t));
|
|
33718
36076
|
}
|
|
33719
36077
|
return inside ? -d : d;
|
|
@@ -34759,7 +37117,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34759
37117
|
c.roundBox(2.4, 0.1, 2.5, 0.04, blue, hx, hy0 + hh + 0.05, 0);
|
|
34760
37118
|
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] });
|
|
34761
37119
|
void door;
|
|
34762
|
-
const
|
|
37120
|
+
const inner2 = CM("#b9a888", { roughness: 0.9 });
|
|
34763
37121
|
c.roundBox(0.4, 0.95, 1.9, 0.03, wood, hx + 0.75, hy0 + 0.48, 0);
|
|
34764
37122
|
c.roundBox(0.42, 0.03, 1.9, 0.01, CM("#2a2b2c"), hx + 0.75, hy0 + 0.97, 0);
|
|
34765
37123
|
[-0.45, 0.45].forEach(function(z) {
|
|
@@ -34778,10 +37136,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34778
37136
|
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 });
|
|
34779
37137
|
}
|
|
34780
37138
|
c.cylinder(0.05, 0.05, 0.14, MT.wood, 0.06, 0, 0, "x", { parent: whl });
|
|
34781
|
-
kit.seat(c, hx - 0.05, hy0, 0, { w: 0.5, h: 0.55, back: 0.45, mat:
|
|
37139
|
+
kit.seat(c, hx - 0.05, hy0, 0, { w: 0.5, h: 0.55, back: 0.45, mat: inner2, headrest: false });
|
|
34782
37140
|
c.roundBox(1.8, 0.12, 0.65, 0.04, CM("#7c5a3b"), hx - 0.2, hy0 + 0.5, -0.72);
|
|
34783
37141
|
c.roundBox(1.7, 0.08, 0.55, 0.04, CM("#6b7c8f", { roughness: 1 }), hx - 0.2, hy0 + 0.6, -0.72);
|
|
34784
|
-
c.roundBox(1.9, 0.02, 2.1, 0.01,
|
|
37142
|
+
c.roundBox(1.9, 0.02, 2.1, 0.01, inner2, hx, hy0 + hh - 0.06, 0).castShadow = false;
|
|
34785
37143
|
c.cylinder(0.1, 0.1, 1.2, MT.trim, hx - 0.75, hy0 + hh + 0.6, 0.65);
|
|
34786
37144
|
c.cylinder(0.13, 0.1, 0.1, MT.trim, hx - 0.75, hy0 + hh + 1.22, 0.65);
|
|
34787
37145
|
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);
|
|
@@ -36386,14 +38744,14 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36386
38744
|
var militaryIds = new Set(military);
|
|
36387
38745
|
|
|
36388
38746
|
// ../game-urban/dist/index.js
|
|
36389
|
-
function
|
|
36390
|
-
const
|
|
38747
|
+
function createLightAllowance(count) {
|
|
38748
|
+
const allowance = {
|
|
36391
38749
|
left: count,
|
|
36392
38750
|
take() {
|
|
36393
|
-
return
|
|
38751
|
+
return allowance.left-- > 0;
|
|
36394
38752
|
}
|
|
36395
38753
|
};
|
|
36396
|
-
return
|
|
38754
|
+
return allowance;
|
|
36397
38755
|
}
|
|
36398
38756
|
var ROOT2 = -1;
|
|
36399
38757
|
var DEFAULT_CYLINDER_SEGMENTS = 14;
|
|
@@ -36405,7 +38763,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36405
38763
|
const parts = [];
|
|
36406
38764
|
const items = [];
|
|
36407
38765
|
const windows = { lit: [], dark: [] };
|
|
36408
|
-
const lights = options.budget ??
|
|
38766
|
+
const lights = options.budget ?? createLightAllowance(Number.POSITIVE_INFINITY);
|
|
36409
38767
|
const pick = (list2) => list2[Math.floor(random() * list2.length)];
|
|
36410
38768
|
const group = (x, y, z, ry = 0, parent = ROOT2) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
|
|
36411
38769
|
const box2 = (x, y, z, width, height, depth, color2, o = {}) => {
|
|
@@ -36697,7 +39055,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36697
39055
|
var lerp2 = (a, b, t) => a + (b - a) * t;
|
|
36698
39056
|
function fillBlock(b, cx, cz, size, minHeight, maxHeight, options = {}) {
|
|
36699
39057
|
const placed = [];
|
|
36700
|
-
const
|
|
39058
|
+
const palette2 = options.palette ?? DEFAULT_FACADES;
|
|
36701
39059
|
const half = (size - STREET_MARGIN) / 2;
|
|
36702
39060
|
const k = options.k || DEFAULT_PARTITIONS;
|
|
36703
39061
|
const lot = (size - STREET_MARGIN) / k;
|
|
@@ -36723,7 +39081,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36723
39081
|
}
|
|
36724
39082
|
const x = cx - half + lot * (i + 0.5);
|
|
36725
39083
|
const z = cz - half + lot * (j + 0.5);
|
|
36726
|
-
const color2 = b.pick(
|
|
39084
|
+
const color2 = b.pick(palette2);
|
|
36727
39085
|
placed.push((options.draw ?? drawBuilding)(b, x, z, width, depth, height, color2, extra ? { ...options, ...extra } : options));
|
|
36728
39086
|
}
|
|
36729
39087
|
}
|
|
@@ -36751,16 +39109,16 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36751
39109
|
b.prism(x, 0, z, w, w * 0.7, d, color2 ?? colors(c).tent, -1, ry);
|
|
36752
39110
|
}
|
|
36753
39111
|
function drawPine(b, x, z, scale = 1, y = 0, c) {
|
|
36754
|
-
const
|
|
36755
|
-
b.cylinder(x, y, z, 0.12 * scale, 0.16 * scale, 1 * scale,
|
|
36756
|
-
const green = b.pick(
|
|
39112
|
+
const palette2 = colors(c);
|
|
39113
|
+
b.cylinder(x, y, z, 0.12 * scale, 0.16 * scale, 1 * scale, palette2.trunk);
|
|
39114
|
+
const green = b.pick(palette2.pines);
|
|
36757
39115
|
b.cone(x, y + 0.8 * scale, z, 1.1 * scale, 2.2 * scale, green);
|
|
36758
39116
|
b.cone(x, y + 2.1 * scale, z, 0.8 * scale, 1.8 * scale, green);
|
|
36759
39117
|
}
|
|
36760
39118
|
function drawTree(b, x, z, scale = 1, c) {
|
|
36761
|
-
const
|
|
36762
|
-
b.cylinder(x, 0, z, 0.12 * scale, 0.18 * scale, 1.8 * scale,
|
|
36763
|
-
b.crown(x, 2.6 * scale, z, 1.3 * scale, b.pick(
|
|
39119
|
+
const palette2 = colors(c);
|
|
39120
|
+
b.cylinder(x, 0, z, 0.12 * scale, 0.18 * scale, 1.8 * scale, palette2.trunk);
|
|
39121
|
+
b.crown(x, 2.6 * scale, z, 1.3 * scale, b.pick(palette2.crowns));
|
|
36764
39122
|
}
|
|
36765
39123
|
function drawContainer(b, x, y, z, ry, color2) {
|
|
36766
39124
|
const group = b.group(x, y, z, ry);
|
|
@@ -36768,11 +39126,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36768
39126
|
for (let k = -2; k <= 2; k++) b.box(k * 0.5, 0, 3.04, 0.06, 2.6, 0.04, 1710618, { parent: group });
|
|
36769
39127
|
}
|
|
36770
39128
|
function drawWatchtower(b, x, z, searchlight = false, c) {
|
|
36771
|
-
const
|
|
36772
|
-
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,
|
|
36773
|
-
b.box(x, 7, z, 3.2, 1.2, 3.2,
|
|
36774
|
-
b.box(x, 8.2, z, 3.6, 0.15, 3.6,
|
|
36775
|
-
for (const side of [-1.5, 1.5]) b.box(x + side, 8.2, z, 0.15, 1.6, 0.15,
|
|
39129
|
+
const palette2 = colors(c).watchtower;
|
|
39130
|
+
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);
|
|
39131
|
+
b.box(x, 7, z, 3.2, 1.2, 3.2, palette2.cabin);
|
|
39132
|
+
b.box(x, 8.2, z, 3.6, 0.15, 3.6, palette2.deck);
|
|
39133
|
+
for (const side of [-1.5, 1.5]) b.box(x + side, 8.2, z, 0.15, 1.6, 0.15, palette2.deck);
|
|
36776
39134
|
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 } });
|
|
36777
39135
|
}
|
|
36778
39136
|
function drawSandbags(b, x, z, length, along, rows = 3, c) {
|
|
@@ -37611,10 +39969,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37611
39969
|
};
|
|
37612
39970
|
return { maxFloors, kinds, plans, layout, stats: stats2, fillBlock: fillBlock2 };
|
|
37613
39971
|
}
|
|
37614
|
-
var
|
|
39972
|
+
var clamp4 = (v, a, b) => v < a ? a : v > b ? b : v;
|
|
37615
39973
|
var lerp22 = (a, b, t) => a + (b - a) * t;
|
|
37616
39974
|
var smoothstep = (a, b, x) => {
|
|
37617
|
-
const t =
|
|
39975
|
+
const t = clamp4((x - a) / (b - a), 0, 1);
|
|
37618
39976
|
return t * t * (3 - 2 * t);
|
|
37619
39977
|
};
|
|
37620
39978
|
var hash2 = (a, b) => {
|
|
@@ -37628,7 +39986,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37628
39986
|
const [bx, by] = points[index + 1];
|
|
37629
39987
|
const dx = bx - ax;
|
|
37630
39988
|
const dy = by - ay;
|
|
37631
|
-
const t =
|
|
39989
|
+
const t = clamp4(((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy), 0, 1);
|
|
37632
39990
|
const d = Math.hypot(px - ax - t * dx, py - ay - t * dy);
|
|
37633
39991
|
if (d < best) best = d;
|
|
37634
39992
|
}
|
|
@@ -37733,7 +40091,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37733
40091
|
const { water, world } = terrain.options;
|
|
37734
40092
|
b.plane(0, water.level, 0, world.size, world.size, water.color, { roughness: water.roughness, metalness: water.metalness });
|
|
37735
40093
|
}
|
|
37736
|
-
var
|
|
40094
|
+
var DEFAULT_LIGHT_ALLOWANCE = 16;
|
|
37737
40095
|
var DEFAULT_URBAN_SEED = 7;
|
|
37738
40096
|
function createUrban(options = {}) {
|
|
37739
40097
|
const entries = mergeById(DEFAULT_POLES, options.packs?.poles);
|
|
@@ -37744,7 +40102,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37744
40102
|
applyPatches(patterns, options.patch?.neighbourhoods);
|
|
37745
40103
|
removeIds(patterns, options.remove?.neighbourhoods);
|
|
37746
40104
|
const composers = { ...COMPOSERS, ...options.composers };
|
|
37747
|
-
const lightBudget = options.lights?.budget ??
|
|
40105
|
+
const lightBudget = options.lights?.budget ?? DEFAULT_LIGHT_ALLOWANCE;
|
|
37748
40106
|
const facades = options.facades ?? DEFAULT_FACADES;
|
|
37749
40107
|
const propColors = { ...DEFAULT_PROP_COLORS, ...options.props };
|
|
37750
40108
|
const towers = options.towers ? options.towers === true ? {} : options.towers : null;
|
|
@@ -37786,7 +40144,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37786
40144
|
wires: (b, points, y, color2) => drawWires(b, points, y, color2, propColors)
|
|
37787
40145
|
}
|
|
37788
40146
|
};
|
|
37789
|
-
const builder = (o = {}) => createUrbanBuilder(o.random ?? createSilhouetteRandom(o.seed ?? DEFAULT_URBAN_SEED), { budget:
|
|
40147
|
+
const builder = (o = {}) => createUrbanBuilder(o.random ?? createSilhouetteRandom(o.seed ?? DEFAULT_URBAN_SEED), { budget: createLightAllowance(o.budget ?? lightBudget) });
|
|
37790
40148
|
const neighbourhoods = {
|
|
37791
40149
|
ids: () => [...patterns.keys()],
|
|
37792
40150
|
get(id) {
|
|
@@ -37853,7 +40211,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37853
40211
|
return (request, b) => {
|
|
37854
40212
|
const context = contexts[request.kind];
|
|
37855
40213
|
const key = `${seed}:${request.kind}:${request.x}:${request.z}`;
|
|
37856
|
-
const id = options.pick ? options.pick(request, context, key) : pickForContext(region, context, key);
|
|
40214
|
+
const id = options.pick ? options.pick(request, context, key) : (options.pickSpecies ?? ((table, spot) => pickForContext(region, table, spot)))(context, key);
|
|
37857
40215
|
b.place("plant", id, request.x, request.z, { scale: request.scale, seed: key });
|
|
37858
40216
|
};
|
|
37859
40217
|
}
|
|
@@ -37998,7 +40356,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37998
40356
|
|
|
37999
40357
|
// src/test-area/scenarios.ts
|
|
38000
40358
|
var POLE_TOPS = { led_double: 9, concrete_arm: 8, cast_iron: 4.8, wood_crossarm: 7, flood_tower: 12.55 };
|
|
38001
|
-
var
|
|
40359
|
+
var STAND_IN_VEHICLE_IDS = ["stand_in_car_a", "stand_in_car_b", "stand_in_van"];
|
|
40360
|
+
var standInSpecies = (context, key) => `stand_in_${context}_${key.length % 3}`;
|
|
40361
|
+
var topOf2 = (parts) => Math.max(...parts.map((part) => "height" in part && "y" in part ? part.y + part.height / 2 : 0));
|
|
38002
40362
|
var placements = (parts, kind) => parts.filter((part) => part.shape === "placement" && part.kind === kind);
|
|
38003
40363
|
var round = (value, digits = 3) => Math.round(value * 10 ** digits) / 10 ** digits;
|
|
38004
40364
|
var createScenarios2 = (deps) => {
|
|
@@ -38014,7 +40374,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38014
40374
|
if (expected === void 0) continue;
|
|
38015
40375
|
const b = urban.builder();
|
|
38016
40376
|
urban.poles.get(id).build(b, { x: 0, z: 0 });
|
|
38017
|
-
const top =
|
|
40377
|
+
const top = topOf2(b.parts);
|
|
38018
40378
|
expect(Math.abs(top - expected) < 1e-6, say("pole_top", id, round(top)), { pole: id, top });
|
|
38019
40379
|
}
|
|
38020
40380
|
}),
|
|
@@ -38082,12 +40442,12 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38082
40442
|
scenario("urban_providers", ({ expect }) => {
|
|
38083
40443
|
const urban = kit();
|
|
38084
40444
|
const cars2 = urban.builder();
|
|
38085
|
-
const downtown2 = urban.neighbourhoods.get("downtown").compose(cars2, { x: 0, z: 0 }, { providers: { vehicle: createVehicleProvider() } });
|
|
40445
|
+
const downtown2 = urban.neighbourhoods.get("downtown").compose(cars2, { x: 0, z: 0 }, { providers: { vehicle: createVehicleProvider({ ids: STAND_IN_VEHICLE_IDS }) } });
|
|
38086
40446
|
const spots = placements(cars2.parts, "vehicle");
|
|
38087
40447
|
expect(spots.length === downtown2.vehicles.length && spots.length > 0, say("vehicles_placed", downtown2.vehicles.length, spots.length));
|
|
38088
|
-
expect(spots.every((spot) =>
|
|
40448
|
+
expect(spots.every((spot) => STAND_IN_VEHICLE_IDS.includes(spot.id)), say("vehicle_ids", [...new Set(spots.map((spot) => spot.id))].join(", ")));
|
|
38089
40449
|
const trees = urban.builder();
|
|
38090
|
-
const mountain = urban.neighbourhoods.get("mountain_resort").compose(trees, { x: 0, z: 0 }, { providers: { plant: createPlantProvider() } });
|
|
40450
|
+
const mountain = urban.neighbourhoods.get("mountain_resort").compose(trees, { x: 0, z: 0 }, { providers: { plant: createPlantProvider({ pickSpecies: standInSpecies }) } });
|
|
38091
40451
|
const planted = placements(trees.parts, "plant");
|
|
38092
40452
|
expect(planted.length === mountain.plants.length && planted.length > 0, say("plants_placed", mountain.plants.length, planted.length));
|
|
38093
40453
|
})
|
|
@@ -38124,6 +40484,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38124
40484
|
var VIEW_FAR = 600;
|
|
38125
40485
|
var GRID = { block: 36, count: 2, width: 10 };
|
|
38126
40486
|
var LAMP_SHARES = { pattern: void 0, all: 1, half: 0.5, none: 0 };
|
|
40487
|
+
var isMesh = (o) => o.isMesh === true;
|
|
38127
40488
|
function createUrbanTestModule(options = {}) {
|
|
38128
40489
|
const locale = (options.locale ?? "pt-BR").toLowerCase().startsWith("en") ? "en" : "pt-BR";
|
|
38129
40490
|
const text2 = TEXT[locale];
|
|
@@ -38150,21 +40511,22 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38150
40511
|
return createUrban({ ...urban, ...patch ? { patch } : {}, lights: { budget: overrides.budget ?? urban.lights?.budget } });
|
|
38151
40512
|
};
|
|
38152
40513
|
const resolversFor = (THREE) => {
|
|
38153
|
-
if (!resolvers.plant) resolvers.plant = createPlantResolver(THREE, { lod: 1 });
|
|
40514
|
+
if (!resolvers.plant) resolvers.plant = createPlantResolver(THREE, { lod: 1, ...options.plantSpec ? { toSpec: options.plantSpec } : {} });
|
|
38154
40515
|
if (!resolvers.vehicle && options.vehicles) resolvers.vehicle = createVehicleResolver(options.vehicles(THREE), { quality: "lite" });
|
|
38155
40516
|
return resolvers;
|
|
38156
40517
|
};
|
|
38157
40518
|
const providersOf = (choice) => {
|
|
38158
40519
|
const providers = {};
|
|
38159
|
-
if (choice === "plants" || choice === "both") providers.plant = createPlantProvider();
|
|
38160
|
-
if ((choice === "vehicles" || choice === "both") && options.vehicles) providers.vehicle = createVehicleProvider();
|
|
40520
|
+
if (choice === "plants" || choice === "both") providers.plant = options.providers?.plant ?? createPlantProvider();
|
|
40521
|
+
if ((choice === "vehicles" || choice === "both") && options.vehicles) providers.vehicle = options.providers?.vehicle ?? createVehicleProvider();
|
|
38161
40522
|
return providers;
|
|
38162
40523
|
};
|
|
40524
|
+
const lightsOption = () => isLightBudget(host?.lights) ? { lights: host.lights } : {};
|
|
38163
40525
|
const draw = (THREE, build, kit, buildSeed) => {
|
|
38164
40526
|
const b = kit.builder({ seed: buildSeed });
|
|
38165
40527
|
build(b, kit);
|
|
38166
40528
|
const document = options.document ?? globalThis.document;
|
|
38167
|
-
const drawn = createUrbanThree(THREE, { ...document ? { document } : {}, resolvers: resolversFor(THREE) }).draw(b.scene());
|
|
40529
|
+
const drawn = createUrbanThree(THREE, { ...document ? { document } : {}, resolvers: resolversFor(THREE), ...lightsOption() }).draw(b.scene());
|
|
38168
40530
|
return { root: drawn.root, drawn };
|
|
38169
40531
|
};
|
|
38170
40532
|
const sample = (THREE, key) => {
|
|
@@ -38173,8 +40535,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38173
40535
|
if (key === "street_grid") {
|
|
38174
40536
|
return draw(THREE, (b, k) => {
|
|
38175
40537
|
k.streets.grid(b, GRID);
|
|
38176
|
-
const
|
|
38177
|
-
k.block.fill(b, { cx: GRID.block / 2, cz: GRID.block / 2, size:
|
|
40538
|
+
const inner2 = GRID.block - GRID.width;
|
|
40539
|
+
k.block.fill(b, { cx: GRID.block / 2, cz: GRID.block / 2, size: inner2 - 4, minHeight: 10, maxHeight: 40, k: 3 });
|
|
38178
40540
|
}, kit, seed);
|
|
38179
40541
|
}
|
|
38180
40542
|
if (key === "terrain_patch") return draw(THREE, (b, k) => k.drawTerrain(b, k.terrain(terrain)), kit, seed);
|
|
@@ -38188,7 +40550,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38188
40550
|
movable: false,
|
|
38189
40551
|
build: (THREE) => {
|
|
38190
40552
|
three = THREE;
|
|
38191
|
-
const made = sample(
|
|
40553
|
+
const made = sample(three, id);
|
|
38192
40554
|
made.root.userData.urbanItem = id;
|
|
38193
40555
|
placed.set(made.root.uuid, made);
|
|
38194
40556
|
return made.root;
|
|
@@ -38219,7 +40581,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38219
40581
|
const b = kit.builder({ seed: settings.seed });
|
|
38220
40582
|
const made = kit.neighbourhoods.get(settings.pattern).compose(b, { x: 0, z: 0 }, { locale, providers: providersOf(settings.providers) });
|
|
38221
40583
|
const document = options.document ?? globalThis.document;
|
|
38222
|
-
const drawn = createUrbanThree(three, { ...document ? { document } : {}, resolvers: resolversFor(three) }).draw(b.scene());
|
|
40584
|
+
const drawn = createUrbanThree(three, { ...document ? { document } : {}, resolvers: resolversFor(three), ...lightsOption() }).draw(b.scene());
|
|
38223
40585
|
drawn.root.position.set(benchOrigin[0], FLOOR_Y, benchOrigin[1]);
|
|
38224
40586
|
host.scene.add(drawn.root);
|
|
38225
40587
|
bench = { root: drawn.root, drawn, plan: made };
|
|
@@ -38288,11 +40650,12 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38288
40650
|
mark(entry.root, new three.Box3Helper(box2, 6737151));
|
|
38289
40651
|
}
|
|
38290
40652
|
if (host.bench.layer("light_radius")) {
|
|
38291
|
-
for (const
|
|
38292
|
-
|
|
40653
|
+
for (const lamp of entry.drawn.lights) {
|
|
40654
|
+
const light = "bulb" in lamp ? { node: lamp.bulb, distance: lamp.part.distance, color: lamp.part.color } : lamp.isLight ? { node: lamp, distance: lamp.distance, color: lamp.color } : null;
|
|
40655
|
+
if (!light) continue;
|
|
38293
40656
|
const reach = new three.Mesh(new (shapeGeometries(three)).SphereGeometry(Math.min(light.distance || 10, 60), 14, 8), new three.MeshBasicMaterial({ color: light.color ?? 16764006, wireframe: true, transparent: true, opacity: 0.25 }));
|
|
38294
|
-
reach.position.copy(light.position);
|
|
38295
|
-
mark(light.parent ?? entry.root, reach);
|
|
40657
|
+
reach.position.copy(light.node.position);
|
|
40658
|
+
mark(light.node.parent ?? entry.root, reach);
|
|
38296
40659
|
}
|
|
38297
40660
|
}
|
|
38298
40661
|
}
|
|
@@ -38301,7 +40664,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38301
40664
|
const frame = (dt) => {
|
|
38302
40665
|
clock += dt;
|
|
38303
40666
|
for (const entry of roots()) entry.drawn.animate(clock);
|
|
38304
|
-
for (const [id, entry] of placed)
|
|
40667
|
+
for (const [id, entry] of placed) {
|
|
40668
|
+
if (entry.root.parent) continue;
|
|
40669
|
+
for (const lamp of entry.drawn.lights) if ("source" in lamp) lamp.source.remove();
|
|
40670
|
+
placed.delete(id);
|
|
40671
|
+
}
|
|
38305
40672
|
if (host?.bench && boxKey() !== helpersShown && (host.bench.layer("light_radius") || host.bench.layer("bounds") || helpersShown !== "")) refreshLayers();
|
|
38306
40673
|
};
|
|
38307
40674
|
const count = () => {
|
|
@@ -38312,14 +40679,16 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38312
40679
|
for (const entry of roots()) {
|
|
38313
40680
|
entry.root.traverse((o) => {
|
|
38314
40681
|
if (o.isLight) lights += 1;
|
|
38315
|
-
if (!o
|
|
40682
|
+
if (!isMesh(o) || o.userData.helper) return;
|
|
38316
40683
|
for (let p = o; p; p = p.parent) if (p.visible === false) return;
|
|
38317
40684
|
drawCalls += 1;
|
|
38318
|
-
|
|
40685
|
+
const instanced = o.isInstancedMesh === true;
|
|
40686
|
+
if (instanced) instances += o.count;
|
|
38319
40687
|
const g = o.geometry;
|
|
38320
40688
|
const perInstance = (g.index ? g.index.count : g.attributes.position.count) / 3;
|
|
38321
|
-
triangles += perInstance * (
|
|
40689
|
+
triangles += perInstance * (instanced ? o.count : 1);
|
|
38322
40690
|
});
|
|
40691
|
+
lights += entry.drawn.lights.filter((lamp) => "source" in lamp).length;
|
|
38323
40692
|
}
|
|
38324
40693
|
return { drawCalls, triangles: Math.round(triangles), lights, instances };
|
|
38325
40694
|
};
|
|
@@ -38392,6 +40761,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38392
40761
|
}
|
|
38393
40762
|
resolvers.plant?.dispose?.();
|
|
38394
40763
|
resolvers.vehicle?.dispose?.();
|
|
40764
|
+
for (const entry of placed.values()) for (const lamp of entry.drawn.lights) if ("source" in lamp) lamp.source.remove();
|
|
40765
|
+
placed.clear();
|
|
38395
40766
|
host = null;
|
|
38396
40767
|
};
|
|
38397
40768
|
}
|