@adatechnology/game-floorplan-three 0.8.2 → 0.9.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.
@@ -70,7 +70,7 @@ var GameFloorplanThree = (() => {
70
70
  shade: () => shade
71
71
  });
72
72
 
73
- // ../game-shapes/dist/chunk-R4RY23RH.js
73
+ // ../game-shapes/dist/chunk-IFXQIDSA.js
74
74
  function canonicalJson(value) {
75
75
  if (value === null) return "null";
76
76
  if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
@@ -1538,6 +1538,107 @@ var GameFloorplanThree = (() => {
1538
1538
  return { positions: positions2, normals, uvs };
1539
1539
  }
1540
1540
 
1541
+ // ../game-contracts/dist/index.js
1542
+ var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
1543
+ var DEFAULT_IGNITION_RULES = {
1544
+ fillCurve: [
1545
+ [0, 0.6],
1546
+ [0.1, 1],
1547
+ [0.35, 0.6],
1548
+ [0.65, 0.3],
1549
+ [0.9, 0.15],
1550
+ [1, 0.12]
1551
+ ],
1552
+ flameFillInfluence: 0.5,
1553
+ flameDefaults: { incendiary: 0.7, tracer: 0.2 },
1554
+ sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },
1555
+ sparkMetalDefault: 0.5,
1556
+ sparkFluid: { diesel: 0.15 },
1557
+ fireNear: { radius: 1.5, chancePerSecond: 0.5 }
1558
+ };
1559
+ var resolveIgnitionRules = (rules) => ({
1560
+ ...DEFAULT_IGNITION_RULES,
1561
+ ...rules,
1562
+ flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },
1563
+ sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },
1564
+ sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },
1565
+ fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear }
1566
+ });
1567
+ var fillFactor = (fill2, curve = DEFAULT_IGNITION_RULES.fillCurve) => {
1568
+ if (curve.length === 0) return 1;
1569
+ const f = Math.min(1, Math.max(0, fill2));
1570
+ if (f <= curve[0][0]) return curve[0][1];
1571
+ for (let i = 1; i < curve.length; i++) {
1572
+ const [x1, y1] = curve[i];
1573
+ const [x0, y0] = curve[i - 1];
1574
+ if (f <= x1) return x1 === x0 ? y1 : y0 + (y1 - y0) * (f - x0) / (x1 - x0);
1575
+ }
1576
+ return curve[curve.length - 1][1];
1577
+ };
1578
+ var sparkTargetFactor = (materialId, metal, rules = DEFAULT_IGNITION_RULES) => !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault;
1579
+ var ignitionChance = (hit, target, rules = DEFAULT_IGNITION_RULES) => {
1580
+ const k = fillFactor(target.fill, rules.fillCurve);
1581
+ const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === void 0 ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k;
1582
+ const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0);
1583
+ const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
1584
+ return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
1585
+ };
1586
+ var DEFERRED_QUEUE_ID = {
1587
+ DESTRUCTION_EVENTS: "destruction.events",
1588
+ BREACH_PAINT: "floorplan.breach-paint",
1589
+ AUDIO_VOICES: "audio.voices",
1590
+ VEHICLE_CREASE: "vehicles.crease"
1591
+ };
1592
+ var DEFERRED_PRIORITY = {
1593
+ [DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
1594
+ [DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
1595
+ [DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
1596
+ [DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
1597
+ };
1598
+ var KIT_ORIGIN_KEY = "kitOrigin";
1599
+ function markKitOrigin(object2, origin) {
1600
+ const node = object2;
1601
+ node.userData = node.userData ?? {};
1602
+ node.userData[KIT_ORIGIN_KEY] = { ...origin };
1603
+ return object2;
1604
+ }
1605
+ var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
1606
+ var GAIT_IDS = ["walk", "run"];
1607
+ var LANDING_IDS = ["light", "heavy"];
1608
+ var BODY_SOUND_IDS = [
1609
+ "jump",
1610
+ "cloth_rustle",
1611
+ "body_fall",
1612
+ "fall_damage",
1613
+ "player_hurt",
1614
+ "player_breath_run",
1615
+ "player_death",
1616
+ "bite",
1617
+ "grab",
1618
+ "punch_swing",
1619
+ "punch_hit_body",
1620
+ "punch_hit_face",
1621
+ "punch_hit_object",
1622
+ "kick_swing",
1623
+ "kick_hit",
1624
+ "kick_hit_object",
1625
+ "shove_two_hands",
1626
+ "knife_swing",
1627
+ "knife_stab",
1628
+ "knife_slash",
1629
+ "knife_hit_object",
1630
+ "blunt_swing",
1631
+ "blunt_hit",
1632
+ "blunt_hit_object",
1633
+ "axe_swing",
1634
+ "axe_chop",
1635
+ "axe_hit_object"
1636
+ ];
1637
+ var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
1638
+ var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
1639
+ var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing) => `land_${surface}_${landing}`));
1640
+ var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
1641
+
1541
1642
  // ../game-shapes-three/dist/index.js
1542
1643
  var UV_BEFORE_NORMAL = /* @__PURE__ */ new Set(["LatheGeometry", "ExtrudeGeometry"]);
1543
1644
  var INDEX_FIRST = /* @__PURE__ */ new Set(["BoxGeometry", "CylinderGeometry", "ConeGeometry", "SphereGeometry", "PlaneGeometry", "CircleGeometry", "RingGeometry", "LatheGeometry", "TorusGeometry"]);
@@ -8997,7 +9098,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
8997
9098
  const em = w.emitters();
8998
9099
  if (em.length && !IV.efx && K.IVK.effects) {
8999
9100
  const g = h.houseGroup;
9000
- IV.efx = ik.gfx.createEffects(g, { maxParticles: 1400, seed: 11, document: DOC });
9101
+ IV.efx = ik.gfx.createEffects(g, { maxParticles: 1400, seed: 11, document: DOC, ...K.IVK.effectSink ? { effects: K.IVK.effectSink } : {} });
9001
9102
  }
9002
9103
  if (IV.efx) {
9003
9104
  const alive = IV.efx.stats().alive;
@@ -10614,7 +10715,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10614
10715
  return null;
10615
10716
  }
10616
10717
  const A = AD.plan ? AD : AD.create({ ...o.adapterOptions || {}, GameInteractables: GI, GameInteractablesHousehold: HH, locale: o.locale || cfg.locale, region: o.region, ...cfg.seedKey ? { seedKey: cfg.seedKey } : {} });
10617
- return { GI, HH, GT, A, gfx: GT.createThreeAdapter(THREE, { document: DOC, Reflector }), items: o.items !== false, effects: o.effects !== false, camera: o.camera || null, onEvent: o.onEvent || null, onSeat: o.onSeat || null };
10718
+ return { GI, HH, GT, A, gfx: GT.createThreeAdapter(THREE, { document: DOC, Reflector }), items: o.items !== false, effects: o.effects !== false, effectSink: o.effectSink || null, camera: o.camera || null, onEvent: o.onEvent || null, onSeat: o.onSeat || null };
10618
10719
  })();
