@adatechnology/game-floorplan-three 0.4.2 → 0.5.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
@@ -66,8 +66,8 @@ function createTranslator(locale) {
66
66
  var OPEN_ROOM_TYPES = /* @__PURE__ */ new Set(["balcony", "terrace", "veranda", "balcony_gourmet"]);
67
67
  var rectsOf = (c) => c.parts && c.parts.length ? c.parts : [[c.u0, c.v0, c.u1, c.v1]];
68
68
  var isOpenArea = (c) => OPEN_ROOM_TYPES.has(c.type) || c.outdoor === true;
69
- function planLod(houses, focus, budget) {
70
- const l = houses.map((c) => ({ c, d: c.distance(focus), n0: c.stats.drawCalls, n1: c.stats.drawCallsLOD1, level: 0 })).sort((a, b) => a.d - b.d);
69
+ function planLod(houses2, focus, budget) {
70
+ const l = houses2.map((c) => ({ c, d: c.distance(focus), n0: c.stats.drawCalls, n1: c.stats.drawCallsLOD1, level: 0 })).sort((a, b) => a.d - b.d);
71
71
  let total = l.reduce((s, x) => s + x.n0, 0);
72
72
  for (let i = l.length - 1; i >= 0 && total > budget; i--) {
73
73
  total -= l[i].n0 - l[i].n1;
@@ -5872,12 +5872,12 @@ function installLightsRoomLight(K, shared) {
5872
5872
  A.invalidate = () => {
5873
5873
  for (let i = 0; i < NS; i++) PL[i].shadow.needsUpdate = true;
5874
5874
  };
5875
- A.update = function(houses, focus, op) {
5875
+ A.update = function(houses2, focus, op) {
5876
5876
  op = op || {};
5877
- houses = (Array.isArray(houses) ? houses : [houses]).filter(Boolean);
5877
+ houses2 = (Array.isArray(houses2) ? houses2 : [houses2]).filter(Boolean);
5878
5878
  _f.set(focus.x, focus.y, focus.z);
5879
5879
  const rooms = [], exterior = [];
5880
- for (const house of houses) {
5880
+ for (const house of houses2) {
5881
5881
  if (house.group && !house.group.visible) continue;
5882
5882
  house.house.updateMatrixWorld(true);
5883
5883
  const M = house.house.matrixWorld, by = /* @__PURE__ */ new Map();
@@ -6267,7 +6267,12 @@ function installDestructionBreach(K, _shared) {
6267
6267
  }
6268
6268
  return null;
6269
6269
  };
6270
- function courses(part, units, opening, paint) {
6270
+ const local = (p) => {
6271
+ house.updateMatrixWorld(true);
6272
+ const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
6273
+ return [v.x, v.y, v.z];
6274
+ };
6275
+ function courses(part, units, opening, chips, paint) {
6271
6276
  const [u0, v0, u1, v1] = part.aabb;
6272
6277
  const alongU = u1 - u0 >= v1 - v0;
6273
6278
  const len = alongU ? u1 - u0 : v1 - v0;
@@ -6291,23 +6296,39 @@ function installDestructionBreach(K, _shared) {
6291
6296
  const x = alongU ? u0 + mid : (u0 + u1) / 2;
6292
6297
  const z = alongU ? (v0 + v1) / 2 : v0 + mid;
6293
6298
  const y = part.y0 + (r + 0.5) * rowH;
6294
- const d = Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]);
6295
- if (d < opening.radius) continue;
6296
- list.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: d < opening.radius + 0.25 || paint === null ? edge : paint });
6299
+ const d = opening ? Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]) : Infinity;
6300
+ if (opening && d < opening.radius) continue;
6301
+ const gone = (c) => Math.abs(c[1] - y) <= rowH / 2 && (alongU ? c[0] >= u0 + a && c[0] <= u0 + b : c[2] >= v0 + a && c[2] <= v0 + b);
6302
+ if (chips.some(gone)) continue;
6303
+ const near = opening && d < opening.radius + 0.25 || chips.some((c) => Math.hypot(c[0] - x, c[1] - y, c[2] - z) < 0.15);
6304
+ list.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: near || paint === null ? edge : paint });
6297
6305
  }
6298
6306
  }
6299
6307
  return list;
6300
6308
  }
