@adatechnology/game-weapons-three 0.10.2 → 0.10.4

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.
@@ -21142,6 +21142,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21142
21142
  },
21143
21143
  hitDebris: "chips",
21144
21144
  breakDebris: "dust",
21145
+ visual: { innerColor: 10197652 },
21145
21146
  thermal: {
21146
21147
  conductivity: 0.15,
21147
21148
  heatCapacity: 2.5
@@ -21172,6 +21173,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21172
21173
  ],
21173
21174
  hitDebris: "chips",
21174
21175
  breakDebris: "dust",
21176
+ visual: { innerColor: 12085567 },
21175
21177
  thermal: {
21176
21178
  conductivity: 0.15,
21177
21179
  heatCapacity: 2.5
@@ -21202,6 +21204,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21202
21204
  ],
21203
21205
  hitDebris: "dust",
21204
21206
  breakDebris: "dust",
21207
+ visual: { innerColor: 14999507 },
21205
21208
  thermal: {
21206
21209
  conductivity: 0.1,
21207
21210
  heatCapacity: 1.5
@@ -21227,6 +21230,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21227
21230
  ],
21228
21231
  hitDebris: "chips",
21229
21232
  breakDebris: "dust",
21233
+ visual: { innerColor: 9407364 },
21230
21234
  thermal: {
21231
21235
  conductivity: 0.2,
21232
21236
  heatCapacity: 2.5
@@ -21984,6 +21988,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21984
21988
  var CHIP_ENERGY = 200;
21985
21989
  var CHIP_SPLASH = 0.5;
21986
21990
  var BAR_BOW = 0.08;
21991
+ var BULLET_JOINT_SPAN = 0.3;
21987
21992
  var CUT_SLICE = 1;
21988
21993
  var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
21989
21994
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -23110,7 +23115,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23110
23115
  emit2({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
23111
23116
  setStage(o, p, 1, {}, src);
23112
23117
  };
23113
- const debris2 = (o, p, kind, point, normal, count, speed, src) => emit2({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed });
23118
+ const debris2 = (o, p, kind, point, normal, count, speed, src, material) => emit2({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed, ...material && material !== p.spec.material ? { material } : {} });
23114
23119
  const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
23115
23120
  const detachEvents = (o, opened, by, src) => {
23116
23121
  for (const j of opened) emit2({ ...base(o, src), type: "detach", joint: j.spec.id, parts: [j.spec.a, j.spec.b], by, groups: o.st.groups.map((g) => [...g]) });
@@ -23151,7 +23156,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23151
23156
  const small = pa.dims[0] * pa.dims[1] * pa.dims[2] <= pb.dims[0] * pb.dims[1] * pb.dims[2] ? pa : pb;
23152
23157
  return Math.max(0.06, 0.5 * Math.min(small.dims[0], small.dims[1], small.dims[2]));
23153
23158
  };
23154
- const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false) => {
23159
+ const jointLength = (o, j) => {
23160
+ const A = partWorldAabb(o, o.parts[j.ia]), B = partWorldAabb(o, o.parts[j.ib]);
23161
+ let len = 0;
23162
+ for (let k = 0; k < 3; k++) len = Math.max(len, Math.min(A.max[k], B.max[k]) - Math.max(A.min[k], B.min[k]));
23163
+ return len;
23164
+ };
23165
+ const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false, span) => {
23155
23166
  if (amount <= 0) return;
23156
23167
  const mine = (o.byPart.get(p.spec.id) ?? []).filter((j) => !j.st.open);
23157
23168
  if (split && mine.length) amount /= mine.length;
@@ -23159,7 +23170,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23159
23170
  if (j.st.open) continue;
23160
23171
  if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
23161
23172
  if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
23162
- j.st.hp -= amount;
23173
+ j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
23163
23174
  if (j.st.hp <= 0) {
23164
23175
  j.st.hp = 0;
23165
23176
  j.st.open = true;
@@ -23188,7 +23199,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23188
23199
  if (spalls.length > MAX_SPALLS) spalls.shift();
23189
23200
  markDamaged(p);
23190
23201
  emit2({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer2.index, point: wp, area: Math.PI * radius * radius });
23191
- debris2(o, p, layer2.mat.hitDebris, wp, normal, 5, 2, src);
23202
+ debris2(o, p, layer2.mat.hitDebris, wp, normal, 5, 2, src, layer2.mat.id);
23192
23203
  setStage(o, p, 1, {}, src);
23193
23204
  };
23194
23205
  const crater = (o, p, wp, normal, radius, src) => {
@@ -23675,7 +23686,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23675
23686
  if (mat.fracture !== "shatter") addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
23676
23687
  } else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
23677
23688
  debris2(o, p, mat.hitDebris, entry2, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
23678
- wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
23689
+ wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
23679
23690
  const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
23680
23691
  const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
23681
23692
  if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
@@ -34131,6 +34142,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34131
34142
  pos.array.set(saved, v0 * 3);
34132
34143
  pos.needsUpdate = true;
34133
34144
  }
34145
+ function noise2(x, y, cell) {
34146
+ const h = (i2, j2) => {
34147
+ const s = Math.sin(i2 * 127.1 + j2 * 311.7) * 43758.5453;
34148
+ return s - Math.floor(s);
34149
+ };
34150
+ const fx = x / cell, fy = y / cell, i = Math.floor(fx), j = Math.floor(fy), u = fx - i, v = fy - j;
34151
+ const su = u * u * (3 - 2 * u), sv = v * v * (3 - 2 * v);
34152
+ return (h(i, j) * (1 - su) + h(i + 1, j) * su) * (1 - sv) + (h(i, j + 1) * (1 - su) + h(i + 1, j + 1) * su) * sv;
34153
+ }
34154
+ var MASK_TEXEL = 0.02;
34155
+ var SPALL_MIN = 0.025;
34156
+ var SPALL_RAGGED = 0.045;
34157
+ var COVER2 = 0.015;
34134
34158
  function installDestructionBreach(K, _shared) {
34135
34159
  const { THREE, fragmentMaterial, invalidate, invalidateShadow } = K;
34136
34160
  function createBreachView(opts) {
@@ -34140,55 +34164,29 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34140
34164
  const unitMaterial = new THREE.MeshLambertMaterial({ color: 16777215 });
34141
34165
  const unitGeometry = new THREE.BoxGeometry(1, 1, 1);
34142
34166
  const tmp = { m: new THREE.Matrix4(), q: new THREE.Quaternion(), p: new THREE.Vector3(), s: new THREE.Vector3(), c: new THREE.Color() };
34143
- const wallColour = (items) => {
34144
- for (const it of items) {
34145
- if (!it.mesh) continue;
34146
- const c = it.mesh.geometry.attributes.color;
34147
- if (!c) continue;
34148
- const a = c.array;
34149
- tmp.c.setRGB(a[it.v0 * 3], a[it.v0 * 3 + 1], a[it.v0 * 3 + 2]);
34150
- return tmp.c.getHex();
34151
- }
34152
- return null;
34153
- };
34154
34167
  const local2 = (p) => {
34155
34168
  house.updateMatrixWorld(true);
34156
34169
  const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
34157
34170
  return [v.x, v.y, v.z];
34158
34171
  };
34159
- function courses(part, units, opening, chips2, paint2) {
34172
+ function cells(part, units) {
34160
34173
  const [u0, v0, u1, v1] = part.aabb;
34161
34174
  const alongU = u1 - u0 >= v1 - v0;
34162
34175
  const len = alongU ? u1 - u0 : v1 - v0;
34163
- const thick = alongU ? v1 - v0 : u1 - u0;
34164
34176
  const [ul, uh] = units.size;
34165
34177
  const rows = Math.max(1, Math.round((part.y1 - part.y0) / uh));
34166
34178
  const rowH = (part.y1 - part.y0) / rows;
34167
34179
  const per = Math.max(1, Math.round(len / ul));
34168
34180
  const w = len / per;
34169
- const edge = fragmentMaterial(part.material).color.getHex();
34170
- const list2 = [];
34181
+ const out = [];
34171
34182
  for (let r = 0; r < rows; r++) {
34172
34183
  const shift = units.bond === "running" && r & 1 ? w / 2 : 0;
34173
- const cells = per + (shift ? 1 : 0);
34174
- for (let i = 0; i < cells; i++) {
34175
- let a = i * w - shift, b = a + w;
34176
- a = Math.max(0, a);
34177
- b = Math.min(len, b);
34178
- if (b - a < 1e-3) continue;
34179
- const mid = (a + b) / 2;
34180
- const x = alongU ? u0 + mid : (u0 + u1) / 2;
34181
- const z = alongU ? (v0 + v1) / 2 : v0 + mid;
34182
- const y = part.y0 + (r + 0.5) * rowH;
34183
- const d = opening ? Math.hypot(x - opening.center[0], y - opening.center[1], z - opening.center[2]) : Infinity;
34184
- if (opening && d < opening.radius) continue;
34185
- 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);
34186
- if (chips2.some(gone)) continue;
34187
- const near = opening && d < opening.radius + 0.25 || chips2.some((c) => Math.hypot(c[0] - x, c[1] - y, c[2] - z) < 0.15);
34188
- list2.push({ x, y, z, sx: alongU ? b - a : thick, sy: rowH, sz: alongU ? thick : b - a, colour: near || paint2 === null ? edge : paint2 });
34184
+ for (let i = 0; i < per + (shift ? 1 : 0); i++) {
34185
+ const a = Math.max(0, i * w - shift), b = Math.min(len, i * w - shift + w);
34186
+ if (b - a > 1e-3) out.push({ a, b, y0: part.y0 + r * rowH, y1: part.y0 + (r + 1) * rowH, r, i });
34189
34187
  }
34190
34188
  }
34191
- return list2;
34189
+ return out;
34192
34190
  }
34193
34191
  const specOf = (id) => opts.spec()?.parts?.find((q) => q.id === id);
34194
34192
  const same = (a, b) => a.every((x, k) => Math.abs(x - b[k]) < 1e-3);
@@ -34202,6 +34200,137 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34202
34200
  return null;
34203
34201
  }
34204
34202
  const riders = (hostId) => structure.parts.filter((q) => q.id !== hostId && specOf(q.id)?.units && hostOf(q.id) === hostId && !structure.meshesOf(q.id).some((it) => it.mesh)).map((q) => q.id);
34203
+ function shell(it, mask) {
34204
+ const g0 = it.mesh.geometry, g = new THREE.BufferGeometry();
34205
+ const src = it.mesh.userData._p0 || g0.attributes.position.array;
34206
+ for (const [name, attr] of Object.entries(g0.attributes)) {
34207
+ const k = attr.itemSize;
34208
+ g.setAttribute(name, new THREE.BufferAttribute((name === "position" ? src : attr.array).slice(it.v0 * k, it.v1 * k), k));
34209
+ }
34210
+ if (g0.index) {
34211
+ const ix = g0.index.array.slice(it.i0, it.i1);
34212
+ for (let k = 0; k < ix.length; k++) ix[k] -= it.v0;
34213
+ g.setIndex(new THREE.BufferAttribute(ix, 1));
34214
+ }
34215
+ g.computeBoundingBox();
34216
+ g.computeBoundingSphere();
34217
+ const base = it.mesh.material;
34218
+ const material = base.clone();
34219
+ const toHouse = { value: new THREE.Matrix4() };
34220
+ material.onBeforeCompile = (sh) => {
34221
+ base.onBeforeCompile?.(sh);
34222
+ sh.uniforms.uToHouse = toHouse;
34223
+ sh.uniforms.uMask = { value: mask.texture };
34224
+ sh.uniforms.uMaskRect = { value: mask.rect };
34225
+ sh.uniforms.uMaskAlongX = { value: mask.alongU ? 1 : 0 };
34226
+ sh.vertexShader = sh.vertexShader.replace("void main() {", "uniform mat4 uToHouse;\nvarying vec3 vHousePos;\nvoid main() {").replace("#include <begin_vertex>", "#include <begin_vertex>\nvHousePos = (uToHouse * vec4(transformed, 1.0)).xyz;");
34227
+ sh.fragmentShader = sh.fragmentShader.replace("void main() {", "uniform sampler2D uMask;\nuniform vec4 uMaskRect;\nuniform float uMaskAlongX;\nvarying vec3 vHousePos;\nvoid main() {\n vec2 mUv = vec2(((uMaskAlongX > 0.5 ? vHousePos.x : vHousePos.z) - uMaskRect.x) / uMaskRect.z, (vHousePos.y - uMaskRect.y) / uMaskRect.w);\n if (mUv.x >= 0.0 && mUv.x <= 1.0 && mUv.y >= 0.0 && mUv.y <= 1.0 && texture2D(uMask, mUv).r > 0.5) discard;");
34228
+ };
34229
+ material.customProgramCacheKey = () => "breachMask" + (base.customProgramCacheKey?.() ?? "");
34230
+ const mesh3 = new THREE.Mesh(g, material);
34231
+ mesh3.name = "breach-shell";
34232
+ mesh3.userData.breachMask = mask;
34233
+ mesh3.position.copy(it.mesh.position);
34234
+ mesh3.quaternion.copy(it.mesh.quaternion);
34235
+ mesh3.scale.copy(it.mesh.scale);
34236
+ mesh3.castShadow = it.mesh.castShadow;
34237
+ mesh3.receiveShadow = it.mesh.receiveShadow;
34238
+ mesh3.renderOrder = it.mesh.renderOrder;
34239
+ mesh3.onBeforeRender = () => {
34240
+ house.updateMatrixWorld();
34241
+ toHouse.value.copy(house.matrixWorld).invert().multiply(mesh3.matrixWorld);
34242
+ };
34243
+ it.mesh.parent.add(mesh3);
34244
+ mesh3.updateMatrixWorld(true);
34245
+ toHouse.value.copy(house.matrixWorld).invert().multiply(mesh3.matrixWorld);
34246
+ return mesh3;
34247
+ }
34248
+ function maskOf(hostId, parts) {
34249
+ const host = structure.byId.get(hostId);
34250
+ const [u0, v0, u1, v1] = host.aabb;
34251
+ const alongU = u1 - u0 >= v1 - v0;
34252
+ const y0 = Math.min(...parts.map((id) => structure.byId.get(id).y0)), y1 = Math.max(...parts.map((id) => structure.byId.get(id).y1));
34253
+ const len = alongU ? u1 - u0 : v1 - v0;
34254
+ const w = Math.max(2, Math.min(2048, Math.ceil(len / MASK_TEXEL))), h = Math.max(2, Math.min(1024, Math.ceil((y1 - y0) / MASK_TEXEL)));
34255
+ const data = new Uint8Array(w * h);
34256
+ const texture = new THREE.DataTexture(data, w, h, THREE.RedFormat);
34257
+ texture.magFilter = THREE.LinearFilter;
34258
+ texture.minFilter = THREE.LinearFilter;
34259
+ texture.needsUpdate = true;
34260
+ return { data, texture, w, h, alongU, start: alongU ? u0 : v0, len, y0, height: y1 - y0, rect: new THREE.Vector4(alongU ? u0 : v0, y0, len, y1 - y0) };
34261
+ }
34262
+ const boxDistance = (s, y, a, b, c, d) => Math.hypot(Math.max(a - s, 0, s - b), Math.max(c - y, 0, y - d));
34263
+ function paint2(view) {
34264
+ const { mask } = view;
34265
+ const along = (p) => (mask.alongU ? p[0] : p[2]) - mask.start;
34266
+ const holes = [];
34267
+ const discs = [];
34268
+ const shown = [];
34269
+ for (const id of view.parts) {
34270
+ const part = structure.byId.get(id), units2 = specOf(id).units, chips2 = view.chips.get(id), opening = view.openings.get(id);
34271
+ const all = cells(part, units2);
34272
+ const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
34273
+ const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
34274
+ if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
34275
+ const gone = (c) => chips2.some((p) => p[1] >= c.y0 && p[1] <= c.y1 && along(p) >= c.a && along(p) <= c.b) || opening && Math.hypot((c.a + c.b) / 2 - along(opening.center), (c.y0 + c.y1) / 2 - opening.center[1]) < opening.radius;
34276
+ const kept = [];
34277
+ for (const c of all) (gone(c) ? holes : kept).push(c);
34278
+ for (const c of kept) shown.push({ c, part, units: units2, thick, mid });
34279
+ }
34280
+ const reach = SPALL_MIN + SPALL_RAGGED;
34281
+ mask.data.fill(0);
34282
+ const stamp = (s0, s1, y0, y1, inside2) => {
34283
+ const i0 = Math.max(0, Math.floor((s0 - reach) / mask.len * mask.w)), i1 = Math.min(mask.w - 1, Math.ceil((s1 + reach) / mask.len * mask.w));
34284
+ const j0 = Math.max(0, Math.floor((y0 - reach - mask.y0) / mask.height * mask.h)), j1 = Math.min(mask.h - 1, Math.ceil((y1 + reach - mask.y0) / mask.height * mask.h));
34285
+ for (let j = j0; j <= j1; j++) {
34286
+ const y = mask.y0 + (j + 0.5) / mask.h * mask.height;
34287
+ for (let i = i0; i <= i1; i++) {
34288
+ const s = (i + 0.5) / mask.w * mask.len;
34289
+ const edge = SPALL_MIN + SPALL_RAGGED * (0.6 * noise2(s, y, 0.03) + 0.4 * noise2(s + 7.3, y - 3.1, 0.011));
34290
+ if (inside2(s, y) <= edge) mask.data[j * mask.w + i] = 255;
34291
+ }
34292
+ }
34293
+ };
34294
+ for (const c of holes) stamp(c.a, c.b, c.y0, c.y1, (s, y) => boxDistance(s, y, c.a, c.b, c.y0, c.y1));
34295
+ for (const d of discs) stamp(d.s - d.r, d.s + d.r, d.y - d.r, d.y + d.r, (s, y) => Math.max(0, Math.hypot(s - d.s, y - d.y) - d.r));
34296
+ mask.texture.needsUpdate = true;
34297
+ const near = (c) => holes.some((h) => boxDistance((c.a + c.b) / 2, (c.y0 + c.y1) / 2, h.a - (c.b - c.a) / 2, h.b + (c.b - c.a) / 2, h.y0 - (c.y1 - c.y0) / 2, h.y1 + (c.y1 - c.y0) / 2) <= reach) || discs.some((d) => boxDistance(d.s, d.y, c.a, c.b, c.y0, c.y1) <= d.r + reach);
34298
+ const list2 = [];
34299
+ const tone = (hex2, k) => tmp.c.setHex(hex2).multiplyScalar(k).getHex();
34300
+ for (const { c, part, units: units2, thick, mid } of shown) {
34301
+ if (!near(c)) continue;
34302
+ const centre = mask.start + (c.a + c.b) / 2, y = (c.y0 + c.y1) / 2, len = c.b - c.a, h = c.y1 - c.y0;
34303
+ const at3 = (s, dy, d, colour) => list2.push({ x: mask.alongU ? centre : mid, y, z: mask.alongU ? mid : centre, sx: mask.alongU ? s : d, sy: dy, sz: mask.alongU ? d : s, colour });
34304
+ const base = fragmentMaterial(part.material).color.getHex();
34305
+ const n = noise2(centre * 3.1, y * 2.7, 0.07);
34306
+ if (units2.mortar === "concrete") {
34307
+ at3(len, h, thick - 2 * COVER2 - 0.03 * n, tone(base, 0.86 + 0.2 * n));
34308
+ } else {
34309
+ at3(len, h, thick - 2 * COVER2 - 0.012, 10327948);
34310
+ at3(len - 0.012, h - 0.012, thick - 2 * COVER2, tone(base, 0.8 + 0.35 * n));
34311
+ }
34312
+ }
34313
+ if (view.units) {
34314
+ view.units.removeFromParent();
34315
+ view.units.dispose();
34316
+ }
34317
+ const units = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
34318
+ units.name = "breach:" + view.host;
34319
+ list2.forEach((u, i) => {
34320
+ tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
34321
+ units.setMatrixAt(i, tmp.m);
34322
+ units.setColorAt(i, tmp.c.setHex(u.colour));
34323
+ });
34324
+ units.count = list2.length;
34325
+ units.instanceMatrix.needsUpdate = true;
34326
+ if (units.instanceColor) units.instanceColor.needsUpdate = true;
34327
+ units.castShadow = true;
34328
+ units.receiveShadow = true;
34329
+ units.visible = lod === 0;
34330
+ house.add(units);
34331
+ view.units = units;
34332
+ view.gone = holes.length;
34333
+ }
34205
34334
  function draw2(partId, change) {
34206
34335
  if (lod !== 0) return false;
34207
34336
  const hostId = hostOf(partId);
@@ -34211,37 +34340,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34211
34340
  const items = structure.meshesOf(hostId).filter((it) => it.mesh);
34212
34341
  if (!items.length) return false;
34213
34342
  const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
34214
- view = { mesh: null, saved: null, items, paint: wallColour(items), units: 0, opening: null, chips: new Map(parts.map((id) => [id, []])), openings: /* @__PURE__ */ new Map() };
34343
+ if (!parts.includes(partId)) return false;
34344
+ const mask = maskOf(hostId, parts);
34345
+ view = { host: hostId, parts, items, mask, shells: [], saved: null, units: null, gone: 0, chips: new Map(parts.map((id) => [id, []])), openings: /* @__PURE__ */ new Map() };
34215
34346
  }
34216
34347
  if (!view.chips.has(partId)) return false;
34217
34348
  if (change.opening) {
34218
34349
  if (view.openings.has(partId)) return false;
34219
34350
  view.openings.set(partId, { center: local2(change.opening.center), radius: change.opening.radius });
34220
- if (partId === hostId) view.opening = view.openings.get(partId);
34221
34351
  }
34222
34352
  if (change.chip) view.chips.get(partId).push(local2(change.chip));
34223
- const list2 = [];
34224
- for (const [id, chips2] of view.chips) list2.push(...courses(structure.byId.get(id), specOf(id).units, view.openings.get(id) ?? null, chips2, view.paint));
34225
- if (view.mesh) {
34226
- view.mesh.removeFromParent();
34227
- view.mesh.dispose();
34353
+ if (!view.saved) {
34354
+ view.shells = view.items.map((it) => shell(it, view.mask));
34355
+ view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
34228
34356
  }
34229
- const mesh3 = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list2.length));
34230
- mesh3.name = "breach:" + hostId;
34231
- list2.forEach((u, i) => {
34232
- tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
34233
- mesh3.setMatrixAt(i, tmp.m);
34234
- mesh3.setColorAt(i, tmp.c.setHex(u.colour));
34235
- });
34236
- mesh3.count = list2.length;
34237
- mesh3.instanceMatrix.needsUpdate = true;
34238
- if (mesh3.instanceColor) mesh3.instanceColor.needsUpdate = true;
34239
- mesh3.castShadow = true;
34240
- mesh3.receiveShadow = true;
34241
- house.add(mesh3);
34242
- if (!view.saved) view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
34243
- view.mesh = mesh3;
34244
- view.units = list2.length;
34357
+ paint2(view);
34245
34358
  views.set(hostId, view);
34246
34359
  invalidate();
34247
34360
  invalidateShadow();
@@ -34253,30 +34366,40 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34253
34366
  else s.data = collapseRange(s.it.mesh, s.v0, s.it.v1);
34254
34367
  }
34255
34368
  }
34369
+ function dispose(view) {
34370
+ view.units.removeFromParent();
34371
+ view.units.dispose();
34372
+ for (const m of view.shells) {
34373
+ m.removeFromParent();
34374
+ m.geometry.dispose();
34375
+ m.material.dispose();
34376
+ }
34377
+ view.mask.texture.dispose();
34378
+ }
34256
34379
  return {
34257
- /** Handles a `stage` event of the house's object. Returns true when the wall was redrawn as courses. */
34380
+ /** Handles a `stage` event of the house's object. Returns true when the wall was cut. */
34258
34381
  stage(event) {
34259
34382
  return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
34260
34383
  },
34261
- /** Handles a `chip` event: the unit that came out leaves a gap in the courses. Returns true when the wall was redrawn. */
34384
+ /** Handles a `chip` event: the unit that came out leaves a hole in the wall where it was. Returns true when the wall was cut. */
34262
34385
  chip(event) {
34263
34386
  return draw2(event.part, { chip: event.center });
34264
34387
  },
34265
- /** The part broke or fell: the courses go (the plain range stays collapsed, the renderer hides the part). */
34388
+ /** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
34266
34389
  drop(partId) {
34267
34390
  const v = views.get(partId);
34268
34391
  if (!v) return;
34269
- v.mesh.removeFromParent();
34270
- v.mesh.dispose();
34392
+ dispose(v);
34271
34393
  views.delete(partId);
34272
34394
  },
34273
- /** LOD 0 draws the courses; LOD 1 and 2 give the wall its box back. */
34395
+ /** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
34274
34396
  setLod(level) {
34275
34397
  const was = lod;
34276
34398
  lod = level;
34277
34399
  if (was === 0 === (level === 0)) return;
34278
34400
  for (const v of views.values()) {
34279
- v.mesh.visible = level === 0;
34401
+ v.units.visible = level === 0;
34402
+ for (const m of v.shells) m.visible = level === 0;
34280
34403
  plain2(v, level !== 0);
34281
34404
  }
34282
34405
  invalidate();
@@ -34285,17 +34408,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34285
34408
  /** Everything back to what it was (after `world.reset`). */
34286
34409
  restore() {
34287
34410
  for (const v of views.values()) {
34288
- v.mesh.removeFromParent();
34289
- v.mesh.dispose();
34411
+ dispose(v);
34290
34412
  if (lod === 0) plain2(v, true);
34291
34413
  }
34292
34414
  views.clear();
34293
34415
  },
34294
- /** The wall's opening, if it has courses. */
34416
+ /** The wall has damage drawn. */
34295
34417
  has(partId) {
34296
34418
  return views.has(partId);
34297
34419
  },
34298
- statistics: () => ({ walls: views.size, units: [...views.values()].reduce((n, v) => n + v.units, 0) })
34420
+ /** Walls with damage drawn, units out of them, and units laid bare at the edge of the cuts. */
34421
+ statistics: () => ({
34422
+ walls: views.size,
34423
+ gone: [...views.values()].reduce((n, v) => n + v.gone, 0),
34424
+ units: [...views.values()].reduce((n, v) => n + v.units.count, 0)
34425
+ })
34299
34426
  };
34300
34427
  }