10619
10720
  const VEG = (() => {
10620
10721
  const l = cfg.vegetation || null, G = l && (l.GameVegetation || (l.buildLot ? l : null)), S = l && (l.GameVegetationSpecies || (l.toSpec ? l : null));
@@ -10631,7 +10732,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10631
10732
 
10632
10733
  // src/lights/room-light.ts
10633
10734
  function installLightsRoomLight(K, shared) {
10634
- const { THREE, rectsOf: rectsOf2 } = K;
10735
+ const { THREE, rectsOf: rectsOf2, cfg } = K;
10635
10736
  const MAX_LIGHTS = 12, EMPTY = [1e9, 1e9, -1e9, -1e9];
10636
10737
  const LZU = { uRB0: { value: new Float32Array(MAX_LIGHTS * 4) }, uRB1: { value: new Float32Array(MAX_LIGHTS * 4) }, uRB2: { value: new Float32Array(MAX_LIGHTS * 4) }, uRBY: { value: new Float32Array(MAX_LIGHTS * 2) }, uREx: { value: new Float32Array(MAX_LIGHTS * 4) }, uIndK: { value: 0.06 } };
10637
10738
  for (let i = 0; i < MAX_LIGHTS; i++) for (const k of ["uRB0", "uRB1", "uRB2", "uREx"]) LZU[k].value.set(EMPTY, i * 4);
@@ -10670,8 +10771,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10670
10771
  o = o || {};
10671
10772
  const scene = o.scene, N = Math.max(1, Math.min(MAX_LIGHTS, o.n || 6)), NS = Math.max(0, Math.min(N, o.shadows != null ? o.shadows : 2)), size = o.shadowSize || 512, MAX_EXTERNAL = o.external != null ? o.external : 2;
10672
10773
  for (const k of Object.keys(shared.mats)) roomLightMask(shared.mats[k]);
10774
+ const budget = o.budget, lease = budget ? budget.lease({ owner: "floorplan.rooms", point: N, shadows: NS }) : null;
10775
+ if (budget && !lease) throw new Error("lighting: the LightBudget has no " + N + " free point slots (" + NS + " with shadow) for the rooms");
10776
+ if (budget) for (const [key, width] of [["uRB0", 4], ["uRB1", 4], ["uRB2", 4], ["uRBY", 2], ["uREx", 4]]) {
10777
+ const next = new Float32Array(budget.sizes.point * width);
10778
+ if (width === 4) for (let i = 0; i < budget.sizes.point; i++) next.set(EMPTY, i * 4);
10779
+ LZU[key].value = next;
10780
+ }
10673
10781
  const PL = [], sig = new Array(N).fill(""), seg = { n: 0 };
10674
- for (let i = 0; i < N; i++) {
10782
+ if (!lease) for (let i = 0; i < N; i++) {
10675
10783
  const l = new THREE.PointLight(16763008, 0, 8, 1.6);
10676
10784
  l.visible = true;
10677
10785
  l.castShadow = i < NS;
@@ -10687,12 +10795,30 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10687
10795
  if (scene) scene.add(l);
10688
10796
  PL.push(l);
10689
10797
  }
10690
- const A = { lights: PL, crates: new Array(N).fill(null), n: N, shadows: NS, stats: { rooms: 0, external: 0, shadows: 0, shadowChanged: false }, indirect: (v) => {
10798
+ const slotOf = (i) => lease ? lease.handles[i].slot : i;
10799
+ const put = (i, v) => {
10800
+ if (lease) {
10801
+ lease.handles[i].update(v.color === void 0 || typeof v.color === "number" ? v : { ...v, color: new THREE.Color(v.color).getHex() });
10802
+ return;
10803
+ }
10804
+ const pl = PL[i];
10805
+ if (v.position) pl.position.set(v.position[0], v.position[1], v.position[2]);
10806
+ if (v.color !== void 0) pl.color.set(v.color);
10807
+ if (v.intensity !== void 0) pl.intensity = v.intensity;
10808
+ if (v.distance !== void 0) pl.distance = v.distance;
10809
+ if (v.decay !== void 0) pl.decay = v.decay;
10810
+ };
10811
+ const effects = lease ? effectLights({ budget, ours: lease.handles.map((h) => h.slot) }) : null;
10812
+ const A = { lights: lease ? lease.handles : PL, crates: new Array(N).fill(null), n: N, shadows: NS, stats: { rooms: 0, external: 0, shadows: 0, shadowChanged: false }, indirect: (v) => {
10691
10813
  LZU.uIndK.value = v;
10692
10814
  } };
10693
10815
  const _p = new THREE.Vector3(), _f = new THREE.Vector3();
10694
10816
  const lim = (v, a, b) => Math.max(a, Math.min(b, v));
10695
10817
  A.invalidate = () => {
10818
+ if (lease) {
10819
+ lease.invalidateShadow();
10820
+ return;
10821
+ }
10696
10822
  for (let i = 0; i < NS; i++) PL[i].shadow.needsUpdate = true;
10697
10823
  };
10698
10824
  A.update = function(houses2, focus, op) {
@@ -10800,47 +10926,44 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10800
10926
  A.stats.external = Ne;
10801
10927
  let shadows = 0;
10802
10928
  for (let i = 0; i < N; i++) {
10803
- const pl = PL[i], g = asg[i], b0 = LZU.uRB0.value, b1 = LZU.uRB1.value, b2 = LZU.uRB2.value, by = LZU.uRBY.value, ex = LZU.uREx.value;
10929
+ const g = asg[i], at = slotOf(i), b0 = LZU.uRB0.value, b1 = LZU.uRB1.value, b2 = LZU.uRB2.value, by = LZU.uRBY.value, ex = LZU.uREx.value;
10804
10930
  A.crates[i] = g ? { k: g.k, rects: g.rects, y: g.y, exterior: !!g.exterior, exclusion: g.exclusion || null, d: g.d } : null;
10805
10931
  if (!g) {
10806
- pl.intensity = 0;
10807
- for (const a of [b0, b1, b2, ex]) a.set(EMPTY, i * 4);
10808
- by.set([0, 0], i * 2);
10932
+ put(i, { intensity: 0 });
10933
+ for (const a of [b0, b1, b2, ex]) a.set(EMPTY, at * 4);
10934
+ by.set([0, 0], at * 2);
10809
10935
  continue;
10810
10936
  }
10811
- pl.position.set(g.pos[0], g.pos[1], g.pos[2]);
10812
- pl.color.set(g.color);
10813
- if (g.exterior) {
10814
- pl.intensity = g.lumens;
10815
- pl.distance = g.dist;
10816
- pl.decay = g.decay;
10817
- } else {
10818
- pl.intensity = (7 + g.range * 0.8) * lim(1 + 0.4 * (Math.sqrt(g.n) - 1), 1, 2.2);
10819
- pl.distance = Math.max(3, g.dm * 1.3 + 0.5);
10820
- pl.decay = 1.6;
10821
- }
10937
+ const lamp = g.exterior ? { intensity: g.lumens, distance: g.dist, decay: g.decay } : { intensity: (7 + g.range * 0.8) * lim(1 + 0.4 * (Math.sqrt(g.n) - 1), 1, 2.2), distance: Math.max(3, g.dm * 1.3 + 0.5), decay: 1.6 };
10938
+ put(i, { position: g.pos, color: g.color, ...lamp });
10822
10939
  const rs = g.rects;
10823
- b0.set(rs[0] || EMPTY, i * 4);
10824
- b1.set(rs[1] || EMPTY, i * 4);
10825
- b2.set(rs[2] || EMPTY, i * 4);
10826
- by.set(g.y, i * 2);
10827
- ex.set(g.exclusion || EMPTY, i * 4);
10940
+ b0.set(rs[0] || EMPTY, at * 4);
10941
+ b1.set(rs[1] || EMPTY, at * 4);
10942
+ b2.set(rs[2] || EMPTY, at * 4);
10943
+ by.set(g.y, at * 2);
10944
+ ex.set(g.exclusion || EMPTY, at * 4);
10828
10945
  if (i < NS) {
10829
10946
  shadows++;
10830
10947
  const s2 = g.house.group.uuid + "|" + g.k + "|" + g.pos.map((v) => v.toFixed(2)).join(",");
10831
10948
  if (sig[i] !== s2) {
10832
10949
  sig[i] = s2;
10833
- pl.shadow.needsUpdate = true;
10950
+ if (lease) lease.invalidateShadow(i);
10951
+ else PL[i].shadow.needsUpdate = true;
10834
10952
  seg.n++;
10835
10953
  A.stats.shadowChanged = true;
10836
10954
  }
10837
- pl.shadow.camera.far = Math.max(4, pl.distance);
10955
+ if (!lease) PL[i].shadow.camera.far = Math.max(4, lamp.distance);
10838
10956
  }
10839
10957
  }
10958
+ if (effects) effects.update(houses2);
10840
10959
  A.stats.shadows = shadows;
10841
10960
  return A.stats;
10842
10961
  };
10843
10962
  A.dispose = () => {
10963
+ if (lease) {
10964
+ lease.release();
10965
+ return;
10966
+ }
10844
10967
  for (const l of PL) {
10845
10968
  if (l.shadow && l.shadow.map) l.shadow.map.dispose();
10846
10969
  l.removeFromParent();
@@ -10848,59 +10971,116 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10848
10971
  };
10849
10972
  return A;
10850
10973
  }
10851
- Object.assign(K, { MAX_LIGHTS, EMPTY, LZU, roomLightMask, lighting });
10852
- }
10853
-
10854
- // ../game-contracts/dist/index.js
10855
- var DEFAULT_IGNITION_RULES = {
10856
- fillCurve: [
10857
- [0, 0.6],
10858
- [0.1, 1],
10859
- [0.35, 0.6],
10860
- [0.65, 0.3],
10861
- [0.9, 0.15],
10862
- [1, 0.12]
10863
- ],
10864
- flameFillInfluence: 0.5,
10865
- flameDefaults: { incendiary: 0.7, tracer: 0.2 },
10866
- sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },
10867
- sparkMetalDefault: 0.5,
10868
- sparkFluid: { diesel: 0.15 },
10869
- fireNear: { radius: 1.5, chancePerSecond: 0.5 }
10870
- };
10871
- var resolveIgnitionRules = (rules) => ({
10872
- ...DEFAULT_IGNITION_RULES,
10873
- ...rules,
10874
- flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },
10875
- sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },
10876
- sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },
10877
- fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear }
10878
- });
10879
- var fillFactor = (fill2, curve = DEFAULT_IGNITION_RULES.fillCurve) => {
10880
- if (curve.length === 0) return 1;
10881
- const f = Math.min(1, Math.max(0, fill2));
10882
- if (f <= curve[0][0]) return curve[0][1];
10883
- for (let i = 1; i < curve.length; i++) {
10884
- const [x1, y1] = curve[i];
10885
- const [x0, y0] = curve[i - 1];
10886
- if (f <= x1) return x1 === x0 ? y1 : y0 + (y1 - y0) * (f - x0) / (x1 - x0);
10974
+ const effectsByBudget = /* @__PURE__ */ new WeakMap();
10975
+ const OPENING_REACH = 2.5, OPENING_SIDE = 0.6;
10976
+ function effectLights(o) {
10977
+ o = o || {};
10978
+ const budget = o.budget;
10979
+ if (!budget) throw new Error("effectLights: a LightBudget is required");
10980
+ const known = effectsByBudget.get(budget);
10981
+ if (known && !o.ours) return known;
10982
+ const ours = new Set(o.ours || []), size = budget.sizes.point, _p = new THREE.Vector3(), P0 = new THREE.Vector3();
10983
+ for (const [key, width] of [["uRB0", 4], ["uRB1", 4], ["uRB2", 4], ["uRBY", 2], ["uREx", 4]]) {
10984
+ const had = LZU[key].value;
10985
+ if (had.length >= size * width) continue;
10986
+ const next = new Float32Array(size * width);
10987
+ if (width === 4) for (let i = 0; i < size; i++) next.set(EMPTY, i * 4);
10988
+ next.set(had);
10989
+ LZU[key].value = next;
10990
+ }
10991
+ const roomBoxes = (house, M) => {
10992
+ const list2 = [];
10993
+ for (const c of house.r.rooms) {
10994
+ if (c.type === "void") continue;
10995
+ const rs = [];
10996
+ for (const [a, b, c2, d] of rectsOf2(c)) {
10997
+ let x0 = 1e9, z0 = 1e9, x1 = -1e9, z1 = -1e9;
10998
+ for (const [u, v] of [[a, b], [c2, b], [a, d], [c2, d]]) {
10999
+ _p.set(u, 0, v).applyMatrix4(M);
11000
+ x0 = Math.min(x0, _p.x);
11001
+ x1 = Math.max(x1, _p.x);
11002
+ z0 = Math.min(z0, _p.z);
11003
+ z1 = Math.max(z1, _p.z);
11004
+ }
11005
+ rs.push([x0 - 0.08, z0 - 0.08, x1 + 0.08, z1 + 0.08]);
11006
+ }
11007
+ _p.set(0, house.y0(c.floor), 0).applyMatrix4(M);
11008
+ list2.push({ c, rs, y: [_p.y - 0.06, _p.y + (c.ceiling || 2.8) + 0.3] });
11009
+ }
11010
+ return list2;
11011
+ };
11012
+ const boxAt = (boxes, x, y, z) => boxes.find((q) => y >= q.y[0] && y <= q.y[1] && q.rs.some((r) => x >= r[0] && x <= r[2] && z >= r[1] && z <= r[3]));
11013
+ const openingSides = (house, M, boxes, into) => {
11014
+ const open = house.internal && house.internal.doorState || /* @__PURE__ */ new Map();
11015
+ const reach = (list2, isDoor) => {
11016
+ for (const p of list2 || []) {
11017
+ if (isDoor && !(open.has(p.id) ? open.get(p.id) : !!p.open)) continue;
11018
+ const alongU = p.axis === "u", y = house.y0(p.floor) + (isDoor ? 1 : ((p.y0 != null ? p.y0 : 0.9) + (p.y1 != null ? p.y1 : 2)) / 2);
11019
+ const at = (side) => _p.set(alongU ? p.at : p.pos + side, y, alongU ? p.pos + side : p.at).applyMatrix4(M).clone();
11020
+ if (at(0).distanceTo(P0) > OPENING_REACH) continue;
11021
+ for (const side of [-OPENING_SIDE, OPENING_SIDE]) {
11022
+ const w = at(side), hit = boxAt(boxes, w.x, w.y, w.z);
11023
+ if (hit) into.set(hit.c, hit);
11024
+ }
11025
+ }
11026
+ };
11027
+ reach(house.r.doors, true);
11028
+ reach(house.r.windows, false);
11029
+ };
11030
+ const clear = (at) => {
11031
+ for (const key of ["uRB0", "uRB1", "uRB2", "uREx"]) LZU[key].value.set(EMPTY, at * 4);
11032
+ LZU.uRBY.value.set([0, 0], at * 2);
11033
+ };
11034
+ const write = (at, into) => {
11035
+ const rs = [];
11036
+ let lo = 1e9, hi = -1e9;
11037
+ for (const q of into.values()) {
11038
+ rs.push(...q.rs);
11039
+ lo = Math.min(lo, q.y[0]);
11040
+ hi = Math.max(hi, q.y[1]);
11041
+ }
11042
+ while (rs.length > 3) {
11043
+ const r23 = rs.pop(), r1 = rs[rs.length - 1];
11044
+ rs[rs.length - 1] = [Math.min(r1[0], r23[0]), Math.min(r1[1], r23[1]), Math.max(r1[2], r23[2]), Math.max(r1[3], r23[3])];
11045
+ }
11046
+ LZU.uRB0.value.set(rs[0] || EMPTY, at * 4);
11047
+ LZU.uRB1.value.set(rs[1] || EMPTY, at * 4);
11048
+ LZU.uRB2.value.set(rs[2] || EMPTY, at * 4);
11049
+ LZU.uRBY.value.set([lo, hi], at * 2);
11050
+ LZU.uREx.value.set(EMPTY, at * 4);
11051
+ };
11052
+ const A = {
11053
+ /** Once per frame, after the budget's `present`: points the mask of every lit slot that is not the house's at the rooms its light reaches. */
11054
+ update(houses2) {
11055
+ const indoors = cfg.effectLightsIndoors !== false, list2 = (Array.isArray(houses2) ? houses2 : [houses2]).filter(Boolean), cache2 = /* @__PURE__ */ new Map();
11056
+ for (let at = 0; at < size; at++) {
11057
+ if (ours.has(at)) continue;
11058
+ const light = budget.group.children[at];
11059
+ if (!indoors || !light || !light.isPointLight || !(light.intensity > 0) || !light.visible) {
11060
+ clear(at);
11061
+ continue;
11062
+ }
11063
+ const into = /* @__PURE__ */ new Map();
11064
+ P0.set(light.position.x, light.position.y, light.position.z);
11065
+ for (const house of list2) {
11066
+ if (house.group && !house.group.visible) continue;
11067
+ house.house.updateMatrixWorld(true);
11068
+ const M = house.house.matrixWorld;
11069
+ let boxes = cache2.get(house);
11070
+ if (!boxes) cache2.set(house, boxes = roomBoxes(house, M));
11071
+ const here = boxAt(boxes, P0.x, P0.y, P0.z);
11072
+ if (here) into.set(here.c, here);
11073
+ openingSides(house, M, boxes, into);
11074
+ }
11075
+ if (into.size) write(at, into);
11076
+ else clear(at);
11077
+ }
11078
+ }
11079
+ };
11080
+ if (!o.ours) effectsByBudget.set(budget, A);
11081
+ return A;
10887
11082
  }
10888
- return curve[curve.length - 1][1];
10889
- };
10890
- var sparkTargetFactor = (materialId, metal, rules = DEFAULT_IGNITION_RULES) => !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault;
10891
- var ignitionChance = (hit, target, rules = DEFAULT_IGNITION_RULES) => {
10892
- const k = fillFactor(target.fill, rules.fillCurve);
10893
- const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === void 0 ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k;
10894
- const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0);
10895
- const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
10896
- return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
10897
- };
10898
- var KIT_ORIGIN_KEY = "kitOrigin";
10899
- function markKitOrigin(object2, origin) {
10900
- const node = object2;
10901
- node.userData = node.userData ?? {};
10902
- node.userData[KIT_ORIGIN_KEY] = { ...origin };
10903
- return object2;
11083
+ Object.assign(K, { MAX_LIGHTS, EMPTY, LZU, roomLightMask, lighting, effectLights });
10904
11084
  }
