@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/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
- cursor.pc = f > 0 ? structurePiece("slab_" + f + "_" + c.type) || structurePiece("slab_" + f + "_" + c.id) || structurePiece("slab_" + f) : null;
1124
- for (const rc of subRects(rectsOf2(c), bur)) bx("plaster", rc[0], y0 - 0.04, rc[1], rc[2], y0, rc[3], col);
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
- cursor.pc = structurePiece("slab_" + f);
1129
- for (const rc of subRects(rs, bur)) bx("plaster", rc[0], y0 - 0.24, rc[1], rc[2], y0 - 0.04, rc[3], C.ceiling != null ? C.ceiling : 15657698);
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
- cursor.pc = structurePiece("slab_" + (f + 1)) || (f === nA - 1 ? structurePiece("slab_ceiling") || structurePiece("slab_top") : null);
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)) bx("plaster", rc[0], y0 + c.ceiling, rc[1], rc[2], y0 + c.ceiling + 0.2, rc[3], C.ceiling != null ? C.ceiling : 15657698);
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 = structurePiece("slab_top");
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
- blunt(room, oid, columns[0].id, centre(columns[0].id), 3e6);
7835
- blunt(room, oid, columns[1].id, centre(columns[1].id), 3e6);
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 THREE.CylinderGeometry(shape.radius, shape.radius, shape.height, 14) : shape.kind === "sphere" ? new THREE.SphereGeometry(shape.radius, 12, 8) : new THREE.BoxGeometry(shape.size[0], shape.size[1], shape.size[2]);
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