34301
34428
  Object.assign(K, { createBreachView });
@@ -39098,8 +39225,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39098
39225
  bedHeight = roadY * 1e3 + z * 14 - (x - params.roadSlopeAnchorX) * 5;
39099
39226
  roughness = 0.55;
39100
39227
  }
39101
- const noise2 = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
39102
- bed[index] = bedHeight + (noise2 - 0.5) * roughness * 2;
39228
+ const noise3 = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
39229
+ bed[index] = bedHeight + (noise3 - 0.5) * roughness * 2;
39103
39230
  vertical[index] = region === 0 || region === 2 ? 0.95 : 1;
39104
39231
  pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, row * 0.21) * 0.5 + hashCell(column, row) * 0.5) * (region === 3 ? 1.25 : 1);
39105
39232
  }
@@ -39574,6 +39701,55 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39574
39701
  ends: 22e-5
39575
39702
  }
39576
39703
  }
39704
+ },
39705
+ plastic_bottle: {
39706
+ models: [
39707
+ "water_bottle",
39708
+ "water_jug_5l",
39709
+ "dirty_water",
39710
+ "sports_drink",
39711
+ "coconut_water",
39712
+ "cooking_oil",
39713
+ "alcohol_70",
39714
+ "fuel_jerrycan"
39715
+ ],
39716
+ rule: {
39717
+ hollow: true,
39718
+ welded: true,
39719
+ skinTags: [
39720
+ "paper",
39721
+ "food"
39722
+ ],
39723
+ materials: {
39724
+ glass: "plastic"
39725
+ }
39726
+ }
39727
+ },
39728
+ glass_bottle: {
39729
+ models: [
39730
+ "cachaca",
39731
+ "communion_wine",
39732
+ "honey_jar",
39733
+ "home_preserves"
39734
+ ],
39735
+ rule: {
39736
+ hollow: true,
39737
+ welded: true,
39738
+ skinTags: [
39739
+ "paper",
39740
+ "food"
39741
+ ]
39742
+ }
39743
+ },
39744
+ drink_carton: {
39745
+ models: [
39746
+ "juice_box",
39747
+ "milk_carton"
39748
+ ],
39749
+ rule: {
39750
+ hollow: true,
39751
+ welded: true
39752
+ }
39577
39753
  }