10905
11085
 
10906
11086
  // ../game-destruction/dist/index.js
@@ -13613,6 +13793,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13613
13793
  const ignitionRules = resolveIgnitionRules(options.ignition);
13614
13794
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
13615
13795
  const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
13796
+ const chainLinkCap = options.chainLinksPerUpdate !== void 0 && options.chainLinksPerUpdate >= 1 ? Math.floor(options.chainLinksPerUpdate) : Infinity;
13616
13797
  const rng2 = createRng(options.seed ?? 1);
13617
13798
  const rnd = () => rng2.next();
13618
13799
  let time = 0;
@@ -14560,7 +14741,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14560
14741
  if (disc < 0) return true;
14561
14742
  return -b - Math.sqrt(disc) > maxDist;
14562
14743
  };
14563
- const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false) => {
14744
+ const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false, precise = false) => {
14564
14745
  const d = vec.normalize(dir);
14565
14746
  let best = null;
14566
14747
  for (const o of objects.values()) {
@@ -14575,13 +14756,25 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14575
14756
  }
14576
14757
  }
14577
14758
  for (const im of worldOnly ? [] : impls.values()) {
14759
+ let boxHit = null;
14578
14760
  for (const ps of im.impl.describe().parts) {
14579
14761
  if (ignore?.has(`${im.impl.id}\0${ps.id}`)) continue;
14580
14762
  const ov = im.poses.get(ps.id);
14581
14763
  const r = rayPartRaw(im.position, im.rotation, ps, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]), origin, d, ov?.pos);