6301
- function open(event) {
6302
- if (lod !== 0 || views.has(event.part) || !event.opening) return false;
6303
- const part = structure.byId.get(event.part);
6304
- const spec = opts.spec()?.parts?.find((q) => q.id === event.part);
6309
+ function draw2(partId, change) {
6310
+ if (lod !== 0) return false;
6311
+ const part = structure.byId.get(partId);
6312
+ const spec = opts.spec()?.parts?.find((q) => q.id === partId);
6305
6313
  if (!part || !spec || !spec.units) return false;
6306
- const items = structure.meshesOf(event.part).filter((it) => it.mesh);
6307
- if (!items.length) return false;
6308
- const list = courses(part, spec.units, event.opening, wallColour(items));
6314
+ let view = views.get(partId);
6315
+ if (!view) {
6316
+ const items = structure.meshesOf(partId).filter((it) => it.mesh);
6317
+ if (!items.length) return false;
6318
+ view = { mesh: null, saved: null, items, paint: wallColour(items), units: 0, opening: null, chips: [] };
6319
+ }
6320
+ if (change.opening) {
6321
+ if (view.opening) return false;
6322
+ view.opening = { center: local(change.opening.center), radius: change.opening.radius };
6323
+ }
6324
+ if (change.chip) view.chips.push(local(change.chip));
6325
+ const list = courses(part, spec.units, view.opening, view.chips, view.paint);
6326
+ if (view.mesh) {
6327
+ view.mesh.removeFromParent();
6328
+ view.mesh.dispose();
6329
+ }
6309
6330
  const mesh = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list.length));