39578
39754
  },
39579
39755
  contents: {
@@ -39589,6 +39765,67 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39589
39765
  liquid: "soda",
39590
39766
  fill: 0.95
39591
39767
  },
39768
+ water_bottle: {
39769
+ liquid: "water",
39770
+ fill: 0.95
39771
+ },
39772
+ water_jug_5l: {
39773
+ liquid: "water",
39774
+ fill: 0.95,
39775
+ box: true
39776
+ },
39777
+ dirty_water: {
39778
+ liquid: "water",
39779
+ fill: 0.9
39780
+ },
39781
+ sports_drink: {
39782
+ liquid: "juice",
39783
+ fill: 0.95
39784
+ },
39785
+ coconut_water: {
39786
+ liquid: "water",
39787
+ fill: 0.9,
39788
+ box: true
39789
+ },
39790
+ juice_box: {
39791
+ liquid: "juice",
39792
+ fill: 0.95,
39793
+ box: true
39794
+ },
39795
+ milk_carton: {
39796
+ liquid: "milk",
39797
+ fill: 0.95,
39798
+ box: true
39799
+ },
39800
+ cachaca: {
39801
+ liquid: "spirits",
39802
+ fill: 0.9
39803
+ },
39804
+ communion_wine: {
39805
+ liquid: "wine",
39806
+ fill: 0.9
39807
+ },
39808
+ cooking_oil: {
39809
+ liquid: "cooking_oil",
39810
+ fill: 0.9
39811
+ },
39812
+ alcohol_70: {
39813
+ liquid: "spirits",
39814
+ fill: 0.9
39815
+ },
39816
+ honey_jar: {
39817
+ liquid: "honey",
39818
+ fill: 0.85
39819
+ },
39820
+ home_preserves: {
39821
+ liquid: "water",
39822
+ fill: 0.5
39823
+ },
39824
+ fuel_jerrycan: {
39825
+ liquid: "fuel",
39826
+ fill: 0.85,
39827
+ box: true
39828
+ },
39592
39829
  canned_beans: {
39593
39830
  liquid: "juice",
39594
39831
  fill: 0.4
@@ -56211,6 +56448,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
56211
56448
  for (const decal2 of this.decals) decal2.on = false;
56212
56449
  this.count = 0;
56213
56450
  }