14582
- if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
14583
- if (!best || r.t0 < best.distance) best = { object: im.impl.id, part: ps.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
14764
+ if (!r || r.t1 < 0 || r.t0 > maxDist) continue;
14765
+ if (r.t0 < 0) {
14766
+ if (precise && im.impl.surfaceRay) boxHit = boxHit ?? { object: im.impl.id, part: ps.id, point: origin, normal: r.n, distance: 0, pathLen: r.t1 };
14767
+ continue;
14768
+ }
14769
+ if (!boxHit || r.t0 < boxHit.distance) boxHit = { object: im.impl.id, part: ps.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
14584
14770
  }
14771
+ if (!boxHit) continue;
14772
+ if (precise && im.impl.surfaceRay) {
14773
+ const far = best ? Math.min(maxDist, best.distance) : maxDist;
14774
+ const implId = im.impl.id;
14775
+ const s = im.impl.surfaceRay({ origin, direction: d, maxDistance: far, ...ignore ? { ignored: (part) => ignore.has(`${implId}\0${part}`) } : {} });
14776
+ if (s && (!best || s.distance < best.distance)) best = { object: implId, part: s.part, point: s.point, normal: s.normal, distance: s.distance, pathLen: s.pathLen };
14777
+ } else if (!best || boxHit.distance < best.distance) best = boxHit;
14585
14778
  }
