@adatechnology/game-floorplan-three 0.2.1 → 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/README.md +25 -0
- package/dist/index.d.ts +100 -3
- package/dist/index.global.js +8710 -269
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +323 -28
- package/dist/index.js.map +1 -1
- package/package.json +18 -17
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/create.ts
|
|
2
2
|
import * as props3d from "@adatechnology/game-props3d";
|
|
3
|
+
import { shapeGeometries } from "@adatechnology/game-shapes-three";
|
|
3
4
|
|
|
4
5
|
// src/locale.ts
|
|
5
6
|
var en = {
|
|
@@ -1106,6 +1107,28 @@ function installHouseFloors(K, shared) {
|
|
|
1106
1107
|
shared.ctx._flights = { r: R, l };
|
|
1107
1108
|
return l;
|
|
1108
1109
|
}
|
|
1110
|
+
const panelRects = (p) => p.footprint && p.footprint.length ? p.footprint : [p.aabb];
|
|
1111
|
+
function tilesOf(parent, rc) {
|
|
1112
|
+
const ps = parent && shared.ctx.panelsOf && shared.ctx.panelsOf.get(parent);
|
|
1113
|
+
if (!ps) return [[parent ? structurePiece(parent) : null, rc]];
|
|
1114
|
+
const out = [], taken = [];
|
|
1115
|
+
for (const p of ps) for (const q of panelRects(p)) {
|
|
1116
|
+
const r = [Math.max(rc[0], q[0]), Math.max(rc[1], q[1]), Math.min(rc[2], q[2]), Math.min(rc[3], q[3])];
|
|
1117
|
+
if (r[2] - r[0] > EPS && r[3] - r[1] > EPS) {
|
|
1118
|
+
out.push([structurePiece(p.id), r]);
|
|
1119
|
+
taken.push(r);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
for (const r of subRects([rc], taken)) out.push([null, r]);
|
|
1123
|
+
return out;
|
|
1124
|
+
}
|
|
1125
|
+
function panelAt(parent, x, z) {
|
|
1126
|
+
const ps = shared.ctx.panelsOf && shared.ctx.panelsOf.get(parent);
|
|
1127
|
+
if (!ps) return structurePiece(parent);
|
|
1128
|
+
for (const p of ps) for (const q of panelRects(p)) if (x >= q[0] - EPS && x <= q[2] + EPS && z >= q[1] - EPS && z <= q[3] + EPS) return structurePiece(p.id);
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
const hasSlab = (id) => !!(shared.ctx.structureIndex[id] || shared.ctx.panelsOf && shared.ctx.panelsOf.has(id));
|
|
1109
1132
|
function floorsAndSlabs() {
|
|
1110
1133
|
const R = shared.ctx.r, nA = R.floors.length, C = shared.ctx.colors;
|
|
1111
1134
|
const V = flights(), holes = (f) => V.filter((v) => v.to === f).flatMap((v) => {
|
|
@@ -1119,23 +1142,30 @@ function installHouseFloors(K, shared) {
|
|
|
1119
1142
|
const y0 = Y0(f), bur = holes(f);
|
|
1120
1143
|
for (const c of R.rooms.filter((c2) => c2.floor === f)) {
|
|
1121
1144
|
if (c.type === "void" || c.outbuilding || c.type === "elevator" && f > 0) continue;
|
|
1122
|
-
const col = floorColor(c);
|
|
1123
|
-
|
|
1124
|
-
|
|
1145
|
+
const col = floorColor(c), parent = f > 0 ? ["slab_" + f + "_" + c.type, "slab_" + f + "_" + c.id, "slab_" + f].find(hasSlab) : null;
|
|
1146
|
+
for (const rc of subRects(rectsOf2(c), bur)) for (const [pc, r] of tilesOf(parent, rc)) {
|
|
1147
|
+
cursor.pc = pc;
|
|
1148
|
+
bx("plaster", r[0], y0 - 0.04, r[1], r[2], y0, r[3], col);
|
|
1149
|
+
}
|
|
1125
1150
|
}
|
|
1126
1151
|
if (f > 0) {
|
|
1127
1152
|
const rs = R.rooms.filter((c) => c.floor === f && c.type !== "void" && c.type !== "elevator").flatMap(rectsOf2);
|
|
1128
|
-
|
|
1129
|
-
|
|
1153
|
+
for (const rc of subRects(rs, bur)) for (const [pc, r] of tilesOf("slab_" + f, rc)) {
|
|
1154
|
+
cursor.pc = pc;
|
|
1155
|
+
bx("plaster", r[0], y0 - 0.24, r[1], r[2], y0 - 0.04, r[3], C.ceiling != null ? C.ceiling : 15657698);
|
|
1156
|
+
}
|
|
1130
1157
|
}
|
|
1131
1158
|
cursor.group = "t" + f;
|
|
1132
|
-
|
|
1159
|
+
const ceilingOf = hasSlab("slab_" + (f + 1)) ? "slab_" + (f + 1) : f === nA - 1 ? ["slab_ceiling", "slab_top"].find(hasSlab) : null;
|
|
1133
1160
|
const above = R.rooms.filter((c) => c.floor === f + 1).flatMap(rectsOf2);
|
|
1134
|
-
for (const c of R.rooms.filter((c2) => c2.floor === f && !OPEN_AREA(c2))) for (const rc of subRects(rectsOf2(c), above))
|
|
1161
|
+
for (const c of R.rooms.filter((c2) => c2.floor === f && !OPEN_AREA(c2))) for (const rc of subRects(rectsOf2(c), above)) for (const [pc, r] of tilesOf(ceilingOf, rc)) {
|
|
1162
|
+
cursor.pc = pc;
|
|
1163
|
+
bx("plaster", r[0], y0 + c.ceiling, r[1], r[2], y0 + c.ceiling + 0.2, r[3], C.ceiling != null ? C.ceiling : 15657698);
|
|
1164
|
+
}
|
|
1135
1165
|
cursor.pc = null;
|
|
1136
1166
|
}
|
|
1137
1167
|
}
|
|
1138
|
-
Object.assign(K, { flights, floorsAndSlabs });
|
|
1168
|
+
Object.assign(K, { flights, floorsAndSlabs, tilesOf, panelAt });
|
|
1139
1169
|
}
|
|
1140
1170
|
|
|
1141
1171
|
// src/house/stairs.ts
|
|
@@ -1920,13 +1950,14 @@ function installRoofSlabs(K, shared) {
|
|
|
1920
1950
|
const { boxC, bx, cursor, drain, roomAt, structurePiece } = K;
|
|
1921
1951
|
const sh = (...a) => K.sh(...a);
|
|
1922
1952
|
function slabs(f, xs, zs, O, U, nx, nz, full, pe, C) {
|
|
1923
|
-
cursor.pc =
|
|
1953
|
+
cursor.pc = null;
|
|
1924
1954
|
const c = C.facade != null ? C.facade : 14209216, cap = sh(c, 1.12), pr = 0.5, t = 0.14, o = 0.08, slab = sh(C.ceiling != null ? C.ceiling : 12104876, 0.85);
|
|
1925
1955
|
const yb0 = pe(xs[0], zs[0], xs[nx], zs[nz]);
|
|
1926
1956
|
const free = (j, i) => !O(j, i) && !U(j, i);
|
|
1927
1957
|
for (let j = 0; j < nz; j++) for (let i = 0; i < nx; i++) {
|
|
1928
1958
|
if (!O(j, i)) continue;
|
|
1929
1959
|
const x0 = xs[i], x1 = xs[i + 1], z0 = zs[j], z1 = zs[j + 1], yb = pe(x0, z0, x1, z1);
|
|
1960
|
+
cursor.pc = K.panelAt("slab_top", (x0 + x1) / 2, (z0 + z1) / 2);
|
|
1930
1961
|
const ex0 = free(j, i - 1) ? o : 0, ex1 = free(j, i + 1) ? o : 0, ez0 = free(j - 1, i) ? o : 0, ez1 = free(j + 1, i) ? o : 0;
|
|
1931
1962
|
bx("plaster", x0 - ex0, yb, z0 - ez0, x1 + ex1, yb + 0.03, z1 + ez1, slab);
|
|
1932
1963
|
const side = (ok, ax0, az0, ax1, az1) => {
|
|
@@ -6000,6 +6031,23 @@ function installLightsRoomLight(K, shared) {
|
|
|
6000
6031
|
// src/destruction/pieces.ts
|
|
6001
6032
|
import { assemblyPart, getAssembly } from "@adatechnology/game-destruction";
|
|
6002
6033
|
import { DENSITY, toDestructibleSpec } from "@adatechnology/game-floorplan-structure";
|
|
6034
|
+
|
|
6035
|
+
// src/destruction/panels.ts
|
|
6036
|
+
import { resolveSlabPanels, slabPanelView } from "@adatechnology/game-floorplan-structure";
|
|
6037
|
+
var NONE = { panels: [], replaced: [], supports: [] };
|
|
6038
|
+
var MEMO = /* @__PURE__ */ new WeakMap();
|
|
6039
|
+
function panelViewOf(structure, enabled) {
|
|
6040
|
+
if (!enabled || !structure || !structure.parts) return NONE;
|
|
6041
|
+
let view = MEMO.get(structure);
|
|
6042
|
+
if (!view) {
|
|
6043
|
+
const options = resolveSlabPanels(true);
|
|
6044
|
+
view = options ? slabPanelView(structure, options) : NONE;
|
|
6045
|
+
MEMO.set(structure, view);
|
|
6046
|
+
}
|
|
6047
|
+
return view;
|
|
6048
|
+
}
|
|
6049
|
+
|
|
6050
|
+
// src/destruction/pieces.ts
|
|
6003
6051
|
function installDestructionPieces(K, shared) {
|
|
6004
6052
|
const { THREE, floorAtY, pieceRecord } = K;
|
|
6005
6053
|
shared.destructionSeq = 0;
|
|
@@ -6079,9 +6127,9 @@ function installDestructionPieces(K, shared) {
|
|
|
6079
6127
|
parts.push(p);
|
|
6080
6128
|
byId.set(p.id, p);
|
|
6081
6129
|
}
|
|
6082
|
-
const base = parts.length;
|
|
6130
|
+
const base = parts.length, view = shared.ctx.panelView || panelViewOf(null, false), panelIds = new Set(view.panels.map((e) => e.id));
|
|
6083
6131
|
for (const [id, pc] of shared.ctx.pieces) {
|
|
6084
|
-
if (byId.has(id) || !meshes.has(id)) continue;
|
|
6132
|
+
if (byId.has(id) || panelIds.has(id) || !meshes.has(id)) continue;
|
|
6085
6133
|
const b = box3(id);
|
|
6086
6134
|
if (!b) continue;
|
|
6087
6135
|
const vol = Math.max(1e-6, (b[3] - b[0]) * (b[4] - b[1]) * (b[5] - b[2]));
|
|
@@ -6108,6 +6156,21 @@ function installDestructionPieces(K, shared) {
|
|
|
6108
6156
|
}
|
|
6109
6157
|
if (m) supports.push({ part: p.id, on: m.id, joint: /^(window|jamb)/.test(p.id) ? "glue" : "clip", bears: false, extra: true });
|
|
6110
6158
|
}
|
|
6159
|
+
const specParts = parts.slice(), specSupports = supports.slice();
|
|
6160
|
+
if (view.panels.length) {
|
|
6161
|
+
const gone = new Set(view.replaced);
|
|
6162
|
+
parts.splice(0, parts.length, ...parts.filter((p) => !gone.has(p.id)));
|
|
6163
|
+
for (const id of gone) byId.delete(id);
|
|
6164
|
+
for (const e of view.panels) {
|
|
6165
|
+
const p = Object.assign({}, e, { meshes: (meshes.get(e.id) || []).length, extra: false }), pc = shared.ctx.pieces.get(e.id);
|
|
6166
|
+
if (pc) {
|
|
6167
|
+
for (const k of ["contains", "explodes", "tags", "form", "thickness", "fill"]) if (pc[k] != null && p[k] == null) p[k] = pc[k];
|
|
6168
|
+
}
|
|
6169
|
+
parts.push(p);
|
|
6170
|
+
byId.set(p.id, p);
|
|
6171
|
+
}
|
|
6172
|
+
supports.splice(0, supports.length, ...supports.filter((s) => !gone.has(s.part) && !gone.has(s.on)), ...view.supports.filter((s) => byId.has(s.part) && (byId.has(s.on) || s.on === "ground")).map((a) => Object.assign({}, a)));
|
|
6173
|
+
}
|
|
6111
6174
|
const structure = {
|
|
6112
6175
|
...E0,
|
|
6113
6176
|
system: r0.construction && r0.construction.system || "masonry",
|
|
@@ -6133,7 +6196,7 @@ function installDestructionPieces(K, shared) {
|
|
|
6133
6196
|
// One part per wall, as the house draws them (a mesh range per wall): the panels of `wallDetail: 'panels'` would name parts the meshes do not have.
|
|
6134
6197
|
// A masonry wall keeps its assembly (layers, units, rebar, breach) on the same box, so its stage 2 can open the courses; `o.wallDetail` still overrides.
|
|
6135
6198
|
spec: (o) => {
|
|
6136
|
-
const spec = toDestructibleSpec(structure, { wallDetail: "coarse", ...o });
|
|
6199
|
+
const spec = toDestructibleSpec({ ...structure, parts: specParts, supports: specSupports }, { wallDetail: "coarse", ...o });
|
|
6137
6200
|
if (o && o.wallDetail && o.wallDetail !== "coarse") return spec;
|
|
6138
6201
|
spec.parts = spec.parts.map((sp) => {
|
|
6139
6202
|
const pc0 = byId.get(sp.id);
|
|
@@ -6143,7 +6206,7 @@ function installDestructionPieces(K, shared) {
|
|
|
6143
6206
|
return Object.keys(extra).length ? { ...sp, ...extra } : sp;
|
|
6144
6207
|
});
|
|
6145
6208
|
spec.parts = spec.parts.map((sp) => {
|
|
6146
|
-
const pc = byId.get(sp.id);
|
|
6209
|
+
const pc = byId.get(sp.id) || (sp.structure ? { role: sp.structure.role, material: sp.material } : null);
|
|
6147
6210
|
if (!pc || pc.unit) return sp;
|
|
6148
6211
|
if (pc.role !== "wall" && pc.role !== "partition") {
|
|
6149
6212
|
const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
|
|
@@ -6677,6 +6740,17 @@ function installBuildBuild(K, shared) {
|
|
|
6677
6740
|
shared.ctx.pieceObjects = /* @__PURE__ */ new Map();
|
|
6678
6741
|
shared.ctx.structureIndex = {};
|
|
6679
6742
|
for (const e of r0.structure && r0.structure.parts || []) shared.ctx.structureIndex[e.id] = e;
|
|
6743
|
+
{
|
|
6744
|
+
const view = shared.ctx.panelView = panelViewOf(r0.structure, !!cfg.destruction);
|
|
6745
|
+
shared.ctx.panelsOf = /* @__PURE__ */ new Map();
|
|
6746
|
+
for (const id of view.replaced) delete shared.ctx.structureIndex[id];
|
|
6747
|
+
for (const e of view.panels) {
|
|
6748
|
+
shared.ctx.structureIndex[e.id] = e;
|
|
6749
|
+
const l = shared.ctx.panelsOf.get(e.slab);
|
|
6750
|
+
if (l) l.push(e);
|
|
6751
|
+
else shared.ctx.panelsOf.set(e.slab, [e]);
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6680
6754
|
shared.ctx.panes = r0.roof && r0.roof.panes || [];
|
|
6681
6755
|
shared.ctx.noPiece = false;
|
|
6682
6756
|
cursor.separate = opts.pieces === "meshes";
|
|
@@ -7161,6 +7235,8 @@ function buildApi(K, shared) {
|
|
|
7161
7235
|
types: TYPES,
|
|
7162
7236
|
mv: props,
|
|
7163
7237
|
applyPart,
|
|
7238
|
+
// the kernel and the shared state, for recorders that run the team's builders into something else than the house (`furnitureSpec`, `tapSpec`): not a stable api
|
|
7239
|
+
internals: { K, shared },
|
|
7164
7240
|
get materials() {
|
|
7165
7241
|
return shared.mats;
|
|
7166
7242
|
},
|
|
@@ -7214,7 +7290,7 @@ function create(THREE, options = {}) {
|
|
|
7214
7290
|
}
|
|
7215
7291
|
return id;
|
|
7216
7292
|
};
|
|
7217
|
-
const K = { t: createTranslator(cfg.locale), seedKey, THREE, cfg, DOC, PROPS, Reflector, FLEET, HP, invalidate, invalidateShadow, colliderChanged, VERSION, rectsOf, isOpenArea, OPEN_ROOM_TYPES };
|
|
7293
|
+
const K = { t: createTranslator(cfg.locale), seedKey, THREE: shapeGeometries(THREE), cfg, DOC, PROPS, Reflector, FLEET, HP, invalidate, invalidateShadow, colliderChanged, VERSION, rectsOf, isOpenArea, OPEN_ROOM_TYPES };
|
|
7218
7294
|
installKernelGeometry(K, shared);
|
|
7219
7295
|
installKernelNoise(K, shared);
|
|
7220
7296
|
installMaterialsTextures(K, shared);
|
|
@@ -7831,13 +7907,11 @@ function createHouseScenarios(text, at = SPECIMEN_AT) {
|
|
|
7831
7907
|
const list = events(room);
|
|
7832
7908
|
const columns = groundColumns(parts);
|
|
7833
7909
|
const laj = slabs(parts);
|
|
7834
|
-
|
|
7835
|
-
|
|
7910
|
+
const first = columns[0];
|
|
7911
|
+
const mate = columns.slice(1).filter((p) => Math.abs(p.position[0] - first.position[0]) < 0.5).sort((a, b) => Math.abs(b.position[2] - first.position[2]) - Math.abs(a.position[2] - first.position[2]))[0] ?? columns[1];
|
|
7912
|
+
blunt(room, oid, first.id, centre(first.id), 3e6);
|
|
7913
|
+
blunt(room, oid, mate.id, centre(mate.id), 3e6);
|
|
7836
7914
|
step(90);
|
|
7837
|
-
for (let next = 2; next < columns.length && !slabs(parts).some((id) => collapsed(list).includes(id)); next++) {
|
|
7838
|
-
blunt(room, oid, columns[next].id, centre(columns[next].id), 3e6);
|
|
7839
|
-
step(90);
|
|
7840
|
-
}
|
|
7841
7915
|
const fell = collapsed(list);
|
|
7842
7916
|
expect(laj.some((id) => fell.includes(id)), fill(T.two_slab_fell, { n: fell.length }));
|
|
7843
7917
|
expect(parts.filter((p) => role(p) === "roof").some((p) => fell.includes(p.id)), T.two_roof_fell);
|
|
@@ -7906,8 +7980,9 @@ function createHouseScenarios(text, at = SPECIMEN_AT) {
|
|
|
7906
7980
|
}
|
|
7907
7981
|
|
|
7908
7982
|
// src/test-area/specimens.ts
|
|
7983
|
+
import { shapeGeometries as shapeGeometries2 } from "@adatechnology/game-shapes-three";
|
|
7909
7984
|
var DEFAULT_SPECIMENS = [
|
|
7910
|
-
{ id: "house_casa_portico", template: "townhouse", seed: 1, portico: true },
|
|
7985
|
+
{ id: "house_casa_portico", template: "townhouse", seed: 1, portico: true, sizing: { safety: 0.72, redundancy: 0.97 } },
|
|
7911
7986
|
{ id: "house_casa_inteira", template: "single_storey_a", seed: 1 },
|
|
7912
7987
|
{ id: "house_casa_wood_frame", template: "wood_frame_house", seed: 1 },
|
|
7913
7988
|
{ id: "house_casa_tronco", template: "log_cabin", seed: 1 },
|
|
@@ -7935,12 +8010,12 @@ var cache = /* @__PURE__ */ new WeakMap();
|
|
|
7935
8010
|
function specimenSpec(THREE, engine, def, houseOptions = {}) {
|
|
7936
8011
|
let byId = cache.get(THREE);
|
|
7937
8012
|
if (!byId) cache.set(THREE, byId = /* @__PURE__ */ new Map());
|
|
7938
|
-
const key = `${def.id}|${def.template}|${def.seed ?? 1}|${def.state ?? "normal"}|${def.portico ? "p" : ""}`;
|
|
8013
|
+
const key = `${def.id}|${def.template}|${def.seed ?? 1}|${def.state ?? "normal"}|${def.portico ? "p" : ""}|${def.sizing ? JSON.stringify(def.sizing) : ""}`;
|
|
7939
8014
|
let made = byId.get(key);
|
|
7940
8015
|
if (!made) {
|
|
7941
8016
|
const plan = engine.generate(def.template, { seed: def.seed ?? 1, ...def.state ? { state: def.state } : {} });
|
|
7942
8017
|
const house = create(THREE, { ...houseOptions, destruction: void 0, destructionRenderer: void 0, interactives: void 0, vegetation: void 0, fleet: null }).build(plan, { quality: "game", terrain: false, noPerimeter: true });
|
|
7943
|
-
const spec = house.structure.spec({ wallDetail: "coarse", ...def.portico ? { portico: true } : {} });
|
|
8018
|
+
const spec = house.structure.spec({ wallDetail: "coarse", ...def.portico ? { portico: true } : {}, ...def.sizing ?? {} });
|
|
7944
8019
|
house.dispose();
|
|
7945
8020
|
made = { spec, plan };
|
|
7946
8021
|
byId.set(key, made);
|
|
@@ -7948,11 +8023,12 @@ function specimenSpec(THREE, engine, def, houseOptions = {}) {
|
|
|
7948
8023
|
return { spec: JSON.parse(JSON.stringify(made.spec)), plan: made.plan };
|
|
7949
8024
|
}
|
|
7950
8025
|
function buildSpecimenMeshes(THREE, spec) {
|
|
8026
|
+
const G = shapeGeometries2(THREE);
|
|
7951
8027
|
const root = new THREE.Group();
|
|
7952
8028
|
for (const part of spec.parts) {
|
|
7953
8029
|
if (part.id === "ground") continue;
|
|
7954
8030
|
const shape = part.shape;
|
|
7955
|
-
const geometry = shape.kind === "cylinder" ? new
|
|
8031
|
+
const geometry = shape.kind === "cylinder" ? new G.CylinderGeometry(shape.radius, shape.radius, shape.height, 14) : shape.kind === "sphere" ? new G.SphereGeometry(shape.radius, 12, 8) : new G.BoxGeometry(shape.size[0], shape.size[1], shape.size[2]);
|
|
7956
8032
|
const glass = /glass/.test(part.material);
|
|
7957
8033
|
const material = new THREE.MeshStandardMaterial({ color: COLOR[part.material] ?? 8947848, roughness: 0.8, metalness: part.material === "steel" || part.material === "aluminium" ? 0.5 : 0, transparent: glass, opacity: glass ? 0.45 : 1 });
|
|
7958
8034
|
const mesh = new THREE.Mesh(geometry, material);
|
|
@@ -7977,6 +8053,7 @@ var HITS = {
|
|
|
7977
8053
|
};
|
|
7978
8054
|
var GAP = 4;
|
|
7979
8055
|
var EYE = 1.7;
|
|
8056
|
+
var TALL_STOREYS = 10;
|
|
7980
8057
|
var bounds = (plan) => ({ width: Number(plan.w) || 10, depth: Number(plan.d) || 10 });
|
|
7981
8058
|
function createFloorplanTestModule(options = {}) {
|
|
7982
8059
|
const locale = options.locale ?? "pt-BR";
|
|
@@ -8362,9 +8439,14 @@ function createFloorplanTestModule(options = {}) {
|
|
|
8362
8439
|
id: "floorplan_typologies",
|
|
8363
8440
|
name: locale === "en" ? "every typology of every package builds, with structure and floors" : "toda tipologia de todo pacote constr\xF3i, com estrutura e andares",
|
|
8364
8441
|
run({ expect }) {
|
|
8442
|
+
const templates = engine?.listTemplates() ?? [];
|
|
8443
|
+
const shortest = (id) => {
|
|
8444
|
+
const range = templates.find((t) => t.id === id)?.floorRange;
|
|
8445
|
+
return range && range[0] >= TALL_STOREYS ? { floors: range[0] } : {};
|
|
8446
|
+
};
|
|
8365
8447
|
for (const group of groups) {
|
|
8366
8448
|
for (const id of group.templates) {
|
|
8367
|
-
const plan = engine.generate(id, { seed: 1 });
|
|
8449
|
+
const plan = engine.generate(id, { seed: 1, ...shortest(id) });
|
|
8368
8450
|
const house = create(THREE ?? host?.THREE, { locale, ...options.house, destruction: void 0, destructionRenderer: void 0, interactives: void 0, vegetation: void 0, fleet: null }).build(plan, { quality: "game", terrain: false, noPerimeter: true });
|
|
8369
8451
|
const ok = house.structure.parts.length > 0 && house.floors.length >= 1 && house.colliders.length > 0;
|
|
8370
8452
|
expect(ok, `${groupName(group)}: ${templateName(id)} (${house.structure.parts.length})`);
|
|
@@ -8400,7 +8482,7 @@ function createFloorplanTestModule(options = {}) {
|
|
|
8400
8482
|
{ kind: "number", id: "budget", name: text.controls.budget, hint: text.hints.budget, min: 60, max: 2e3, step: 10, default: settings.budget },
|
|
8401
8483
|
choice("floor_view", ["all", "0", "1", "2"], "all", text.options.floor_view),
|
|
8402
8484
|
choice("hit", Object.keys(HITS), "burst_9mm", text.options.hit),
|
|
8403
|
-
{ kind: "action", id: "build", name: text.actions.build },
|
|
8485
|
+
{ kind: "action", id: "build", name: text.actions.build, slow: true },
|
|
8404
8486
|
{ kind: "action", id: "enter", name: text.actions.enter },
|
|
8405
8487
|
{ kind: "action", id: "up", name: text.actions.up },
|
|
8406
8488
|
{ kind: "action", id: "down", name: text.actions.down },
|
|
@@ -8441,7 +8523,8 @@ function createFloorplanTestModule(options = {}) {
|
|
|
8441
8523
|
time += dt;
|
|
8442
8524
|
if (dirty && time - dirtyAt > 0.4) {
|
|
8443
8525
|
dirty = false;
|
|
8444
|
-
build();
|
|
8526
|
+
if (lent.loading) void lent.loading.busy(text.actions.build, () => build());
|
|
8527
|
+
else build();
|
|
8445
8528
|
}
|
|
8446
8529
|
const c = cameraWorld();
|
|
8447
8530
|
const auto = settings.lod === "auto";
|
|
@@ -8476,6 +8559,216 @@ function createFloorplanTestModule(options = {}) {
|
|
|
8476
8559
|
}
|
|
8477
8560
|
};
|
|
8478
8561
|
}
|
|
8562
|
+
|
|
8563
|
+
// src/furniture/spec.ts
|
|
8564
|
+
import { inferJoints } from "@adatechnology/game-destruction";
|
|
8565
|
+
import { residentialItems } from "@adatechnology/game-floorplan-residential";
|
|
8566
|
+
var WOOD_THIN = 0.035;
|
|
8567
|
+
var FLOOR_TOUCH = 5e-3;
|
|
8568
|
+
var LOOSE = /* @__PURE__ */ new Set(["paper"]);
|
|
8569
|
+
var BASE_MATERIAL = {
|
|
8570
|
+
wood: "wood_hard",
|
|
8571
|
+
metal: "steel",
|
|
8572
|
+
fabric: "fabric",
|
|
8573
|
+
glass: "glass",
|
|
8574
|
+
plain: "plastic",
|
|
8575
|
+
plant: "foliage",
|
|
8576
|
+
picture: "paper",
|
|
8577
|
+
emissive: "glass"
|
|
8578
|
+
};
|
|
8579
|
+
var renderers = /* @__PURE__ */ new WeakMap();
|
|
8580
|
+
function rendererOf(THREE, document2) {
|
|
8581
|
+
let r = renderers.get(THREE);
|
|
8582
|
+
if (!r) {
|
|
8583
|
+
r = create(THREE, document2 ? { document: document2 } : {});
|
|
8584
|
+
renderers.set(THREE, r);
|
|
8585
|
+
}
|
|
8586
|
+
return r;
|
|
8587
|
+
}
|
|
8588
|
+
function recordSpec(THREE, name, root, run, opts) {
|
|
8589
|
+
const o = { chairs: opts.chairs };
|
|
8590
|
+
const materialOf = { ...BASE_MATERIAL, ...opts.materials };
|
|
8591
|
+
const drawn = [];
|
|
8592
|
+
const counters = /* @__PURE__ */ new Map();
|
|
8593
|
+
const q = new THREE.Quaternion();
|
|
8594
|
+
const eul = new THREE.Euler();
|
|
8595
|
+
const xyz = new THREE.Euler();
|
|
8596
|
+
const v = new THREE.Vector3();
|
|
8597
|
+
const loc = (M, lx, lz) => {
|
|
8598
|
+
const c = Math.cos(M.ry);
|
|
8599
|
+
const s = Math.sin(M.ry);
|
|
8600
|
+
return [M.x + lx * c + lz * s, M.z - lx * s + lz * c];
|
|
8601
|
+
};
|
|
8602
|
+
const record = (M, key, shape, px, py, pz, rx, ry, rz, color) => {
|
|
8603
|
+
if (o.chairs === false && M !== root || key === "water") return;
|
|
8604
|
+
q.setFromEuler(eul.set(rx, ry, rz, "YXZ"));
|
|
8605
|
+
const turned = rx !== 0 || ry !== 0 || rz !== 0;
|
|
8606
|
+
const size = shape.kind === "box" ? shape.size : shape.kind === "cylinder" ? [shape.radius * 2, shape.height, shape.radius * 2] : [shape.radius * 2, shape.radius * 2, shape.radius * 2];
|
|
8607
|
+
const min = [Infinity, Infinity, Infinity];
|
|
8608
|
+
const max = [-Infinity, -Infinity, -Infinity];
|
|
8609
|
+
for (const sx of [-0.5, 0.5]) for (const sy of [-0.5, 0.5]) for (const sz of [-0.5, 0.5]) {
|
|
8610
|
+
v.set(sx * size[0], sy * size[1], sz * size[2]).applyQuaternion(q);
|
|
8611
|
+
const c = [v.x + px - root.x, v.y + py, v.z + pz - root.z];
|
|
8612
|
+
for (let k = 0; k < 3; k++) {
|
|
8613
|
+
min[k] = Math.min(min[k], c[k]);
|
|
8614
|
+
max[k] = Math.max(max[k], c[k]);
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
8617
|
+
const n = counters.get(key) ?? 0;
|
|
8618
|
+
counters.set(key, n + 1);
|
|
8619
|
+
let rotation = null;
|
|
8620
|
+
if (turned) {
|
|
8621
|
+
xyz.setFromQuaternion(q, "XYZ");
|
|
8622
|
+
rotation = [xyz.x, xyz.y, xyz.z];
|
|
8623
|
+
}
|
|
8624
|
+
drawn.push({ id: `${key}_${n}`, key, shape, position: [px - root.x, py, pz - root.z], rotation, color, min, max });
|
|
8625
|
+
};
|
|
8626
|
+
const A = {
|
|
8627
|
+
box(M, mat, x, y, z, bw, bh, bd, color, ry = 0, rx = 0, rz = 0) {
|
|
8628
|
+
const [px, pz] = loc(M, x, z);
|
|
8629
|
+
record(M, mat, { kind: "box", size: [bw, bh, bd] }, px, M.base + y + bh / 2, pz, rx, M.ry + ry, rz, color);
|
|
8630
|
+
},
|
|
8631
|
+
cylinder(M, mat, x, y, z, r, ch, color, rx = 0, rz = 0) {
|
|
8632
|
+
const [px, pz] = loc(M, x, z);
|
|
8633
|
+
record(M, mat, { kind: "cylinder", radius: r, height: ch }, px, M.base + y + (rx || rz ? 0 : ch / 2), pz, rx || 0, M.ry, rz || 0, color);
|
|
8634
|
+
},
|
|
8635
|
+
leaf() {
|
|
8636
|
+
},
|
|
8637
|
+
plane() {
|
|
8638
|
+
},
|
|
8639
|
+
light() {
|
|
8640
|
+
}
|
|
8641
|
+
};
|
|
8642
|
+
run(A);
|
|
8643
|
+
if (!drawn.length) throw new Error(`game-floorplan-three: the builder of "${name}" drew no box or cylinder`);
|
|
8644
|
+
const parts = drawn.map((p) => {
|
|
8645
|
+
const thin = Math.min(...p.shape.kind === "box" ? p.shape.size : p.shape.kind === "cylinder" ? [p.shape.radius * 2, p.shape.height] : [p.shape.radius * 2]);
|
|
8646
|
+
let material = materialOf[p.key] ?? "plastic";
|
|
8647
|
+
if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
|
|
8648
|
+
const part = { id: p.id, material, shape: p.shape, position: p.position };
|
|
8649
|
+
if (p.rotation) part.rotation = p.rotation;
|
|
8650
|
+
if (!LOOSE.has(material) && p.min[1] <= FLOOR_TOUCH) part.anchored = true;
|
|
8651
|
+
return part;
|
|
8652
|
+
});
|
|
8653
|
+
const byId = new Map(drawn.map((p) => [p.id, p]));
|
|
8654
|
+
const joints = [];
|
|
8655
|
+
for (const j of inferJoints(parts, 0.01)) {
|
|
8656
|
+
const a = parts.find((p) => p.id === j.a);
|
|
8657
|
+
const b = parts.find((p) => p.id === j.b);
|
|
8658
|
+
const la = LOOSE.has(a.material);
|
|
8659
|
+
const lb = LOOSE.has(b.material);
|
|
8660
|
+
if (la && lb) continue;
|
|
8661
|
+
if (la || lb) {
|
|
8662
|
+
const [loose, other] = la ? [a, b] : [b, a];
|
|
8663
|
+
if (Math.abs(byId.get(loose.id).min[1] - byId.get(other.id).max[1]) > 0.02) continue;
|
|
8664
|
+
joints.push({ id: j.id, a: j.a, b: j.b, kind: "resting" });
|
|
8665
|
+
continue;
|
|
8666
|
+
}
|
|
8667
|
+
joints.push(j);
|
|
8668
|
+
}
|
|
8669
|
+
const all = drawn.reduce(
|
|
8670
|
+
(m, p) => ({ x0: Math.min(m.x0, p.min[0]), x1: Math.max(m.x1, p.max[0]), z0: Math.min(m.z0, p.min[2]), z1: Math.max(m.z1, p.max[2]) }),
|
|
8671
|
+
{ x0: Infinity, x1: -Infinity, z0: Infinity, z1: -Infinity }
|
|
8672
|
+
);
|
|
8673
|
+
const footprint = (all.x1 - all.x0) * (all.z1 - all.z0);
|
|
8674
|
+
let top = null;
|
|
8675
|
+
for (const p of drawn) {
|
|
8676
|
+
const part = parts.find((x) => x.id === p.id);
|
|
8677
|
+
if (part.shape.kind === "sphere" || part.rotation || part.material === "fabric" || LOOSE.has(part.material)) continue;
|
|
8678
|
+
const area = (p.max[0] - p.min[0]) * (p.max[2] - p.min[2]);
|
|
8679
|
+
if (area < 0.6 * footprint || p.max[1] < 0.2) continue;
|
|
8680
|
+
if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
|
|
8681
|
+
}
|
|
8682
|
+
const spec = { name, parts, joints, tags: ["furniture"] };
|
|
8683
|
+
const colors = new Map(drawn.map((p) => [p.id, p.color]));
|
|
8684
|
+
const build = (T3) => {
|
|
8685
|
+
const group = new T3.Group();
|
|
8686
|
+
group.name = name;
|
|
8687
|
+
const mats = /* @__PURE__ */ new Map();
|
|
8688
|
+
for (const p of parts) {
|
|
8689
|
+
const color = colors.get(p.id) ?? 8947848;
|
|
8690
|
+
const metal = p.material === "steel";
|
|
8691
|
+
const glass = p.material === "glass";
|
|
8692
|
+
const key = `${color}:${metal}:${glass}`;
|
|
8693
|
+
let mat = mats.get(key);
|
|
8694
|
+
if (!mat) {
|
|
8695
|
+
mat = new T3.MeshStandardMaterial({ color, roughness: metal ? 0.45 : 0.85, metalness: metal ? 0.7 : 0, transparent: glass, opacity: glass ? 0.4 : 1 });
|
|
8696
|
+
mats.set(key, mat);
|
|
8697
|
+
}
|
|
8698
|
+
const s = p.shape;
|
|
8699
|
+
const geo = s.kind === "box" ? new T3.BoxGeometry(s.size[0], s.size[1], s.size[2]) : s.kind === "cylinder" ? new T3.CylinderGeometry(s.radius, s.radius, s.height, 14) : new T3.SphereGeometry(s.radius, 12, 8);
|
|
8700
|
+
const mesh = new T3.Mesh(geo, mat);
|
|
8701
|
+
mesh.name = p.id;
|
|
8702
|
+
mesh.userData.destructionPart = p.id;
|
|
8703
|
+
mesh.userData.destructionMaterial = p.material;
|
|
8704
|
+
mesh.position.set(p.position[0], p.position[1], p.position[2]);
|
|
8705
|
+
if (p.rotation) mesh.rotation.set(p.rotation[0], p.rotation[1], p.rotation[2], "XYZ");
|
|
8706
|
+
mesh.castShadow = !glass;
|
|
8707
|
+
mesh.receiveShadow = true;
|
|
8708
|
+
group.add(mesh);
|
|
8709
|
+
}
|
|
8710
|
+
return group;
|
|
8711
|
+
};
|
|
8712
|
+
return { spec, build, top };
|
|
8713
|
+
}
|
|
8714
|
+
function furnitureSpec(THREE, type, opts = {}) {
|
|
8715
|
+
const renderer = rendererOf(THREE, opts.document);
|
|
8716
|
+
const ent = renderer.types[type];
|
|
8717
|
+
if (!ent) throw new Error(`game-floorplan-three: no builder for the furniture type "${type}"`);
|
|
8718
|
+
const entry = (opts.catalog ?? residentialItems).find((i) => i.type === type);
|
|
8719
|
+
const w = opts.w ?? entry?.w;
|
|
8720
|
+
const d = opts.d ?? entry?.d;
|
|
8721
|
+
const h = opts.h ?? entry?.h;
|
|
8722
|
+
if (w === void 0 || d === void 0 || h === void 0) throw new Error(`game-floorplan-three: furniture type "${type}" is not in the catalogue, so give w, d and h`);
|
|
8723
|
+
const o = opts.options ?? {};
|
|
8724
|
+
const seed = Math.round(opts.seed ?? 1);
|
|
8725
|
+
const plain = /shelf|bookcase|rack/.test(type) ? "paper" : "plastic";
|
|
8726
|
+
const root = { x: seed, z: 0, ry: 0, w, d, h, y: 0, base: 0, palette: o.palette, seed: `furniture_${type}_${seed}` };
|
|
8727
|
+
const { K, shared } = renderer.internals;
|
|
8728
|
+
const before = K.PARTS.length;
|
|
8729
|
+
const made = recordSpec(
|
|
8730
|
+
THREE,
|
|
8731
|
+
type,
|
|
8732
|
+
root,
|
|
8733
|
+
(A) => {
|
|
8734
|
+
const saved = { api0: shared.api0, ctx: shared.ctx };
|
|
8735
|
+
shared.api0 = A;
|
|
8736
|
+
shared.ctx = shared.ctx ?? {};
|
|
8737
|
+
try {
|
|
8738
|
+
ent.f(root, A);
|
|
8739
|
+
} finally {
|
|
8740
|
+
shared.api0 = saved.api0;
|
|
8741
|
+
shared.ctx = saved.ctx;
|
|
8742
|
+
}
|
|
8743
|
+
},
|
|
8744
|
+
{ materials: { plain, ...o.materials }, chairs: o.chairs }
|
|
8745
|
+
);
|
|
8746
|
+
const spouts = K.PARTS.slice(before).filter((p) => p.type === "water_tap").map((p) => [p.pos[0] - root.x, p.pos[1], p.pos[2] - root.z]);
|
|
8747
|
+
K.PARTS.length = before;
|
|
8748
|
+
return { spec: made.spec, build: made.build, top: made.top, size: { w, d, h }, spouts };
|
|
8749
|
+
}
|
|
8750
|
+
function tapSpec(THREE, opts = {}) {
|
|
8751
|
+
const renderer = rendererOf(THREE, opts.document);
|
|
8752
|
+
const { K, shared } = renderer.internals;
|
|
8753
|
+
const kind = opts.kind ?? "counter";
|
|
8754
|
+
const root = { x: 1, z: 0, ry: 0, w: 0.3, d: 0.3, h: 0.3, y: 0, base: 0, seed: `tap_${kind}` };
|
|
8755
|
+
const before = K.PARTS.length;
|
|
8756
|
+
const made = recordSpec(THREE, kind === "counter" ? "tap" : "wall_tap", root, (A) => {
|
|
8757
|
+
const kit = K.kit2(root, A);
|
|
8758
|
+
const saved = shared.api0;
|
|
8759
|
+
shared.api0 = A;
|
|
8760
|
+
try {
|
|
8761
|
+
if (kind === "counter") K.counterTap(kit, 0, 0, 0, opts.reach ?? 0.2, 0.17, {});
|
|
8762
|
+
else K.wallTap(kit, 0, 0.3, 0, 1, 0.1, {});
|
|
8763
|
+
} finally {
|
|
8764
|
+
shared.api0 = saved;
|
|
8765
|
+
}
|
|
8766
|
+
}, { materials: {} });
|
|
8767
|
+
const water = K.PARTS.slice(before).find((p) => p.type === "water_tap");
|
|
8768
|
+
if (!water) throw new Error("game-floorplan-three: the tap drew no water thread");
|
|
8769
|
+
K.PARTS.length = before;
|
|
8770
|
+
return { spec: made.spec, build: made.build, spout: [water.pos[0] - root.x, water.pos[1], water.pos[2] - root.z] };
|
|
8771
|
+
}
|
|
8479
8772
|
export {
|
|
8480
8773
|
DEFAULT_SPECIMENS,
|
|
8481
8774
|
FLOORPLAN_AREA_ID,
|
|
@@ -8491,12 +8784,14 @@ export {
|
|
|
8491
8784
|
createHouseScenarios,
|
|
8492
8785
|
createTranslator,
|
|
8493
8786
|
en,
|
|
8787
|
+
furnitureSpec,
|
|
8494
8788
|
isOpenArea,
|
|
8495
8789
|
locales,
|
|
8496
8790
|
navigationDefOf,
|
|
8497
8791
|
planLod,
|
|
8498
8792
|
ptBR,
|
|
8499
8793
|
rectsOf,
|
|
8500
|
-
specimenSpec
|
|
8794
|
+
specimenSpec,
|
|
8795
|
+
tapSpec
|
|
8501
8796
|
};
|
|
8502
8797
|
//# sourceMappingURL=index.js.map
|