56451
+ /**
56452
+ * Takes away the marks inside a box: world `center`, `half` extents along the axes of the world `rotation` (a quaternion, x y z w; none means the
56453
+ * world axes). The surface they were on is gone (a brick came out of a wall with its bullet holes). Returns how many went.
56454
+ */
56455
+ clearInside(center, half, rotation = [0, 0, 0, 1]) {
56456
+ const qx = -rotation[0], qy = -rotation[1], qz = -rotation[2], qw = rotation[3];
56457
+ let gone = 0;
56458
+ for (const decal2 of this.decals) {
56459
+ if (!decal2.on) continue;
56460
+ const vx2 = decal2.x - center[0], vy2 = decal2.y - center[1], vz2 = decal2.z - center[2];
56461
+ const tx = 2 * (qy * vz2 - qz * vy2), ty = 2 * (qz * vx2 - qx * vz2), tz = 2 * (qx * vy2 - qy * vx2);
56462
+ const lx = vx2 + qw * tx + (qy * tz - qz * ty), ly = vy2 + qw * ty + (qz * tx - qx * tz), lz = vz2 + qw * tz + (qx * ty - qy * tx);
56463
+ if (Math.abs(lx) > half[0] || Math.abs(ly) > half[1] || Math.abs(lz) > half[2]) continue;
56464
+ decal2.on = false;
56465
+ gone++;
56466
+ }
56467
+ return gone;
56468
+ }
56214
56469
  update(dt) {
56215
56470
  this.count = 0;
56216
56471
  for (const decal2 of this.decals) {