14586
14779
  return best;
14587
14780
  };
@@ -16065,7 +16258,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16065
16258
  };
16066
16259
  const detonatePrimed = (until) => {
16067
16260
  const end = time;
16068
- for (let next = nextPrimed(until); next; next = nextPrimed(until)) {
16261
+ let fired = 0;
16262
+ for (let next = nextPrimed(until); next && fired < chainLinkCap; next = nextPrimed(until)) {
16263
+ fired++;
16069
16264
  primed.delete(next.p);
16070
16265
  time = Math.max(0, Math.min(end, next.p.rt.chainDue));
16071
16266
  next.p.rt.chainDue = void 0;
@@ -16135,6 +16330,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16135
16330
  updateBound(o);
16136
16331
  },
16137
16332
  remove(id) {
16333
+ impls.get(id)?.impl.dispose?.();
16138
16334
  objects.delete(id);
16139
16335
  impls.delete(id);
16140
16336
  },
@@ -16210,7 +16406,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16210
16406
  return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
16211
16407
  },
16212
16408
  raycast(origin, direction, maxDistance = Infinity) {
16213
- const r = raycastInternal(origin, direction, maxDistance);
16409
+ const r = raycastInternal(origin, direction, maxDistance, void 0, false, true);
16214
16410
  return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
16215
16411
  },
16216
16412
  sweepBox(box3, direction, distance2, options2 = {}) {
@@ -16235,11 +16431,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16235
16431
  let org = origin;
16236
16432
  let E = energy;
16237
16433
  for (let i = 0; i < 4 && E > 0; i++) {
16238
- const rh = raycastInternal(org, d, Infinity, ignore);
16434
+ const rh = raycastInternal(org, d, Infinity, ignore, false, true);
16239
16435
  if (!rh) break;
16240
16436
  const im = impls.get(rh.object);
16241
16437
  if (im) {
16242
- const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
16438
+ const r = implHit(im, { type: "ballistic", point: rh.point, part: rh.part, normal: rh.normal, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
16243
16439
  r.part = rh.part;
16244
16440
  r.point = rh.point;
16245
16441
  r.exitEnergy = r.penetrated ? E * 0.5 : 0;
@@ -16561,12 +16757,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16561
16757
  breach: { area: 0.1 }
16562
16758
  }
16563
16759
  ];
16760
+ var SHIPPED = structuredClone(assemblies_default);
16564
16761
  var ASSEMBLIES = assemblies_default;
16565
- var getAssembly = (id) => ASSEMBLIES.find((a) => a.id === id);
16762
+ var assembliesOver = (defs) => {
16763
+ const get = (id) => defs.find((a) => a.id === id);
16764
+ return {
16765
+ list: () => defs,
16766
+ get,
16767
+ register(def) {
16768
+ const at = defs.findIndex((a) => a.id === def.id);
16769
+ if (at >= 0) defs[at] = def;
16770
+ else defs.push(def);
16771
+ },
16772
+ part: (assemblyId, options) => buildAssemblyPart(get(assemblyId), assemblyId, options)
16773
+ };
16774
+ };
16775
+ var defaultSet;
16776
+ var defaultAssemblies = () => defaultSet ?? (defaultSet = assembliesOver(ASSEMBLIES));
16777
+ var getAssembly = (id) => defaultAssemblies().get(id);
16566
16778
  var faceCount = (layer) => layer.faces === "both" ? 2 : 1;
16567
16779
  var assemblyDepth = (def) => def.depth ?? def.core.thickness + (def.layers ?? []).reduce((sum, l) => sum + l.thickness * faceCount(l), 0);
16568
- var assemblyPart = (assemblyId, options) => {
16569
- const def = getAssembly(assemblyId);
16780
+ var buildAssemblyPart = (def, assemblyId, options) => {
16570
16781
  if (!def) throw new Error(`unknown assembly "${assemblyId}"`);
16571
16782
  const densityOf2 = (id) => {
16572
16783
  const m = options.materials?.get(id) ?? MATERIALS2.find((x) => x.id === id);
@@ -16593,6 +16804,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16593
16804
  ...options.extra
16594
16805
  };
16595
16806
  };
16807
+ var assemblyPart = (assemblyId, options) => defaultAssemblies().part(assemblyId, options);
16596
16808
  var buildFrame = (o = {}) => {
16597
16809
  const spanX = o.spanX ?? 8, spanZ = o.spanZ ?? 8;
16598
16810
  const nx = Math.max(2, o.columnsX ?? 3), nz = Math.max(2, o.columnsZ ?? 3);
@@ -19774,6 +19986,34 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19774
19986
  }
19775
19987
  function installDestructionBreach(K, _shared) {
19776
19988
  const { THREE, fragmentMaterial, invalidate, invalidateShadow } = K;
19989
+ const paintState = { attached: false };
19990
+ const paintQueues = /* @__PURE__ */ new Set();
19991
+ const paintQueue = {
19992
+ id: DEFERRED_QUEUE_ID.BREACH_PAINT,
19993
+ pending: () => {
19994
+ let n = 0;
19995
+ for (const q of paintQueues) n += q.pending();
19996
+ return n;
19997
+ },
19998
+ run(deadline, clock) {
19999
+ let ran = 0;
20000
+ for (const q of [...paintQueues]) {
20001
+ if (q.pending() === 0) continue;
20002
+ if (ran > 0 && clock() >= deadline) break;
20003
+ ran += q.run(deadline, clock);
20004
+ }
20005
+ return ran;
20006
+ },
20007
+ flush() {
20008
+ for (const q of [...paintQueues]) q.flush();
20009
+ },
20010
+ attach() {
20011
+ paintState.attached = true;
20012
+ },
20013
+ detach() {
20014
+ paintState.attached = false;
20015
+ }
20016
+ };
19777
20017
  function createBreachView(opts) {
19778
20018
  const { house, structure } = opts;
19779
20019
  const views = /* @__PURE__ */ new Map();
@@ -20088,8 +20328,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20088
20328
  else s.data = collapseRange(s.it.mesh, s.v0, s.it.v1);
20089
20329
  }
20090
20330
  }
20091
- function flush(budgetMs = paintBudgetMs) {
20331
+ function flush(budgetMs) {
20092
20332
  if (dirty.size === 0) return 0;
20333
+ if (budgetMs === void 0 && paintState.attached) return dirty.size;
20334
+ budgetMs ?? (budgetMs = paintBudgetMs);
20093
20335
  const deadline = performance.now() + budgetMs;
20094
20336
  for (const view of dirty) {
20095
20337
  dirty.delete(view);
@@ -20100,6 +20342,34 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20100
20342
  invalidateShadow();
20101
20343
  return dirty.size;
20102
20344
  }
20345
+ const queue = {
20346
+ id: DEFERRED_QUEUE_ID.BREACH_PAINT,
20347
+ pending: () => dirty.size,
20348
+ run(deadline, clock) {
20349
+ let ran = 0;
20350
+ for (const view of dirty) {
20351
+ dirty.delete(view);
20352
+ paint(view);
20353
+ ran++;
20354
+ if (clock() >= deadline) break;
20355
+ }
20356
+ if (ran > 0) {
20357
+ invalidate();
20358
+ invalidateShadow();
20359
+ }
20360
+ return ran;
20361
+ },
20362
+ flush() {
20363
+ flush(Infinity);
20364
+ },
20365
+ attach() {
20366
+ paintState.attached = true;
20367
+ },
20368
+ detach() {
20369
+ paintState.attached = false;
20370
+ }
20371
+ };
20372
+ paintQueues.add(queue);
20103
20373
  function dispose(view) {
20104
20374
  dirty.delete(view);
20105
20375
  if (view.units) {
@@ -20134,6 +20404,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20134
20404
  */
20135
20405
  claim() {
20136
20406
  var _a;
20407
+ paintQueues.add(queue);
20137
20408
  for (const q of opts.spec()?.parts ?? []) {
20138
20409
  if (!q.units) continue;
20139
20410
  const host = hostOf(q.id);
@@ -20186,6 +20457,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20186
20457
  dropped.length = 0;
20187
20458
  },
20188
20459
  flush,
20460
+ /** Takes this house's painting out of the renderer's queue (the house goes away); `claim` puts it back. */
20461
+ release() {
20462
+ paintQueues.delete(queue);
20463
+ },
20464
+ /** The painting of this house alone. */
20465
+ deferredQueue: () => queue,
20189
20466
  /** The wall has damage drawn. */
20190
20467
  has(partId) {
20191
20468
  return views.has(partId);
@@ -20200,7 +20477,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20200
20477
  })
20201
20478
  };
20202
20479
  }
20203
- Object.assign(K, { createBreachView });
20480
+ Object.assign(K, { createBreachView, breachPaintQueue: paintQueue });
20204
20481
  }
20205
20482
 
20206
20483
  // src/destruction/world.ts
@@ -20455,6 +20732,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20455
20732
  if (R && D.id && D.active) R.unbind(D.id);
20456
20733
  if (D.active && D.world.has(D.id)) D.world.remove(D.id);
20457
20734
  D.active = false;
20735
+ breach.release();
20458
20736
  };
20459
20737
  D.follow = (world, id, spec) => {
20460
20738
  D.spec = spec;
@@ -20489,6 +20767,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20489
20767
  return { restore: () => breach.restore(), dispose: () => {
20490
20768
  offFollow();
20491
20769
  breach.restore();
20770
+ breach.release();
20492
20771
  }, update: () => {
20493
20772
  breach.flush();
20494
20773
  } };
@@ -21101,7 +21380,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21101
21380
 
21102
21381
  // src/build/api.ts
21103
21382
  function buildApi(K, shared) {
21104
- const { cfg, VERSION: VERSION2, ATLAS, TEX, applyPart, carGroup, carModel, props, TYPES, roomLightMask, lighting, newState, build } = K;
21383
+ const { cfg, VERSION: VERSION2, ATLAS, TEX, applyPart, carGroup, carModel, props, TYPES, roomLightMask, lighting, effectLights, newState, build } = K;
21105
21384
  return {
21106
21385
  version: VERSION2,
21107
21386
  build,
@@ -21116,10 +21395,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21116
21395
  },
21117
21396
  roomLightMask,
21118
21397
  lighting,
21398
+ effectLights,
21119
21399
  setNight(n) {
21120
21400
  shared.mats.glass.emissive.set(n ? 9067040 : 0);
21121
21401
  shared.mats.glass.opacity = n ? 0.4 : 0.24;
21122
21402
  },
21403
+ /** The chipped-plaster painting of every house of this renderer as a `DeferredQueue` for a frame budget (id `floorplan.breach-paint`); asking for it changes nothing. */
21404
+ deferredQueue: () => K.breachPaintQueue,
21123
21405
  cars: { model: carModel, group: carGroup, info: (k, seed) => {
21124
21406
  const m = carModel(k, seed);
21125
21407
  return m && { parts: m.parts.length, tris: m.tris, L: m.L, W: m.W, H: m.H };
@@ -21586,6 +21868,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21586
21868
  return { off, reset: () => navigator.undoChanges(houseId) };
21587
21869
  }
21588
21870
 
21871
+ // ../game-render-three/dist/index.js
21872
+ var DEFAULT_ANGLE = Math.PI / 3;
21873
+ function isLightBudget(value) {
21874
+ const candidate = value;
21875
+ return typeof candidate === "object" && candidate !== null && typeof candidate.acquire === "function" && typeof candidate.addSource === "function" && typeof candidate.lease === "function" && typeof candidate.present === "function";
21876
+ }
21877
+ var EFFECT_STEP_SECONDS = 1 / 50;
21878
+
21589
21879
  // src/test-area/text.ts
21590
21880
  var TEXT = {
21591
21881
  en: {
@@ -21608,7 +21898,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21608
21898
  hit: { burst_9mm: "9 mm burst", burst_762: "7.62 burst", sledge: "Sledgehammer", c4: "C4", rpg: "RPG warhead" },
21609
21899
  state: { normal: "Normal", fortified: "Fortified", abandoned: "Abandoned" }
21610
21900
  },
21611
- layers: { roof: "Roof", lot: "Lot (ground, wall, gate)", night: "Night (lit windows and lamps)" },
21901
+ layers: { roof: "Roof", lot: "Lot (ground, wall, gate)", night: "Night (lit windows and lamps)", room_lights: "Room lights (one lamp per lit room, stopped by the walls)" },
21612
21902
  metrics: { houses: "Houses", parts: "Parts", standing: "Standing", broken: "Broken", holes: "Holes", draw_calls: "Draw calls", triangles: "Triangles", lod: "Level of detail (nearest)", floor: "Your floor", doors_open: "Doors open", target: "You aim at", planned: "Planned draw calls" },
21613
21903
  sheet: { kicker: "House", title: "House of the bench", template: "Typology", seed: "Seed", state: "State", floors: "Floors", size: "Size", parts: "Structure parts", look: "You aim at", none: "nothing to use", no_engine: "The page gave no floorplan engine" },
21614
21904
  specimens: {
@@ -21674,7 +21964,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21674
21964
  hit: { burst_9mm: "Rajada de 9 mm", burst_762: "Rajada de 7,62", sledge: "Marreta", c4: "C4", rpg: "Ogiva de RPG" },
21675
21965
  state: { normal: "Normal", fortified: "Fortificada", abandoned: "Abandonada" }
21676
21966
  },
21677
- layers: { roof: "Telhado", lot: "Lote (ch\xE3o, muro, port\xE3o)", night: "Noite (janelas e l\xE2mpadas acesas)" },
21967
+ layers: { roof: "Telhado", lot: "Lote (ch\xE3o, muro, port\xE3o)", night: "Noite (janelas e l\xE2mpadas acesas)", room_lights: "Luz por c\xF4modo (uma l\xE2mpada por c\xF4modo aceso, barrada pelas paredes)" },
21678
21968
  metrics: { houses: "Casas", parts: "Pe\xE7as", standing: "De p\xE9", broken: "Quebradas", holes: "Furos", draw_calls: "Draw calls", triangles: "Tri\xE2ngulos", lod: "N\xEDvel de detalhe (a mais perto)", floor: "Seu andar", doors_open: "Portas abertas", target: "Voc\xEA mira em", planned: "Draw calls planejados" },
21679
21969
  sheet: { kicker: "Casa", title: "Casa da bancada", template: "Tipologia", seed: "Semente", state: "Estado", floors: "Andares", size: "Tamanho", parts: "Pe\xE7as da estrutura", look: "Voc\xEA mira em", none: "nada para usar", no_engine: "A p\xE1gina n\xE3o deu o motor de plantas" },
21680
21970
  specimens: {
@@ -21971,6 +22261,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21971
22261
  let THREE = null;
21972
22262
  let houseRenderer = null;
21973
22263
  let houseRendererKey = "";
22264
+ let stopPaintQueue;
22265
+ let roomLights = null;
21974
22266
  const placed = [];
21975
22267
  let time = 0;
21976
22268
  let dirty = false;
@@ -21998,20 +22290,29 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21998
22290
  const rendererFor = () => {
21999
22291
  const key = `${settings.quality}|${locale}`;
22000
22292
  if (!houseRenderer || houseRendererKey !== key) {
22293
+ stopPaintQueue?.();
22294
+ dropRoomLights();
22001
22295
  houseRenderer?.dispose?.();
22002
22296
  houseRenderer = create3(THREE, {
22003
22297
  locale,
22004
22298
  lod: { interior: 10, hide: 40 },
22005
22299
  breachPaintBudgetMs: 4,
22006
22300
  ...options.house,
22301
+ ...options.house?.interactives ? { interactives: { effectSink: () => host?.effects, ...options.house.interactives } } : {},
22007
22302
  quality: settings.quality,
22008
22303
  ...options.world ? { destruction: options.world } : {},
22009
22304
  ...options.renderer ? { destructionRenderer: options.renderer } : {}
22010
22305
  });
22011
22306
  houseRendererKey = key;
22307
+ const paintQueue = houseRenderer.deferredQueue?.();
22308
+ stopPaintQueue = paintQueue ? host?.defer?.(paintQueue, DEFERRED_PRIORITY[DEFERRED_QUEUE_ID.BREACH_PAINT]) : void 0;
22012
22309
  }
22013
22310
  return houseRenderer;
22014
22311
  };
22312
+ const dropRoomLights = () => {
22313
+ roomLights?.dispose();
22314
+ roomLights = null;
22315
+ };
22015
22316
  const clearHouses = () => {
22016
22317
  for (const p of placed) p.house.dispose();
22017
22318
  placed.length = 0;
@@ -22249,6 +22550,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22249
22550
  }
22250
22551
  if (change.kind === "layer") {
22251
22552
  if (change.id === "roof") showAll();
22553
+ else if (change.id === "room_lights") dropRoomLights();
22252
22554
  else if (placed.length > 0) {
22253
22555
  dirty = true;
22254
22556
  dirtyAt = time;
@@ -22415,7 +22717,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22415
22717
  layers: () => [
22416
22718
  { id: "roof", name: text.layers.roof, default: true },
22417
22719
  { id: "lot", name: text.layers.lot, default: false },
22418
- { id: "night", name: text.layers.night, default: false }
22720
+ { id: "night", name: text.layers.night, default: false },
22721
+ { id: "room_lights", name: text.layers.room_lights, default: false }
22419
22722
  ],
22420
22723
  metrics: () => [
22421
22724
  { id: "houses_built", name: text.metrics.houses, read: () => placed.length },
@@ -22461,6 +22764,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22461
22764
  updateSheet();
22462
22765
  }
22463
22766
  });
22767
+ const stopEffectLights = lent.onFrame(
22768
+ () => {
22769
+ const budget = lent.lights;
22770
+ if (!isLightBudget(budget) || placed.length === 0 || !houseRenderer) return;
22771
+ const houses2 = placed.map((p) => p.house);
22772
+ if (lent.bench?.layer("room_lights") ?? false) {
22773
+ roomLights ?? (roomLights = houseRenderer.lighting({ scene: lent.scene, n: 3, shadows: 0, budget }));
22774
+ roomLights.update(houses2, cameraWorld(), {});
22775
+ return;
22776
+ }
22777
+ dropRoomLights();
22778
+ houseRenderer.effectLights?.({ budget })?.update(houses2);
22779
+ },
22780
+ { phase: "present", order: 20 }
22781
+ );
22464
22782
  const onKey = (event) => {
22465
22783
  if (event.code === "KeyE" && !event.repeat && placed.length > 0) useNearest();
22466
22784
  };
@@ -22468,9 +22786,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22468
22786
  return () => {
22469
22787
  stopChange?.();
22470
22788
  stopFrame();
22789
+ stopEffectLights();
22790
+ dropRoomLights();
22471
22791
  globalThis.removeEventListener?.("keydown", onKey);
22472
22792
  for (const remove of removers.splice(0)) remove();
22473
22793
  clearHouses();
22794
+ stopPaintQueue?.();
22795
+ stopPaintQueue = void 0;
22474
22796
  houseRenderer?.dispose?.();
22475
22797
  houseRenderer = null;
22476
22798
  host = null;