@adatechnology/game-urban-three 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-6VTMXFGC.js +993 -0
- package/dist/chunk-6VTMXFGC.js.map +1 -0
- package/dist/index.d.ts +20 -130
- package/dist/index.global.js +2531 -169
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +15 -940
- package/dist/index.js.map +1 -1
- package/dist/testing-BzxRwx1k.d.ts +144 -0
- package/dist/testing.d.ts +5 -0
- package/dist/testing.js +19 -0
- package/dist/testing.js.map +1 -0
- package/package.json +12 -7
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-IFXQIDSA.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));
|
|
@@ -1803,6 +1894,27 @@ var GameUrbanThree = (() => {
|
|
|
1803
1894
|
disposables.push(mesh2.geometry, mesh2.material);
|
|
1804
1895
|
return mesh2;
|
|
1805
1896
|
};
|
|
1897
|
+
const forward = new THREE.Vector3(0, 0, -1);
|
|
1898
|
+
const aimAt = (node, from, to) => {
|
|
1899
|
+
node.quaternion.setFromUnitVectors(forward, new THREE.Vector3(to.x - from.x, to.y - from.y, to.z - from.z).normalize());
|
|
1900
|
+
};
|
|
1901
|
+
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 });
|
|
1902
|
+
const drawBudgetedLight = (part, animators, lights) => {
|
|
1903
|
+
const bulb = new THREE.Object3D();
|
|
1904
|
+
bulb.position.set(part.x, part.y, part.z);
|
|
1905
|
+
if (part.kind === "point") {
|
|
1906
|
+
const source2 = sourceOf(part, bulb, {});
|
|
1907
|
+
lights.push({ bulb, part, source: source2 });
|
|
1908
|
+
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) }));
|
|
1909
|
+
return [bulb];
|
|
1910
|
+
}
|
|
1911
|
+
aimAt(bulb, part, part.target);
|
|
1912
|
+
const source = sourceOf(part, bulb, { kind: "spot", angle: part.angle, penumbra: part.penumbra });
|
|
1913
|
+
lights.push({ bulb, part, source });
|
|
1914
|
+
const { sweep } = part;
|
|
1915
|
+
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 }));
|
|
1916
|
+
return [bulb];
|
|
1917
|
+
};
|
|
1806
1918
|
const drawPart = (part, animators, lights) => {
|
|
1807
1919
|
switch (part.shape) {
|
|
1808
1920
|
case "group": {
|
|
@@ -1916,6 +2028,7 @@ var GameUrbanThree = (() => {
|
|
|
1916
2028
|
return [holder];
|
|
1917
2029
|
}
|
|
1918
2030
|
case "light": {
|
|
2031
|
+
if (options.lights) return drawBudgetedLight(part, animators, lights);
|
|
1919
2032
|
if (part.kind === "point") {
|
|
1920
2033
|
const light2 = new THREE.PointLight(part.color, part.intensity, part.distance, part.decay);
|
|
1921
2034
|
light2.position.set(part.x, part.y, part.z);
|
|
@@ -1996,6 +2109,7 @@ var GameUrbanThree = (() => {
|
|
|
1996
2109
|
lights,
|
|
1997
2110
|
animate: (t) => animators.forEach((step) => step(t)),
|
|
1998
2111
|
dispose: () => {
|
|
2112
|
+
if (options.lights) for (const lamp of lights) lamp.source.remove();
|
|
1999
2113
|
root.parent?.remove(root);
|
|
2000
2114
|
for (const item of disposables.splice(0)) item.dispose?.();
|
|
2001
2115
|
for (const geometry of geometries.values()) geometry.dispose();
|
|
@@ -3149,8 +3263,8 @@ var GameUrbanThree = (() => {
|
|
|
3149
3263
|
const n = o.lod === 2 ? 4 : 6;
|
|
3150
3264
|
for (let i = 0; i < n; i++) {
|
|
3151
3265
|
const c = jitter(col(i), rng, 4e-3, 0.03, 0.02);
|
|
3152
|
-
const
|
|
3153
|
-
petal(b, f, { angle: i / n * TAU + (
|
|
3266
|
+
const inner2 = i % 2;
|
|
3267
|
+
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
3268
|
}
|
|
3155
3269
|
break;
|
|
3156
3270
|
}
|
|
@@ -3719,7 +3833,7 @@ var GameUrbanThree = (() => {
|
|
|
3719
3833
|
const bloom = spec.bloom;
|
|
3720
3834
|
const bloomOn = !!bloom && inSeason(ctx, bloom.seasons);
|
|
3721
3835
|
const replaces = bloomOn && !!bloom?.replacesLeaves;
|
|
3722
|
-
const
|
|
3836
|
+
const palette2 = (spec.leafColor ?? DEFAULT_LEAF).map(rgb);
|
|
3723
3837
|
const amount = leafAmount(ctx, deciduous && !replaces);
|
|
3724
3838
|
const baseKeep = spec.density ?? 1;
|
|
3725
3839
|
const keepThreshold = clamp(amount * baseKeep, 0, 1.3);
|
|
@@ -3743,7 +3857,7 @@ var GameUrbanThree = (() => {
|
|
|
3743
3857
|
const anchors = drawTreeWood(ctx, tree, (br, i) => drawBranch(ctx.wood, br, bark, ctx.lod, i < 0 ? 0 : i * 0.37));
|
|
3744
3858
|
ctx.piece("crown", "crown", "trunk");
|
|
3745
3859
|
const colorFor = (tip) => {
|
|
3746
|
-
const pc =
|
|
3860
|
+
const pc = palette2[Math.floor(tip.roll * palette2.length) % palette2.length];
|
|
3747
3861
|
let base = leafTint(ctx, pc, deciduous, tip.roll);
|
|
3748
3862
|
if (bloomOn && bloom) {
|
|
3749
3863
|
const bc = rgb(bloom.colors[Math.floor((tip.seed >>> 8) % 1e3 / 1e3 * bloom.colors.length)]);
|
|
@@ -3769,12 +3883,12 @@ var GameUrbanThree = (() => {
|
|
|
3769
3883
|
for (let bi = 0; bi < tree.branches.length; bi++) {
|
|
3770
3884
|
const br = tree.branches[bi];
|
|
3771
3885
|
if (br.level !== 1) continue;
|
|
3772
|
-
const
|
|
3886
|
+
const r22 = new Rng(Math.floor(br.radii[0] * 1e6) ^ bi * 7919);
|
|
3773
3887
|
const mid = br.pts[Math.floor(br.pts.length * 0.55)];
|
|
3774
|
-
const roll =
|
|
3888
|
+
const roll = r22.next();
|
|
3775
3889
|
if (roll > keepThreshold) continue;
|
|
3776
3890
|
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:
|
|
3891
|
+
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
3892
|
}
|
|
3779
3893
|
}
|
|
3780
3894
|
const crown = tree.crown;
|
|
@@ -3812,17 +3926,17 @@ var GameUrbanThree = (() => {
|
|
|
3812
3926
|
}
|
|
3813
3927
|
}
|
|
3814
3928
|
if (ctx.condition === "overgrown" && ctx.lod < 2) {
|
|
3815
|
-
const
|
|
3816
|
-
clump(ctx.foliage, { ...o, center: add(g.p, [
|
|
3929
|
+
const r22 = new Rng(t0.seed ^ 4660);
|
|
3930
|
+
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
3931
|
}
|
|
3818
3932
|
if ((shape === "weeping" || sparse) && ctx.lod < 2) {
|
|
3819
|
-
const
|
|
3933
|
+
const r22 = new Rng(t0.seed ^ 20817);
|
|
3820
3934
|
const n = ctx.lod === 0 ? sparse ? 7 : 10 : sparse ? 3 : 4;
|
|
3821
3935
|
for (let i = 0; i < n; i++) {
|
|
3822
|
-
const ang =
|
|
3823
|
-
const rr = g.r *
|
|
3936
|
+
const ang = r22.range(0, TAU);
|
|
3937
|
+
const rr = g.r * r22.range(0.3, 0.95);
|
|
3824
3938
|
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 *
|
|
3939
|
+
const hang = g.r * r22.range(1.4, 2.4) * (sparse ? 1.3 : 1);
|
|
3826
3940
|
const g0 = shade(base, 0.75);
|
|
3827
3941
|
const g1 = mix(base, [0.78, 0.82, 0.4], 0.35);
|
|
3828
3942
|
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 +3945,17 @@ var GameUrbanThree = (() => {
|
|
|
3831
3945
|
}
|
|
3832
3946
|
}
|
|
3833
3947
|
if (bloomOn && bloom && ctx.lod === 0) {
|
|
3834
|
-
const
|
|
3948
|
+
const r22 = new Rng(Math.floor(height * 1e3) + 77);
|
|
3835
3949
|
for (const a of groups) {
|
|
3836
3950
|
const n = replaces ? 6 : 5;
|
|
3837
3951
|
for (let i = 0; i < n; i++) {
|
|
3838
|
-
const ang =
|
|
3839
|
-
const y =
|
|
3952
|
+
const ang = r22.range(0, TAU);
|
|
3953
|
+
const y = r22.range(0.1, 1);
|
|
3840
3954
|
const rr = Math.sqrt(1 - y * y);
|
|
3841
3955
|
const d = [Math.cos(ang) * rr, y, Math.sin(ang) * rr];
|
|
3842
3956
|
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,
|
|
3957
|
+
const bc = rgb(r22.pick(bloom.colors));
|
|
3958
|
+
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
3959
|
}
|
|
3846
3960
|
}
|
|
3847
3961
|
}
|
|
@@ -3849,41 +3963,41 @@ var GameUrbanThree = (() => {
|
|
|
3849
3963
|
if (spec.fruit && ctx.condition !== "dry") {
|
|
3850
3964
|
const f = spec.fruit;
|
|
3851
3965
|
const count = Math.round(f.count * ctx.pick(1, 0.3, 0));
|
|
3852
|
-
const
|
|
3966
|
+
const r22 = new Rng(Math.floor(height * 733) + 5);
|
|
3853
3967
|
const fr = f.radius ?? 0.07;
|
|
3854
3968
|
if (f.onTrunk) {
|
|
3855
3969
|
const mains = tree.branches.filter((b2) => b2.level === 1);
|
|
3856
3970
|
for (let i = 0; i < count; i++) {
|
|
3857
|
-
const onBranch = mains.length > 0 &&
|
|
3858
|
-
const br = onBranch ? mains[
|
|
3971
|
+
const onBranch = mains.length > 0 && r22.next() > 0.45;
|
|
3972
|
+
const br = onBranch ? mains[r22.int(0, mains.length - 1)] : tree.trunk;
|
|
3859
3973
|
const tpts = br.pts;
|
|
3860
|
-
const u = onBranch ?
|
|
3974
|
+
const u = onBranch ? r22.range(0.05, 0.75) : r22.range(0.03, Math.min(0.7, trunkFraction * 1.05 / 0.93));
|
|
3861
3975
|
const idx = Math.min(tpts.length - 2, Math.floor(u * (tpts.length - 1)));
|
|
3862
3976
|
const k = u * (tpts.length - 1) - idx;
|
|
3863
3977
|
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
3978
|
const rad = lerp(br.radii[idx], br.radii[idx + 1], k);
|
|
3865
|
-
const ang =
|
|
3866
|
-
const c = rgb(
|
|
3867
|
-
const out = [Math.cos(ang),
|
|
3979
|
+
const ang = r22.range(0, TAU);
|
|
3980
|
+
const c = rgb(r22.pick(f.colors));
|
|
3981
|
+
const out = [Math.cos(ang), r22.range(-0.3, 0.5), Math.sin(ang)];
|
|
3868
3982
|
const center = add(pos, mul(norm(out), rad + fr * 0.4));
|
|
3869
|
-
const rr = fr *
|
|
3983
|
+
const rr = fr * r22.range(0.8, 1.2);
|
|
3870
3984
|
ctx.fruitItem(() => octa(ctx.fruit, center, [rr, rr, rr], (h) => shade(c, 0.75 + h * 0.6), 0));
|
|
3871
3985
|
}
|
|
3872
3986
|
} else {
|
|
3873
3987
|
for (let i = 0; i < count; i++) {
|
|
3874
3988
|
const a = groups[i % groups.length];
|
|
3875
|
-
const ang =
|
|
3876
|
-
const rr = a.r *
|
|
3989
|
+
const ang = r22.range(0, TAU);
|
|
3990
|
+
const rr = a.r * r22.range(0.45, 0.9);
|
|
3877
3991
|
const depth = Math.sqrt(Math.max(0, 1 - (rr / a.r) ** 2)) * 0.8;
|
|
3878
3992
|
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 *
|
|
3993
|
+
const c = rgb(r22.pick(f.colors));
|
|
3994
|
+
const s = fr * r22.range(0.9, 1.15);
|
|
3881
3995
|
const drop = s * 3.2;
|
|
3882
3996
|
const centre = [top[0], top[1] - drop - s * 0.6, top[2]];
|
|
3883
3997
|
ctx.fruitItem(() => {
|
|
3884
3998
|
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
3999
|
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:
|
|
4000
|
+
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
4001
|
});
|
|
3888
4002
|
}
|
|
3889
4003
|
}
|
|
@@ -5217,7 +5331,7 @@ var GameUrbanThree = (() => {
|
|
|
5217
5331
|
const R = (spec.radius ?? 0.6) * (over ? 1.2 : 1);
|
|
5218
5332
|
const H = (spec.height ?? (shape === "hedge" ? 1.1 : R * 1.5)) * (over ? 1.15 : 1);
|
|
5219
5333
|
const density = (spec.density ?? 1) * leafAmount(ctx, false);
|
|
5220
|
-
const
|
|
5334
|
+
const palette2 = (spec.leafColor ?? DEFAULT_LEAF2).map((c) => tintGreen(rgb(c), ctx.condition));
|
|
5221
5335
|
const stemC = rgb(spec.stemColor ?? 6966324);
|
|
5222
5336
|
const flowers = spec.flowers && !dead && !(ctx.season === "winter" && rng.fork("w").next() < 0.5);
|
|
5223
5337
|
const variegation = spec.variegation?.map(rgb);
|
|
@@ -5251,7 +5365,7 @@ var GameUrbanThree = (() => {
|
|
|
5251
5365
|
const sections = Math.max(1, Math.round(length / (width * 1.1)));
|
|
5252
5366
|
const secLen = length / sections;
|
|
5253
5367
|
const grid = ctx.lod === 0 ? [6, 4, 4] : ctx.lod === 1 ? [4, 3, 3] : [3, 2, 2];
|
|
5254
|
-
const base = dead ? mix(
|
|
5368
|
+
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
5369
|
for (let i = 0; i < sections; i++) {
|
|
5256
5370
|
const sr = rng.fork(i * 17 + 3);
|
|
5257
5371
|
const cx = -length / 2 + secLen * (i + 0.5);
|
|
@@ -5311,7 +5425,7 @@ var GameUrbanThree = (() => {
|
|
|
5311
5425
|
}
|
|
5312
5426
|
anchors.forEach((a, idx) => {
|
|
5313
5427
|
const crn = idx % 2 === 0;
|
|
5314
|
-
const base = variegation && crn ? variegation[idx % variegation.length] :
|
|
5428
|
+
const base = variegation && crn ? variegation[idx % variegation.length] : palette2[idx % palette2.length];
|
|
5315
5429
|
const o = {
|
|
5316
5430
|
center: a.p,
|
|
5317
5431
|
radius: a.r,
|
|
@@ -5584,29 +5698,29 @@ var GameUrbanThree = (() => {
|
|
|
5584
5698
|
const a1 = (i + 1) / sides * TAU;
|
|
5585
5699
|
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
5700
|
}
|
|
5587
|
-
const
|
|
5701
|
+
const inner2 = generate2(spec.plant, { seed: ctx.rng.fork("plant").seed(), lod: ctx.lod, condition: ctx.condition, season: ctx.season });
|
|
5588
5702
|
const yOff = soilY - 0.02;
|
|
5589
5703
|
const tagsOf = (part) => {
|
|
5590
|
-
const mesh2 =
|
|
5704
|
+
const mesh2 = inner2.parts[part];
|
|
5591
5705
|
if (!mesh2) return void 0;
|
|
5592
5706
|
const tags = new Array(mesh2.triangleCount).fill("");
|
|
5593
|
-
for (const pc of
|
|
5707
|
+
for (const pc of inner2.pieces) {
|
|
5594
5708
|
if (pc.part !== part) continue;
|
|
5595
5709
|
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
5710
|
for (let t = pc.indexStart / 3; t < (pc.indexStart + pc.indexCount) / 3; t++) tags[t] = pc.id;
|
|
5597
5711
|
}
|
|
5598
5712
|
return (t) => tags[t];
|
|
5599
5713
|
};
|
|
5600
|
-
if (
|
|
5601
|
-
if (
|
|
5602
|
-
if (
|
|
5603
|
-
if (
|
|
5604
|
-
if (
|
|
5714
|
+
if (inner2.parts.wood) ctx.wood.appendMesh(inner2.parts.wood, 0, yOff, 0, 0, 1, tagsOf("wood"));
|
|
5715
|
+
if (inner2.parts.spines) ctx.spines.appendMesh(inner2.parts.spines, 0, yOff, 0, 0, 1, tagsOf("spines"));
|
|
5716
|
+
if (inner2.parts.foliage) ctx.foliage.appendMesh(inner2.parts.foliage, 0, yOff, 0, 0, 1, tagsOf("foliage"));
|
|
5717
|
+
if (inner2.parts.flowers) ctx.flowers.appendMesh(inner2.parts.flowers, 0, yOff, 0, 0, 1, tagsOf("flowers"));
|
|
5718
|
+
if (inner2.parts.fruit) ctx.fruit.appendMesh(inner2.parts.fruit, 0, yOff, 0, 0, 1, tagsOf("fruit"));
|
|
5605
5719
|
return {
|
|
5606
5720
|
collider: { kind: "cylinder", radius: Math.max(R * 1.1, 0.1), height: h },
|
|
5607
|
-
bendable:
|
|
5608
|
-
thorny:
|
|
5609
|
-
foliageMaterial:
|
|
5721
|
+
bendable: inner2.bendable,
|
|
5722
|
+
thorny: inner2.thorny,
|
|
5723
|
+
foliageMaterial: inner2.parts.foliage?.material
|
|
5610
5724
|
};
|
|
5611
5725
|
}
|
|
5612
5726
|
var GENERATORS = {
|
|
@@ -8745,7 +8859,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
8745
8859
|
const key = `${part.id}|${part.seed}`;
|
|
8746
8860
|
if (!made.has(key)) {
|
|
8747
8861
|
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 });
|
|
8862
|
+
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
8863
|
made.set(key, createPlantObject(THREE, mesh2, materials));
|
|
8750
8864
|
} catch {
|
|
8751
8865
|
made.set(key, null);
|
|
@@ -8763,26 +8877,2182 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
8763
8877
|
}
|
|
8764
8878
|
|
|
8765
8879
|
// ../game-floorplan/dist/index.js
|
|
8766
|
-
var
|
|
8767
|
-
|
|
8768
|
-
|
|
8880
|
+
var createRegistries = () => ({
|
|
8881
|
+
CATALOG: {},
|
|
8882
|
+
LIBRARIES: {},
|
|
8883
|
+
LIBRARY_ALIAS: {},
|
|
8884
|
+
TEMPLATES: {},
|
|
8885
|
+
STYLES: {},
|
|
8886
|
+
STYLE_ALIAS: {},
|
|
8887
|
+
PALETTE_STYLES: {},
|
|
8888
|
+
ROOM_COLORS: [],
|
|
8889
|
+
PASSES: [],
|
|
8890
|
+
DECOR_PROFILES: {},
|
|
8891
|
+
GROUND_OVERLAYS: [],
|
|
8892
|
+
STATE_PLUGINS: { normal: { apply: () => [] } },
|
|
8893
|
+
WALL_TOP_HEIGHTS: { none: 0 },
|
|
8894
|
+
SEED_KEYS: { rooms: {}, items: {} },
|
|
8895
|
+
stateDistribution: { weights: null, seedKey: "state" },
|
|
8896
|
+
RESULT_EXTENSIONS: [],
|
|
8897
|
+
FINDS_PROVIDERS: [],
|
|
8898
|
+
VIEWER_LAYERS: [],
|
|
8899
|
+
LOCALES: {},
|
|
8900
|
+
ZONES: {},
|
|
8901
|
+
PERMEABLE_GROUNDS: /* @__PURE__ */ new Set()
|
|
8902
|
+
});
|
|
8903
|
+
var defaultRegistries;
|
|
8904
|
+
var activeRegistries;
|
|
8905
|
+
var getDefaultRegistries = () => defaultRegistries ?? (defaultRegistries = createRegistries());
|
|
8906
|
+
var registries = () => activeRegistries ?? getDefaultRegistries();
|
|
8907
|
+
function withRegistries(scope, run) {
|
|
8908
|
+
const previous = activeRegistries;
|
|
8909
|
+
activeRegistries = scope;
|
|
8910
|
+
try {
|
|
8911
|
+
return run();
|
|
8912
|
+
} finally {
|
|
8913
|
+
activeRegistries = previous;
|
|
8914
|
+
}
|
|
8915
|
+
}
|
|
8769
8916
|
function registerItem(def) {
|
|
8770
8917
|
const h = def.h;
|
|
8771
8918
|
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;
|
|
8919
|
+
registries().CATALOG[def.type] = item;
|
|
8773
8920
|
return item;
|
|
8774
8921
|
}
|
|
8775
8922
|
function registerItems(list2) {
|
|
8776
8923
|
for (const d of list2) registerItem(d);
|
|
8777
8924
|
}
|
|
8778
|
-
|
|
8925
|
+
function registerLibrary(id, library) {
|
|
8926
|
+
registries().LIBRARIES[id] = library;
|
|
8927
|
+
}
|
|
8928
|
+
function registerLibraryAlias(alias, id) {
|
|
8929
|
+
registries().LIBRARY_ALIAS[alias] = id;
|
|
8930
|
+
}
|
|
8931
|
+
function registerTemplate(t) {
|
|
8932
|
+
registries().TEMPLATES[t.id] = t;
|
|
8933
|
+
return t;
|
|
8934
|
+
}
|
|
8935
|
+
function registerStyle(style) {
|
|
8936
|
+
registries().STYLES[style.id] = style;
|
|
8937
|
+
for (const a of style.aliases || []) registries().STYLE_ALIAS[a] = style.id;
|
|
8938
|
+
}
|
|
8939
|
+
function registerPaletteStyle(id, def) {
|
|
8940
|
+
registries().PALETTE_STYLES[id] = def;
|
|
8941
|
+
}
|
|
8942
|
+
function registerRoomColors(list2) {
|
|
8943
|
+
for (const d of list2) {
|
|
8944
|
+
const i = registries().ROOM_COLORS.findIndex((x) => x.type === d.type);
|
|
8945
|
+
if (i >= 0) registries().ROOM_COLORS.splice(i, 1);
|
|
8946
|
+
registries().ROOM_COLORS.push(d);
|
|
8947
|
+
}
|
|
8948
|
+
registries().ROOM_COLORS.sort((a, b) => a.order - b.order);
|
|
8949
|
+
}
|
|
8950
|
+
function registerDecorProfile(styleId, profile) {
|
|
8951
|
+
registries().DECOR_PROFILES[styleId] = profile;
|
|
8952
|
+
}
|
|
8953
|
+
function registerPass(name, run) {
|
|
8954
|
+
const i = registries().PASSES.findIndex((p) => p.name === name);
|
|
8955
|
+
if (i >= 0) registries().PASSES[i] = { name, run };
|
|
8956
|
+
else registries().PASSES.push({ name, run });
|
|
8957
|
+
}
|
|
8958
|
+
function registerGroundOverlay(rule) {
|
|
8959
|
+
registries().GROUND_OVERLAYS.push(rule);
|
|
8960
|
+
}
|
|
8961
|
+
function registerWallTop(id, height) {
|
|
8962
|
+
registries().WALL_TOP_HEIGHTS[id] = height;
|
|
8963
|
+
}
|
|
8964
|
+
function registerSeedKeys(keys) {
|
|
8965
|
+
Object.assign(registries().SEED_KEYS.rooms, keys.rooms);
|
|
8966
|
+
Object.assign(registries().SEED_KEYS.items, keys.items);
|
|
8967
|
+
}
|
|
8968
|
+
function registerState(name, plugin) {
|
|
8969
|
+
registries().STATE_PLUGINS[name] = plugin;
|
|
8970
|
+
}
|
|
8971
|
+
function setStateDistribution(weights, seedKey = "state") {
|
|
8972
|
+
registries().stateDistribution.weights = weights;
|
|
8973
|
+
registries().stateDistribution.seedKey = seedKey;
|
|
8974
|
+
}
|
|
8975
|
+
var listStates = () => Object.keys(registries().STATE_PLUGINS);
|
|
8976
|
+
function resolveStyleId(id) {
|
|
8977
|
+
const s = id ?? "";
|
|
8978
|
+
return registries().STYLE_ALIAS[s] ?? s;
|
|
8979
|
+
}
|
|
8980
|
+
function styleOf(id) {
|
|
8981
|
+
return registries().STYLES[id] || registries().STYLES.budget || Object.values(registries().STYLES)[0];
|
|
8982
|
+
}
|
|
8983
|
+
function registerResultExtension(name, run, options = {}) {
|
|
8984
|
+
const entry = { name, run, after: options.after || [], seedKey: options.seedKey ?? name };
|
|
8985
|
+
const i = registries().RESULT_EXTENSIONS.findIndex((x) => x.name === name);
|
|
8986
|
+
if (i >= 0) registries().RESULT_EXTENSIONS[i] = entry;
|
|
8987
|
+
else registries().RESULT_EXTENSIONS.push(entry);
|
|
8988
|
+
}
|
|
8989
|
+
function registerFindsProvider(provider) {
|
|
8990
|
+
registries().FINDS_PROVIDERS.push(provider);
|
|
8991
|
+
}
|
|
8992
|
+
function registerViewerLayer(layer) {
|
|
8993
|
+
const i = registries().VIEWER_LAYERS.findIndex((x) => x.id === layer.id);
|
|
8994
|
+
if (i >= 0) registries().VIEWER_LAYERS[i] = layer;
|
|
8995
|
+
else registries().VIEWER_LAYERS.push(layer);
|
|
8996
|
+
}
|
|
8997
|
+
function mulberry322(a) {
|
|
8998
|
+
return function() {
|
|
8999
|
+
a |= 0;
|
|
9000
|
+
a = a + 1831565813 | 0;
|
|
9001
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
9002
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
9003
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
9004
|
+
};
|
|
9005
|
+
}
|
|
9006
|
+
function hashString2(input) {
|
|
9007
|
+
const s = String(input);
|
|
9008
|
+
let h = 1779033703 ^ s.length;
|
|
9009
|
+
for (let i = 0; i < s.length; i++) {
|
|
9010
|
+
h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
|
|
9011
|
+
h = h << 13 | h >>> 19;
|
|
9012
|
+
}
|
|
9013
|
+
h = Math.imul(h ^ h >>> 16, 2246822507);
|
|
9014
|
+
h = Math.imul(h ^ h >>> 13, 3266489909);
|
|
9015
|
+
return (h ^ h >>> 16) >>> 0;
|
|
9016
|
+
}
|
|
9017
|
+
var createRng2 = (seed, key) => mulberry322(hashString2(seed + "|" + key));
|
|
9018
|
+
function pickWeighted(rng, weights) {
|
|
9019
|
+
const keys = Object.keys(weights);
|
|
9020
|
+
let total = 0;
|
|
9021
|
+
for (const k of keys) total += weights[k];
|
|
9022
|
+
let x = rng() * total;
|
|
9023
|
+
for (const k of keys) {
|
|
9024
|
+
x -= weights[k];
|
|
9025
|
+
if (x < 0) return k;
|
|
9026
|
+
}
|
|
9027
|
+
return keys[keys.length - 1];
|
|
9028
|
+
}
|
|
9029
|
+
function weightedOrder(candidates, rng) {
|
|
9030
|
+
const arr = candidates.slice();
|
|
9031
|
+
const out = [];
|
|
9032
|
+
while (arr.length) {
|
|
9033
|
+
let total = 0;
|
|
9034
|
+
for (const c of arr) total += c.weight;
|
|
9035
|
+
let x = rng() * total;
|
|
9036
|
+
let i = 0;
|
|
9037
|
+
for (; i < arr.length - 1; i++) {
|
|
9038
|
+
x -= arr[i].weight;
|
|
9039
|
+
if (x < 0) break;
|
|
9040
|
+
}
|
|
9041
|
+
out.push(arr.splice(i, 1)[0]);
|
|
9042
|
+
}
|
|
9043
|
+
return out;
|
|
9044
|
+
}
|
|
9045
|
+
function shuffle(arr, rng) {
|
|
9046
|
+
const a = arr.slice();
|
|
9047
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
9048
|
+
const j = Math.floor(rng() * (i + 1));
|
|
9049
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
9050
|
+
}
|
|
9051
|
+
return a;
|
|
9052
|
+
}
|
|
9053
|
+
function orderOf(extensions) {
|
|
9054
|
+
const ordered = [];
|
|
9055
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
9056
|
+
const visit = (extension) => {
|
|
9057
|
+
if (ordered.includes(extension)) return;
|
|
9058
|
+
if (visiting.has(extension.name)) throw new Error("Result extensions depend on each other: " + extension.name);
|
|
9059
|
+
visiting.add(extension.name);
|
|
9060
|
+
for (const name of extension.after) {
|
|
9061
|
+
const before = extensions.find((x) => x.name === name);
|
|
9062
|
+
if (before) visit(before);
|
|
9063
|
+
}
|
|
9064
|
+
visiting.delete(extension.name);
|
|
9065
|
+
ordered.push(extension);
|
|
9066
|
+
};
|
|
9067
|
+
extensions.forEach(visit);
|
|
9068
|
+
return ordered;
|
|
9069
|
+
}
|
|
9070
|
+
function runResultExtensions(plan, result, seed, options) {
|
|
9071
|
+
for (const extension of orderOf(registries().RESULT_EXTENSIONS)) {
|
|
9072
|
+
result[extension.name] = extension.run({ plan, result, seed, rng: createRng2(seed, extension.seedKey), options });
|
|
9073
|
+
}
|
|
9074
|
+
}
|
|
9075
|
+
function findsFor(query) {
|
|
9076
|
+
return answerOf(query) ?? [];
|
|
9077
|
+
}
|
|
9078
|
+
function findsAnswer(query) {
|
|
9079
|
+
return answerOf(query);
|
|
9080
|
+
}
|
|
9081
|
+
function answerOf(query) {
|
|
9082
|
+
for (const provider of registries().FINDS_PROVIDERS) {
|
|
9083
|
+
const stacks = provider.findsFor(query);
|
|
9084
|
+
if (stacks !== null) return stacks;
|
|
9085
|
+
}
|
|
9086
|
+
return null;
|
|
9087
|
+
}
|
|
9088
|
+
var WALL_THICKNESS = 0.15;
|
|
9089
|
+
var MARGIN = WALL_THICKNESS / 2;
|
|
9090
|
+
var EPS = 1e-6;
|
|
9091
|
+
var TOL = 1e-3;
|
|
9092
|
+
var DOOR_CLEARANCE = 0.9;
|
|
9093
|
+
var PLAYER_RADIUS = 0.24;
|
|
9094
|
+
var DOOR_EDGE_CLEARANCE = 0.12;
|
|
9095
|
+
var WINDOW_EDGE_CLEARANCE = 0.3;
|
|
9096
|
+
var OVERHEAD_TOP = 2.15;
|
|
9097
|
+
var WINDOWLESS_ROOM_TYPES = /* @__PURE__ */ new Set([
|
|
9098
|
+
"stockroom",
|
|
9099
|
+
"kitchen",
|
|
9100
|
+
"laundry",
|
|
9101
|
+
"bathroom",
|
|
9102
|
+
"void",
|
|
9103
|
+
"elevator",
|
|
9104
|
+
"stairs",
|
|
9105
|
+
"pantry",
|
|
9106
|
+
"closet",
|
|
9107
|
+
"corridor",
|
|
9108
|
+
"hall",
|
|
9109
|
+
"balcony",
|
|
9110
|
+
"terrace",
|
|
9111
|
+
"garage",
|
|
9112
|
+
"storage",
|
|
9113
|
+
"porch",
|
|
9114
|
+
"tent",
|
|
9115
|
+
"outhouse"
|
|
9116
|
+
]);
|
|
9117
|
+
var CAR_CLEARANCE = { driver: 0.8, side: 0.6, end: 0.6 };
|
|
9118
|
+
var r2 = (x) => Math.round(x * 100) / 100;
|
|
9119
|
+
var clamp3 = (x, a, b) => Math.max(a, Math.min(b, x));
|
|
9120
|
+
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;
|
|
9121
|
+
var inner = (p) => [p[0] + MARGIN, p[1] + MARGIN, p[2] - MARGIN, p[3] - MARGIN];
|
|
9122
|
+
var eq = (a, b) => Math.abs(a - b) < TOL;
|
|
9123
|
+
var inflate2 = (r, e) => [r[0] - e, r[1] - e, r[2] + e, r[3] + e];
|
|
9124
|
+
var areaOf = (r) => (r[2] - r[0]) * (r[3] - r[1]);
|
|
9125
|
+
var topOf = (m) => (m.y || 0) + m.h;
|
|
9126
|
+
var extent = (size, f) => Math.abs(f[1]) === 1 ? [size[0], size[1]] : [size[1], size[0]];
|
|
9127
|
+
var cardinal = (dx, dz) => Math.abs(dx) > Math.abs(dz) ? [Math.sign(dx), 0] : [0, Math.sign(dz) || 1];
|
|
9128
|
+
function clearRect(r, f, clearance) {
|
|
9129
|
+
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]];
|
|
9130
|
+
}
|
|
9131
|
+
function insideUnion(r, inners) {
|
|
9132
|
+
const e = 1e-4;
|
|
9133
|
+
const points = [
|
|
9134
|
+
[r[0] + e, r[1] + e],
|
|
9135
|
+
[r[2] - e, r[1] + e],
|
|
9136
|
+
[r[0] + e, r[3] - e],
|
|
9137
|
+
[r[2] - e, r[3] - e],
|
|
9138
|
+
[(r[0] + r[2]) / 2, (r[1] + r[3]) / 2]
|
|
9139
|
+
];
|
|
9140
|
+
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));
|
|
9141
|
+
}
|
|
9142
|
+
function cutIntervals(intervals, a, b) {
|
|
9143
|
+
const out = [];
|
|
9144
|
+
for (const [x, y] of intervals) {
|
|
9145
|
+
if (b <= x || a >= y) {
|
|
9146
|
+
out.push([x, y]);
|
|
9147
|
+
continue;
|
|
9148
|
+
}
|
|
9149
|
+
if (a > x) out.push([x, a]);
|
|
9150
|
+
if (b < y) out.push([b, y]);
|
|
9151
|
+
}
|
|
9152
|
+
return out;
|
|
9153
|
+
}
|
|
9154
|
+
function exteriorEdge(room, side, w, d, c) {
|
|
9155
|
+
const cand = [];
|
|
9156
|
+
for (const p of room.parts) {
|
|
9157
|
+
if (side === "front" && eq(p[1], 0)) cand.push({ axis: "u", pos: 0, a0: p[0], a1: p[2] });
|
|
9158
|
+
else if (side === "back" && eq(p[3], d)) cand.push({ axis: "u", pos: d, a0: p[0], a1: p[2] });
|
|
9159
|
+
else if (side === "left" && eq(p[0], 0)) cand.push({ axis: "v", pos: 0, a0: p[1], a1: p[3] });
|
|
9160
|
+
else if (side === "right" && eq(p[2], w)) cand.push({ axis: "v", pos: w, a0: p[1], a1: p[3] });
|
|
9161
|
+
}
|
|
9162
|
+
if (!cand.length) return null;
|
|
9163
|
+
if (c != null) {
|
|
9164
|
+
const k = cand.find((x) => c >= x.a0 - TOL && c <= x.a1 + TOL);
|
|
9165
|
+
if (k) return k;
|
|
9166
|
+
}
|
|
9167
|
+
return cand.sort((x, y) => y.a1 - y.a0 - (x.a1 - x.a0))[0];
|
|
9168
|
+
}
|
|
9169
|
+
function sharedEdge(A, B, c) {
|
|
9170
|
+
const cand = [];
|
|
9171
|
+
for (const pa of A.parts)
|
|
9172
|
+
for (const pb of B.parts) {
|
|
9173
|
+
let o;
|
|
9174
|
+
if (eq(pa[2], pb[0]) || eq(pa[0], pb[2])) {
|
|
9175
|
+
o = [Math.max(pa[1], pb[1]), Math.min(pa[3], pb[3])];
|
|
9176
|
+
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] });
|
|
9177
|
+
}
|
|
9178
|
+
if (eq(pa[3], pb[1]) || eq(pa[1], pb[3])) {
|
|
9179
|
+
o = [Math.max(pa[0], pb[0]), Math.min(pa[2], pb[2])];
|
|
9180
|
+
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] });
|
|
9181
|
+
}
|
|
9182
|
+
}
|
|
9183
|
+
if (!cand.length) return null;
|
|
9184
|
+
if (c != null) {
|
|
9185
|
+
const k = cand.find((x) => c >= x.a0 - TOL && c <= x.a1 + TOL);
|
|
9186
|
+
if (k) return k;
|
|
9187
|
+
}
|
|
9188
|
+
return cand.sort((x, y) => y.a1 - y.a0 - (x.a1 - x.a0))[0];
|
|
9189
|
+
}
|
|
9190
|
+
function openingZones(u, v, axis, width, depth) {
|
|
9191
|
+
const h = width / 2;
|
|
9192
|
+
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]];
|
|
9193
|
+
}
|
|
9194
|
+
var openingDepth = (width, leaf2) => Math.max(0.9, leaf2) + MARGIN;
|
|
9195
|
+
function entryPoints(ab) {
|
|
9196
|
+
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]];
|
|
9197
|
+
}
|
|
9198
|
+
function normalizeRoom(c, floorIndex) {
|
|
9199
|
+
const parts = [c.rect].concat(c.parts || []).map((p) => p.map(r2));
|
|
9200
|
+
return {
|
|
9201
|
+
id: c.id,
|
|
9202
|
+
type: c.type,
|
|
9203
|
+
label: c.label,
|
|
9204
|
+
floor: floorIndex,
|
|
9205
|
+
parts,
|
|
9206
|
+
library: c.library || null,
|
|
9207
|
+
without: c.without || [],
|
|
9208
|
+
overrides: c.overrides || {},
|
|
9209
|
+
extras: c.extras || [],
|
|
9210
|
+
slots: c.slots || null,
|
|
9211
|
+
link: c.link || null,
|
|
9212
|
+
outdoor: !!c.outdoor,
|
|
9213
|
+
ceiling: c.ceiling || null,
|
|
9214
|
+
seedKey: c.seedKey
|
|
9215
|
+
};
|
|
9216
|
+
}
|
|
9217
|
+
function usableParts(room, extra) {
|
|
9218
|
+
const m = MARGIN + (extra || 0);
|
|
9219
|
+
const out = [];
|
|
9220
|
+
for (const p of room.parts) {
|
|
9221
|
+
out.push([p[0] + m, p[1] + m, p[2] - m, p[3] - m]);
|
|
9222
|
+
for (let k = 0; k < 4; k++) {
|
|
9223
|
+
const spans = [];
|
|
9224
|
+
for (const q of room.parts) {
|
|
9225
|
+
if (q === p) continue;
|
|
9226
|
+
if (k === 0 && eq(q[2], p[0])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
9227
|
+
else if (k === 2 && eq(q[0], p[2])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
|
|
9228
|
+
else if (k === 1 && eq(q[3], p[1])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
9229
|
+
else if (k === 3 && eq(q[1], p[3])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
|
|
9230
|
+
}
|
|
9231
|
+
spans.sort((x, y) => x[0] - y[0]);
|
|
9232
|
+
const merged = [];
|
|
9233
|
+
for (const span of spans) {
|
|
9234
|
+
const last = merged[merged.length - 1];
|
|
9235
|
+
if (last && span[0] <= last[1] + TOL) last[1] = Math.max(last[1], span[1]);
|
|
9236
|
+
else merged.push([span[0], span[1]]);
|
|
9237
|
+
}
|
|
9238
|
+
for (const [a, b] of merged) {
|
|
9239
|
+
if (b - a <= 0.3) continue;
|
|
9240
|
+
const x0 = a + m;
|
|
9241
|
+
const x1 = b - m;
|
|
9242
|
+
if (x1 <= x0) continue;
|
|
9243
|
+
if (k === 0) out.push([p[0], x0, p[0] + m, x1]);
|
|
9244
|
+
else if (k === 2) out.push([p[2] - m, x0, p[2], x1]);
|
|
9245
|
+
else if (k === 1) out.push([x0, p[1], x1, p[1] + m]);
|
|
9246
|
+
else out.push([x0, p[3] - m, x1, p[3]]);
|
|
9247
|
+
}
|
|
9248
|
+
}
|
|
9249
|
+
}
|
|
9250
|
+
return out;
|
|
9251
|
+
}
|
|
9252
|
+
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);
|
|
9253
|
+
function buildWalls(rooms, doors, openPairs, floorIndex) {
|
|
9254
|
+
const groups = {};
|
|
9255
|
+
const add2 = (axis, pos, a, b, id, side) => {
|
|
9256
|
+
const k = axis + "|" + pos.toFixed(2);
|
|
9257
|
+
(groups[k] = groups[k] || { axis, pos, es: [] }).es.push({ a, b, id, side });
|
|
9258
|
+
};
|
|
9259
|
+
for (const c of rooms)
|
|
9260
|
+
for (const p of c.parts) {
|
|
9261
|
+
add2("u", p[1], p[0], p[2], c.id, 1);
|
|
9262
|
+
add2("u", p[3], p[0], p[2], c.id, -1);
|
|
9263
|
+
add2("v", p[0], p[1], p[3], c.id, 1);
|
|
9264
|
+
add2("v", p[2], p[1], p[3], c.id, -1);
|
|
9265
|
+
}
|
|
9266
|
+
const par = (x, y) => openPairs.some((o) => o[0] === x && o[1] === y || o[0] === y && o[1] === x);
|
|
9267
|
+
const typeOf = {};
|
|
9268
|
+
for (const c of rooms) typeOf[c.id] = c.type;
|
|
9269
|
+
const out = [];
|
|
9270
|
+
for (const g of Object.values(groups)) {
|
|
9271
|
+
const breaks = [...new Set(g.es.flatMap((e) => [r2(e.a), r2(e.b)]))].sort((x, y) => x - y);
|
|
9272
|
+
const segs = [];
|
|
9273
|
+
for (let i = 0; i < breaks.length - 1; i++) {
|
|
9274
|
+
const p = breaks[i];
|
|
9275
|
+
const q = breaks[i + 1];
|
|
9276
|
+
const m = (p + q) / 2;
|
|
9277
|
+
const after = g.es.find((e) => e.side === 1 && m > e.a && m < e.b);
|
|
9278
|
+
const before = g.es.find((e) => e.side === -1 && m > e.a && m < e.b);
|
|
9279
|
+
if (!after && !before) continue;
|
|
9280
|
+
const A = before ? before.id : "outside";
|
|
9281
|
+
const B = after ? after.id : "outside";
|
|
9282
|
+
if (A === B || A !== "outside" && B !== "outside" && par(A, B)) continue;
|
|
9283
|
+
const last = segs[segs.length - 1];
|
|
9284
|
+
if (last && last.A === A && last.B === B && eq(last.q, p)) last.q = q;
|
|
9285
|
+
else segs.push({ A, B, p, q });
|
|
9286
|
+
}
|
|
9287
|
+
for (const s of segs) {
|
|
9288
|
+
let pieces = [[s.p, s.q]];
|
|
9289
|
+
for (const pt of doors) {
|
|
9290
|
+
if (pt.axis !== g.axis || !eq(pt.pos, g.pos)) continue;
|
|
9291
|
+
const c0 = pt.at - pt.width / 2;
|
|
9292
|
+
const c1 = pt.at + pt.width / 2;
|
|
9293
|
+
const n = [];
|
|
9294
|
+
for (const [x, y] of pieces) {
|
|
9295
|
+
if (c1 <= x + TOL || c0 >= y - TOL) {
|
|
9296
|
+
n.push([x, y]);
|
|
9297
|
+
continue;
|
|
9298
|
+
}
|
|
9299
|
+
if (c0 > x + TOL) n.push([x, c0]);
|
|
9300
|
+
if (c1 < y - TOL) n.push([c1, y]);
|
|
9301
|
+
}
|
|
9302
|
+
pieces = n;
|
|
9303
|
+
}
|
|
9304
|
+
for (const [x, y] of pieces) {
|
|
9305
|
+
const isOpen = (t) => t === "balcony" || t === "terrace" || t === "porch";
|
|
9306
|
+
const isVoid = typeOf[s.A] === "void" || typeOf[s.B] === "void";
|
|
9307
|
+
if (isVoid && (s.A === "outside" || s.B === "outside")) continue;
|
|
9308
|
+
const railing = isVoid || isOpen(typeOf[s.A]) && s.B === "outside" || isOpen(typeOf[s.B]) && s.A === "outside";
|
|
9309
|
+
out.push({
|
|
9310
|
+
floor: floorIndex,
|
|
9311
|
+
axis: g.axis,
|
|
9312
|
+
u0: g.axis === "u" ? x : g.pos,
|
|
9313
|
+
v0: g.axis === "u" ? g.pos : x,
|
|
9314
|
+
u1: g.axis === "u" ? y : g.pos,
|
|
9315
|
+
v1: g.axis === "u" ? g.pos : y,
|
|
9316
|
+
thickness: WALL_THICKNESS,
|
|
9317
|
+
sides: [s.A, s.B],
|
|
9318
|
+
exterior: s.A === "outside" || s.B === "outside",
|
|
9319
|
+
type: railing ? "railing" : "wall",
|
|
9320
|
+
h: railing ? 1.1 : 0
|
|
9321
|
+
});
|
|
9322
|
+
}
|
|
9323
|
+
}
|
|
9324
|
+
}
|
|
9325
|
+
return out;
|
|
9326
|
+
}
|
|
9327
|
+
var STEP = 0.05;
|
|
9328
|
+
var MAX_CANDIDATES = 80;
|
|
9329
|
+
function carCandidates(slot, item, car) {
|
|
9330
|
+
const front = [slot.frontU, slot.frontV];
|
|
9331
|
+
const zone = slot._cell.R;
|
|
9332
|
+
const alongV = Math.abs(front[1]) === 1;
|
|
9333
|
+
const [extentU, extentV] = extent([item.w, item.d], front);
|
|
9334
|
+
const across = alongV ? extentU : extentV;
|
|
9335
|
+
const along = alongV ? extentV : extentU;
|
|
9336
|
+
const a = alongV ? 0 : 1;
|
|
9337
|
+
const b = alongV ? 1 : 0;
|
|
9338
|
+
const driverSign = alongV ? front[1] : -front[0];
|
|
9339
|
+
const frontSign = alongV ? front[1] : front[0];
|
|
9340
|
+
const lowClear = driverSign < 0 ? car.driver : car.side;
|
|
9341
|
+
const highClear = driverSign < 0 ? car.side : car.driver;
|
|
9342
|
+
const lo = zone[a] + lowClear;
|
|
9343
|
+
const hi = zone[a + 2] - highClear - across;
|
|
9344
|
+
const alongLo = zone[b] + car.end;
|
|
9345
|
+
const alongHi = zone[b + 2] - car.end - along;
|
|
9346
|
+
if (hi < lo - 1e-6 || alongHi < alongLo - 1e-6) return [];
|
|
9347
|
+
const preferred = (alongV ? slot.x : slot.z) - across / 2;
|
|
9348
|
+
const alongStart = frontSign < 0 ? alongHi : alongLo;
|
|
9349
|
+
const positions = [];
|
|
9350
|
+
for (let p = lo; p <= hi + 1e-6; p += STEP) positions.push(Math.min(p, hi));
|
|
9351
|
+
positions.sort((x, y) => Math.abs(x - preferred) - Math.abs(y - preferred));
|
|
9352
|
+
return positions.slice(0, MAX_CANDIDATES).map((p) => {
|
|
9353
|
+
const rect = alongV ? [p, alongStart, p + across, alongStart + along] : [alongStart, p, alongStart + along, p + across];
|
|
9354
|
+
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];
|
|
9355
|
+
return { rect, clear };
|
|
9356
|
+
});
|
|
9357
|
+
}
|
|
9358
|
+
function zonesOfFloor(plan, floorIndex) {
|
|
9359
|
+
const out = [];
|
|
9360
|
+
if (floorIndex >= 0) {
|
|
9361
|
+
for (const p of plan.doors) if (p.floor === floorIndex) out.push(...p.zones);
|
|
9362
|
+
} else {
|
|
9363
|
+
for (const p of plan.doors) if (p.floor === 0 && p.exterior) out.push(...p.zones);
|
|
9364
|
+
for (const g of plan.gates) out.push(...g.zones);
|
|
9365
|
+
for (const c of plan.paths) out.push(...c.rects);
|
|
9366
|
+
out.push(...plan.reservations);
|
|
9367
|
+
}
|
|
9368
|
+
return out;
|
|
9369
|
+
}
|
|
9370
|
+
function openingsOfFloor(plan, floorIndex) {
|
|
9371
|
+
if (floorIndex >= 0) return plan.doors.filter((p) => p.floor === floorIndex);
|
|
9372
|
+
return plan.doors.filter((p) => p.floor === 0 && p.exterior).concat(plan.gates);
|
|
9373
|
+
}
|
|
9374
|
+
function windowZone(j, outside) {
|
|
9375
|
+
const h = j.width / 2 + 0.1;
|
|
9376
|
+
const depth = 0.5;
|
|
9377
|
+
let n;
|
|
9378
|
+
if (j.side === "front") n = [0, 1];
|
|
9379
|
+
else if (j.side === "back") n = [0, -1];
|
|
9380
|
+
else if (j.side === "left") n = [1, 0];
|
|
9381
|
+
else n = [-1, 0];
|
|
9382
|
+
if (outside) n = [-n[0], -n[1]];
|
|
9383
|
+
const u = j.u;
|
|
9384
|
+
const v = j.v;
|
|
9385
|
+
if (j.axis === "u") return n[1] > 0 ? [u - h, v - MARGIN, u + h, v + depth] : [u - h, v - depth, u + h, v + MARGIN];
|
|
9386
|
+
return n[0] > 0 ? [u - MARGIN, v - h, u + depth, v + h] : [u - depth, v - h, u + MARGIN, v + h];
|
|
9387
|
+
}
|
|
9388
|
+
function blocksWindow(it, win, overheadOff) {
|
|
9389
|
+
const height = Math.max(it.h, it.windowHeight || 0, it.overhead && !overheadOff ? OVERHEAD_TOP : 0);
|
|
9390
|
+
return !it.window || it.h > 1 + EPS || height > win.y0 - 0.05 + EPS;
|
|
9391
|
+
}
|
|
9392
|
+
function windowsOfRoom(plan, room) {
|
|
9393
|
+
if (!room.outdoor) return plan.windows.filter((j) => j.room === room.id);
|
|
9394
|
+
return plan.windows.filter(
|
|
9395
|
+
(j) => j.floor === 0 && room.parts.some((p) => {
|
|
9396
|
+
if (j.side === "front") return eq(p[3], j.pos) && j.u >= p[0] && j.u <= p[2];
|
|
9397
|
+
if (j.side === "back") return eq(p[1], j.pos) && j.u >= p[0] && j.u <= p[2];
|
|
9398
|
+
if (j.side === "left") return eq(p[2], j.pos) && j.v >= p[1] && j.v <= p[3];
|
|
9399
|
+
return eq(p[0], j.pos) && j.v >= p[1] && j.v <= p[3];
|
|
9400
|
+
})
|
|
9401
|
+
);
|
|
9402
|
+
}
|
|
9403
|
+
function roomAt(plan, u, v, floorIndex) {
|
|
9404
|
+
const list2 = floorIndex < 0 ? plan.zones : plan.rooms;
|
|
9405
|
+
return list2.find((c) => c.floor === floorIndex && inRect(c, u, v)) || null;
|
|
9406
|
+
}
|
|
9407
|
+
function isOpenSide(plan, room, pu, pv) {
|
|
9408
|
+
const o = roomAt(plan, pu, pv, room.floor);
|
|
9409
|
+
if (!o) return false;
|
|
9410
|
+
if (o.id === room.id) return true;
|
|
9411
|
+
if (room.outdoor) return true;
|
|
9412
|
+
return (plan.openPairs[room.floor] || []).some((x) => x[0] === room.id && x[1] === o.id || x[1] === room.id && x[0] === o.id);
|
|
9413
|
+
}
|
|
9414
|
+
function wallBodies(plan, floor) {
|
|
9415
|
+
const cache2 = plan.wallBodies = plan.wallBodies || {};
|
|
9416
|
+
if (cache2[floor]) return cache2[floor];
|
|
9417
|
+
const doors = plan.doors.filter((p) => p.floor === floor);
|
|
9418
|
+
return cache2[floor] = plan.walls.filter((p) => p.floor === floor && (p.type === "wall" || p.type === "railing")).map((p) => {
|
|
9419
|
+
const a0 = p.axis === "u" ? p.u0 : p.v0;
|
|
9420
|
+
const a1 = p.axis === "u" ? p.u1 : p.v1;
|
|
9421
|
+
const pos = p.axis === "u" ? p.v0 : p.u0;
|
|
9422
|
+
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)));
|
|
9423
|
+
const e0 = atDoor(a0) ? 0 : MARGIN;
|
|
9424
|
+
const e1 = atDoor(a1) ? 0 : MARGIN;
|
|
9425
|
+
return p.axis === "u" ? [a0 - e0, pos - MARGIN, a1 + e1, pos + MARGIN] : [pos - MARGIN, a0 - e0, pos + MARGIN, a1 + e1];
|
|
9426
|
+
});
|
|
9427
|
+
}
|
|
9428
|
+
function createGrid(plan, room, ignored, radius = plan.playerRadius) {
|
|
9429
|
+
const gs = room.outdoor ? 0.3 : 0.1;
|
|
9430
|
+
let u0 = 1e9;
|
|
9431
|
+
let v0 = 1e9;
|
|
9432
|
+
let u1 = -1e9;
|
|
9433
|
+
let v1 = -1e9;
|
|
9434
|
+
for (const p of room.parts) {
|
|
9435
|
+
u0 = Math.min(u0, p[0]);
|
|
9436
|
+
v0 = Math.min(v0, p[1]);
|
|
9437
|
+
u1 = Math.max(u1, p[2]);
|
|
9438
|
+
v1 = Math.max(v1, p[3]);
|
|
9439
|
+
}
|
|
9440
|
+
const nx = Math.ceil((u1 - u0) / gs);
|
|
9441
|
+
const nz = Math.ceil((v1 - v0) / gs);
|
|
9442
|
+
const insideCells = new Uint8Array(nx * nz);
|
|
9443
|
+
const insets = room.outdoor ? usableParts(room, 0.08) : room.parts;
|
|
9444
|
+
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);
|
|
9445
|
+
const tooClose = (u, v) => bodies.some((r) => {
|
|
9446
|
+
const dx = Math.max(r[0] - u, 0, u - r[2]);
|
|
9447
|
+
const dz = Math.max(r[1] - v, 0, v - r[3]);
|
|
9448
|
+
return dx * dx + dz * dz < radius * radius - 1e-9;
|
|
9449
|
+
});
|
|
9450
|
+
for (let j = 0; j < nz; j++)
|
|
9451
|
+
for (let i = 0; i < nx; i++) {
|
|
9452
|
+
const u = u0 + (i + 0.5) * gs;
|
|
9453
|
+
const v = v0 + (j + 0.5) * gs;
|
|
9454
|
+
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;
|
|
9455
|
+
}
|
|
9456
|
+
const entries = [];
|
|
9457
|
+
for (const ab of openingsOfFloor(plan, room.floor))
|
|
9458
|
+
if (!(ignored && ignored.has(ab.id)))
|
|
9459
|
+
for (const pt of entryPoints(ab)) {
|
|
9460
|
+
if (!inRect(room, pt[0], pt[1])) continue;
|
|
9461
|
+
const i = Math.floor((pt[0] - u0) / gs);
|
|
9462
|
+
const j = Math.floor((pt[1] - v0) / gs);
|
|
9463
|
+
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
9464
|
+
}
|
|
9465
|
+
if (!room.outdoor)
|
|
9466
|
+
for (const pair of plan.openPairs[room.floor] || []) {
|
|
9467
|
+
const otherId = pair[0] === room.id ? pair[1] : pair[1] === room.id ? pair[0] : null;
|
|
9468
|
+
const other = otherId && plan.rooms.find((c) => c.id === otherId && c.floor === room.floor);
|
|
9469
|
+
if (!other) continue;
|
|
9470
|
+
const edge = sharedEdge(room, other, null);
|
|
9471
|
+
if (!edge || edge.a1 - edge.a0 > 2) continue;
|
|
9472
|
+
const mid = (edge.a0 + edge.a1) / 2;
|
|
9473
|
+
for (const offset of [-0.4, 0.4]) {
|
|
9474
|
+
const pt = edge.axis === "u" ? [mid, edge.pos + offset] : [edge.pos + offset, mid];
|
|
9475
|
+
if (!inRect(room, pt[0], pt[1])) continue;
|
|
9476
|
+
const i = Math.floor((pt[0] - u0) / gs);
|
|
9477
|
+
const j = Math.floor((pt[1] - v0) / gs);
|
|
9478
|
+
if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
|
|
9479
|
+
}
|
|
9480
|
+
}
|
|
9481
|
+
const R = inner(room.parts[0]);
|
|
9482
|
+
return { gs, u0, v0, nx, nz, inside: insideCells, entries, center: [(R[0] + R[2]) / 2, (R[1] + R[3]) / 2] };
|
|
9483
|
+
}
|
|
9484
|
+
function reachable(g, solids, inflation) {
|
|
9485
|
+
const { gs, u0, v0, nx, nz, inside } = g;
|
|
9486
|
+
const blocked = new Uint8Array(nx * nz);
|
|
9487
|
+
for (const r of solids) {
|
|
9488
|
+
const i0 = Math.max(0, Math.floor((r[0] - inflation - u0) / gs));
|
|
9489
|
+
const i1 = Math.min(nx - 1, Math.floor((r[2] + inflation - u0) / gs));
|
|
9490
|
+
const j0 = Math.max(0, Math.floor((r[1] - inflation - v0) / gs));
|
|
9491
|
+
const j1 = Math.min(nz - 1, Math.floor((r[3] + inflation - v0) / gs));
|
|
9492
|
+
for (let j = j0; j <= j1; j++)
|
|
9493
|
+
for (let i = i0; i <= i1; i++) {
|
|
9494
|
+
const u = u0 + (i + 0.5) * gs;
|
|
9495
|
+
const v = v0 + (j + 0.5) * gs;
|
|
9496
|
+
if (u > r[0] - inflation && u < r[2] + inflation && v > r[1] - inflation && v < r[3] + inflation) blocked[j * nx + i] = 1;
|
|
9497
|
+
}
|
|
9498
|
+
}
|
|
9499
|
+
let best = -1;
|
|
9500
|
+
let bestDist = 1e9;
|
|
9501
|
+
for (let j = 0; j < nz; j++)
|
|
9502
|
+
for (let i = 0; i < nx; i++) {
|
|
9503
|
+
const k = j * nx + i;
|
|
9504
|
+
if (!inside[k] || blocked[k]) continue;
|
|
9505
|
+
const dist2 = Math.hypot(u0 + (i + 0.5) * gs - g.center[0], v0 + (j + 0.5) * gs - g.center[1]);
|
|
9506
|
+
if (dist2 < bestDist) {
|
|
9507
|
+
bestDist = dist2;
|
|
9508
|
+
best = k;
|
|
9509
|
+
}
|
|
9510
|
+
}
|
|
9511
|
+
if (best < 0) return false;
|
|
9512
|
+
if (!g.entries.length) return true;
|
|
9513
|
+
for (const [i, j] of g.entries) if (blocked[j * nx + i]) return false;
|
|
9514
|
+
const visited = new Uint8Array(nx * nz);
|
|
9515
|
+
const queue = new Int32Array(nx * nz);
|
|
9516
|
+
let h = 0;
|
|
9517
|
+
let t = 0;
|
|
9518
|
+
const s = g.entries[0][1] * nx + g.entries[0][0];
|
|
9519
|
+
visited[s] = 1;
|
|
9520
|
+
queue[t++] = s;
|
|
9521
|
+
while (h < t) {
|
|
9522
|
+
const k = queue[h++];
|
|
9523
|
+
const i = k % nx;
|
|
9524
|
+
const j = (k - i) / nx;
|
|
9525
|
+
for (const [di, dj] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
9526
|
+
const a = i + di;
|
|
9527
|
+
const b = j + dj;
|
|
9528
|
+
if (a < 0 || b < 0 || a >= nx || b >= nz) continue;
|
|
9529
|
+
const q = b * nx + a;
|
|
9530
|
+
if (visited[q] || !inside[q] || blocked[q]) continue;
|
|
9531
|
+
visited[q] = 1;
|
|
9532
|
+
queue[t++] = q;
|
|
9533
|
+
}
|
|
9534
|
+
}
|
|
9535
|
+
if (!visited[best]) return false;
|
|
9536
|
+
for (const [i, j] of g.entries) if (!visited[j * nx + i]) return false;
|
|
9537
|
+
return true;
|
|
9538
|
+
}
|
|
9539
|
+
function candidatesOf(cats) {
|
|
9540
|
+
const out = [];
|
|
9541
|
+
for (const [cat, weight] of Object.entries(cats)) {
|
|
9542
|
+
const exact = cat[0] === "=";
|
|
9543
|
+
const its = Object.values(registries().CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
|
|
9544
|
+
for (const it of its) out.push({ it, weight: weight / its.length });
|
|
9545
|
+
}
|
|
9546
|
+
return out;
|
|
9547
|
+
}
|
|
9548
|
+
function itemRect(slot, it, shift) {
|
|
9549
|
+
const c = slot._cell;
|
|
9550
|
+
const f = c.f;
|
|
9551
|
+
const [extentU, extentV] = extent([it.w, it.d], f);
|
|
9552
|
+
const R = c.R;
|
|
9553
|
+
let u0 = (c.rect ? (c.rect[0] + c.rect[2]) / 2 : slot.x) - extentU / 2;
|
|
9554
|
+
let v0 = (c.rect ? (c.rect[1] + c.rect[3]) / 2 : slot.z) - extentV / 2;
|
|
9555
|
+
if (shift) {
|
|
9556
|
+
if (slot.side === "left" || slot.side === "right") v0 += shift;
|
|
9557
|
+
else u0 += shift;
|
|
9558
|
+
}
|
|
9559
|
+
for (const s of c.enclosed) {
|
|
9560
|
+
if (s === "left") u0 = R[0];
|
|
9561
|
+
else if (s === "right") u0 = R[2] - extentU;
|
|
9562
|
+
else if (s === "front") v0 = R[1];
|
|
9563
|
+
else if (s === "back") v0 = R[3] - extentV;
|
|
9564
|
+
}
|
|
9565
|
+
return [u0, v0, u0 + extentU, v0 + extentV];
|
|
9566
|
+
}
|
|
9567
|
+
var RELAXED_RADIUS = 0.12;
|
|
9568
|
+
var MESH_FRONT = { "+z": [0, 1], "-z": [0, -1], "+x": [1, 0], "-x": [-1, 0] };
|
|
9569
|
+
function rotationFor(f, front) {
|
|
9570
|
+
const meshFront = MESH_FRONT[front] || [0, 1];
|
|
9571
|
+
let ry = Math.atan2(f[0], f[1]) - Math.atan2(meshFront[0], meshFront[1]);
|
|
9572
|
+
if (ry > Math.PI + EPS) ry -= 2 * Math.PI;
|
|
9573
|
+
if (ry < -Math.PI - EPS) ry += 2 * Math.PI;
|
|
9574
|
+
return r2(ry);
|
|
9575
|
+
}
|
|
9576
|
+
function fillRoom(plan, room, slots2, rng) {
|
|
9577
|
+
const items = [];
|
|
9578
|
+
const missing = [];
|
|
9579
|
+
const state = {};
|
|
9580
|
+
const solids = [];
|
|
9581
|
+
const reserves = [];
|
|
9582
|
+
const rims = [];
|
|
9583
|
+
const inners = usableParts(room, 0);
|
|
9584
|
+
const grid = createGrid(plan, room);
|
|
9585
|
+
const relaxedGrid = room.outdoor ? grid : createGrid(plan, room, null, RELAXED_RADIUS);
|
|
9586
|
+
const zones = zonesOfFloor(plan, room.floor);
|
|
9587
|
+
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
9588
|
+
const inflation = room.outdoor ? 0.15 : plan.playerRadius;
|
|
9589
|
+
const units = [];
|
|
9590
|
+
const seen = {};
|
|
9591
|
+
for (const s of slots2) {
|
|
9592
|
+
if (s.group) {
|
|
9593
|
+
if (seen[s.group]) {
|
|
9594
|
+
seen[s.group].members.push(s);
|
|
9595
|
+
continue;
|
|
9596
|
+
}
|
|
9597
|
+
const u = { group: s.group, members: [s] };
|
|
9598
|
+
seen[s.group] = u;
|
|
9599
|
+
units.push(u);
|
|
9600
|
+
} else units.push({ group: null, members: [s] });
|
|
9601
|
+
}
|
|
9602
|
+
for (const u of units) u.required = u.members.some((m) => m.required);
|
|
9603
|
+
const queue = units.filter((u) => u.required).concat(units.filter((u) => !u.required));
|
|
9604
|
+
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)));
|
|
9605
|
+
const done = /* @__PURE__ */ new Set();
|
|
9606
|
+
const ready = (u) => dependencies(u).every((id) => done.has(id));
|
|
9607
|
+
const make2 = (s, it, r, parent, overheadOff) => {
|
|
9608
|
+
const f = [s.frontU, s.frontV];
|
|
9609
|
+
const id = room.id + "." + s.id;
|
|
9610
|
+
const m = {
|
|
9611
|
+
id,
|
|
9612
|
+
type: it.type,
|
|
9613
|
+
category: it.category,
|
|
9614
|
+
x: r2((r[0] + r[2]) / 2),
|
|
9615
|
+
z: r2((r[1] + r[3]) / 2),
|
|
9616
|
+
ry: rotationFor(f, it.front),
|
|
9617
|
+
w: it.w,
|
|
9618
|
+
d: it.d,
|
|
9619
|
+
h: it.h,
|
|
9620
|
+
y: parent ? r2((parent.y || 0) + parent.h) : 0,
|
|
9621
|
+
frontU: f[0],
|
|
9622
|
+
frontV: f[1],
|
|
9623
|
+
room: room.id,
|
|
9624
|
+
floor: room.floor,
|
|
9625
|
+
outdoor: !!room.outdoor,
|
|
9626
|
+
slot: s.id,
|
|
9627
|
+
on: parent ? s.on : null,
|
|
9628
|
+
mode: parent ? s.anchor === "inside" ? "inside" : "on" : null,
|
|
9629
|
+
clearance: s.clearance,
|
|
9630
|
+
flat: it.flat,
|
|
9631
|
+
low: it.low,
|
|
9632
|
+
aabb: r.map(r2),
|
|
9633
|
+
_r: r,
|
|
9634
|
+
_seedId: registries().SEED_KEYS.items[id] ?? id
|
|
9635
|
+
};
|
|
9636
|
+
if (it.overhead) m.overhead = !overheadOff;
|
|
9637
|
+
if (it.category === "car") {
|
|
9638
|
+
m.driver = [f[1], -f[0]];
|
|
9639
|
+
m.carClearance = plan.carClearance;
|
|
9640
|
+
}
|
|
9641
|
+
if (parent && s.anchor === "inside") m.y = r2(parent.y || 0);
|
|
9642
|
+
if (room.link) m.link = { from: room.link[0], to: room.link[1] };
|
|
9643
|
+
state[s.id] = m;
|
|
9644
|
+
items.push(m);
|
|
9645
|
+
return m;
|
|
9646
|
+
};
|
|
9647
|
+
const tryFit = (s, relax, overheadOff) => {
|
|
9648
|
+
const cands = weightedOrder(candidatesOf(s.cats), rng);
|
|
9649
|
+
if (s.anchor === "on" || s.anchor === "inside") {
|
|
9650
|
+
const parent = state[s.on];
|
|
9651
|
+
if (!parent) return null;
|
|
9652
|
+
for (const { it } of cands) {
|
|
9653
|
+
if (it.w > parent.w + EPS || it.d > parent.d + EPS) continue;
|
|
9654
|
+
const f = [parent.frontU, parent.frontV];
|
|
9655
|
+
const r = parent.aabb;
|
|
9656
|
+
const [extentU, extentV] = extent([it.w, it.d], f);
|
|
9657
|
+
const cu = (r[0] + r[2]) / 2 - f[0] * (parent.d / 2 - it.d / 2);
|
|
9658
|
+
const cv = (r[1] + r[3]) / 2 - f[1] * (parent.d / 2 - it.d / 2);
|
|
9659
|
+
return make2(s, it, [cu - extentU / 2, cv - extentV / 2, cu + extentU / 2, cv + extentV / 2], parent);
|
|
9660
|
+
}
|
|
9661
|
+
return null;
|
|
9662
|
+
}
|
|
9663
|
+
for (const { it } of cands) {
|
|
9664
|
+
if (it.w > s.w + EPS || it.d > s.d + EPS) continue;
|
|
9665
|
+
const f = [s.frontU, s.frontV];
|
|
9666
|
+
const slide = s.anchor === "wall" ? s.slide == null ? 2 : s.slide : 0;
|
|
9667
|
+
const shifts = [0];
|
|
9668
|
+
for (let k = 1; k * 0.1 <= slide + 1e-9; k++) shifts.push(k * 0.1, -k * 0.1);
|
|
9669
|
+
let found = null;
|
|
9670
|
+
const car = it.category === "car" && !relax;
|
|
9671
|
+
const tries = car ? carCandidates(s, it, plan.carClearance) : shifts.map((shift) => ({ rect: itemRect(s, it, shift) }));
|
|
9672
|
+
for (const attempt of tries) {
|
|
9673
|
+
const r3 = attempt.rect;
|
|
9674
|
+
if (!insideUnion(r3, inners)) continue;
|
|
9675
|
+
if (car && (!insideUnion(attempt.clear, inners) || solids.some((o) => overlaps(attempt.clear, o)))) continue;
|
|
9676
|
+
if (it.flat) {
|
|
9677
|
+
found = { rect: r3 };
|
|
9678
|
+
break;
|
|
9679
|
+
}
|
|
9680
|
+
if (zones.some((z) => overlaps(r3, z))) continue;
|
|
9681
|
+
if (windowZones.some(({ z, j }) => blocksWindow(it, j, overheadOff) && overlaps(r3, z))) continue;
|
|
9682
|
+
if (solids.some((o) => overlaps(r3, o))) continue;
|
|
9683
|
+
if (!it.deck && rims.some((a) => overlaps(r3, a))) continue;
|
|
9684
|
+
let cl2 = car ? attempt.clear : null;
|
|
9685
|
+
if (!car && !relax && s.clearance > 0 && !it.low) {
|
|
9686
|
+
cl2 = clearRect(r3, f, s.clearance);
|
|
9687
|
+
if (!insideUnion(cl2, inners)) continue;
|
|
9688
|
+
if (items.some((m2) => !m2.low && !m2.flat && !m2.on && overlaps(cl2, m2._r))) continue;
|
|
9689
|
+
}
|
|
9690
|
+
if (!it.low && reserves.some((q) => overlaps(r3, q))) continue;
|
|
9691
|
+
let rimRect2 = null;
|
|
9692
|
+
if (it.rim) {
|
|
9693
|
+
rimRect2 = [r3[0] - it.rim, r3[1] - it.rim, r3[2] + it.rim, r3[3] + it.rim];
|
|
9694
|
+
if (!insideUnion(rimRect2, inners) || solids.some((o) => overlaps(rimRect2, o)) || zones.some((z) => overlaps(rimRect2, z))) continue;
|
|
9695
|
+
}
|
|
9696
|
+
if (!(relax ? reachable(relaxedGrid, solids.concat([r3]), RELAXED_RADIUS) : reachable(grid, solids.concat([r3]), inflation))) continue;
|
|
9697
|
+
found = { rect: r3, cl: cl2, rimRect: rimRect2 };
|
|
9698
|
+
break;
|
|
9699
|
+
}
|
|
9700
|
+
if (!found) continue;
|
|
9701
|
+
const { rect: r, cl, rimRect } = found;
|
|
9702
|
+
if (it.flat) return make2(s, it, r, null);
|
|
9703
|
+
const m = make2(s, it, r, null, overheadOff);
|
|
9704
|
+
if (cl) reserves.push(cl);
|
|
9705
|
+
if (rimRect) rims.push(rimRect);
|
|
9706
|
+
solids.push(r);
|
|
9707
|
+
return m;
|
|
9708
|
+
}
|
|
9709
|
+
return null;
|
|
9710
|
+
};
|
|
9711
|
+
const process2 = (u) => {
|
|
9712
|
+
const pr = u.members[0].chance;
|
|
9713
|
+
const x = rng();
|
|
9714
|
+
const arr = u.members.slice();
|
|
9715
|
+
const members = [];
|
|
9716
|
+
while (arr.length) members.push(arr.splice(arr.length > 1 ? Math.floor(rng() * arr.length) : 0, 1)[0]);
|
|
9717
|
+
const applicable = members.filter((m) => m.valid && (!m.requires || state[m.requires]) && (!m.on || state[m.on]));
|
|
9718
|
+
if (!u.required && x >= pr) return;
|
|
9719
|
+
let ok = false;
|
|
9720
|
+
for (const m of applicable) {
|
|
9721
|
+
if (tryFit(m, false)) {
|
|
9722
|
+
ok = true;
|
|
9723
|
+
break;
|
|
9724
|
+
}
|
|
9725
|
+
}
|
|
9726
|
+
if (!ok)
|
|
9727
|
+
for (const m of applicable) {
|
|
9728
|
+
if (tryFit(m, false, true)) {
|
|
9729
|
+
ok = true;
|
|
9730
|
+
break;
|
|
9731
|
+
}
|
|
9732
|
+
}
|
|
9733
|
+
if (!ok && u.required)
|
|
9734
|
+
for (const m of applicable) {
|
|
9735
|
+
if (tryFit(m, true, true)) {
|
|
9736
|
+
ok = true;
|
|
9737
|
+
break;
|
|
9738
|
+
}
|
|
9739
|
+
}
|
|
9740
|
+
if (!ok && u.required && (applicable.length || !u.members.some((m) => m.requires))) missing.push({ room: room.id, slot: u.group || u.members[0].id });
|
|
9741
|
+
};
|
|
9742
|
+
const pending = queue.slice();
|
|
9743
|
+
while (pending.length) {
|
|
9744
|
+
let i = pending.findIndex(ready);
|
|
9745
|
+
if (i < 0) i = 0;
|
|
9746
|
+
const u = pending.splice(i, 1)[0];
|
|
9747
|
+
process2(u);
|
|
9748
|
+
for (const m of u.members) done.add(m.id);
|
|
9749
|
+
}
|
|
9750
|
+
return { items, missing };
|
|
9751
|
+
}
|
|
9752
|
+
var STREAM = {
|
|
9753
|
+
facade: "fachada",
|
|
9754
|
+
lot: "lote",
|
|
9755
|
+
decor: "decor",
|
|
9756
|
+
roomPrefix: "c:",
|
|
9757
|
+
palettePrefix: "paleta:"
|
|
9758
|
+
};
|
|
9759
|
+
var placementKit = {
|
|
9760
|
+
MARGIN,
|
|
9761
|
+
EPS,
|
|
9762
|
+
r2,
|
|
9763
|
+
clamp: clamp3,
|
|
9764
|
+
overlaps,
|
|
9765
|
+
inner,
|
|
9766
|
+
inflate: inflate2,
|
|
9767
|
+
areaOf,
|
|
9768
|
+
topOf,
|
|
9769
|
+
extent,
|
|
9770
|
+
insideUnion,
|
|
9771
|
+
usableParts,
|
|
9772
|
+
zonesOfFloor,
|
|
9773
|
+
windowsOfRoom,
|
|
9774
|
+
windowZone,
|
|
9775
|
+
roomAt,
|
|
9776
|
+
reachable,
|
|
9777
|
+
createGrid,
|
|
9778
|
+
clearRect,
|
|
9779
|
+
pickWeighted,
|
|
9780
|
+
createRng: createRng2,
|
|
9781
|
+
roomSeedKey: (room) => room.seedKey ?? registries().SEED_KEYS.rooms[room.id] ?? room.id,
|
|
9782
|
+
shuffle,
|
|
9783
|
+
facingRotation: (f, type) => rotationFor(f, registries().CATALOG[type].front),
|
|
9784
|
+
cutIntervals,
|
|
9785
|
+
blocksWindow
|
|
9786
|
+
};
|
|
9787
|
+
function createPlacementContext(plan, items) {
|
|
9788
|
+
const added = [];
|
|
9789
|
+
const counters = {};
|
|
9790
|
+
const make2 = (room, type, r, f, o) => {
|
|
9791
|
+
const it = registries().CATALOG[type];
|
|
9792
|
+
const n = counters[room.id + type] = (counters[room.id + type] || 0) + 1;
|
|
9793
|
+
return Object.assign(
|
|
9794
|
+
{
|
|
9795
|
+
id: room.id + ".dec_" + type + n,
|
|
9796
|
+
type,
|
|
9797
|
+
category: it.category,
|
|
9798
|
+
x: r2((r[0] + r[2]) / 2),
|
|
9799
|
+
z: r2((r[1] + r[3]) / 2),
|
|
9800
|
+
ry: r2(Math.atan2(f[0], f[1])),
|
|
9801
|
+
w: it.w,
|
|
9802
|
+
d: it.d,
|
|
9803
|
+
h: it.h,
|
|
9804
|
+
y: 0,
|
|
9805
|
+
frontU: f[0],
|
|
9806
|
+
frontV: f[1],
|
|
9807
|
+
room: room.id,
|
|
9808
|
+
floor: room.floor,
|
|
9809
|
+
outdoor: !!room.outdoor,
|
|
9810
|
+
slot: "dec_" + type + n,
|
|
9811
|
+
on: null,
|
|
9812
|
+
mode: null,
|
|
9813
|
+
clearance: 0,
|
|
9814
|
+
flat: !!it.flat,
|
|
9815
|
+
low: it.low,
|
|
9816
|
+
aabb: r.map(r2),
|
|
9817
|
+
_r: r,
|
|
9818
|
+
decor: true,
|
|
9819
|
+
mount: it.mount
|
|
9820
|
+
},
|
|
9821
|
+
o || {}
|
|
9822
|
+
);
|
|
9823
|
+
};
|
|
9824
|
+
const add2 = (m) => {
|
|
9825
|
+
added.push(m);
|
|
9826
|
+
items.push(m);
|
|
9827
|
+
return m;
|
|
9828
|
+
};
|
|
9829
|
+
const itemsOfRoom = (c) => items.filter((m) => m.room === c.id && m.floor === c.floor);
|
|
9830
|
+
const extraSolids = {};
|
|
9831
|
+
const solidsOfRoom = (c) => itemsOfRoom(c).filter((m) => !m.flat && !m.on && (!m.decor || m.mount === "floor")).map((m) => m._r).concat(extraSolids[c.id] || []);
|
|
9832
|
+
const gridCache = {};
|
|
9833
|
+
let ignored = null;
|
|
9834
|
+
const gridOf = (c) => gridCache[c.id] || (gridCache[c.id] = createGrid(plan, c, ignored));
|
|
9835
|
+
function placeOnFloor(room, type, rng2, optsIn) {
|
|
9836
|
+
const opts = Object.assign({}, optsIn || {});
|
|
9837
|
+
const it0 = registries().CATALOG[type];
|
|
9838
|
+
if (it0.flat) opts.flat = true;
|
|
9839
|
+
const it = registries().CATALOG[type];
|
|
9840
|
+
const inners = usableParts(room, 0);
|
|
9841
|
+
const solidList = solidsOfRoom(room);
|
|
9842
|
+
const zones = zonesOfFloor(plan, room.floor);
|
|
9843
|
+
const occupied = itemsOfRoom(room);
|
|
9844
|
+
const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
|
|
9845
|
+
const landings = it0.flat ? [] : items.filter((m) => m.type === "stairs" && m.floor === room.floor && m.aabb).map((m) => {
|
|
9846
|
+
const r = m.aabb;
|
|
9847
|
+
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]];
|
|
9848
|
+
});
|
|
9849
|
+
const cand = opts.cands ? opts.cands.slice() : [];
|
|
9850
|
+
if (!opts.cands)
|
|
9851
|
+
for (const p of inners) {
|
|
9852
|
+
const extentU = opts.dim ? opts.dim[0] : it.w;
|
|
9853
|
+
const extentV = opts.dim ? opts.dim[1] : it.d;
|
|
9854
|
+
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]);
|
|
9855
|
+
for (let k = 0; k < 6; k++) {
|
|
9856
|
+
const t = rng2();
|
|
9857
|
+
const side = Math.floor(rng2() * 4);
|
|
9858
|
+
const x = side < 2 ? p[0] + t * Math.max(0, p[2] - p[0] - extentU) : side === 2 ? p[0] : p[2] - extentU;
|
|
9859
|
+
const z = side >= 2 ? p[1] + t * Math.max(0, p[3] - p[1] - extentV) : side === 0 ? p[1] : p[3] - extentV;
|
|
9860
|
+
cand.push([x, z, x + extentU, z + extentV]);
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
for (const r of opts.ordered ? cand : shuffle(cand, rng2)) {
|
|
9864
|
+
if (!insideUnion(r, inners)) continue;
|
|
9865
|
+
if (zones.some((z) => overlaps(r, z)) || landings.some((z) => overlaps(r, z))) continue;
|
|
9866
|
+
if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r, z))) continue;
|
|
9867
|
+
if (solidList.some((o) => overlaps(r, o))) continue;
|
|
9868
|
+
if (opts.flat) {
|
|
9869
|
+
if (!(opts.without && opts.without(r))) return r;
|
|
9870
|
+
continue;
|
|
9871
|
+
}
|
|
9872
|
+
if (occupied.some((m) => !m.low && !m.flat && !m.on && !m.decor && m.clearance > 0 && overlaps(r, clearRect(m._r, [m.frontU, m.frontV], m.clearance)))) continue;
|
|
9873
|
+
if (opts.without && opts.without(r)) continue;
|
|
9874
|
+
if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), room.outdoor ? 0.16 : plan.playerRadius)) continue;
|
|
9875
|
+
return r;
|
|
9876
|
+
}
|
|
9877
|
+
return null;
|
|
9878
|
+
}
|
|
9879
|
+
const roomById = (id) => plan.rooms.find((c) => c.id === id) || plan.zones.find((c) => c.id === id);
|
|
9880
|
+
return {
|
|
9881
|
+
make: make2,
|
|
9882
|
+
add: add2,
|
|
9883
|
+
placeOnFloor,
|
|
9884
|
+
itemsOfRoom,
|
|
9885
|
+
solidsOfRoom,
|
|
9886
|
+
gridOf,
|
|
9887
|
+
extraSolids,
|
|
9888
|
+
roomById,
|
|
9889
|
+
setIgnored: (s) => {
|
|
9890
|
+
ignored = s;
|
|
9891
|
+
for (const k of Object.keys(gridCache)) delete gridCache[k];
|
|
9892
|
+
},
|
|
9893
|
+
added
|
|
9894
|
+
};
|
|
9895
|
+
}
|
|
9896
|
+
function groundCover(plan, items, ctx) {
|
|
9897
|
+
if (!plan.lot) return;
|
|
9898
|
+
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])]);
|
|
9899
|
+
const groundPiece = (z, type, r, layer, i) => {
|
|
9900
|
+
const m = ctx.make(z, type, r, [0, 1], {
|
|
9901
|
+
id: z.id + ".ground" + (layer ? "_overlay" : "") + i,
|
|
9902
|
+
slot: "ground" + (layer ? "_overlay" : "") + i,
|
|
9903
|
+
w: r2(r[2] - r[0]),
|
|
9904
|
+
d: r2(r[3] - r[1]),
|
|
9905
|
+
h: 0,
|
|
9906
|
+
polygon: polygonOf(r),
|
|
9907
|
+
layer,
|
|
9908
|
+
zone: z.id,
|
|
9909
|
+
outdoor: true,
|
|
9910
|
+
floor: -1
|
|
9911
|
+
});
|
|
9912
|
+
return ctx.add(m);
|
|
9913
|
+
};
|
|
9914
|
+
for (const z of plan.zones) {
|
|
9915
|
+
z.parts.forEach((p, i) => groundPiece(z, z.ground, p, 0, i));
|
|
9916
|
+
let k = 0;
|
|
9917
|
+
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];
|
|
9918
|
+
for (const rule of registries().GROUND_OVERLAYS) {
|
|
9919
|
+
const triggers = items.filter((m) => m.room === z.id && m.category === rule.category);
|
|
9920
|
+
if (rule.merge) {
|
|
9921
|
+
if (triggers.length && z.ground !== rule.unlessGround) {
|
|
9922
|
+
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]);
|
|
9923
|
+
const q = clip(inflate2(u, rule.inflate));
|
|
9924
|
+
if (q) groundPiece(z, rule.ground, q, 1, k++);
|
|
9925
|
+
}
|
|
9926
|
+
} else {
|
|
9927
|
+
for (const m of triggers) {
|
|
9928
|
+
const q = clip(inflate2(m._r, rule.inflate));
|
|
9929
|
+
if (q) groundPiece(z, rule.ground, q, 1, k++);
|
|
9930
|
+
}
|
|
9931
|
+
}
|
|
9932
|
+
}
|
|
9933
|
+
}
|
|
9934
|
+
}
|
|
9935
|
+
function runPostProcess(plan, items, seed) {
|
|
9936
|
+
const ctx = createPlacementContext(plan, items);
|
|
9937
|
+
const rng = createRng2(seed, STREAM.decor);
|
|
9938
|
+
for (const pass of registries().PASSES) pass.run({ plan, items, seed, rng, ctx, kit: placementKit, catalog: registries().CATALOG, profiles: registries().DECOR_PROFILES });
|
|
9939
|
+
groundCover(plan, items, ctx);
|
|
9940
|
+
return ctx;
|
|
9941
|
+
}
|
|
8779
9942
|
function registerLocale(code, table) {
|
|
9943
|
+
const { LOCALES: locales } = registries();
|
|
8780
9944
|
const cur = locales[code] = locales[code] || {};
|
|
8781
9945
|
for (const ns of Object.keys(table)) cur[ns] = Object.assign(cur[ns] || {}, table[ns]);
|
|
8782
9946
|
}
|
|
8783
|
-
var
|
|
9947
|
+
var listLocales = () => Object.keys(registries().LOCALES);
|
|
9948
|
+
function translate(ns, key, locale = "en", vars) {
|
|
9949
|
+
const { LOCALES: locales } = registries();
|
|
9950
|
+
const raw = locales[locale]?.[ns]?.[key] ?? locales.en?.[ns]?.[key] ?? key;
|
|
9951
|
+
return vars ? raw.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m) : raw;
|
|
9952
|
+
}
|
|
9953
|
+
var CLOSE = 1e-6;
|
|
9954
|
+
var rounded = (r) => r.map(r2);
|
|
9955
|
+
var touches = (a, r) => a[0] <= r[2] + CLOSE && a[2] >= r[0] - CLOSE && a[1] <= r[3] + CLOSE && a[3] >= r[1] - CLOSE;
|
|
9956
|
+
function addExteriorDoorPaths(plan, c) {
|
|
9957
|
+
const { gates, paths, zones, setback, marginLeft, marginRight, backyard, w } = c;
|
|
9958
|
+
const social = gates.find((g) => g.type === "pedestrian");
|
|
9959
|
+
const socialPath = social ? paths.find((p) => p.from === social.id && p.type === "pedestrian") : null;
|
|
9960
|
+
const parkingZone = zones.find((z) => z.id === "parking");
|
|
9961
|
+
const parking = parkingZone ? parkingZone.parts[0] : null;
|
|
9962
|
+
const midY = setback > 0 ? -setback / 2 : 0;
|
|
9963
|
+
const joinToSocial = (x, width, rects, points) => {
|
|
9964
|
+
if (!socialPath) return;
|
|
9965
|
+
const h = width / 2;
|
|
9966
|
+
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];
|
|
9967
|
+
const targetX = x < horizontal[0] ? horizontal[0] + h : x > horizontal[2] ? horizontal[2] - h : x;
|
|
9968
|
+
if (Math.abs(targetX - x) > CLOSE) {
|
|
9969
|
+
rects.push([Math.min(x, targetX) - h, midY - h, Math.max(x, targetX) + h, midY + h]);
|
|
9970
|
+
points.push([targetX, midY]);
|
|
9971
|
+
}
|
|
9972
|
+
};
|
|
9973
|
+
for (const door of plan.doors.filter((q) => q.floor === 0 && q.exterior)) {
|
|
9974
|
+
if (paths.some((p) => p.to === door.id)) continue;
|
|
9975
|
+
const half = Math.max(1, Math.min(door.width, 2)) / 2;
|
|
9976
|
+
const rects = [];
|
|
9977
|
+
const points = [];
|
|
9978
|
+
const isTarget = c.target && door.a === c.target.a && door.side === c.target.side;
|
|
9979
|
+
let fromId = social ? social.id : null;
|
|
9980
|
+
let width = 1;
|
|
9981
|
+
const id = isTarget ? "entrance_path" : "path_" + door.id;
|
|
9982
|
+
if (door.side === "front") {
|
|
9983
|
+
if (setback > 0) {
|
|
9984
|
+
const stub = Math.min(1, setback);
|
|
9985
|
+
rects.push([door.u - half, -stub, door.u + half, 0]);
|
|
9986
|
+
points.push([door.u, 0], [door.u, -stub]);
|
|
9987
|
+
if (-stub > midY) {
|
|
9988
|
+
rects.push([door.u - width / 2, midY - width / 2, door.u + width / 2, -stub]);
|
|
9989
|
+
points.push([door.u, midY]);
|
|
9990
|
+
}
|
|
9991
|
+
joinToSocial(door.u, width, rects, points);
|
|
9992
|
+
} else {
|
|
9993
|
+
rects.push([door.u - half, -1.2, door.u + half, 0]);
|
|
9994
|
+
points.push([door.u, 0], [door.u, -1.2]);
|
|
9995
|
+
}
|
|
9996
|
+
} else {
|
|
9997
|
+
const lanes = [];
|
|
9998
|
+
if (marginLeft > 0.5) lanes.push({ x: -marginLeft / 2, width: marginLeft, side: "left" });
|
|
9999
|
+
if (marginRight > 0.5) lanes.push({ x: w + marginRight / 2, width: marginRight, side: "right" });
|
|
10000
|
+
const targets = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
10001
|
+
let best = null;
|
|
10002
|
+
for (const lane of lanes) {
|
|
10003
|
+
if (door.side === "left" && lane.side !== "left" || door.side === "right" && lane.side !== "right") continue;
|
|
10004
|
+
const laneWidth = Math.min(0.9, lane.width - 0.1);
|
|
10005
|
+
const h = laneWidth / 2;
|
|
10006
|
+
const rearStrip = Math.min(0.8, backyard);
|
|
10007
|
+
const rearY = door.v + rearStrip + laneWidth / 2;
|
|
10008
|
+
const overlapsParking = !!parking && lane.x + h > parking[0] && lane.x - h < parking[2];
|
|
10009
|
+
const frontY = overlapsParking || setback <= 0 ? 0 : -0.6;
|
|
10010
|
+
let frontStub = null;
|
|
10011
|
+
if (!overlapsParking && setback > 0) {
|
|
10012
|
+
const dir = social && social.u < lane.x ? -1 : 1;
|
|
10013
|
+
const band = [-0.6 - h, -0.6 + h];
|
|
10014
|
+
let nearestX = social ? social.u : lane.x;
|
|
10015
|
+
let nearestGap = Infinity;
|
|
10016
|
+
for (const r of targets) {
|
|
10017
|
+
if (!(r[1] <= band[1] && r[3] >= band[0])) continue;
|
|
10018
|
+
const edge = dir < 0 ? r[2] : r[0];
|
|
10019
|
+
const gap = Math.abs(edge - lane.x);
|
|
10020
|
+
if ((dir < 0 ? edge <= lane.x : edge >= lane.x) && gap < nearestGap) {
|
|
10021
|
+
nearestGap = gap;
|
|
10022
|
+
nearestX = edge;
|
|
10023
|
+
}
|
|
10024
|
+
}
|
|
10025
|
+
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];
|
|
10026
|
+
}
|
|
10027
|
+
const vertical = [lane.x - h, frontY - (frontY < 0 ? h : 0), lane.x + h, (door.side === "back" ? rearY : door.v) + laneWidth / 2];
|
|
10028
|
+
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;
|
|
10029
|
+
const cost = (horizontal ? horizontal[2] - horizontal[0] : 0) + (frontStub ? frontStub[2] - frontStub[0] : 0);
|
|
10030
|
+
if (!best || cost < best.cost) best = { lane, laneWidth, half: h, vertical, horizontal, frontStub, cost, rearStrip, rearY, frontY, overlapsParking };
|
|
10031
|
+
}
|
|
10032
|
+
if (best) {
|
|
10033
|
+
const { lane, laneWidth, half: h, vertical, horizontal, frontStub, rearStrip, rearY, frontY, overlapsParking } = best;
|
|
10034
|
+
width = laneWidth;
|
|
10035
|
+
if (overlapsParking) fromId = (gates.find((g) => g.type === "garage") || social || {}).id || null;
|
|
10036
|
+
if (door.side === "back") {
|
|
10037
|
+
rects.push([door.u - half, door.v, door.u + half, door.v + rearStrip + laneWidth], horizontal, vertical);
|
|
10038
|
+
points.push([door.u, door.v], [door.u, rearY], [lane.x, rearY], [lane.x, frontY]);
|
|
10039
|
+
} else {
|
|
10040
|
+
const x0 = door.side === "left" ? lane.x - h : w;
|
|
10041
|
+
const x1 = door.side === "left" ? 0 : lane.x + h;
|
|
10042
|
+
rects.push([Math.min(x0, x1), door.v - half, Math.max(x0, x1), door.v + half], vertical);
|
|
10043
|
+
points.push([door.u, door.v], [lane.x, door.v], [lane.x, frontY]);
|
|
10044
|
+
}
|
|
10045
|
+
if (frontStub) {
|
|
10046
|
+
rects.push(frontStub);
|
|
10047
|
+
points.push([frontStub[0] < lane.x ? frontStub[0] : frontStub[2], -0.6]);
|
|
10048
|
+
}
|
|
10049
|
+
} else {
|
|
10050
|
+
const stub = 1;
|
|
10051
|
+
if (door.side === "back") rects.push([door.u - half, door.v, door.u + half, door.v + Math.min(stub, backyard)]);
|
|
10052
|
+
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]);
|
|
10053
|
+
points.push([door.u, door.v]);
|
|
10054
|
+
}
|
|
10055
|
+
}
|
|
10056
|
+
const reached = rects.slice();
|
|
10057
|
+
const pool = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
|
|
10058
|
+
let grows = true;
|
|
10059
|
+
while (grows) {
|
|
10060
|
+
grows = false;
|
|
10061
|
+
for (const r of pool) {
|
|
10062
|
+
if (!reached.includes(r) && reached.some((a) => touches(a, r))) {
|
|
10063
|
+
reached.push(r);
|
|
10064
|
+
grows = true;
|
|
10065
|
+
}
|
|
10066
|
+
}
|
|
10067
|
+
}
|
|
10068
|
+
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);
|
|
10069
|
+
const gate = gates.find((q) => q.type === "pedestrian" && inside(q)) || gates.find((q) => q.type !== "cargo" && inside(q));
|
|
10070
|
+
if (gate) fromId = gate.id;
|
|
10071
|
+
paths.push({ id, type: "pedestrian", from: fromId, to: door.id, width, rects: rects.map(rounded), points: points.map((q) => q.map(r2)) });
|
|
10072
|
+
}
|
|
10073
|
+
}
|
|
10074
|
+
function defineZone(zone) {
|
|
10075
|
+
if (!zone || !zone.id) throw new Error("defineZone: id is required");
|
|
10076
|
+
registries().ZONES[zone.id] = zone;
|
|
10077
|
+
return zone;
|
|
10078
|
+
}
|
|
10079
|
+
var getZone = (id) => registries().ZONES[id];
|
|
10080
|
+
var listZones = () => Object.values(registries().ZONES);
|
|
8784
10081
|
function registerPermeableGround(...types) {
|
|
8785
|
-
for (const t of types) PERMEABLE_GROUNDS.add(t);
|
|
10082
|
+
for (const t of types) registries().PERMEABLE_GROUNDS.add(t);
|
|
10083
|
+
}
|
|
10084
|
+
function resolveZone(z) {
|
|
10085
|
+
if (!z) return null;
|
|
10086
|
+
if (typeof z === "string") {
|
|
10087
|
+
const found = registries().ZONES[z];
|
|
10088
|
+
if (!found) throw new Error("Unknown zone: " + z);
|
|
10089
|
+
return found;
|
|
10090
|
+
}
|
|
10091
|
+
return z;
|
|
10092
|
+
}
|
|
10093
|
+
var floorsOfTemplate = (tpl, op) => (tpl.floorsFn ? tpl.floorsFn(op) : tpl.floors || []).length;
|
|
10094
|
+
var useOf = (tpl) => tpl.use || "residential_single";
|
|
10095
|
+
function measure(plan) {
|
|
10096
|
+
const floors = plan.floors;
|
|
10097
|
+
const top = floors.reduce((m, f) => Math.max(m, f.y0 + f.ceiling), 0);
|
|
10098
|
+
const number = plan.floorNumber != null ? plan.floorNumber : floors.length;
|
|
10099
|
+
const rooms = (plan.rooms || []).filter((r) => r.floor >= 0);
|
|
10100
|
+
const area3 = (r) => r.parts.reduce((s, p) => s + (p[2] - p[0]) * (p[3] - p[1]), 0);
|
|
10101
|
+
const footprint = plan.w * plan.d;
|
|
10102
|
+
const built = rooms.reduce((s, r) => s + area3(r), 0);
|
|
10103
|
+
return { storeys: number, height: r2(top), footprint, built };
|
|
10104
|
+
}
|
|
10105
|
+
function checkLot(zoneIn, lot, plan) {
|
|
10106
|
+
const zone = resolveZone(zoneIn);
|
|
10107
|
+
const out = [];
|
|
10108
|
+
const m = measure(plan);
|
|
10109
|
+
const use = plan.use || "residential_single";
|
|
10110
|
+
if (!zone.allowedUses.includes(use)) out.push({ rule: "use", actual: use, limit: zone.allowedUses.join(",") });
|
|
10111
|
+
if (m.storeys > zone.maxFloors) out.push({ rule: "floors", actual: m.storeys, limit: zone.maxFloors });
|
|
10112
|
+
if (m.height > zone.maxHeight + 1e-9) out.push({ rule: "height", actual: m.height, limit: zone.maxHeight });
|
|
10113
|
+
if (!lot) return out;
|
|
10114
|
+
const [u0, v0, u1, v1] = lot.rect;
|
|
10115
|
+
const width = r2(u1 - u0);
|
|
10116
|
+
const depth = r2(v1 - v0);
|
|
10117
|
+
const area3 = width * depth;
|
|
10118
|
+
const party = plan.partyWalls || [];
|
|
10119
|
+
const sides = [party.includes("left") ? Infinity : lot.sideYard.left, party.includes("right") ? Infinity : lot.sideYard.right];
|
|
10120
|
+
const left = sides[0];
|
|
10121
|
+
const right = sides[1];
|
|
10122
|
+
if (lot.setback < zone.setbacks.front - 1e-9) out.push({ rule: "setback_front", actual: lot.setback, limit: zone.setbacks.front });
|
|
10123
|
+
if (Math.min(left, right) < zone.setbacks.side - 1e-9) out.push({ rule: "setback_side", actual: Math.min(left, right), limit: zone.setbacks.side });
|
|
10124
|
+
if (lot.backyard < zone.setbacks.back - 1e-9) out.push({ rule: "setback_back", actual: lot.backyard, limit: zone.setbacks.back });
|
|
10125
|
+
if (area3 < zone.minLot - 1e-9) out.push({ rule: "min_lot", actual: r2(area3), limit: zone.minLot });
|
|
10126
|
+
if (width < zone.minFrontage - 1e-9) out.push({ rule: "min_frontage", actual: width, limit: zone.minFrontage });
|
|
10127
|
+
const ground = m.footprint;
|
|
10128
|
+
const coverage = ground / area3;
|
|
10129
|
+
if (coverage > zone.maxCoverage + 1e-9) out.push({ rule: "coverage", actual: r2(coverage), limit: zone.maxCoverage });
|
|
10130
|
+
const far = m.built / area3;
|
|
10131
|
+
if (far > zone.maxFar + 1e-9) out.push({ rule: "far", actual: r2(far), limit: zone.maxFar });
|
|
10132
|
+
let permeable = 0;
|
|
10133
|
+
for (const z of lot.zones) if (registries().PERMEABLE_GROUNDS.has(z.ground)) for (const p of z.parts) permeable += (p[2] - p[0]) * (p[3] - p[1]);
|
|
10134
|
+
const permeability = permeable / area3;
|
|
10135
|
+
if (permeability < zone.permeableMin - 1e-9) out.push({ rule: "permeable", actual: r2(permeability), limit: zone.permeableMin });
|
|
10136
|
+
return out;
|
|
10137
|
+
}
|
|
10138
|
+
function clampOptionsToZone(tpl, op, zone, adjustments) {
|
|
10139
|
+
if (tpl.singleFloor) {
|
|
10140
|
+
const ceiling2 = (tpl.floors || tpl.floorsFn?.(op) || [])[0]?.ceiling ?? 2.8;
|
|
10141
|
+
const current2 = op.floor || 3;
|
|
10142
|
+
const limit2 = Math.max(1, Math.min(zone.maxFloors, Math.floor(zone.maxHeight / ceiling2 + 1e-9)));
|
|
10143
|
+
if (current2 <= limit2) return op;
|
|
10144
|
+
adjustments.push({ field: "floor", from: current2, to: limit2, reason: current2 > zone.maxFloors ? "max_floors" : "max_height" });
|
|
10145
|
+
return Object.assign({}, op, { floor: limit2 });
|
|
10146
|
+
}
|
|
10147
|
+
if (!tpl.floorRange || !tpl.floorsFn) return op;
|
|
10148
|
+
const current = floorsOfTemplate(tpl, op);
|
|
10149
|
+
const floors = tpl.floorsFn(op);
|
|
10150
|
+
const ceiling = floors.reduce((m, f) => Math.max(m, f.ceiling), 0) || 2.8;
|
|
10151
|
+
const byHeight = Math.max(1, Math.floor(zone.maxHeight / ceiling + 1e-9));
|
|
10152
|
+
const limit = Math.max(tpl.floorRange[0], Math.min(zone.maxFloors, byHeight, tpl.floorRange[1]));
|
|
10153
|
+
if (current <= limit) return op;
|
|
10154
|
+
adjustments.push({ field: "floors", from: current, to: limit, reason: current > zone.maxFloors ? "max_floors" : "max_height" });
|
|
10155
|
+
return Object.assign({}, op, { floors: limit });
|
|
10156
|
+
}
|
|
10157
|
+
function clampLotToZone(cfg, zone, adjustments, partyWalls = []) {
|
|
10158
|
+
const raise = (field, key, min, reason) => {
|
|
10159
|
+
if (cfg[key] < min) {
|
|
10160
|
+
adjustments.push({ field, from: cfg[key], to: min, reason });
|
|
10161
|
+
cfg[key] = min;
|
|
10162
|
+
}
|
|
10163
|
+
};
|
|
10164
|
+
raise("frontSetback", "frontSetback", zone.setbacks.front, "setback_front");
|
|
10165
|
+
if (!partyWalls.includes("left")) raise("marginLeft", "marginLeft", zone.setbacks.side, "setback_side");
|
|
10166
|
+
if (!partyWalls.includes("right")) raise("marginRight", "marginRight", zone.setbacks.side, "setback_side");
|
|
10167
|
+
raise("backyardDepth", "backyardDepth", zone.setbacks.back, "setback_back");
|
|
10168
|
+
}
|
|
10169
|
+
var ADJUSTABLE_ZONES = /* @__PURE__ */ new Set(["garden", "backyard", "side_yard"]);
|
|
10170
|
+
function enforcePermeability(lot, zones, zone, adjustments) {
|
|
10171
|
+
const target = [...registries().PERMEABLE_GROUNDS][0];
|
|
10172
|
+
if (!target) return;
|
|
10173
|
+
const [u0, v0, u1, v1] = lot.rect;
|
|
10174
|
+
const area3 = (u1 - u0) * (v1 - v0);
|
|
10175
|
+
const areaOfZone = (z) => z.parts.reduce((s, p) => s + (p[2] - p[0]) * (p[3] - p[1]), 0);
|
|
10176
|
+
let permeable = zones.filter((z) => registries().PERMEABLE_GROUNDS.has(z.ground)).reduce((s, z) => s + areaOfZone(z), 0);
|
|
10177
|
+
if (permeable / area3 >= zone.permeableMin) return;
|
|
10178
|
+
const candidates = zones.filter((z) => ADJUSTABLE_ZONES.has(z.type) && !registries().PERMEABLE_GROUNDS.has(z.ground)).sort((a, b) => areaOfZone(b) - areaOfZone(a));
|
|
10179
|
+
for (const z of candidates) {
|
|
10180
|
+
if (permeable / area3 >= zone.permeableMin) break;
|
|
10181
|
+
adjustments.push({ field: "ground:" + z.id, from: z.ground, to: target, reason: "permeable_min" });
|
|
10182
|
+
z.ground = target;
|
|
10183
|
+
permeable += areaOfZone(z);
|
|
10184
|
+
}
|
|
10185
|
+
}
|
|
10186
|
+
var floorsOf = (tpl, op) => tpl.floorsFn ? tpl.floorsFn(op || {}) : tpl.floors;
|
|
10187
|
+
function clampSize(tpl, w, d, op) {
|
|
10188
|
+
const L = tpl.limitsFn ? tpl.limitsFn(op || {}) : tpl.limits;
|
|
10189
|
+
const P0 = tpl.defaultsFn ? tpl.defaultsFn(op || {}) : tpl.defaults;
|
|
10190
|
+
const ww = w == null ? P0.w : clamp3(w, L.w[0], L.w[1]);
|
|
10191
|
+
const dd = d == null ? P0.d : clamp3(d, L.d[0], L.d[1]);
|
|
10192
|
+
return { w: r2(ww), d: r2(dd) };
|
|
10193
|
+
}
|
|
10194
|
+
function buildPlan(tpl, wIn, dIn, opIn, bc = {}) {
|
|
10195
|
+
const op = opIn || {};
|
|
10196
|
+
const { w, d } = clampSize(tpl, wIn, dIn, op);
|
|
10197
|
+
const styleId = resolveStyleId(op.style || (typeof tpl.style === "function" ? tpl.style(op) : tpl.style) || "budget");
|
|
10198
|
+
const FLOOR_DEFS = floorsOf(tpl, op);
|
|
10199
|
+
const style = styleOf(styleId);
|
|
10200
|
+
const plan = {
|
|
10201
|
+
w,
|
|
10202
|
+
d,
|
|
10203
|
+
tpl,
|
|
10204
|
+
floorDefs: FLOOR_DEFS,
|
|
10205
|
+
style: styleId,
|
|
10206
|
+
floors: [],
|
|
10207
|
+
rooms: [],
|
|
10208
|
+
zones: [],
|
|
10209
|
+
doors: [],
|
|
10210
|
+
windows: [],
|
|
10211
|
+
walls: [],
|
|
10212
|
+
gates: [],
|
|
10213
|
+
paths: [],
|
|
10214
|
+
lot: null,
|
|
10215
|
+
openPairs: {},
|
|
10216
|
+
reservations: [],
|
|
10217
|
+
options: op,
|
|
10218
|
+
playerRadius: op.playerRadius ?? PLAYER_RADIUS,
|
|
10219
|
+
carClearance: Object.assign({}, CAR_CLEARANCE, op.carClearance)
|
|
10220
|
+
};
|
|
10221
|
+
FLOOR_DEFS.forEach((A, floorIndex) => {
|
|
10222
|
+
const floorNumber = tpl.singleFloor ? op.floor || 3 : floorIndex + 1;
|
|
10223
|
+
plan.floors.push({
|
|
10224
|
+
index: floorIndex,
|
|
10225
|
+
name: tpl.singleFloor ? "floor_n" : A.label,
|
|
10226
|
+
number: tpl.singleFloor ? floorNumber : void 0,
|
|
10227
|
+
y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : tpl.cumulativeCeilings ? FLOOR_DEFS.slice(0, floorIndex).reduce((t, q) => t + q.ceiling, 0) : floorIndex * A.ceiling,
|
|
10228
|
+
ceiling: A.ceiling
|
|
10229
|
+
});
|
|
10230
|
+
plan.openPairs[floorIndex] = A.openPairs || [];
|
|
10231
|
+
const list2 = A.rooms(w, d, op).map((c) => normalizeRoom(c, floorIndex));
|
|
10232
|
+
for (const c of list2) {
|
|
10233
|
+
if (plan.rooms.some((x) => x.id === c.id)) throw new Error("Duplicate room: " + c.id);
|
|
10234
|
+
plan.rooms.push(c);
|
|
10235
|
+
}
|
|
10236
|
+
const byId2 = {};
|
|
10237
|
+
for (const c of list2) byId2[c.id] = c;
|
|
10238
|
+
const floorDoors = [];
|
|
10239
|
+
(A.doors(w, d, op) || []).forEach((sp, i) => {
|
|
10240
|
+
const a = byId2[sp.a];
|
|
10241
|
+
if (!a) throw new Error("Door references a missing room: " + sp.a);
|
|
10242
|
+
let edge;
|
|
10243
|
+
if (sp.edge) edge = sp.edge;
|
|
10244
|
+
else if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
|
|
10245
|
+
else {
|
|
10246
|
+
const b = byId2[sp.b];
|
|
10247
|
+
if (!b) throw new Error("Door references a missing room: " + sp.b);
|
|
10248
|
+
edge = sharedEdge(a, b, sp.at);
|
|
10249
|
+
}
|
|
10250
|
+
if (!edge) throw new Error("Door without a wall: " + sp.a + " / " + sp.b);
|
|
10251
|
+
const width = sp.width;
|
|
10252
|
+
const margin = width / 2 + (sp.clearance ?? tpl.clearance?.door ?? DOOR_EDGE_CLEARANCE);
|
|
10253
|
+
if (edge.a1 - edge.a0 < width + 0.24) throw new Error("Wall too short for the door: " + sp.a + " / " + sp.b);
|
|
10254
|
+
const c = clamp3(sp.at != null ? sp.at : (edge.a0 + edge.a1) / 2, edge.a0 + margin, edge.a1 - margin);
|
|
10255
|
+
const exterior = sp.b === "outside";
|
|
10256
|
+
const leaf2 = exterior && sp.steel ? 0 : width > 1.1 ? width / 2 : width;
|
|
10257
|
+
const u = edge.axis === "u" ? c : edge.pos;
|
|
10258
|
+
const v = edge.axis === "u" ? edge.pos : c;
|
|
10259
|
+
floorDoors.push({
|
|
10260
|
+
id: "d" + floorIndex + "_" + i,
|
|
10261
|
+
floor: floorIndex,
|
|
10262
|
+
u: r2(u),
|
|
10263
|
+
v: r2(v),
|
|
10264
|
+
axis: edge.axis,
|
|
10265
|
+
pos: edge.pos,
|
|
10266
|
+
at: r2(c),
|
|
10267
|
+
width,
|
|
10268
|
+
a: sp.a,
|
|
10269
|
+
b: sp.b,
|
|
10270
|
+
opensTo: sp.opensTo || sp.a,
|
|
10271
|
+
exterior,
|
|
10272
|
+
side: sp.side || null,
|
|
10273
|
+
leaves: exterior && sp.steel ? 0 : width > 1.1 ? 2 : 1,
|
|
10274
|
+
model: sp.model || (sp.steel ? "steel" : width > 1.1 ? "double" : "single"),
|
|
10275
|
+
type: sp.type || "door",
|
|
10276
|
+
steps: sp.steps || 0,
|
|
10277
|
+
hinge: "start",
|
|
10278
|
+
zones: openingZones(u, v, edge.axis, width, openingDepth(width, leaf2))
|
|
10279
|
+
});
|
|
10280
|
+
});
|
|
10281
|
+
plan.doors.push(...floorDoors);
|
|
10282
|
+
const floorWalls = (tpl.noWalls ? [] : buildWalls(list2, floorDoors, A.openPairs || [], floorIndex)).map((p) => Object.assign(p, { h: p.h || A.ceiling }));
|
|
10283
|
+
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 });
|
|
10284
|
+
plan.windows.push(...windowsOut);
|
|
10285
|
+
plan.walls.push(...floorWalls);
|
|
10286
|
+
});
|
|
10287
|
+
if (tpl.config) plan.config = tpl.config(op, w, d);
|
|
10288
|
+
if (tpl.reservations) tpl.reservations(plan);
|
|
10289
|
+
buildLot2(plan, tpl, w, d, op, style, styleId, bc);
|
|
10290
|
+
return plan;
|
|
10291
|
+
}
|
|
10292
|
+
function placeWindows(params) {
|
|
10293
|
+
const { list: list2, byId: byId2, floorWalls, windowDefs, floorIndex, ceiling, w, d, clearance } = params;
|
|
10294
|
+
const stretches = floorWalls.filter((p) => p.exterior && p.type === "wall");
|
|
10295
|
+
const found = [];
|
|
10296
|
+
const groups = [];
|
|
10297
|
+
let order = 0;
|
|
10298
|
+
for (const def of windowDefs) {
|
|
10299
|
+
const room = byId2[def.room];
|
|
10300
|
+
if (!room) throw new Error("Window references a missing room: " + def.room);
|
|
10301
|
+
const edge = exteriorEdge(room, def.side, w, d, def.at);
|
|
10302
|
+
if (!edge) continue;
|
|
10303
|
+
const centres = def.n ? Array.from({ length: def.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (def.n + 1)) : [def.at];
|
|
10304
|
+
for (const centre of centres) {
|
|
10305
|
+
const onEdge = stretches.filter((t) => t.axis === edge.axis && eq(edge.axis === "u" ? t.v0 : t.u0, edge.pos) && t.sides.includes(def.room));
|
|
10306
|
+
const start = (t) => edge.axis === "u" ? t.u0 : t.v0;
|
|
10307
|
+
const end = (t) => edge.axis === "u" ? t.u1 : t.v1;
|
|
10308
|
+
const gap = (t) => Math.min(Math.abs(centre - start(t)), Math.abs(centre - end(t)));
|
|
10309
|
+
const target = onEdge.find((t) => centre >= start(t) - TOL && centre <= end(t) + TOL) || onEdge.slice().sort((x, y) => gap(x) - gap(y))[0];
|
|
10310
|
+
if (!target) continue;
|
|
10311
|
+
let group = groups.find((x) => x.wall === target && x.def.room === def.room);
|
|
10312
|
+
if (!group) groups.push(group = { wall: target, edge, start: start(target), end: end(target), def, entries: [] });
|
|
10313
|
+
group.entries.push({ def, centre, order: order++ });
|
|
10314
|
+
}
|
|
10315
|
+
}
|
|
10316
|
+
for (const g of groups) {
|
|
10317
|
+
const room = byId2[g.def.room];
|
|
10318
|
+
const bath = room.type === "bathroom";
|
|
10319
|
+
const length = g.end - g.start;
|
|
10320
|
+
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 }));
|
|
10321
|
+
const fits = (l) => l.reduce((t, x) => t + x.width, 0) + (l.length + 1) * clearance <= length + TOL;
|
|
10322
|
+
while (its.length && !fits(its)) {
|
|
10323
|
+
const k = its.reduce((m, x, i, arr) => x.width > arr[m].width ? i : m, 0);
|
|
10324
|
+
if (its[k].width > 0.6 + TOL) its[k] = Object.assign({}, its[k], { width: Math.max(0.6, r2(its[k].width - 0.1)) });
|
|
10325
|
+
else its.pop();
|
|
10326
|
+
}
|
|
10327
|
+
if (!its.length) continue;
|
|
10328
|
+
const space = (length - its.reduce((t, x) => t + x.width, 0)) / (its.length + 1);
|
|
10329
|
+
let pos = g.start + space;
|
|
10330
|
+
for (const x of its) {
|
|
10331
|
+
const c = pos + x.width / 2;
|
|
10332
|
+
pos += x.width + space;
|
|
10333
|
+
const u = g.edge.axis === "u" ? c : g.edge.pos;
|
|
10334
|
+
const v = g.edge.axis === "u" ? g.edge.pos : c;
|
|
10335
|
+
let y0 = x.def.y0 || 1;
|
|
10336
|
+
let y1 = x.def.y1 || 2.15;
|
|
10337
|
+
if (bath) {
|
|
10338
|
+
y0 = Math.max(y0, 2.1);
|
|
10339
|
+
y1 = Math.min(Math.max(y1, y0 + 0.4), ceiling - 0.25);
|
|
10340
|
+
}
|
|
10341
|
+
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 });
|
|
10342
|
+
}
|
|
10343
|
+
}
|
|
10344
|
+
for (const room of list2) {
|
|
10345
|
+
if (WINDOWLESS_ROOM_TYPES.has(room.type) || found.some((j) => j.room === room.id)) continue;
|
|
10346
|
+
let best = null;
|
|
10347
|
+
for (const t of stretches) {
|
|
10348
|
+
if (!t.sides.includes(room.id)) continue;
|
|
10349
|
+
const i0 = t.axis === "u" ? t.u0 : t.v0;
|
|
10350
|
+
const i1 = t.axis === "u" ? t.u1 : t.v1;
|
|
10351
|
+
const length = i1 - i0;
|
|
10352
|
+
const width = Math.min(room.type === "bathroom" ? 0.6 : 1.2, length - 2 * clearance);
|
|
10353
|
+
if (width < 0.6 - TOL || best && best.length >= length) continue;
|
|
10354
|
+
const side = t.axis === "u" ? t.sides[0] === "outside" ? "front" : "back" : t.sides[0] === "outside" ? "left" : "right";
|
|
10355
|
+
const c = (i0 + i1) / 2;
|
|
10356
|
+
const pos = t.axis === "u" ? t.v0 : t.u0;
|
|
10357
|
+
const bath = room.type === "bathroom";
|
|
10358
|
+
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 } };
|
|
10359
|
+
}
|
|
10360
|
+
if (best) found.push(best.window);
|
|
10361
|
+
}
|
|
10362
|
+
found.sort((x, y) => x._order - y._order);
|
|
10363
|
+
found.forEach((j, i) => {
|
|
10364
|
+
delete j._order;
|
|
10365
|
+
j.id = "w" + floorIndex + "_" + i;
|
|
10366
|
+
});
|
|
10367
|
+
return found;
|
|
10368
|
+
}
|
|
10369
|
+
function buildLot2(plan, tpl, w, d, op, style, styleId, bc) {
|
|
10370
|
+
const seed = op.seed == null ? 1 : op.seed;
|
|
10371
|
+
const rFacade = createRng2(seed, STREAM.facade);
|
|
10372
|
+
const facadeStyle = style.facade;
|
|
10373
|
+
const palette2 = style.swatches;
|
|
10374
|
+
const color2 = palette2[Math.floor(rFacade() * palette2.length)];
|
|
10375
|
+
const extra = tpl.facade || {};
|
|
10376
|
+
plan.facade = {
|
|
10377
|
+
style: styleId,
|
|
10378
|
+
color: { name: color2[0], hex: color2[1] },
|
|
10379
|
+
finish: pickWeighted(rFacade, facadeStyle.finish),
|
|
10380
|
+
grilles: rFacade() < facadeStyle.grilles,
|
|
10381
|
+
roof: pickWeighted(rFacade, facadeStyle.roof),
|
|
10382
|
+
balcony: !!extra.balcony,
|
|
10383
|
+
veranda: !!extra.veranda,
|
|
10384
|
+
steelDoor: !!extra.steelDoor,
|
|
10385
|
+
floors: plan.floorDefs.length
|
|
10386
|
+
};
|
|
10387
|
+
if (tpl.facadeExtra) tpl.facadeExtra(plan.facade, rFacade, w, d, op);
|
|
10388
|
+
if (plan.floorDefs.length > 1 && plan.facade.roof === "fiber_cement") plan.facade.roof = "colonial";
|
|
10389
|
+
if (!tpl.lot) return;
|
|
10390
|
+
const cfg = tpl.lot(w, d, style, op);
|
|
10391
|
+
if (bc.zone) clampLotToZone(cfg, bc.zone, bc.adjustments || [], tpl.partyWalls);
|
|
10392
|
+
const rLot = createRng2(seed, STREAM.lot);
|
|
10393
|
+
const setback = cfg.frontSetback;
|
|
10394
|
+
const marginL = cfg.marginLeft;
|
|
10395
|
+
const marginR = cfg.marginRight;
|
|
10396
|
+
const backDepth = cfg.backyardDepth;
|
|
10397
|
+
const lot = {
|
|
10398
|
+
rect: [-marginL, -setback, w + marginR, d + backDepth],
|
|
10399
|
+
setback,
|
|
10400
|
+
sideYard: { left: marginL, right: marginR },
|
|
10401
|
+
backyard: backDepth,
|
|
10402
|
+
style: styleId,
|
|
10403
|
+
zones: [],
|
|
10404
|
+
perimeter: [],
|
|
10405
|
+
gates: [],
|
|
10406
|
+
paths: [],
|
|
10407
|
+
pool: !!cfg.pool
|
|
10408
|
+
};
|
|
10409
|
+
const ZONES = [];
|
|
10410
|
+
const libs = Object.assign({ garden: "garden", backyard: "backyard", parking: "parking", sideYard: "side_yard" }, cfg.libraries || {});
|
|
10411
|
+
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 }));
|
|
10412
|
+
if (setback > 0) {
|
|
10413
|
+
let parts = [[-marginL, -setback, w + marginR, 0]];
|
|
10414
|
+
if (cfg.parking) {
|
|
10415
|
+
const garageU = cfg.garage.u;
|
|
10416
|
+
const vw = cfg.parking.width;
|
|
10417
|
+
const x0 = Math.max(-marginL, garageU - vw / 2);
|
|
10418
|
+
const x1 = Math.min(w + marginR, garageU + vw / 2);
|
|
10419
|
+
parts = [];
|
|
10420
|
+
if (x0 + marginL > 0.8) parts.push([-marginL, -setback, x0, 0]);
|
|
10421
|
+
if (w + marginR - x1 > 0.8) parts.push([x1, -setback, w + marginR, 0]);
|
|
10422
|
+
addZone("parking", "parking", "parking", [[x0, -setback, x1, 0]], libs.parking);
|
|
10423
|
+
if (!parts.length) parts = [[x1, -setback, w + marginR, 0]];
|
|
10424
|
+
}
|
|
10425
|
+
parts.sort((a, b) => b[2] - b[0] - (a[2] - a[0]));
|
|
10426
|
+
addZone("garden", "garden", "garden", parts, libs.garden);
|
|
10427
|
+
}
|
|
10428
|
+
const yard = cfg.yardZones;
|
|
10429
|
+
if (backDepth > 0 && yard) {
|
|
10430
|
+
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);
|
|
10431
|
+
} else if (backDepth > 0) addZone("backyard", "backyard", "backyard", [[-marginL, d, w + marginR, d + backDepth]], libs.backyard);
|
|
10432
|
+
for (const q of cfg.extraZones || []) addZone(q.id, q.type, q.label || q.id, q.parts, q.library);
|
|
10433
|
+
if (marginL > 0.5) addZone("side_left", "side_yard", "side_left", [[-marginL, 0, 0, d]], libs.sideYard);
|
|
10434
|
+
if (marginR > 0.5) addZone("side_right", "side_yard", "side_right", [[w, 0, w + marginR, d]], libs.sideYard);
|
|
10435
|
+
const gates = [];
|
|
10436
|
+
const addGate = (type, model, u, v, width, side, toTarget) => {
|
|
10437
|
+
const g = {
|
|
10438
|
+
id: "g" + gates.length,
|
|
10439
|
+
type,
|
|
10440
|
+
model,
|
|
10441
|
+
u: r2(u),
|
|
10442
|
+
v: r2(v),
|
|
10443
|
+
axis: "u",
|
|
10444
|
+
width,
|
|
10445
|
+
height: type === "pedestrian" ? style.height : Math.min(style.height, 2.2),
|
|
10446
|
+
side,
|
|
10447
|
+
to: toTarget || null,
|
|
10448
|
+
material: "iron"
|
|
10449
|
+
};
|
|
10450
|
+
g.zones = openingZones(u, v, "u", width, openingDepth(width, type === "pedestrian" ? width : 0));
|
|
10451
|
+
gates.push(g);
|
|
10452
|
+
return g;
|
|
10453
|
+
};
|
|
10454
|
+
if (cfg.pedestrian && setback > 0) addGate("pedestrian", "pedestrian", cfg.pedestrian.u, -setback, 1, "front", "door");
|
|
10455
|
+
if (cfg.garage && setback > 0) addGate("garage", pickWeighted(rLot, style.gate.garage), cfg.garage.u, -setback, cfg.garage.width, "front", cfg.garage.to);
|
|
10456
|
+
if (cfg.cargo) addGate("cargo", "sliding", cfg.cargo.u, d + backDepth, cfg.cargo.width, "back", "stockroom");
|
|
10457
|
+
plan.gates = gates;
|
|
10458
|
+
lot.gates = gates;
|
|
10459
|
+
const paths = [];
|
|
10460
|
+
const target = (a, side) => plan.doors.find((p) => p.exterior && p.a === a && p.side === side);
|
|
10461
|
+
for (const g of gates) {
|
|
10462
|
+
if (g.type === "pedestrian" && cfg.doorTarget) {
|
|
10463
|
+
const pt = target(cfg.doorTarget.a, cfg.doorTarget.side);
|
|
10464
|
+
if (!pt) continue;
|
|
10465
|
+
const gateU = g.u;
|
|
10466
|
+
const dx = pt.u;
|
|
10467
|
+
const ym = -setback / 2;
|
|
10468
|
+
const L = 1;
|
|
10469
|
+
const h = L / 2;
|
|
10470
|
+
const rs = [];
|
|
10471
|
+
if (Math.abs(gateU - dx) < 0.05) rs.push([dx - h, -setback, dx + h, 0]);
|
|
10472
|
+
else {
|
|
10473
|
+
rs.push([gateU - h, -setback, gateU + h, ym + h]);
|
|
10474
|
+
rs.push([Math.min(gateU, dx) - h, ym - h, Math.max(gateU, dx) + h, ym + h]);
|
|
10475
|
+
rs.push([dx - h, ym - h, dx + h, 0]);
|
|
10476
|
+
}
|
|
10477
|
+
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]] });
|
|
10478
|
+
} else if (g.type === "garage" && g.to === "inner_garage") {
|
|
10479
|
+
const pt = plan.doors.find((p) => p.exterior && p.side === "front" && Math.abs(p.u - g.u) < 0.6 && p.width >= 2.5);
|
|
10480
|
+
if (!pt) continue;
|
|
10481
|
+
const hw = Math.max(g.width, pt.width) / 2;
|
|
10482
|
+
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]] });
|
|
10483
|
+
} else if (g.type === "cargo") {
|
|
10484
|
+
const pt = target("stockroom", "back");
|
|
10485
|
+
if (!pt) continue;
|
|
10486
|
+
const hw = Math.max(g.width, pt.width) / 2;
|
|
10487
|
+
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]] });
|
|
10488
|
+
}
|
|
10489
|
+
}
|
|
10490
|
+
if (tpl.paths) paths.push(...tpl.paths(plan, { gates, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, d }));
|
|
10491
|
+
else addExteriorDoorPaths(plan, { gates, paths, zones: ZONES, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, target: cfg.doorTarget });
|
|
10492
|
+
plan.paths = paths;
|
|
10493
|
+
lot.paths = paths;
|
|
10494
|
+
const boundary = (rule) => {
|
|
10495
|
+
const type = pickWeighted(rLot, rule.type);
|
|
10496
|
+
const top = type === "railing" ? "none" : pickWeighted(rLot, rule.top);
|
|
10497
|
+
const wallHeight = type === "railing" ? 1.2 : type === "fence" ? style.fenceHeight ?? 2 : style.height;
|
|
10498
|
+
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 };
|
|
10499
|
+
};
|
|
10500
|
+
const A = { front: boundary(style.front), left: boundary(style.sideYard), right: boundary(style.sideYard), back: boundary(style.back) };
|
|
10501
|
+
const seg = (side, u0, v0, u1, v1) => {
|
|
10502
|
+
if (Math.abs(u1 - u0) + Math.abs(v1 - v0) < 0.05) return;
|
|
10503
|
+
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]));
|
|
10504
|
+
};
|
|
10505
|
+
const cutRun = (side, de, to, vfix, gs) => {
|
|
10506
|
+
let pieces = [[de, to]];
|
|
10507
|
+
for (const g of gs) {
|
|
10508
|
+
const g0 = g.u - g.width / 2;
|
|
10509
|
+
const g1 = g.u + g.width / 2;
|
|
10510
|
+
const n = [];
|
|
10511
|
+
for (const [x, y] of pieces) {
|
|
10512
|
+
if (g1 <= x || g0 >= y) {
|
|
10513
|
+
n.push([x, y]);
|
|
10514
|
+
continue;
|
|
10515
|
+
}
|
|
10516
|
+
if (g0 > x) n.push([x, g0]);
|
|
10517
|
+
if (g1 < y) n.push([g1, y]);
|
|
10518
|
+
}
|
|
10519
|
+
pieces = n;
|
|
10520
|
+
}
|
|
10521
|
+
for (const [x, y] of pieces) seg(side, x, vfix, y, vfix);
|
|
10522
|
+
};
|
|
10523
|
+
if (setback > 0)
|
|
10524
|
+
cutRun("front", -marginL, w + marginR, -setback, gates.filter((g) => g.side === "front"));
|
|
10525
|
+
cutRun("back", -marginL, w + marginR, d + backDepth, gates.filter((g) => g.side === "back"));
|
|
10526
|
+
const vi = setback > 0 ? -setback : 0;
|
|
10527
|
+
if (marginL > 0.5) seg("left", -marginL, vi, -marginL, d + backDepth);
|
|
10528
|
+
else if (backDepth > 0) seg("left", 0, d, 0, d + backDepth);
|
|
10529
|
+
if (marginR > 0.5) seg("right", w + marginR, vi, w + marginR, d + backDepth);
|
|
10530
|
+
else if (backDepth > 0) seg("right", w, d, w, d + backDepth);
|
|
10531
|
+
for (const z of ZONES) z.ground = (cfg.grounds || {})[z.id] || (cfg.grounds || {})[z.type] || pickWeighted(rLot, style.grounds[z.type] || { concrete: 1 });
|
|
10532
|
+
if (bc.zone) enforcePermeability(lot, ZONES, bc.zone, bc.adjustments || []);
|
|
10533
|
+
plan.zones = ZONES;
|
|
10534
|
+
lot.zones = ZONES;
|
|
10535
|
+
plan.lot = lot;
|
|
10536
|
+
}
|
|
10537
|
+
var HIGH_SATURATION = 0.4;
|
|
10538
|
+
var MIN_WALL_FLOOR_CONTRAST = 20;
|
|
10539
|
+
var MIN_FABRIC_FLOOR_CONTRAST = 15;
|
|
10540
|
+
var SCHEMES = ["analogous", "complementary", "split_complementary", "triad"];
|
|
10541
|
+
var HARMONY_OFFSETS = { analogous: [-30, 30], complementary: [180, 165], split_complementary: [150, 210], triad: [120, 240] };
|
|
10542
|
+
var ROLE_FABRIC_MAIN = "fabric_main";
|
|
10543
|
+
var ROLE_FABRIC_ACCENT = "fabric_accent";
|
|
10544
|
+
var ROLE_FLOOR_REFERENCE = "floor_living";
|
|
10545
|
+
var hslToInt = (h, s, l) => {
|
|
10546
|
+
h = (h % 360 + 360) % 360;
|
|
10547
|
+
s = clamp3(s, 0, 1);
|
|
10548
|
+
l = clamp3(l, 0, 1);
|
|
10549
|
+
const a = s * Math.min(l, 1 - l);
|
|
10550
|
+
const f = (n) => {
|
|
10551
|
+
const k = (n + h / 30) % 12;
|
|
10552
|
+
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
10553
|
+
};
|
|
10554
|
+
return Math.round(f(0) * 255) << 16 | Math.round(f(8) * 255) << 8 | Math.round(f(4) * 255);
|
|
10555
|
+
};
|
|
10556
|
+
var intToHsl = (n) => {
|
|
10557
|
+
const r = (n >> 16 & 255) / 255;
|
|
10558
|
+
const g = (n >> 8 & 255) / 255;
|
|
10559
|
+
const b = (n & 255) / 255;
|
|
10560
|
+
const mx = Math.max(r, g, b);
|
|
10561
|
+
const mn = Math.min(r, g, b);
|
|
10562
|
+
const l = (mx + mn) / 2;
|
|
10563
|
+
const d = mx - mn;
|
|
10564
|
+
let h = 0;
|
|
10565
|
+
let s = 0;
|
|
10566
|
+
if (d > 1e-9) {
|
|
10567
|
+
s = d / (1 - Math.abs(2 * l - 1));
|
|
10568
|
+
h = mx === r ? (g - b) / d + (g < b ? 6 : 0) : mx === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
10569
|
+
h *= 60;
|
|
10570
|
+
}
|
|
10571
|
+
return { h, s, l };
|
|
10572
|
+
};
|
|
10573
|
+
var lstar = (n) => {
|
|
10574
|
+
const linear = (c) => (c /= 255) <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
10575
|
+
const Y = 0.2126 * linear(n >> 16 & 255) + 0.7152 * linear(n >> 8 & 255) + 0.0722 * linear(n & 255);
|
|
10576
|
+
return Y > 216 / 24389 ? 116 * Math.cbrt(Y) - 16 : Y * 24389 / 27;
|
|
10577
|
+
};
|
|
10578
|
+
var hexString = (n) => "#" + (n >>> 0).toString(16).padStart(6, "0");
|
|
10579
|
+
function palette(o) {
|
|
10580
|
+
const opt = o || {};
|
|
10581
|
+
let style = resolveStyleId(opt.style || "budget");
|
|
10582
|
+
if (!registries().PALETTE_STYLES[style]) style = registries().PALETTE_STYLES.mid ? "mid" : Object.keys(registries().PALETTE_STYLES)[0];
|
|
10583
|
+
const def = registries().PALETTE_STYLES[style];
|
|
10584
|
+
const state = opt.state || "normal";
|
|
10585
|
+
const seed = opt.seed == null ? 1 : opt.seed;
|
|
10586
|
+
const rng = createRng2(seed, STREAM.palettePrefix + (def.seedKey ?? style));
|
|
10587
|
+
const table = def.facadeColors;
|
|
10588
|
+
const base = table[Math.floor(rng() * table.length)];
|
|
10589
|
+
const scheme = SCHEMES[Math.floor(rng() * 4)];
|
|
10590
|
+
const offsets = HARMONY_OFFSETS[scheme];
|
|
10591
|
+
const jit = (v, a) => v + (rng() - 0.5) * a;
|
|
10592
|
+
const hsl = (h, s, l) => ({ h: (h % 360 + 360) % 360, s: clamp3(s, 0, 1), l: clamp3(l, 0, 1) });
|
|
10593
|
+
const H0 = base[1];
|
|
10594
|
+
const H1 = H0 + offsets[0];
|
|
10595
|
+
const H2 = H0 + offsets[1];
|
|
10596
|
+
const draw = { rng, base, H0, H1, H2, jit, hsl };
|
|
10597
|
+
const early = def.early(draw);
|
|
10598
|
+
const { wallMode, sMax } = early;
|
|
10599
|
+
const R = Object.assign({}, early.roles);
|
|
10600
|
+
const ctx = Object.assign({ style, sMax, wallMode, generalFloor: early.generalFloor }, draw);
|
|
10601
|
+
for (const c of registries().ROOM_COLORS) {
|
|
10602
|
+
const [wh, wl] = c.wall(ctx);
|
|
10603
|
+
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));
|
|
10604
|
+
const pi = c.floor || early.generalFloor;
|
|
10605
|
+
R["floor_" + c.type] = hsl(pi[0], Math.min(pi[1], wallMode ? 0.18 : 0.3), jit(pi[2], 0.04));
|
|
10606
|
+
}
|
|
10607
|
+
Object.assign(R, def.late(draw, early));
|
|
10608
|
+
registries().STATE_PLUGINS[state]?.adjustPalette?.(R);
|
|
10609
|
+
const adjust = (color2, ref, min, preferredDir) => {
|
|
10610
|
+
for (let i = 0; i < 60; i++) {
|
|
10611
|
+
if (Math.abs(lstar(hslToInt(color2.h, color2.s, color2.l)) - lstar(hslToInt(ref.h, ref.s, ref.l))) >= min) return;
|
|
10612
|
+
const refL = lstar(hslToInt(ref.h, ref.s, ref.l));
|
|
10613
|
+
let dir = preferredDir || (refL > 50 ? -1 : 1);
|
|
10614
|
+
if (dir < 0 && color2.l <= 0.06 || dir > 0 && color2.l >= 0.94) dir = -dir;
|
|
10615
|
+
color2.l = clamp3(color2.l + dir * 0.015, 0.04, 0.96);
|
|
10616
|
+
}
|
|
10617
|
+
};
|
|
10618
|
+
const types = registries().ROOM_COLORS.map((c) => c.type);
|
|
10619
|
+
for (const c of types) {
|
|
10620
|
+
const w = R["wall_" + c];
|
|
10621
|
+
const p = R["floor_" + c];
|
|
10622
|
+
if (w.s > 0.3) p.s = Math.min(p.s, 0.2);
|
|
10623
|
+
if (p.s > HIGH_SATURATION) p.s = HIGH_SATURATION - 0.05;
|
|
10624
|
+
adjust(p, w, MIN_WALL_FLOOR_CONTRAST + 2, lstar(hslToInt(w.h, w.s, w.l)) > 50 ? -1 : 1);
|
|
10625
|
+
}
|
|
10626
|
+
const floorRef = R[ROLE_FLOOR_REFERENCE] || R["floor_" + types[0]];
|
|
10627
|
+
const fabric = R[ROLE_FABRIC_MAIN];
|
|
10628
|
+
adjust(fabric, floorRef, MIN_FABRIC_FLOOR_CONTRAST + 3);
|
|
10629
|
+
adjust(R[ROLE_FABRIC_ACCENT], floorRef, MIN_FABRIC_FLOOR_CONTRAST + 3);
|
|
10630
|
+
for (const c of types) {
|
|
10631
|
+
const p = R["floor_" + c];
|
|
10632
|
+
if (Math.abs(lstar(hslToInt(fabric.h, fabric.s, fabric.l)) - lstar(hslToInt(p.h, p.s, p.l))) < MIN_FABRIC_FLOOR_CONTRAST)
|
|
10633
|
+
adjust(p, fabric, MIN_FABRIC_FLOOR_CONTRAST + 2, lstar(hslToInt(fabric.h, fabric.s, fabric.l)) > 50 ? -1 : 1);
|
|
10634
|
+
}
|
|
10635
|
+
const out = { meta: { seed, style, state, scheme, base: base[0], accentMode: wallMode ? "wall" : "object" } };
|
|
10636
|
+
for (const k of Object.keys(R)) out[k] = hslToInt(R[k].h, R[k].s, R[k].l);
|
|
10637
|
+
const num2 = (k) => out[k];
|
|
10638
|
+
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;
|
|
10639
|
+
const search = (color2, ok) => {
|
|
10640
|
+
const h = intToHsl(color2);
|
|
10641
|
+
let bestValue = null;
|
|
10642
|
+
let md = 9;
|
|
10643
|
+
for (let l = 0.02; l <= 0.98; l += 5e-3) {
|
|
10644
|
+
const n = hslToInt(h.h, h.s, l);
|
|
10645
|
+
if (ok(n) && Math.abs(l - h.l) < md) {
|
|
10646
|
+
md = Math.abs(l - h.l);
|
|
10647
|
+
bestValue = n;
|
|
10648
|
+
}
|
|
10649
|
+
}
|
|
10650
|
+
return bestValue;
|
|
10651
|
+
};
|
|
10652
|
+
for (const c of types) {
|
|
10653
|
+
if (pairOk(num2("wall_" + c), num2("floor_" + c))) continue;
|
|
10654
|
+
const n = search(num2("floor_" + c), (p) => pairOk(num2("wall_" + c), p));
|
|
10655
|
+
if (n != null) {
|
|
10656
|
+
out["floor_" + c] = n;
|
|
10657
|
+
continue;
|
|
10658
|
+
}
|
|
10659
|
+
const w = search(num2("wall_" + c), (w2) => pairOk(w2, num2("floor_" + c)));
|
|
10660
|
+
if (w != null) out["wall_" + c] = w;
|
|
10661
|
+
}
|
|
10662
|
+
return out;
|
|
10663
|
+
}
|
|
10664
|
+
var SIDE_NORMAL = { left: [1, 0], right: [-1, 0], front: [0, 1], back: [0, -1] };
|
|
10665
|
+
var FIXED_FACING = { "u+": [1, 0], "u-": [-1, 0], "v+": [0, 1], "v-": [0, -1] };
|
|
10666
|
+
var SIDE_OPPOSITE = { front: "back", back: "front", left: "right", right: "left" };
|
|
10667
|
+
var WINDOW_SIDE = { front: "front", back: "back", left: "left", right: "right" };
|
|
10668
|
+
function slotDefsOf(plan, room) {
|
|
10669
|
+
const name = room.library || room.type;
|
|
10670
|
+
const key = registries().LIBRARY_ALIAS[name] || name;
|
|
10671
|
+
const library = room.slots ? room.slots : registries().LIBRARIES[key];
|
|
10672
|
+
if (!library) return [];
|
|
10673
|
+
const R = inner(room.parts[0]);
|
|
10674
|
+
let defs = typeof library === "function" ? library({ w: R[2] - R[0], d: R[3] - R[1] }) : library.slice();
|
|
10675
|
+
defs = defs.map((x) => Object.assign({}, x));
|
|
10676
|
+
for (const id of room.without || []) defs = defs.filter((x) => x.id !== id);
|
|
10677
|
+
for (const [id, aj] of Object.entries(room.overrides || {})) {
|
|
10678
|
+
const i = defs.findIndex((x) => x.id === id);
|
|
10679
|
+
if (i < 0) continue;
|
|
10680
|
+
if (aj === null) defs.splice(i, 1);
|
|
10681
|
+
else defs[i] = Object.assign(defs[i], aj);
|
|
10682
|
+
}
|
|
10683
|
+
defs.push(...(room.extras || []).map((x) => Object.assign({}, x)));
|
|
10684
|
+
const out = [];
|
|
10685
|
+
for (const def of defs) {
|
|
10686
|
+
def.parent = def.on;
|
|
10687
|
+
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 }));
|
|
10688
|
+
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 }));
|
|
10689
|
+
else if (def.anchor === "window" && def.n == null) {
|
|
10690
|
+
const windowList = windowsOfRoom(plan, room);
|
|
10691
|
+
windowList.forEach((_j, i) => out.push(Object.assign({}, def, { id: def.id + "@w" + i, n: i, group: def.group || def.id })));
|
|
10692
|
+
} else out.push(def);
|
|
10693
|
+
}
|
|
10694
|
+
return out;
|
|
10695
|
+
}
|
|
10696
|
+
function resolveSlots(plan) {
|
|
10697
|
+
const all = [];
|
|
10698
|
+
for (const room of plan.rooms.concat(plan.zones)) {
|
|
10699
|
+
const defs = slotDefsOf(plan, room);
|
|
10700
|
+
const byId2 = {};
|
|
10701
|
+
const zones = room.type === "stairs" ? plan.floors.flatMap((_, floorIndex) => zonesOfFloor(plan, floorIndex)) : zonesOfFloor(plan, room.floor);
|
|
10702
|
+
for (const def of defs) {
|
|
10703
|
+
const s = resolveSlot(plan, room, def, byId2, zones);
|
|
10704
|
+
byId2[def.id] = s;
|
|
10705
|
+
all.push(s);
|
|
10706
|
+
}
|
|
10707
|
+
}
|
|
10708
|
+
return all;
|
|
10709
|
+
}
|
|
10710
|
+
function resolveSlot(plan, room, def, byId2, zones) {
|
|
10711
|
+
const base = {
|
|
10712
|
+
id: def.id,
|
|
10713
|
+
room: room.id,
|
|
10714
|
+
floor: room.floor,
|
|
10715
|
+
outdoor: room.outdoor,
|
|
10716
|
+
anchor: def.anchor,
|
|
10717
|
+
side: def.side || def.corner || null,
|
|
10718
|
+
cats: def.cats,
|
|
10719
|
+
slide: def.slide,
|
|
10720
|
+
required: !!def.required,
|
|
10721
|
+
chance: def.required ? 1 : def.chance == null ? 1 : def.chance,
|
|
10722
|
+
clearance: def.clearance == null ? 0.5 : def.clearance,
|
|
10723
|
+
front: def.facing || "center",
|
|
10724
|
+
group: def.group || null,
|
|
10725
|
+
requires: def.requires || null,
|
|
10726
|
+
on: def.parent || null,
|
|
10727
|
+
target: def.target || null,
|
|
10728
|
+
valid: true,
|
|
10729
|
+
reason: null
|
|
10730
|
+
};
|
|
10731
|
+
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 });
|
|
10732
|
+
if (def.anchor === "on" || def.anchor === "inside") {
|
|
10733
|
+
const parent = byId2[def.parent];
|
|
10734
|
+
if (!parent || !parent.valid) return invalid("invalid_parent");
|
|
10735
|
+
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 });
|
|
10736
|
+
}
|
|
10737
|
+
const size = def.size;
|
|
10738
|
+
const roomCenter = (() => {
|
|
10739
|
+
const R0 = inner(room.parts[0]);
|
|
10740
|
+
return [(R0[0] + R0[2]) / 2, (R0[1] + R0[3]) / 2];
|
|
10741
|
+
})();
|
|
10742
|
+
const openings = openingsOfFloor(plan, room.floor);
|
|
10743
|
+
let point = [0, 0];
|
|
10744
|
+
const targetPoint = (rule2) => {
|
|
10745
|
+
if (rule2 === "door") {
|
|
10746
|
+
let best = null;
|
|
10747
|
+
let bestDist = 1e9;
|
|
10748
|
+
for (const ab of openings) {
|
|
10749
|
+
const dist2 = Math.hypot(ab.u - point[0], ab.v - point[1]);
|
|
10750
|
+
if (dist2 < bestDist) {
|
|
10751
|
+
bestDist = dist2;
|
|
10752
|
+
best = ab;
|
|
10753
|
+
}
|
|
10754
|
+
}
|
|
10755
|
+
return best ? [best.u, best.v] : roomCenter;
|
|
10756
|
+
}
|
|
10757
|
+
if (rule2 === "target") {
|
|
10758
|
+
const t = byId2[def.target];
|
|
10759
|
+
return t && t.valid ? [t.x, t.z] : roomCenter;
|
|
10760
|
+
}
|
|
10761
|
+
return roomCenter;
|
|
10762
|
+
};
|
|
10763
|
+
const free = (rect) => !!def.floorFree || !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
|
|
10764
|
+
const rule = def.facing || "center";
|
|
10765
|
+
let R = inner(room.parts[Math.min(def.part || 0, room.parts.length - 1)]);
|
|
10766
|
+
if (def.anchor === "wall" || def.anchor === "window") {
|
|
10767
|
+
let side = def.side;
|
|
10768
|
+
let a = null;
|
|
10769
|
+
let win = null;
|
|
10770
|
+
if (def.anchor === "window") {
|
|
10771
|
+
const windowList = windowsOfRoom(plan, room);
|
|
10772
|
+
win = windowList[def.n];
|
|
10773
|
+
if (!win) return invalid("no_window");
|
|
10774
|
+
side = room.outdoor ? SIDE_OPPOSITE[win.side] : WINDOW_SIDE[win.side];
|
|
10775
|
+
const cand = room.parts.map(inner).find(
|
|
10776
|
+
(r) => (win.axis === "u" ? win.u >= r[0] - TOL && win.u <= r[2] + TOL : win.v >= r[1] - TOL && win.v <= r[3] + TOL) && (side === "left" && eq(r[0], win.pos + MARGIN) || side === "right" && eq(r[2], win.pos - MARGIN) || side === "front" && eq(r[1], win.pos + MARGIN) || side === "back" && eq(r[3], win.pos - MARGIN))
|
|
10777
|
+
);
|
|
10778
|
+
if (!cand) return invalid("window_outside_room");
|
|
10779
|
+
R = cand;
|
|
10780
|
+
a = win.axis === "u" ? win.u : win.v;
|
|
10781
|
+
}
|
|
10782
|
+
const n = SIDE_NORMAL[side];
|
|
10783
|
+
if (!n) return invalid("invalid_side");
|
|
10784
|
+
const horizontal = side === "front" || side === "back";
|
|
10785
|
+
const a0 = horizontal ? R[0] : R[1];
|
|
10786
|
+
const a1 = horizontal ? R[2] : R[3];
|
|
10787
|
+
const len2 = a1 - a0;
|
|
10788
|
+
const depth = size[1];
|
|
10789
|
+
if (depth > (horizontal ? R[3] - R[1] : R[2] - R[0]) + EPS) return invalid("larger_than_room");
|
|
10790
|
+
const hw = Math.min(size[0], len2) / 2;
|
|
10791
|
+
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]];
|
|
10792
|
+
const nominal = a != null ? a : a0 + hw + (def.pos == null ? 0.5 : def.pos) * Math.max(0, len2 - 2 * hw) + (def.shift || 0);
|
|
10793
|
+
const wp = side === "left" ? R[0] - MARGIN : side === "right" ? R[2] + MARGIN : side === "front" ? R[1] - MARGIN : R[3] + MARGIN;
|
|
10794
|
+
const pa = clamp3(nominal, a0 + hw, a1 - hw);
|
|
10795
|
+
let found = null;
|
|
10796
|
+
const steps = [0];
|
|
10797
|
+
if (def.anchor !== "window") for (let k = 1; k <= 60; k++) steps.push(k, -k);
|
|
10798
|
+
for (const k of steps) {
|
|
10799
|
+
const ac = pa + k * 0.05;
|
|
10800
|
+
if (ac - hw < a0 - EPS || ac + hw > a1 + EPS) continue;
|
|
10801
|
+
if (free(cell(ac))) {
|
|
10802
|
+
found = ac;
|
|
10803
|
+
break;
|
|
10804
|
+
}
|
|
10805
|
+
}
|
|
10806
|
+
if (found == null) return invalid("blocked_by_door");
|
|
10807
|
+
const sp = horizontal ? [found, wp - n[1] * 0.1] : [wp - n[0] * 0.1, found];
|
|
10808
|
+
if (isOpenSide(plan, room, sp[0], sp[1])) return invalid("open_wall");
|
|
10809
|
+
const rect = cell(found);
|
|
10810
|
+
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 } });
|
|
10811
|
+
}
|
|
10812
|
+
if (def.anchor === "corner") {
|
|
10813
|
+
const [cornerV, cornerU] = def.corner.split("-");
|
|
10814
|
+
const normalV = SIDE_NORMAL[cornerV];
|
|
10815
|
+
const normalU = SIDE_NORMAL[cornerU];
|
|
10816
|
+
if (!normalV || !normalU) return invalid("invalid_corner");
|
|
10817
|
+
const kx = cornerU === "left" ? R[0] : R[2];
|
|
10818
|
+
const kz = cornerV === "front" ? R[1] : R[3];
|
|
10819
|
+
point = [kx, kz];
|
|
10820
|
+
let f;
|
|
10821
|
+
if (FIXED_FACING[rule]) {
|
|
10822
|
+
f = FIXED_FACING[rule];
|
|
10823
|
+
if (!(f[0] === normalU[0] && f[1] === normalU[1] || f[0] === normalV[0] && f[1] === normalV[1])) f = normalU;
|
|
10824
|
+
} else {
|
|
10825
|
+
const t = targetPoint(rule);
|
|
10826
|
+
f = Math.abs(t[0] - kx) >= Math.abs(t[1] - kz) ? normalU : normalV;
|
|
10827
|
+
}
|
|
10828
|
+
const [extentU, extentV] = extent(size, f);
|
|
10829
|
+
if (extentU > R[2] - R[0] + EPS || extentV > R[3] - R[1] + EPS) return invalid("larger_than_room");
|
|
10830
|
+
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]];
|
|
10831
|
+
if (!free(rect)) return invalid("blocked_by_door");
|
|
10832
|
+
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");
|
|
10833
|
+
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 } });
|
|
10834
|
+
}
|
|
10835
|
+
if (def.anchor === "center") {
|
|
10836
|
+
let cx = R[0] + (def.fx == null ? 0.5 : def.fx) * (R[2] - R[0]) + (def.dx || 0);
|
|
10837
|
+
let cz = R[1] + (def.fz == null ? 0.5 : def.fz) * (R[3] - R[1]) + (def.dz || 0);
|
|
10838
|
+
point = [cx, cz];
|
|
10839
|
+
let f;
|
|
10840
|
+
if (FIXED_FACING[rule]) f = FIXED_FACING[rule];
|
|
10841
|
+
else {
|
|
10842
|
+
const t = targetPoint(rule);
|
|
10843
|
+
const dx = t[0] - cx;
|
|
10844
|
+
const dz = t[1] - cz;
|
|
10845
|
+
f = Math.hypot(dx, dz) < 0.05 ? [0, 1] : cardinal(dx, dz);
|
|
10846
|
+
}
|
|
10847
|
+
const [extentU, extentV] = extent(size, f);
|
|
10848
|
+
if (extentU > R[2] - R[0] + EPS || extentV > R[3] - R[1] + EPS) return invalid("larger_than_room");
|
|
10849
|
+
cx = clamp3(cx, R[0] + extentU / 2, R[2] - extentU / 2);
|
|
10850
|
+
cz = clamp3(cz, R[1] + extentV / 2, R[3] - extentV / 2);
|
|
10851
|
+
const rect = [cx - extentU / 2, cz - extentV / 2, cx + extentU / 2, cz + extentV / 2];
|
|
10852
|
+
if (!free(rect)) return invalid("blocked_by_door");
|
|
10853
|
+
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 } });
|
|
10854
|
+
}
|
|
10855
|
+
return invalid("unknown_anchor");
|
|
10856
|
+
}
|
|
10857
|
+
function templateOf(t) {
|
|
10858
|
+
if (typeof t === "string") {
|
|
10859
|
+
if (!registries().TEMPLATES[t]) throw new Error("Unknown template: " + t);
|
|
10860
|
+
return registries().TEMPLATES[t];
|
|
10861
|
+
}
|
|
10862
|
+
return t;
|
|
10863
|
+
}
|
|
10864
|
+
var cleanSlot = (s) => {
|
|
10865
|
+
const o = {};
|
|
10866
|
+
for (const k of Object.keys(s)) if (k[0] !== "_") o[k] = s[k];
|
|
10867
|
+
o.cats = Object.keys(s.cats || {});
|
|
10868
|
+
return o;
|
|
10869
|
+
};
|
|
10870
|
+
function slots(template, options) {
|
|
10871
|
+
const tpl = templateOf(template);
|
|
10872
|
+
const o = options || {};
|
|
10873
|
+
const plan = buildPlan(tpl, o.w, o.d, o);
|
|
10874
|
+
const locale = o.locale || "en";
|
|
10875
|
+
return resolveSlots(plan).map(cleanSlot).map((s) => s.reason ? Object.assign(s, { reason: translate("reasons", s.reason, locale) }) : s);
|
|
10876
|
+
}
|
|
10877
|
+
function pickState(requested, seed) {
|
|
10878
|
+
if (requested && registries().STATE_PLUGINS[requested]) return requested;
|
|
10879
|
+
const dist2 = registries().stateDistribution.weights;
|
|
10880
|
+
if (!dist2) return "normal";
|
|
10881
|
+
const x = createRng2(seed, registries().stateDistribution.seedKey)();
|
|
10882
|
+
let acc = 0;
|
|
10883
|
+
const keys = Object.keys(dist2);
|
|
10884
|
+
for (const k of keys) {
|
|
10885
|
+
acc += dist2[k];
|
|
10886
|
+
if (x < acc) return k;
|
|
10887
|
+
}
|
|
10888
|
+
return keys[keys.length - 1];
|
|
10889
|
+
}
|
|
10890
|
+
function colorOf(m, seed) {
|
|
10891
|
+
const spec = m.colorSpec || registries().CATALOG[m.type]?.color || { role: "wood" };
|
|
10892
|
+
if ("role" in spec) return spec.role;
|
|
10893
|
+
if ("hex" in spec) return spec.hex;
|
|
10894
|
+
return spec.pick[hashString2(seed + "|" + (m._seedId ?? m.id)) % spec.pick.length];
|
|
10895
|
+
}
|
|
10896
|
+
function applyState(plan, items, ctx, seed, state) {
|
|
10897
|
+
const plugin = registries().STATE_PLUGINS[state];
|
|
10898
|
+
if (!plugin || state === "normal") return [];
|
|
10899
|
+
const c = {
|
|
10900
|
+
plan,
|
|
10901
|
+
items,
|
|
10902
|
+
seed,
|
|
10903
|
+
state,
|
|
10904
|
+
rng: createRng2(seed, plugin.seedKey ?? "state:" + state),
|
|
10905
|
+
ctx,
|
|
10906
|
+
kit: placementKit,
|
|
10907
|
+
catalog: registries().CATALOG,
|
|
10908
|
+
wallTopHeights: registries().WALL_TOP_HEIGHTS
|
|
10909
|
+
};
|
|
10910
|
+
return plugin.apply(c);
|
|
10911
|
+
}
|
|
10912
|
+
var loc = (key, locale, ns = "labels", vars) => key ? translate(ns, key, locale, vars) : key;
|
|
10913
|
+
function generate3(template, options) {
|
|
10914
|
+
const tpl = templateOf(template);
|
|
10915
|
+
const o = options || {};
|
|
10916
|
+
const seed = o.seed == null ? 1 : o.seed;
|
|
10917
|
+
const locale = o.locale || "en";
|
|
10918
|
+
let op = Object.assign({}, o, { seed });
|
|
10919
|
+
const zone = resolveZone(o.zone);
|
|
10920
|
+
const adjustments = [];
|
|
10921
|
+
if (zone) op = clampOptionsToZone(tpl, op, zone, adjustments);
|
|
10922
|
+
const plan = buildPlan(tpl, op.w, op.d, op, { zone, adjustments });
|
|
10923
|
+
const all = resolveSlots(plan);
|
|
10924
|
+
const items = [];
|
|
10925
|
+
const missing = [];
|
|
10926
|
+
for (const room of plan.rooms.concat(plan.zones)) {
|
|
10927
|
+
const sl = all.filter((s) => s.room === room.id && s.floor === room.floor);
|
|
10928
|
+
const r = fillRoom(plan, room, sl, createRng2(seed, STREAM.roomPrefix + (room.seedKey ?? registries().SEED_KEYS.rooms[room.id] ?? room.id)));
|
|
10929
|
+
items.push(...r.items);
|
|
10930
|
+
missing.push(...r.missing);
|
|
10931
|
+
}
|
|
10932
|
+
const ctx = runPostProcess(plan, items, seed);
|
|
10933
|
+
const state = pickState(o.state, seed);
|
|
10934
|
+
const stateItemsAll = applyState(plan, items, ctx, seed, state);
|
|
10935
|
+
const colors2 = palette({ seed, style: plan.style, state });
|
|
10936
|
+
const strip = (m) => {
|
|
10937
|
+
const x = {};
|
|
10938
|
+
for (const k of Object.keys(m)) if (k[0] !== "_" && k !== "colorSpec") x[k] = m[k];
|
|
10939
|
+
return x;
|
|
10940
|
+
};
|
|
10941
|
+
const L = plan.lot;
|
|
10942
|
+
const lot = L ? {
|
|
10943
|
+
rect: L.rect.map(r2),
|
|
10944
|
+
setback: r2(L.setback),
|
|
10945
|
+
backyard: r2(L.backyard),
|
|
10946
|
+
sideYard: L.sideYard,
|
|
10947
|
+
style: L.style,
|
|
10948
|
+
pool: L.pool,
|
|
10949
|
+
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 })),
|
|
10950
|
+
perimeter: L.perimeter.map((s) => Object.assign({ color: "wall" }, s)),
|
|
10951
|
+
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" })),
|
|
10952
|
+
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 }))
|
|
10953
|
+
} : null;
|
|
10954
|
+
const facade = Object.assign({}, plan.facade, {
|
|
10955
|
+
color: { name: loc(colors2.meta.base, locale, "facadeColors"), key: colors2.meta.base, hex: hexString(colors2.facade) },
|
|
10956
|
+
roles: { facade: "facade", detail: "facade_detail", roof: "roof", wall: "wall", gate: "gate" }
|
|
10957
|
+
});
|
|
10958
|
+
const itemsOut = items.map((m) => {
|
|
10959
|
+
const out = strip(m);
|
|
10960
|
+
out.color = colorOf(m, seed);
|
|
10961
|
+
return out;
|
|
10962
|
+
});
|
|
10963
|
+
const stateItemsOut = stateItemsAll.map((m) => {
|
|
10964
|
+
const out = strip(m);
|
|
10965
|
+
if (m.category) out.color = colorOf(m, seed);
|
|
10966
|
+
return out;
|
|
10967
|
+
});
|
|
10968
|
+
const result = {
|
|
10969
|
+
template: tpl.id,
|
|
10970
|
+
name: translate("templates", tpl.id, locale),
|
|
10971
|
+
use: useOf(tpl),
|
|
10972
|
+
seed,
|
|
10973
|
+
style: plan.style,
|
|
10974
|
+
state,
|
|
10975
|
+
w: plan.w,
|
|
10976
|
+
d: plan.d,
|
|
10977
|
+
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 })),
|
|
10978
|
+
rooms: plan.rooms.map((c) => ({
|
|
10979
|
+
id: c.id,
|
|
10980
|
+
type: c.type,
|
|
10981
|
+
label: c.label,
|
|
10982
|
+
name: loc(c.label, locale),
|
|
10983
|
+
floor: c.floor,
|
|
10984
|
+
u0: c.parts[0][0],
|
|
10985
|
+
v0: c.parts[0][1],
|
|
10986
|
+
u1: c.parts[0][2],
|
|
10987
|
+
v1: c.parts[0][3],
|
|
10988
|
+
parts: c.parts,
|
|
10989
|
+
link: c.link,
|
|
10990
|
+
ceiling: c.ceiling || plan.floors[c.floor].ceiling,
|
|
10991
|
+
color: { wall: "wall_" + c.type, floor: "floor_" + c.type }
|
|
10992
|
+
})),
|
|
10993
|
+
walls: plan.walls,
|
|
10994
|
+
doors: plan.doors,
|
|
10995
|
+
windows: plan.windows,
|
|
10996
|
+
openPairs: [].concat(...Object.values(plan.openPairs)),
|
|
10997
|
+
facade,
|
|
10998
|
+
lot,
|
|
10999
|
+
items: itemsOut,
|
|
11000
|
+
stateItems: stateItemsOut,
|
|
11001
|
+
colors: colors2,
|
|
11002
|
+
building: tpl.building ? tpl.building(plan.w, plan.d, op) : null,
|
|
11003
|
+
missing
|
|
11004
|
+
};
|
|
11005
|
+
if (tpl.singleFloor) result.floorNumber = op.floor || 3;
|
|
11006
|
+
result.partyWalls = tpl.partyWalls || [];
|
|
11007
|
+
if (plan.config) result.config = plan.config;
|
|
11008
|
+
runResultExtensions(plan, result, seed, op);
|
|
11009
|
+
if (zone) {
|
|
11010
|
+
const report = { zone: zone.id, adjustments, violations: checkLot(zone, lot, Object.assign({}, result, { rooms: plan.rooms })) };
|
|
11011
|
+
result.zoning = report;
|
|
11012
|
+
}
|
|
11013
|
+
return result;
|
|
11014
|
+
}
|
|
11015
|
+
var isSolid = (m) => !m.flat && !m.on && (!m.decor || m.mount === "floor");
|
|
11016
|
+
function validate(result, options) {
|
|
11017
|
+
const locale = options?.locale || result.locale || "en";
|
|
11018
|
+
const t = (key, vars) => translate("messages", key, locale, vars);
|
|
11019
|
+
const errors = [];
|
|
11020
|
+
const solids = result.items.filter(isSolid);
|
|
11021
|
+
for (let i = 0; i < solids.length; i++)
|
|
11022
|
+
for (let j = i + 1; j < solids.length; j++)
|
|
11023
|
+
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 }));
|
|
11024
|
+
for (const m of solids) {
|
|
11025
|
+
if (m.barricade) continue;
|
|
11026
|
+
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 }));
|
|
11027
|
+
if (result.lot) {
|
|
11028
|
+
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 }));
|
|
11029
|
+
}
|
|
11030
|
+
}
|
|
11031
|
+
for (const f of result.missing) errors.push(t("missing_required", { room: f.room, slot: f.slot }));
|
|
11032
|
+
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 }));
|
|
11033
|
+
return errors;
|
|
11034
|
+
}
|
|
11035
|
+
function limitsOf(template, options) {
|
|
11036
|
+
const tpl = templateOf(template);
|
|
11037
|
+
const op = options || {};
|
|
11038
|
+
return { limits: tpl.limitsFn ? tpl.limitsFn(op) : tpl.limits, defaults: tpl.defaultsFn ? tpl.defaultsFn(op) : tpl.defaults };
|
|
11039
|
+
}
|
|
11040
|
+
function listTemplates(locale = "en") {
|
|
11041
|
+
return Object.values(registries().TEMPLATES).map((t) => {
|
|
11042
|
+
const a = floorsOf(t, {});
|
|
11043
|
+
return {
|
|
11044
|
+
id: t.id,
|
|
11045
|
+
name: translate("templates", t.id, locale),
|
|
11046
|
+
use: useOf(t),
|
|
11047
|
+
style: typeof t.style === "function" ? t.style({}) : t.style,
|
|
11048
|
+
limits: t.limits,
|
|
11049
|
+
defaults: t.defaults,
|
|
11050
|
+
floors: a.length,
|
|
11051
|
+
floorRange: t.floorRange || null,
|
|
11052
|
+
lot: !!t.lot,
|
|
11053
|
+
options: t.options || {}
|
|
11054
|
+
};
|
|
11055
|
+
});
|
|
8786
11056
|
}
|
|
8787
11057
|
function registerGroundItems() {
|
|
8788
11058
|
const flat = { category: "ground_cover", w: 1, d: 1, h: 0, decor: true, mount: "ground", flat: true };
|
|
@@ -8794,6 +11064,14 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
8794
11064
|
{ type: "dirt", ...flat, color: { role: "dirt" } }
|
|
8795
11065
|
]);
|
|
8796
11066
|
}
|
|
11067
|
+
var slotKit = {
|
|
11068
|
+
wall: (id, side, pos, size, cats, o) => Object.assign({ id, anchor: "wall", side, pos, size, cats }, o),
|
|
11069
|
+
corner: (id, corner, size, cats, o) => Object.assign({ id, anchor: "corner", corner, size, cats }, o),
|
|
11070
|
+
center: (id, fx, fz, size, cats, o) => Object.assign({ id, anchor: "center", fx, fz, size, cats }, o),
|
|
11071
|
+
windowSlot: (id, size, cats, o) => Object.assign({ id, anchor: "window", size, cats }, o),
|
|
11072
|
+
on: (id, parent, cats, o) => Object.assign({ id, anchor: "on", on: parent, cats }, o),
|
|
11073
|
+
inside: (id, parent, cats, o) => Object.assign({ id, anchor: "inside", on: parent, cats }, o)
|
|
11074
|
+
};
|
|
8797
11075
|
var en_default = {
|
|
8798
11076
|
labels: {
|
|
8799
11077
|
floor_n: "Floor {n}",
|
|
@@ -8988,6 +11266,82 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
8988
11266
|
reason_permeable_min: "\xC1rea perme\xE1vel da zona"
|
|
8989
11267
|
}
|
|
8990
11268
|
};
|
|
11269
|
+
var version = "0.1.0";
|
|
11270
|
+
function buildEngine(scope) {
|
|
11271
|
+
const scoped = (fn) => ((...args) => withRegistries(scope, () => fn(...args)));
|
|
11272
|
+
return {
|
|
11273
|
+
version,
|
|
11274
|
+
generate: scoped(generate3),
|
|
11275
|
+
slots: scoped(slots),
|
|
11276
|
+
validate: scoped(validate),
|
|
11277
|
+
palette: scoped(palette),
|
|
11278
|
+
limitsOf: scoped(limitsOf),
|
|
11279
|
+
listTemplates: scoped(listTemplates),
|
|
11280
|
+
listStates: scoped(listStates),
|
|
11281
|
+
// content registration
|
|
11282
|
+
registerItem: scoped(registerItem),
|
|
11283
|
+
registerItems: scoped(registerItems),
|
|
11284
|
+
registerLibrary: scoped(registerLibrary),
|
|
11285
|
+
registerLibraryAlias: scoped(registerLibraryAlias),
|
|
11286
|
+
registerTemplate: scoped(registerTemplate),
|
|
11287
|
+
registerStyle: scoped(registerStyle),
|
|
11288
|
+
registerPaletteStyle: scoped(registerPaletteStyle),
|
|
11289
|
+
registerRoomColors: scoped(registerRoomColors),
|
|
11290
|
+
registerDecorProfile: scoped(registerDecorProfile),
|
|
11291
|
+
registerPass: scoped(registerPass),
|
|
11292
|
+
registerGroundOverlay: scoped(registerGroundOverlay),
|
|
11293
|
+
registerWallTop: scoped(registerWallTop),
|
|
11294
|
+
registerSeedKeys: scoped(registerSeedKeys),
|
|
11295
|
+
registerState: scoped(registerState),
|
|
11296
|
+
setStateDistribution: scoped(setStateDistribution),
|
|
11297
|
+
registerLocale: scoped(registerLocale),
|
|
11298
|
+
registerResultExtension: scoped(registerResultExtension),
|
|
11299
|
+
registerFindsProvider: scoped(registerFindsProvider),
|
|
11300
|
+
findsFor: scoped(findsFor),
|
|
11301
|
+
findsAnswer: scoped(findsAnswer),
|
|
11302
|
+
registerViewerLayer: scoped(registerViewerLayer),
|
|
11303
|
+
viewerLayers: scope.VIEWER_LAYERS,
|
|
11304
|
+
blocksWindow: scoped(blocksWindow),
|
|
11305
|
+
// zoning
|
|
11306
|
+
defineZone: scoped(defineZone),
|
|
11307
|
+
getZone: scoped(getZone),
|
|
11308
|
+
listZones: scoped(listZones),
|
|
11309
|
+
checkLot: scoped(checkLot),
|
|
11310
|
+
registerPermeableGround: scoped(registerPermeableGround),
|
|
11311
|
+
// kits and data
|
|
11312
|
+
slotKit,
|
|
11313
|
+
translate: scoped(translate),
|
|
11314
|
+
listLocales: scoped(listLocales),
|
|
11315
|
+
templates: scope.TEMPLATES,
|
|
11316
|
+
catalog: scope.CATALOG,
|
|
11317
|
+
libraries: scope.LIBRARIES,
|
|
11318
|
+
styles: scope.STYLES,
|
|
11319
|
+
paletteStyles: scope.PALETTE_STYLES,
|
|
11320
|
+
createRng: createRng2,
|
|
11321
|
+
mulberry32: mulberry322,
|
|
11322
|
+
hashString: hashString2,
|
|
11323
|
+
overlaps,
|
|
11324
|
+
windowZone,
|
|
11325
|
+
openingZones,
|
|
11326
|
+
color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
|
|
11327
|
+
constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
|
|
11328
|
+
};
|
|
11329
|
+
}
|
|
11330
|
+
function seedBase(scope) {
|
|
11331
|
+
withRegistries(scope, () => {
|
|
11332
|
+
registerGroundItems();
|
|
11333
|
+
registerPermeableGround("lawn", "dirt");
|
|
11334
|
+
registerLocale("en", en_default);
|
|
11335
|
+
registerLocale("pt-BR", pt_BR_default);
|
|
11336
|
+
});
|
|
11337
|
+
}
|
|
11338
|
+
var defaultEngine;
|
|
11339
|
+
function defaultFloorplan() {
|
|
11340
|
+
if (defaultEngine) return defaultEngine;
|
|
11341
|
+
seedBase(getDefaultRegistries());
|
|
11342
|
+
defaultEngine = buildEngine(getDefaultRegistries());
|
|
11343
|
+
return defaultEngine;
|
|
11344
|
+
}
|
|
8991
11345
|
function createSilhouetteRandom(seed) {
|
|
8992
11346
|
let state = seed;
|
|
8993
11347
|
return () => {
|
|
@@ -9188,10 +11542,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
9188
11542
|
}
|
|
9189
11543
|
return b.finish({ x: p.x, z: p.z, width: w + 0.5, depth: d + 0.5, height: 4.6 });
|
|
9190
11544
|
}
|
|
9191
|
-
|
|
9192
|
-
registerPermeableGround("lawn", "dirt");
|
|
9193
|
-
registerLocale("en", en_default);
|
|
9194
|
-
registerLocale("pt-BR", pt_BR_default);
|
|
11545
|
+
var engine = defaultFloorplan();
|
|
9195
11546
|
|
|
9196
11547
|
// ../game-vehicles-fleet/dist/index.js
|
|
9197
11548
|
var cars = [
|
|
@@ -14074,7 +16425,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14074
16425
|
const THREE = kit.THREE;
|
|
14075
16426
|
const trl = fx.t;
|
|
14076
16427
|
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,
|
|
16428
|
+
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
16429
|
const GLOSS = CM("#07080a", { roughness: 0.18, metalness: 0.5 });
|
|
14079
16430
|
const ALLOY = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 });
|
|
14080
16431
|
const ALLOYD = CM("#3a3e42", { metalness: 0.6, roughness: 0.35 });
|
|
@@ -14088,7 +16439,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14088
16439
|
const a = (y - o.ys) / (o.belt - o.ys);
|
|
14089
16440
|
return o.kt * a * a;
|
|
14090
16441
|
}
|
|
14091
|
-
const b =
|
|
16442
|
+
const b = clamp5((o.ys - y) / (o.ys - o.yb), 0, 1.25);
|
|
14092
16443
|
return o.kb * b * b;
|
|
14093
16444
|
}
|
|
14094
16445
|
const f = function(x, y, z) {
|
|
@@ -14168,13 +16519,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14168
16519
|
});
|
|
14169
16520
|
}
|
|
14170
16521
|
function roofAt(gh, x) {
|
|
14171
|
-
const rR = gh.roofR || gh.roof, t =
|
|
16522
|
+
const rR = gh.roofR || gh.roof, t = clamp5((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
|
|
14172
16523
|
return rR + (gh.roof - rR) * t + (gh.crown || 0) * 4 * t * (1 - t);
|
|
14173
16524
|
}
|
|
14174
16525
|
function windows(gh) {
|
|
14175
16526
|
const inn = 0.07, rise = gh.rise || 0;
|
|
14176
16527
|
function ybAt(x) {
|
|
14177
|
-
return gh.belt + 0.035 + rise *
|
|
16528
|
+
return gh.belt + 0.035 + rise * clamp5((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
|
|
14178
16529
|
}
|
|
14179
16530
|
function lx(x0, x1, y, top) {
|
|
14180
16531
|
return x0 + (x1 - x0) * (y - gh.belt) / (top - gh.belt);
|
|
@@ -14377,7 +16728,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14377
16728
|
}
|
|
14378
16729
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: tot * (full ? 2 : 1), E, sd };
|
|
14379
16730
|
G2.at = function(u, y) {
|
|
14380
|
-
const fi =
|
|
16731
|
+
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
16732
|
function L(M) {
|
|
14382
16733
|
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
16734
|
}
|
|
@@ -14514,10 +16865,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14514
16865
|
m.rotation.order = "XYZ";
|
|
14515
16866
|
return m;
|
|
14516
16867
|
}
|
|
14517
|
-
function regLight(c, kind, label,
|
|
14518
|
-
const L = { group: null, inner, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
16868
|
+
function regLight(c, kind, label, inner2, lens, pos) {
|
|
16869
|
+
const L = { group: null, inner: inner2, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
14519
16870
|
c.lights.push(L);
|
|
14520
|
-
const pt = c.part(kind, label,
|
|
16871
|
+
const pt = c.part(kind, label, inner2[0], pos, 0.8);
|
|
14521
16872
|
pt.light = L;
|
|
14522
16873
|
return L;
|
|
14523
16874
|
}
|
|
@@ -14549,7 +16900,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14549
16900
|
du: 0.018,
|
|
14550
16901
|
dv: 0.012
|
|
14551
16902
|
});
|
|
14552
|
-
const pu = pad / Gd.len,
|
|
16903
|
+
const pu = pad / Gd.len, inner2 = [], lens = [];
|
|
14553
16904
|
const yb = function(u) {
|
|
14554
16905
|
return sh(u)[0];
|
|
14555
16906
|
}, yt = function(u) {
|
|
@@ -14574,13 +16925,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14574
16925
|
ring2.castShadow = false;
|
|
14575
16926
|
const b = onSurf(c.sphere(rad, MT.head, 0, 0, 0, 1, 0.55, 1), s, 0.01);
|
|
14576
16927
|
b.castShadow = false;
|
|
14577
|
-
|
|
16928
|
+
inner2.push(b);
|
|
14578
16929
|
});
|
|
14579
16930
|
(o.drl || []).forEach(function(d) {
|
|
14580
|
-
|
|
16931
|
+
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
16932
|
});
|
|
14582
16933
|
(o.amber || []).forEach(function(d) {
|
|
14583
|
-
|
|
16934
|
+
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
16935
|
});
|
|
14585
16936
|
const ln = skin(c, Gd, MT.lens, {
|
|
14586
16937
|
yb: function(u) {
|
|
@@ -14594,9 +16945,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14594
16945
|
order: 3
|
|
14595
16946
|
});
|
|
14596
16947
|
lens.push(ln);
|
|
14597
|
-
if (!
|
|
16948
|
+
if (!inner2.length) inner2.push(ln);
|
|
14598
16949
|
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")),
|
|
16950
|
+
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")), inner2, lens, cs);
|
|
14600
16951
|
return Gd;
|
|
14601
16952
|
};
|
|
14602
16953
|
K.tail = function(sd, o) {
|
|
@@ -14610,7 +16961,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14610
16961
|
du: 0.018,
|
|
14611
16962
|
dv: 0.012
|
|
14612
16963
|
});
|
|
14613
|
-
const pu = pad / Gd.len,
|
|
16964
|
+
const pu = pad / Gd.len, inner2 = [], lens = [], yb = function(u) {
|
|
14614
16965
|
return sh(u)[0];
|
|
14615
16966
|
}, yt = function(u) {
|
|
14616
16967
|
return sh(u)[1];
|
|
@@ -14625,15 +16976,15 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14625
16976
|
off: 3e-3,
|
|
14626
16977
|
nv: 4
|
|
14627
16978
|
});
|
|
14628
|
-
|
|
16979
|
+
inner2.push(skin(c, Gd, o.main || TAILD, { u0: pu, u1: 1 - pu, yb, yt, off: 6e-3, nv: 4 }));
|
|
14629
16980
|
(o.hi || []).forEach(function(d) {
|
|
14630
|
-
|
|
16981
|
+
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
16982
|
});
|
|
14632
16983
|
(o.rev || []).forEach(function(d) {
|
|
14633
|
-
|
|
16984
|
+
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
16985
|
});
|
|
14635
16986
|
(o.amber || []).forEach(function(d) {
|
|
14636
|
-
|
|
16987
|
+
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
16988
|
});
|
|
14638
16989
|
lens.push(
|
|
14639
16990
|
skin(c, Gd, o.lens || MT.lensRed, {
|
|
@@ -14652,7 +17003,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14652
17003
|
c,
|
|
14653
17004
|
"taillight",
|
|
14654
17005
|
o.label || (sd > 0 ? trl("labels.right_taillight") : trl("labels.left_taillight")),
|
|
14655
|
-
|
|
17006
|
+
inner2,
|
|
14656
17007
|
lens,
|
|
14657
17008
|
Gd.at(0.4, (rg[0] + rg[1]) / 2).p
|
|
14658
17009
|
);
|
|
@@ -14957,7 +17308,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
14957
17308
|
c.roundBox(x1 - x0, 0.08, 0.035, 0.015, MT.trim, (x0 + x1) / 2, by + 0.05, sd * (hw - 8e-3));
|
|
14958
17309
|
if (s.clad)
|
|
14959
17310
|
s.arches.forEach(function(a) {
|
|
14960
|
-
const a0 = Math.asin(
|
|
17311
|
+
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
17312
|
tor.rotation.z = a0;
|
|
14962
17313
|
tor.scale.z = 0.8;
|
|
14963
17314
|
});
|
|
@@ -15088,7 +17439,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
15088
17439
|
c.cylinder(0.06, 0.06, 0.012, MT.head, x + 0.055, y, z, "x");
|
|
15089
17440
|
}
|
|
15090
17441
|
function sm(t) {
|
|
15091
|
-
t =
|
|
17442
|
+
t = clamp5(t, 0, 1);
|
|
15092
17443
|
return t * t * (3 - 2 * t);
|
|
15093
17444
|
}
|
|
15094
17445
|
function sideGrid(S, sd, o) {
|
|
@@ -15105,7 +17456,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
15105
17456
|
}
|
|
15106
17457
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: Math.abs(o.x1 - o.x0) };
|
|
15107
17458
|
G2.at = function(u, y) {
|
|
15108
|
-
const fi =
|
|
17459
|
+
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
17460
|
function L(M) {
|
|
15110
17461
|
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
17462
|
}
|
|
@@ -16833,7 +19184,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
16833
19184
|
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
19185
|
const gyb = function(u) {
|
|
16835
19186
|
const z = Math.abs(g.zu(u));
|
|
16836
|
-
return z <= zb ? yB0 : yB0 + (yT - 0.02 - yB0) *
|
|
19187
|
+
return z <= zb ? yB0 : yB0 + (yT - 0.02 - yB0) * clamp5((z - zb) / (zt - zb), 0, 1);
|
|
16837
19188
|
};
|
|
16838
19189
|
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
19190
|
outline(c2, g, { u0: g.uz(-zt), u1: g.uz(zt), yb: gyb, yt: yT }, 0.012, LH, 0.024);
|
|
@@ -21783,8 +24134,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
21783
24134
|
kit.steeringWheel(c, 1.5, 1.42, -0.62, { r: 0.2, tilt: 0.9, col: 0.35 });
|
|
21784
24135
|
c.roundBox(0.35, 0.3, 0.7, 0.04, CM("#25261f"), 1.85, 1.45, -0.6);
|
|
21785
24136
|
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 -
|
|
24137
|
+
for (let r22 = 0; r22 < 3; r22++)
|
|
24138
|
+
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
24139
|
const ramp2 = new THREE.Group(), RL = 1.36, rmat = paintMat(c, col);
|
|
21789
24140
|
ramp2.position.set(R - 0.05, 0.62, 0);
|
|
21790
24141
|
ramp2.rotation.z = -PI / 2;
|
|
@@ -22991,7 +25342,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
22991
25342
|
const THREE = kit.THREE;
|
|
22992
25343
|
const trl = fx.t;
|
|
22993
25344
|
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,
|
|
25345
|
+
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
25346
|
const GLOSS = CM("#07080a", { roughness: 0.18, metalness: 0.5 });
|
|
22996
25347
|
const ALLOY = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 });
|
|
22997
25348
|
const ALLOYD = CM("#3a3e42", { metalness: 0.6, roughness: 0.35 });
|
|
@@ -23010,7 +25361,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23010
25361
|
const a = (y - o.ys) / (o.belt - o.ys);
|
|
23011
25362
|
return o.kt * a * a;
|
|
23012
25363
|
}
|
|
23013
|
-
const b =
|
|
25364
|
+
const b = clamp5((o.ys - y) / (o.ys - o.yb), 0, 1.25);
|
|
23014
25365
|
return o.kb * b * b;
|
|
23015
25366
|
}
|
|
23016
25367
|
const f = function(x, y, z) {
|
|
@@ -23190,13 +25541,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23190
25541
|
return L;
|
|
23191
25542
|
}
|
|
23192
25543
|
function roofAt(gh, x) {
|
|
23193
|
-
const rR = gh.roofR || gh.roof, t =
|
|
25544
|
+
const rR = gh.roofR || gh.roof, t = clamp5((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
|
|
23194
25545
|
return rR + (gh.roof - rR) * t + (gh.crown || 0) * 4 * t * (1 - t);
|
|
23195
25546
|
}
|
|
23196
25547
|
function windows(gh) {
|
|
23197
25548
|
const inn = 0.07, rise = gh.rise || 0;
|
|
23198
25549
|
function ybAt(x) {
|
|
23199
|
-
return gh.belt + 0.035 + rise *
|
|
25550
|
+
return gh.belt + 0.035 + rise * clamp5((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
|
|
23200
25551
|
}
|
|
23201
25552
|
function lx(x0, x1, y, top) {
|
|
23202
25553
|
return x0 + (x1 - x0) * (y - gh.belt) / (top - gh.belt);
|
|
@@ -23407,7 +25758,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23407
25758
|
}
|
|
23408
25759
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: tot * (full ? 2 : 1), E, sd };
|
|
23409
25760
|
G2.at = function(u, y) {
|
|
23410
|
-
const fi =
|
|
25761
|
+
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
25762
|
function L(M) {
|
|
23412
25763
|
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
25764
|
}
|
|
@@ -23544,10 +25895,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23544
25895
|
m.rotation.order = "XYZ";
|
|
23545
25896
|
return m;
|
|
23546
25897
|
}
|
|
23547
|
-
function regLight(c, kind, label,
|
|
23548
|
-
const L = { group: null, inner, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
25898
|
+
function regLight(c, kind, label, inner2, lens, pos) {
|
|
25899
|
+
const L = { group: null, inner: inner2, lens, kind, tint: kind === "headlight" ? "clear" : "red", broken: false };
|
|
23549
25900
|
c.lights.push(L);
|
|
23550
|
-
const pt = c.part(kind, label,
|
|
25901
|
+
const pt = c.part(kind, label, inner2[0], pos, 0.8);
|
|
23551
25902
|
pt.light = L;
|
|
23552
25903
|
return L;
|
|
23553
25904
|
}
|
|
@@ -23579,7 +25930,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23579
25930
|
du: 0.018,
|
|
23580
25931
|
dv: 0.012
|
|
23581
25932
|
});
|
|
23582
|
-
const pu = pad / Gd.len,
|
|
25933
|
+
const pu = pad / Gd.len, inner2 = [], lens = [];
|
|
23583
25934
|
const yb = function(u) {
|
|
23584
25935
|
return sh(u)[0];
|
|
23585
25936
|
}, yt = function(u) {
|
|
@@ -23604,13 +25955,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23604
25955
|
ring2.castShadow = false;
|
|
23605
25956
|
const b = onSurf(c.sphere(rad, MT.head, 0, 0, 0, 1, 0.55, 1), s, 0.01);
|
|
23606
25957
|
b.castShadow = false;
|
|
23607
|
-
|
|
25958
|
+
inner2.push(b);
|
|
23608
25959
|
});
|
|
23609
25960
|
(o.drl || []).forEach(function(d) {
|
|
23610
|
-
|
|
25961
|
+
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
25962
|
});
|
|
23612
25963
|
(o.amber || []).forEach(function(d) {
|
|
23613
|
-
|
|
25964
|
+
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
25965
|
});
|
|
23615
25966
|
const ln = skin(c, Gd, MT.lens, {
|
|
23616
25967
|
yb: function(u) {
|
|
@@ -23624,9 +25975,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23624
25975
|
order: 3
|
|
23625
25976
|
});
|
|
23626
25977
|
lens.push(ln);
|
|
23627
|
-
if (!
|
|
25978
|
+
if (!inner2.length) inner2.push(ln);
|
|
23628
25979
|
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")),
|
|
25980
|
+
regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")), inner2, lens, cs);
|
|
23630
25981
|
return Gd;
|
|
23631
25982
|
};
|
|
23632
25983
|
K.tail = function(sd, o) {
|
|
@@ -23640,7 +25991,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23640
25991
|
du: 0.018,
|
|
23641
25992
|
dv: 0.012
|
|
23642
25993
|
});
|
|
23643
|
-
const pu = pad / Gd.len,
|
|
25994
|
+
const pu = pad / Gd.len, inner2 = [], lens = [], yb = function(u) {
|
|
23644
25995
|
return sh(u)[0];
|
|
23645
25996
|
}, yt = function(u) {
|
|
23646
25997
|
return sh(u)[1];
|
|
@@ -23655,15 +26006,15 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23655
26006
|
off: 3e-3,
|
|
23656
26007
|
nv: 4
|
|
23657
26008
|
});
|
|
23658
|
-
|
|
26009
|
+
inner2.push(skin(c, Gd, o.main || TAILD, { u0: pu, u1: 1 - pu, yb, yt, off: 6e-3, nv: 4 }));
|
|
23659
26010
|
(o.hi || []).forEach(function(d) {
|
|
23660
|
-
|
|
26011
|
+
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
26012
|
});
|
|
23662
26013
|
(o.rev || []).forEach(function(d) {
|
|
23663
|
-
|
|
26014
|
+
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
26015
|
});
|
|
23665
26016
|
(o.amber || []).forEach(function(d) {
|
|
23666
|
-
|
|
26017
|
+
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
26018
|
});
|
|
23668
26019
|
lens.push(
|
|
23669
26020
|
skin(c, Gd, o.lens || MT.lensRed, {
|
|
@@ -23682,7 +26033,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23682
26033
|
c,
|
|
23683
26034
|
"taillight",
|
|
23684
26035
|
o.label || (sd > 0 ? trl("labels.right_taillight") : trl("labels.left_taillight")),
|
|
23685
|
-
|
|
26036
|
+
inner2,
|
|
23686
26037
|
lens,
|
|
23687
26038
|
Gd.at(0.4, (rg[0] + rg[1]) / 2).p
|
|
23688
26039
|
);
|
|
@@ -23968,7 +26319,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
23968
26319
|
c.roundBox(x1 - x0, 0.08, 0.035, 0.015, MT.trim, (x0 + x1) / 2, by + 0.05, sd * (hw - 8e-3));
|
|
23969
26320
|
if (s.clad)
|
|
23970
26321
|
s.arches.forEach(function(a) {
|
|
23971
|
-
const a0 = Math.asin(
|
|
26322
|
+
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
26323
|
tor.rotation.z = a0;
|
|
23973
26324
|
tor.scale.z = 0.8;
|
|
23974
26325
|
});
|
|
@@ -24089,7 +26440,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
24089
26440
|
c.cylinder(0.06, 0.06, 0.012, MT.head, x + 0.055, y, z, "x");
|
|
24090
26441
|
}
|
|
24091
26442
|
function sm(t) {
|
|
24092
|
-
t =
|
|
26443
|
+
t = clamp5(t, 0, 1);
|
|
24093
26444
|
return t * t * (3 - 2 * t);
|
|
24094
26445
|
}
|
|
24095
26446
|
function sideGrid(S, sd, o) {
|
|
@@ -24106,7 +26457,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
24106
26457
|
}
|
|
24107
26458
|
const G2 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: Math.abs(o.x1 - o.x0) };
|
|
24108
26459
|
G2.at = function(u, y) {
|
|
24109
|
-
const fi =
|
|
26460
|
+
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
26461
|
function L(M) {
|
|
24111
26462
|
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
26463
|
}
|
|
@@ -24265,7 +26616,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
24265
26616
|
return function(c, I) {
|
|
24266
26617
|
const gh = I.gh, col = c.meta.color;
|
|
24267
26618
|
function yb(x) {
|
|
24268
|
-
return gh.belt + 0.035 + (gh.rise || 0) *
|
|
26619
|
+
return gh.belt + 0.035 + (gh.rise || 0) * clamp5((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
|
|
24269
26620
|
}
|
|
24270
26621
|
;
|
|
24271
26622
|
[1, -1].forEach(function(sd) {
|
|
@@ -27136,7 +29487,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
27136
29487
|
return n.y > 0.3 && ce.x > 0.9 && ce.y > 0.6;
|
|
27137
29488
|
},
|
|
27138
29489
|
sdf: function(x, y, z) {
|
|
27139
|
-
const t =
|
|
29490
|
+
const t = clamp5((x - 1.17) / 0.5, 0, 1);
|
|
27140
29491
|
return Math.max(0.94 - x, x - 1.69, Math.abs(z) - (0.44 - 0.16 * t * t));
|
|
27141
29492
|
}
|
|
27142
29493
|
});
|
|
@@ -31134,7 +33485,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31134
33485
|
const THREE = kit.THREE;
|
|
31135
33486
|
const trl = fx.t;
|
|
31136
33487
|
const lbl = fx.label;
|
|
31137
|
-
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial,
|
|
33488
|
+
const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, clamp5 = kit.clamp, smooth2 = kit.smooth;
|
|
31138
33489
|
const CLAD = CM("#1a1b1d", { roughness: 0.82 }), LINER = CM("#1c1d1f", { roughness: 0.92 }), RIMDARK = CM("#2a2d30", { metalness: 0.6, roughness: 0.4 });
|
|
31139
33490
|
const RIMSIL = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 }), RIMGUN = CM("#55595d", { metalness: 0.7, roughness: 0.35 });
|
|
31140
33491
|
function axles(WB, oF, L, dF, dR) {
|
|
@@ -31165,16 +33516,16 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31165
33516
|
const m = c.meta, W = m.width, by = o.bottom != null ? o.bottom : m.clearance, top = o.top != null ? o.top : m.belt;
|
|
31166
33517
|
const Wg = m.glasshouse && m.glasshouse.Wg || W, front = m.front, rear = m.rear;
|
|
31167
33518
|
const tuck = o.tuck == null ? 0.045 : o.tuck, peak = o.peak || 0.45;
|
|
31168
|
-
const B = o.sh != null ? o.sh :
|
|
33519
|
+
const B = o.sh != null ? o.sh : clamp5(1 - (Wg + 0.06) / W, 0, 0.06);
|
|
31169
33520
|
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
33521
|
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
33522
|
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
33523
|
function sc(y) {
|
|
31173
|
-
const t =
|
|
33524
|
+
const t = clamp5((y - by) / (top - by), 0, 1);
|
|
31174
33525
|
return t < peak ? 1 - tuck * Math.pow(1 - t / peak, 2) : 1 - B * Math.pow((t - peak) / (1 - peak), 2);
|
|
31175
33526
|
}
|
|
31176
33527
|
function F(v2) {
|
|
31177
|
-
const a = smooth2((v2.x - (front - Rf)) / Rf), b = smooth2((rear + Rr - v2.x) / Rr), zz =
|
|
33528
|
+
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
33529
|
const bf = smooth2((v2.x - (front - Rb)) / Rb), br = smooth2((rear + Rb - v2.x) / Rb);
|
|
31179
33530
|
const cr = crown * (1 - zz) * smooth2((v2.y - y0) / (top - y0)) * smooth2((v2.x - xc) / 0.3);
|
|
31180
33531
|
v2.x += -bowF * zz * bf + bowR * zz * br;
|
|
@@ -31251,7 +33602,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31251
33602
|
o = o || {};
|
|
31252
33603
|
const wd = o.w || 0.075, dp = o.d || 0.045, mat = o.mat || CLAD, out = [];
|
|
31253
33604
|
arches.forEach(function(a) {
|
|
31254
|
-
const a0 = Math.asin(
|
|
33605
|
+
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
33606
|
const sh = new THREE.Shape();
|
|
31256
33607
|
if (o.trap) {
|
|
31257
33608
|
const yb0 = Math.sin(a0) * r1, xb0 = Math.cos(a0) * r1, top = r1 * 0.93, xt = r1 * 0.52;
|
|
@@ -31600,7 +33951,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31600
33951
|
function roof(c, drop, x0, x1) {
|
|
31601
33952
|
const m = c.meta, b = m.glasshouse.belt, h = m.top - b;
|
|
31602
33953
|
function dy(x, y) {
|
|
31603
|
-
const t =
|
|
33954
|
+
const t = clamp5((y - b) / h, 0, 1);
|
|
31604
33955
|
return t <= 0 ? 0 : drop * smooth2((x0 - x) / (x0 - x1)) * t;
|
|
31605
33956
|
}
|
|
31606
33957
|
c.root.updateMatrixWorld(true);
|
|
@@ -32794,8 +35145,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
32794
35145
|
setOnHit(pt, function(c2, p0) {
|
|
32795
35146
|
const o = p0.obj;
|
|
32796
35147
|
if (o && o.userData.hung && o.parent && o.parent !== c2.vehicle) {
|
|
32797
|
-
o.parent.rotation.x =
|
|
32798
|
-
o.parent.rotation.y =
|
|
35148
|
+
o.parent.rotation.x = clamp5(o.parent.rotation.x, -0.22, 0.22);
|
|
35149
|
+
o.parent.rotation.y = clamp5(o.parent.rotation.y, -0.08, 0.08);
|
|
32799
35150
|
}
|
|
32800
35151
|
});
|
|
32801
35152
|
});
|
|
@@ -33401,7 +35752,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33401
35752
|
c2.cylinder(0.03, 0.04, 0.08, MT.chrome, sx + 2.35, fl + 0.99, -0.75);
|
|
33402
35753
|
c2.part("fuel_tank", trl("labels.oxygen_cylinder"), null, new V3(sx + 2.35, fl + 0.5, -0.75), 0.5);
|
|
33403
35754
|
c2.roundBox(0.04, 0.26, 0.34, 0.02, MT.screen, 0.1, 1.7, 0.5);
|
|
33404
|
-
const
|
|
35755
|
+
const lightPanel = c2.roundBox(
|
|
33405
35756
|
2.3,
|
|
33406
35757
|
0.02,
|
|
33407
35758
|
W - 0.3,
|
|
@@ -33411,7 +35762,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33411
35762
|
m.top - 0.06,
|
|
33412
35763
|
0
|
|
33413
35764
|
);
|
|
33414
|
-
|
|
35765
|
+
lightPanel.castShadow = false;
|
|
33415
35766
|
m.roofItems = beaconBar;
|
|
33416
35767
|
}
|
|
33417
35768
|
});
|
|
@@ -33446,7 +35797,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33446
35797
|
const shared2 = fx.shared;
|
|
33447
35798
|
const trl = fx.t;
|
|
33448
35799
|
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,
|
|
35800
|
+
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
35801
|
function colorize(geo, fn) {
|
|
33451
35802
|
const p = geo.attributes.position, col = new Float32Array(p.count * 3);
|
|
33452
35803
|
for (let i = 0; i < p.count; i++) {
|
|
@@ -33462,7 +35813,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33462
35813
|
return a + (b - a) * t;
|
|
33463
35814
|
}
|
|
33464
35815
|
function sstep(a, b, x) {
|
|
33465
|
-
const t =
|
|
35816
|
+
const t = clamp5((x - a) / (b - a), 0, 1);
|
|
33466
35817
|
return t * t * (3 - 2 * t);
|
|
33467
35818
|
}
|
|
33468
35819
|
function tab(T) {
|
|
@@ -33539,7 +35890,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33539
35890
|
o,
|
|
33540
35891
|
sec,
|
|
33541
35892
|
t: function(x) {
|
|
33542
|
-
return
|
|
35893
|
+
return clamp5((x - o.x0) / o.L, 0, 1);
|
|
33543
35894
|
},
|
|
33544
35895
|
/* half beam of the hull at height y (inside, so floors and decks do not leak) */
|
|
33545
35896
|
half: function(x, y) {
|
|
@@ -33713,7 +36064,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
33713
36064
|
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
33714
36065
|
const a = poly[i], b = poly[j];
|
|
33715
36066
|
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 =
|
|
36067
|
+
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
36068
|
d = Math.min(d, Math.hypot(wx - vx * t, wy - vy * t));
|
|
33718
36069
|
}
|
|
33719
36070
|
return inside ? -d : d;
|
|
@@ -34759,7 +37110,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34759
37110
|
c.roundBox(2.4, 0.1, 2.5, 0.04, blue, hx, hy0 + hh + 0.05, 0);
|
|
34760
37111
|
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
37112
|
void door;
|
|
34762
|
-
const
|
|
37113
|
+
const inner2 = CM("#b9a888", { roughness: 0.9 });
|
|
34763
37114
|
c.roundBox(0.4, 0.95, 1.9, 0.03, wood, hx + 0.75, hy0 + 0.48, 0);
|
|
34764
37115
|
c.roundBox(0.42, 0.03, 1.9, 0.01, CM("#2a2b2c"), hx + 0.75, hy0 + 0.97, 0);
|
|
34765
37116
|
[-0.45, 0.45].forEach(function(z) {
|
|
@@ -34778,10 +37129,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34778
37129
|
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
37130
|
}
|
|
34780
37131
|
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:
|
|
37132
|
+
kit.seat(c, hx - 0.05, hy0, 0, { w: 0.5, h: 0.55, back: 0.45, mat: inner2, headrest: false });
|
|
34782
37133
|
c.roundBox(1.8, 0.12, 0.65, 0.04, CM("#7c5a3b"), hx - 0.2, hy0 + 0.5, -0.72);
|
|
34783
37134
|
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,
|
|
37135
|
+
c.roundBox(1.9, 0.02, 2.1, 0.01, inner2, hx, hy0 + hh - 0.06, 0).castShadow = false;
|
|
34785
37136
|
c.cylinder(0.1, 0.1, 1.2, MT.trim, hx - 0.75, hy0 + hh + 0.6, 0.65);
|
|
34786
37137
|
c.cylinder(0.13, 0.1, 0.1, MT.trim, hx - 0.75, hy0 + hh + 1.22, 0.65);
|
|
34787
37138
|
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 +38737,14 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36386
38737
|
var militaryIds = new Set(military);
|
|
36387
38738
|
|
|
36388
38739
|
// ../game-urban/dist/index.js
|
|
36389
|
-
function
|
|
36390
|
-
const
|
|
38740
|
+
function createLightAllowance(count) {
|
|
38741
|
+
const allowance = {
|
|
36391
38742
|
left: count,
|
|
36392
38743
|
take() {
|
|
36393
|
-
return
|
|
38744
|
+
return allowance.left-- > 0;
|
|
36394
38745
|
}
|
|
36395
38746
|
};
|
|
36396
|
-
return
|
|
38747
|
+
return allowance;
|
|
36397
38748
|
}
|
|
36398
38749
|
var ROOT2 = -1;
|
|
36399
38750
|
var DEFAULT_CYLINDER_SEGMENTS = 14;
|
|
@@ -36405,7 +38756,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36405
38756
|
const parts = [];
|
|
36406
38757
|
const items = [];
|
|
36407
38758
|
const windows = { lit: [], dark: [] };
|
|
36408
|
-
const lights = options.budget ??
|
|
38759
|
+
const lights = options.budget ?? createLightAllowance(Number.POSITIVE_INFINITY);
|
|
36409
38760
|
const pick = (list2) => list2[Math.floor(random() * list2.length)];
|
|
36410
38761
|
const group = (x, y, z, ry = 0, parent = ROOT2) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
|
|
36411
38762
|
const box2 = (x, y, z, width, height, depth, color2, o = {}) => {
|
|
@@ -36697,7 +39048,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36697
39048
|
var lerp2 = (a, b, t) => a + (b - a) * t;
|
|
36698
39049
|
function fillBlock(b, cx, cz, size, minHeight, maxHeight, options = {}) {
|
|
36699
39050
|
const placed = [];
|
|
36700
|
-
const
|
|
39051
|
+
const palette2 = options.palette ?? DEFAULT_FACADES;
|
|
36701
39052
|
const half = (size - STREET_MARGIN) / 2;
|
|
36702
39053
|
const k = options.k || DEFAULT_PARTITIONS;
|
|
36703
39054
|
const lot = (size - STREET_MARGIN) / k;
|
|
@@ -36723,7 +39074,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36723
39074
|
}
|
|
36724
39075
|
const x = cx - half + lot * (i + 0.5);
|
|
36725
39076
|
const z = cz - half + lot * (j + 0.5);
|
|
36726
|
-
const color2 = b.pick(
|
|
39077
|
+
const color2 = b.pick(palette2);
|
|
36727
39078
|
placed.push((options.draw ?? drawBuilding)(b, x, z, width, depth, height, color2, extra ? { ...options, ...extra } : options));
|
|
36728
39079
|
}
|
|
36729
39080
|
}
|
|
@@ -36751,16 +39102,16 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36751
39102
|
b.prism(x, 0, z, w, w * 0.7, d, color2 ?? colors(c).tent, -1, ry);
|
|
36752
39103
|
}
|
|
36753
39104
|
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(
|
|
39105
|
+
const palette2 = colors(c);
|
|
39106
|
+
b.cylinder(x, y, z, 0.12 * scale, 0.16 * scale, 1 * scale, palette2.trunk);
|
|
39107
|
+
const green = b.pick(palette2.pines);
|
|
36757
39108
|
b.cone(x, y + 0.8 * scale, z, 1.1 * scale, 2.2 * scale, green);
|
|
36758
39109
|
b.cone(x, y + 2.1 * scale, z, 0.8 * scale, 1.8 * scale, green);
|
|
36759
39110
|
}
|
|
36760
39111
|
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(
|
|
39112
|
+
const palette2 = colors(c);
|
|
39113
|
+
b.cylinder(x, 0, z, 0.12 * scale, 0.18 * scale, 1.8 * scale, palette2.trunk);
|
|
39114
|
+
b.crown(x, 2.6 * scale, z, 1.3 * scale, b.pick(palette2.crowns));
|
|
36764
39115
|
}
|
|
36765
39116
|
function drawContainer(b, x, y, z, ry, color2) {
|
|
36766
39117
|
const group = b.group(x, y, z, ry);
|
|
@@ -36768,11 +39119,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36768
39119
|
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
39120
|
}
|
|
36770
39121
|
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,
|
|
39122
|
+
const palette2 = colors(c).watchtower;
|
|
39123
|
+
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);
|
|
39124
|
+
b.box(x, 7, z, 3.2, 1.2, 3.2, palette2.cabin);
|
|
39125
|
+
b.box(x, 8.2, z, 3.6, 0.15, 3.6, palette2.deck);
|
|
39126
|
+
for (const side of [-1.5, 1.5]) b.box(x + side, 8.2, z, 0.15, 1.6, 0.15, palette2.deck);
|
|
36776
39127
|
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
39128
|
}
|
|
36778
39129
|
function drawSandbags(b, x, z, length, along, rows = 3, c) {
|
|
@@ -37611,10 +39962,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37611
39962
|
};
|
|
37612
39963
|
return { maxFloors, kinds, plans, layout, stats: stats2, fillBlock: fillBlock2 };
|
|
37613
39964
|
}
|
|
37614
|
-
var
|
|
39965
|
+
var clamp4 = (v, a, b) => v < a ? a : v > b ? b : v;
|
|
37615
39966
|
var lerp22 = (a, b, t) => a + (b - a) * t;
|
|
37616
39967
|
var smoothstep = (a, b, x) => {
|
|
37617
|
-
const t =
|
|
39968
|
+
const t = clamp4((x - a) / (b - a), 0, 1);
|
|
37618
39969
|
return t * t * (3 - 2 * t);
|
|
37619
39970
|
};
|
|
37620
39971
|
var hash2 = (a, b) => {
|
|
@@ -37628,7 +39979,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37628
39979
|
const [bx, by] = points[index + 1];
|
|
37629
39980
|
const dx = bx - ax;
|
|
37630
39981
|
const dy = by - ay;
|
|
37631
|
-
const t =
|
|
39982
|
+
const t = clamp4(((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy), 0, 1);
|
|
37632
39983
|
const d = Math.hypot(px - ax - t * dx, py - ay - t * dy);
|
|
37633
39984
|
if (d < best) best = d;
|
|
37634
39985
|
}
|
|
@@ -37733,7 +40084,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37733
40084
|
const { water, world } = terrain.options;
|
|
37734
40085
|
b.plane(0, water.level, 0, world.size, world.size, water.color, { roughness: water.roughness, metalness: water.metalness });
|
|
37735
40086
|
}
|
|
37736
|
-
var
|
|
40087
|
+
var DEFAULT_LIGHT_ALLOWANCE = 16;
|
|
37737
40088
|
var DEFAULT_URBAN_SEED = 7;
|
|
37738
40089
|
function createUrban(options = {}) {
|
|
37739
40090
|
const entries = mergeById(DEFAULT_POLES, options.packs?.poles);
|
|
@@ -37744,7 +40095,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37744
40095
|
applyPatches(patterns, options.patch?.neighbourhoods);
|
|
37745
40096
|
removeIds(patterns, options.remove?.neighbourhoods);
|
|
37746
40097
|
const composers = { ...COMPOSERS, ...options.composers };
|
|
37747
|
-
const lightBudget = options.lights?.budget ??
|
|
40098
|
+
const lightBudget = options.lights?.budget ?? DEFAULT_LIGHT_ALLOWANCE;
|
|
37748
40099
|
const facades = options.facades ?? DEFAULT_FACADES;
|
|
37749
40100
|
const propColors = { ...DEFAULT_PROP_COLORS, ...options.props };
|
|
37750
40101
|
const towers = options.towers ? options.towers === true ? {} : options.towers : null;
|
|
@@ -37786,7 +40137,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37786
40137
|
wires: (b, points, y, color2) => drawWires(b, points, y, color2, propColors)
|
|
37787
40138
|
}
|
|
37788
40139
|
};
|
|
37789
|
-
const builder = (o = {}) => createUrbanBuilder(o.random ?? createSilhouetteRandom(o.seed ?? DEFAULT_URBAN_SEED), { budget:
|
|
40140
|
+
const builder = (o = {}) => createUrbanBuilder(o.random ?? createSilhouetteRandom(o.seed ?? DEFAULT_URBAN_SEED), { budget: createLightAllowance(o.budget ?? lightBudget) });
|
|
37790
40141
|
const neighbourhoods = {
|
|
37791
40142
|
ids: () => [...patterns.keys()],
|
|
37792
40143
|
get(id) {
|
|
@@ -37853,7 +40204,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37853
40204
|
return (request, b) => {
|
|
37854
40205
|
const context = contexts[request.kind];
|
|
37855
40206
|
const key = `${seed}:${request.kind}:${request.x}:${request.z}`;
|
|
37856
|
-
const id = options.pick ? options.pick(request, context, key) : pickForContext(region, context, key);
|
|
40207
|
+
const id = options.pick ? options.pick(request, context, key) : (options.pickSpecies ?? ((table, spot) => pickForContext(region, table, spot)))(context, key);
|
|
37857
40208
|
b.place("plant", id, request.x, request.z, { scale: request.scale, seed: key });
|
|
37858
40209
|
};
|
|
37859
40210
|
}
|
|
@@ -37998,7 +40349,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
37998
40349
|
|
|
37999
40350
|
// src/test-area/scenarios.ts
|
|
38000
40351
|
var POLE_TOPS = { led_double: 9, concrete_arm: 8, cast_iron: 4.8, wood_crossarm: 7, flood_tower: 12.55 };
|
|
38001
|
-
var
|
|
40352
|
+
var STAND_IN_VEHICLE_IDS = ["stand_in_car_a", "stand_in_car_b", "stand_in_van"];
|
|
40353
|
+
var standInSpecies = (context, key) => `stand_in_${context}_${key.length % 3}`;
|
|
40354
|
+
var topOf2 = (parts) => Math.max(...parts.map((part) => "height" in part && "y" in part ? part.y + part.height / 2 : 0));
|
|
38002
40355
|
var placements = (parts, kind) => parts.filter((part) => part.shape === "placement" && part.kind === kind);
|
|
38003
40356
|
var round = (value, digits = 3) => Math.round(value * 10 ** digits) / 10 ** digits;
|
|
38004
40357
|
var createScenarios2 = (deps) => {
|
|
@@ -38014,7 +40367,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38014
40367
|
if (expected === void 0) continue;
|
|
38015
40368
|
const b = urban.builder();
|
|
38016
40369
|
urban.poles.get(id).build(b, { x: 0, z: 0 });
|
|
38017
|
-
const top =
|
|
40370
|
+
const top = topOf2(b.parts);
|
|
38018
40371
|
expect(Math.abs(top - expected) < 1e-6, say("pole_top", id, round(top)), { pole: id, top });
|
|
38019
40372
|
}
|
|
38020
40373
|
}),
|
|
@@ -38082,12 +40435,12 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38082
40435
|
scenario("urban_providers", ({ expect }) => {
|
|
38083
40436
|
const urban = kit();
|
|
38084
40437
|
const cars2 = urban.builder();
|
|
38085
|
-
const downtown2 = urban.neighbourhoods.get("downtown").compose(cars2, { x: 0, z: 0 }, { providers: { vehicle: createVehicleProvider() } });
|
|
40438
|
+
const downtown2 = urban.neighbourhoods.get("downtown").compose(cars2, { x: 0, z: 0 }, { providers: { vehicle: createVehicleProvider({ ids: STAND_IN_VEHICLE_IDS }) } });
|
|
38086
40439
|
const spots = placements(cars2.parts, "vehicle");
|
|
38087
40440
|
expect(spots.length === downtown2.vehicles.length && spots.length > 0, say("vehicles_placed", downtown2.vehicles.length, spots.length));
|
|
38088
|
-
expect(spots.every((spot) =>
|
|
40441
|
+
expect(spots.every((spot) => STAND_IN_VEHICLE_IDS.includes(spot.id)), say("vehicle_ids", [...new Set(spots.map((spot) => spot.id))].join(", ")));
|
|
38089
40442
|
const trees = urban.builder();
|
|
38090
|
-
const mountain = urban.neighbourhoods.get("mountain_resort").compose(trees, { x: 0, z: 0 }, { providers: { plant: createPlantProvider() } });
|
|
40443
|
+
const mountain = urban.neighbourhoods.get("mountain_resort").compose(trees, { x: 0, z: 0 }, { providers: { plant: createPlantProvider({ pickSpecies: standInSpecies }) } });
|
|
38091
40444
|
const planted = placements(trees.parts, "plant");
|
|
38092
40445
|
expect(planted.length === mountain.plants.length && planted.length > 0, say("plants_placed", mountain.plants.length, planted.length));
|
|
38093
40446
|
})
|
|
@@ -38150,21 +40503,22 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38150
40503
|
return createUrban({ ...urban, ...patch ? { patch } : {}, lights: { budget: overrides.budget ?? urban.lights?.budget } });
|
|
38151
40504
|
};
|
|
38152
40505
|
const resolversFor = (THREE) => {
|
|
38153
|
-
if (!resolvers.plant) resolvers.plant = createPlantResolver(THREE, { lod: 1 });
|
|
40506
|
+
if (!resolvers.plant) resolvers.plant = createPlantResolver(THREE, { lod: 1, ...options.plantSpec ? { toSpec: options.plantSpec } : {} });
|
|
38154
40507
|
if (!resolvers.vehicle && options.vehicles) resolvers.vehicle = createVehicleResolver(options.vehicles(THREE), { quality: "lite" });
|
|
38155
40508
|
return resolvers;
|
|
38156
40509
|
};
|
|
38157
40510
|
const providersOf = (choice) => {
|
|
38158
40511
|
const providers = {};
|
|
38159
|
-
if (choice === "plants" || choice === "both") providers.plant = createPlantProvider();
|
|
38160
|
-
if ((choice === "vehicles" || choice === "both") && options.vehicles) providers.vehicle = createVehicleProvider();
|
|
40512
|
+
if (choice === "plants" || choice === "both") providers.plant = options.providers?.plant ?? createPlantProvider();
|
|
40513
|
+
if ((choice === "vehicles" || choice === "both") && options.vehicles) providers.vehicle = options.providers?.vehicle ?? createVehicleProvider();
|
|
38161
40514
|
return providers;
|
|
38162
40515
|
};
|
|
40516
|
+
const lightsOption = () => isLightBudget(host?.lights) ? { lights: host.lights } : {};
|
|
38163
40517
|
const draw = (THREE, build, kit, buildSeed) => {
|
|
38164
40518
|
const b = kit.builder({ seed: buildSeed });
|
|
38165
40519
|
build(b, kit);
|
|
38166
40520
|
const document = options.document ?? globalThis.document;
|
|
38167
|
-
const drawn = createUrbanThree(THREE, { ...document ? { document } : {}, resolvers: resolversFor(THREE) }).draw(b.scene());
|
|
40521
|
+
const drawn = createUrbanThree(THREE, { ...document ? { document } : {}, resolvers: resolversFor(THREE), ...lightsOption() }).draw(b.scene());
|
|
38168
40522
|
return { root: drawn.root, drawn };
|
|
38169
40523
|
};
|
|
38170
40524
|
const sample = (THREE, key) => {
|
|
@@ -38173,8 +40527,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38173
40527
|
if (key === "street_grid") {
|
|
38174
40528
|
return draw(THREE, (b, k) => {
|
|
38175
40529
|
k.streets.grid(b, GRID);
|
|
38176
|
-
const
|
|
38177
|
-
k.block.fill(b, { cx: GRID.block / 2, cz: GRID.block / 2, size:
|
|
40530
|
+
const inner2 = GRID.block - GRID.width;
|
|
40531
|
+
k.block.fill(b, { cx: GRID.block / 2, cz: GRID.block / 2, size: inner2 - 4, minHeight: 10, maxHeight: 40, k: 3 });
|
|
38178
40532
|
}, kit, seed);
|
|
38179
40533
|
}
|
|
38180
40534
|
if (key === "terrain_patch") return draw(THREE, (b, k) => k.drawTerrain(b, k.terrain(terrain)), kit, seed);
|
|
@@ -38219,7 +40573,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38219
40573
|
const b = kit.builder({ seed: settings.seed });
|
|
38220
40574
|
const made = kit.neighbourhoods.get(settings.pattern).compose(b, { x: 0, z: 0 }, { locale, providers: providersOf(settings.providers) });
|
|
38221
40575
|
const document = options.document ?? globalThis.document;
|
|
38222
|
-
const drawn = createUrbanThree(three, { ...document ? { document } : {}, resolvers: resolversFor(three) }).draw(b.scene());
|
|
40576
|
+
const drawn = createUrbanThree(three, { ...document ? { document } : {}, resolvers: resolversFor(three), ...lightsOption() }).draw(b.scene());
|
|
38223
40577
|
drawn.root.position.set(benchOrigin[0], FLOOR_Y, benchOrigin[1]);
|
|
38224
40578
|
host.scene.add(drawn.root);
|
|
38225
40579
|
bench = { root: drawn.root, drawn, plan: made };
|
|
@@ -38288,11 +40642,12 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38288
40642
|
mark(entry.root, new three.Box3Helper(box2, 6737151));
|
|
38289
40643
|
}
|
|
38290
40644
|
if (host.bench.layer("light_radius")) {
|
|
38291
|
-
for (const
|
|
38292
|
-
|
|
40645
|
+
for (const lamp of entry.drawn.lights) {
|
|
40646
|
+
const light = lamp.bulb ? { node: lamp.bulb, distance: lamp.part.distance, color: lamp.part.color } : lamp.isPointLight || lamp.isSpotLight ? { node: lamp, distance: lamp.distance, color: lamp.color } : null;
|
|
40647
|
+
if (!light) continue;
|
|
38293
40648
|
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);
|
|
40649
|
+
reach.position.copy(light.node.position);
|
|
40650
|
+
mark(light.node.parent ?? entry.root, reach);
|
|
38296
40651
|
}
|
|
38297
40652
|
}
|
|
38298
40653
|
}
|
|
@@ -38301,7 +40656,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38301
40656
|
const frame = (dt) => {
|
|
38302
40657
|
clock += dt;
|
|
38303
40658
|
for (const entry of roots()) entry.drawn.animate(clock);
|
|
38304
|
-
for (const [id, entry] of placed)
|
|
40659
|
+
for (const [id, entry] of placed) {
|
|
40660
|
+
if (entry.root.parent) continue;
|
|
40661
|
+
for (const lamp of entry.drawn.lights) lamp.source?.remove();
|
|
40662
|
+
placed.delete(id);
|
|
40663
|
+
}
|
|
38305
40664
|
if (host?.bench && boxKey() !== helpersShown && (host.bench.layer("light_radius") || host.bench.layer("bounds") || helpersShown !== "")) refreshLayers();
|
|
38306
40665
|
};
|
|
38307
40666
|
const count = () => {
|
|
@@ -38320,6 +40679,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38320
40679
|
const perInstance = (g.index ? g.index.count : g.attributes.position.count) / 3;
|
|
38321
40680
|
triangles += perInstance * (o.isInstancedMesh ? o.count : 1);
|
|
38322
40681
|
});
|
|
40682
|
+
lights += entry.drawn.lights.filter((lamp) => lamp.source).length;
|
|
38323
40683
|
}
|
|
38324
40684
|
return { drawCalls, triangles: Math.round(triangles), lights, instances };
|
|
38325
40685
|
};
|
|
@@ -38392,6 +40752,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
38392
40752
|
}
|
|
38393
40753
|
resolvers.plant?.dispose?.();
|
|
38394
40754
|
resolvers.vehicle?.dispose?.();
|
|
40755
|
+
for (const entry of placed.values()) for (const lamp of entry.drawn.lights) lamp.source?.remove();
|
|
40756
|
+
placed.clear();
|
|
38395
40757
|
host = null;
|
|
38396
40758
|
};
|
|
38397
40759
|
}
|