6310
- mesh.name = "breach:" + event.part;
6331
+ mesh.name = "breach:" + partId;
6311
6332
  list.forEach((u, i) => {
6312
6333
  tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
6313
6334
  mesh.setMatrixAt(i, tmp.m);
@@ -6319,8 +6340,10 @@ function installDestructionBreach(K, _shared) {
6319
6340
  mesh.castShadow = true;
6320
6341
  mesh.receiveShadow = true;
6321
6342
  house.add(mesh);
6322
- const saved = items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
6323
- views.set(event.part, { mesh, saved, units: list.length, opening: event.opening });
6343
+ if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
6344
+ view.mesh = mesh;
6345
+ view.units = list.length;
6346
+ views.set(partId, view);
6324
6347
  invalidate();
6325
6348
  invalidateShadow();
6326
6349
  return true;
@@ -6334,7 +6357,11 @@ function installDestructionBreach(K, _shared) {
6334
6357
  return {
6335
6358
  /** Handles a `stage` event of the house's object. Returns true when the wall was redrawn as courses. */
6336
6359
  stage(event) {
6337
- return event.stage === 2 ? open(event) : false;
6360
+ return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
6361
+ },
6362
+ /** Handles a `chip` event: the unit that came out leaves a gap in the courses. Returns true when the wall was redrawn. */
6363
+ chip(event) {
6364
+ return draw2(event.part, { chip: event.center });
6338
6365
  },
6339
6366
  /** The part broke or fell: the courses go (the plain range stays collapsed, the renderer hides the part). */
6340
6367
  drop(partId) {
@@ -6566,6 +6593,9 @@ function installDestructionWorld(K, shared) {
6566
6593
  case "stage":
6567
6594
  breach.stage(e);
6568
6595
  break;
6596
+ case "chip":
6597
+ breach.chip(e);
6598
+ break;
6569
6599
  case "collapse":
6570
6600
  for (const id of e.parts) {
6571
6601
  breach.drop(id);
@@ -6620,6 +6650,32 @@ function installDestructionWorld(K, shared) {
6620
6650
  if (D.active && D.world.has(D.id)) D.world.remove(D.id);
6621
6651
  D.active = false;
6622
6652
  };
6653
+ D.follow = (world, id, spec) => {
6654
+ D.spec = spec;
6655
+ const offFollow = world.on("*", (e) => {
6656
+ if (e.object !== id) return;
6657
+ switch (e.type) {
6658
+ case "stage":
6659
+ breach.stage(e);
6660
+ break;
6661
+ case "chip":
6662
+ breach.chip(e);
6663
+ break;
6664
+ case "fracture":
6665
+ breach.drop(e.part);
6666
+ break;
6667
+ case "collapse":
6668
+ for (const p of e.parts) breach.drop(p);
6669
+ break;
6670
+ default:
6671
+ break;
6672
+ }
6673
+ });
6674
+ return { restore: () => breach.restore(), dispose: () => {
6675
+ offFollow();
6676
+ breach.restore();
6677
+ } };
6678
+ };
6623
6679
  D.piece = (id) => D.active ? D.world.part(D.id, id) : null;
6624
6680
  D.state = () => D.active ? D.world.state(D.id) : null;
6625
6681
  D.reset = () => {
@@ -8566,6 +8622,20 @@ import { inferJoints } from "@adatechnology/game-destruction";
8566
8622
  import { markKitOrigin as markKitOrigin2 } from "@adatechnology/game-contracts";
8567
8623
  import { residentialItems } from "@adatechnology/game-floorplan-residential";
8568
8624
  var WOOD_THIN = 0.035;
8625
+ var SHELF_SPAN = 1.5;
8626
+ var SHELVING = /shelf|bookcase|rack/;
8627
+ function carcass(parts, drawn) {
8628
+ const all = [...drawn.values()];
8629
+ const width = Math.max(...all.map((p) => p.max[0])) - Math.min(...all.map((p) => p.min[0]));
8630
+ for (const part of parts) {
8631
+ if (LOOSE.has(part.material) || part.shape.kind !== "box" || part.rotation) continue;
8632
+ const [x, y, z] = part.shape.size;
8633
+ if (y >= x && y >= z) {
8634
+ if (z < x && x >= 0.6 * width) continue;
8635
+ part.structure = { role: "column" };
8636
+ } else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
8637
+ }
8638
+ }
8569
8639
  var FLOOR_TOUCH = 5e-3;
8570
8640
  var LOOSE = /* @__PURE__ */ new Set(["paper"]);
8571
8641
  var BASE_MATERIAL = {
@@ -8681,6 +8751,7 @@ function recordSpec(THREE, name, root, run, opts) {
8681
8751
  if (area < 0.6 * footprint || p.max[1] < 0.2) continue;
8682
8752
  if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
8683
8753
  }
8754
+ if (opts.carcass) carcass(parts, byId);
8684
8755
  const spec = { name, parts, joints, tags: ["furniture"] };
8685
8756
  const colors = new Map(drawn.map((p) => [p.id, p.color]));
8686
8757
  const build = (T3) => {
@@ -8724,7 +8795,7 @@ function furnitureSpec(THREE, type, opts = {}) {
8724
8795
  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`);
8725
8796
  const o = opts.options ?? {};
8726
8797
  const seed = Math.round(opts.seed ?? 1);
8727
- const plain = /shelf|bookcase|rack/.test(type) ? "paper" : "plastic";
8798
+ const plain = SHELVING.test(type) ? "paper" : "plastic";
8728
8799
  const root = { x: seed, z: 0, ry: 0, w, d, h, y: 0, base: 0, palette: o.palette, seed: `furniture_${type}_${seed}` };
8729
8800
  const { K, shared } = renderer.internals;
8730
8801
  const before = K.PARTS.length;
@@ -8743,7 +8814,7 @@ function furnitureSpec(THREE, type, opts = {}) {
8743
8814
  shared.ctx = saved.ctx;
8744
8815
  }
8745
8816
  },
8746
- { materials: { plain, ...o.materials }, chairs: o.chairs }
8817
+ { materials: { plain, ...o.materials }, chairs: o.chairs, carcass: SHELVING.test(type) }
8747
8818
  );
8748
8819
  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]);
8749
8820
  K.PARTS.length = before;
@@ -8811,7 +8882,9 @@ function wallPlan(options) {
8811
8882
  function draw(THREE, plan, options) {
8812
8883
  return create(THREE, { ...options, fleet: null }).build(plan, { quality: "game", terrain: false, noPerimeter: true, pieces: "meshes" });
8813
8884
  }
8885
+ var houses = /* @__PURE__ */ new WeakMap();
8814
8886
  function label(house) {
8887
+ houses.set(house.group, house);
8815
8888
  for (const [id, items] of Object.entries(house.destruction.ranges())) {
8816
8889
  for (const item of items) {
8817
8890
  const object = item.isObject3D ? item : item.mesh;
@@ -8824,12 +8897,13 @@ function wallPiece(THREE, options) {
8824
8897
  const plan = wallPlan(options);
8825
8898
  const houseOptions = options.house ?? {};
8826
8899
  const probe = draw(THREE, plan, { ...houseOptions, destruction: void 0, destructionRenderer: void 0 });
8827
- const spec = probe.structure.spec({ wallDetail: "coarse" });
8900
+ const spec = probe.structure.spec();
8828
8901
  probe.dispose();
8829
8902
  return {
8830
8903
  plan,
8831
8904
  spec,
8832
- build: (T) => label(draw(T, plan, houseOptions))
8905
+ build: (T) => label(draw(T, plan, houseOptions)),
8906
+ follow: (root, world, objectId) => houses.get(root)?.destruction?.follow?.(world, objectId, spec) ?? null
8833
8907
  };
8834
8908
  }
8835
8909
  export {