@adatechnology/game-floorplan-three 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.global.js +661 -109
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +257 -76
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -6030,7 +6030,7 @@ function installLightsRoomLight(K, shared) {
|
|
|
6030
6030
|
|
|
6031
6031
|
// src/destruction/pieces.ts
|
|
6032
6032
|
import { assemblyPart, getAssembly } from "@adatechnology/game-destruction";
|
|
6033
|
-
import { DENSITY, toDestructibleSpec } from "@adatechnology/game-floorplan-structure";
|
|
6033
|
+
import { DENSITY, MEMBER_CAGE, toDestructibleSpec } from "@adatechnology/game-floorplan-structure";
|
|
6034
6034
|
|
|
6035
6035
|
// src/destruction/panels.ts
|
|
6036
6036
|
import { resolveSlabPanels, slabPanelView } from "@adatechnology/game-floorplan-structure";
|
|
@@ -6210,7 +6210,11 @@ function installDestructionPieces(K, shared) {
|
|
|
6210
6210
|
if (!pc || pc.unit) return sp;
|
|
6211
6211
|
if (pc.role !== "wall" && pc.role !== "partition") {
|
|
6212
6212
|
const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
|
|
6213
|
-
|
|
6213
|
+
const out = def && def.reinforcement && !sp.reinforcement ? { ...sp, reinforcement: { ...def.reinforcement } } : sp;
|
|
6214
|
+
if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
|
|
6215
|
+
return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
|
|
6216
|
+
}
|
|
6217
|
+
return out;
|
|
6214
6218
|
}
|
|
6215
6219
|
if (!pc.assembly) return sp;
|
|
6216
6220
|
const [u0, v0, u1, v1] = pc.aabb, alongU = u1 - u0 >= v1 - v0;
|
|
@@ -6247,6 +6251,19 @@ function restoreRange(mesh, v0, saved) {
|
|
|
6247
6251
|
pos.array.set(saved, v0 * 3);
|
|
6248
6252
|
pos.needsUpdate = true;
|
|
6249
6253
|
}
|
|
6254
|
+
function noise2(x, y, cell) {
|
|
6255
|
+
const h = (i2, j2) => {
|
|
6256
|
+
const s = Math.sin(i2 * 127.1 + j2 * 311.7) * 43758.5453;
|
|
6257
|
+
return s - Math.floor(s);
|
|
6258
|
+
};
|
|
6259
|
+
const fx = x / cell, fy = y / cell, i = Math.floor(fx), j = Math.floor(fy), u = fx - i, v = fy - j;
|
|
6260
|
+
const su = u * u * (3 - 2 * u), sv = v * v * (3 - 2 * v);
|
|
6261
|
+
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;
|
|
6262
|
+
}
|
|
6263
|
+
var MASK_TEXEL = 0.02;
|
|
6264
|
+
var SPALL_MIN = 0.025;
|
|
6265
|
+
var SPALL_RAGGED = 0.045;
|
|
6266
|
+
var COVER = 0.015;
|
|
6250
6267
|
function installDestructionBreach(K, _shared) {
|
|
6251
6268
|
const { THREE, fragmentMaterial, invalidate, invalidateShadow } = K;
|
|
6252
6269
|
function createBreachView(opts) {
|
|
@@ -6256,94 +6273,198 @@ function installDestructionBreach(K, _shared) {
|
|
|
6256
6273
|
const unitMaterial = new THREE.MeshLambertMaterial({ color: 16777215 });
|
|
6257
6274
|
const unitGeometry = new THREE.BoxGeometry(1, 1, 1);
|
|
6258
6275
|
const tmp = { m: new THREE.Matrix4(), q: new THREE.Quaternion(), p: new THREE.Vector3(), s: new THREE.Vector3(), c: new THREE.Color() };
|
|
6259
|
-
const wallColour = (items) => {
|
|
6260
|
-
for (const it of items) {
|
|
6261
|
-
if (!it.mesh) continue;
|
|
6262
|
-
const c = it.mesh.geometry.attributes.color;
|
|
6263
|
-
if (!c) continue;
|
|
6264
|
-
const a = c.array;
|
|
6265
|
-
tmp.c.setRGB(a[it.v0 * 3], a[it.v0 * 3 + 1], a[it.v0 * 3 + 2]);
|
|
6266
|
-
return tmp.c.getHex();
|
|
6267
|
-
}
|
|
6268
|
-
return null;
|
|
6269
|
-
};
|
|
6270
6276
|
const local = (p) => {
|
|
6271
6277
|
house.updateMatrixWorld(true);
|
|
6272
6278
|
const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
|
|
6273
6279
|
return [v.x, v.y, v.z];
|
|
6274
6280
|
};
|
|
6275
|
-
function
|
|
6281
|
+
function cells(part, units) {
|
|
6276
6282
|
const [u0, v0, u1, v1] = part.aabb;
|
|
6277
6283
|
const alongU = u1 - u0 >= v1 - v0;
|
|
6278
6284
|
const len = alongU ? u1 - u0 : v1 - v0;
|
|
6279
|
-
const thick = alongU ? v1 - v0 : u1 - u0;
|
|
6280
6285
|
const [ul, uh] = units.size;
|
|
6281
6286
|
const rows = Math.max(1, Math.round((part.y1 - part.y0) / uh));
|
|
6282
6287
|
const rowH = (part.y1 - part.y0) / rows;
|
|
6283
6288
|
const per = Math.max(1, Math.round(len / ul));
|
|
6284
6289
|
const w = len / per;
|
|
6285
|
-
const
|
|
6286
|
-
const list = [];
|
|
6290
|
+
const out = [];
|
|
6287
6291
|
for (let r = 0; r < rows; r++) {
|
|
6288
6292
|
const shift = units.bond === "running" && r & 1 ? w / 2 : 0;
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
a = Math.max(0, a);
|
|
6293
|
-
b = Math.min(len, b);
|
|
6294
|
-
if (b - a < 1e-3) continue;
|
|
6295
|
-
const mid = (a + b) / 2;
|
|
6296
|
-
const x = alongU ? u0 + mid : (u0 + u1) / 2;
|
|
6297
|
-
const z = alongU ? (v0 + v1) / 2 : v0 + mid;
|
|
6298
|
-
const y = part.y0 + (r + 0.5) * rowH;
|
|
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 });
|
|
6293
|
+
for (let i = 0; i < per + (shift ? 1 : 0); i++) {
|
|
6294
|
+
const a = Math.max(0, i * w - shift), b = Math.min(len, i * w - shift + w);
|
|
6295
|
+
if (b - a > 1e-3) out.push({ a, b, y0: part.y0 + r * rowH, y1: part.y0 + (r + 1) * rowH, r, i });
|
|
6305
6296
|
}
|
|
6306
6297
|
}
|
|
6307
|
-
return
|
|
6298
|
+
return out;
|
|
6299
|
+
}
|
|
6300
|
+
const specOf = (id) => opts.spec()?.parts?.find((q) => q.id === id);
|
|
6301
|
+
const same = (a, b) => a.every((x, k) => Math.abs(x - b[k]) < 1e-3);
|
|
6302
|
+
function hostOf(partId) {
|
|
6303
|
+
if (structure.meshesOf(partId).some((it) => it.mesh)) return partId;
|
|
6304
|
+
const part = structure.byId.get(partId);
|
|
6305
|
+
if (!part) return null;
|
|
6306
|
+
for (const q of structure.parts) {
|
|
6307
|
+
if (q.id !== partId && same(q.aabb, part.aabb) && Math.abs(q.y1 - part.y0) < 0.01 && structure.meshesOf(q.id).some((it) => it.mesh)) return q.id;
|
|
6308
|
+
}
|
|
6309
|
+
return null;
|
|
6310
|
+
}
|
|
6311
|
+
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);
|
|
6312
|
+
function shell(it, mask) {
|
|
6313
|
+
const g0 = it.mesh.geometry, g = new THREE.BufferGeometry();
|
|
6314
|
+
const src = it.mesh.userData._p0 || g0.attributes.position.array;
|
|
6315
|
+
for (const [name, attr] of Object.entries(g0.attributes)) {
|
|
6316
|
+
const k = attr.itemSize;
|
|
6317
|
+
g.setAttribute(name, new THREE.BufferAttribute((name === "position" ? src : attr.array).slice(it.v0 * k, it.v1 * k), k));
|
|
6318
|
+
}
|
|
6319
|
+
if (g0.index) {
|
|
6320
|
+
const ix = g0.index.array.slice(it.i0, it.i1);
|
|
6321
|
+
for (let k = 0; k < ix.length; k++) ix[k] -= it.v0;
|
|
6322
|
+
g.setIndex(new THREE.BufferAttribute(ix, 1));
|
|
6323
|
+
}
|
|
6324
|
+
g.computeBoundingBox();
|
|
6325
|
+
g.computeBoundingSphere();
|
|
6326
|
+
const base = it.mesh.material;
|
|
6327
|
+
const material = base.clone();
|
|
6328
|
+
const toHouse = { value: new THREE.Matrix4() };
|
|
6329
|
+
material.onBeforeCompile = (sh) => {
|
|
6330
|
+
base.onBeforeCompile?.(sh);
|
|
6331
|
+
sh.uniforms.uToHouse = toHouse;
|
|
6332
|
+
sh.uniforms.uMask = { value: mask.texture };
|
|
6333
|
+
sh.uniforms.uMaskRect = { value: mask.rect };
|
|
6334
|
+
sh.uniforms.uMaskAlongX = { value: mask.alongU ? 1 : 0 };
|
|
6335
|
+
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;");
|
|
6336
|
+
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;");
|
|
6337
|
+
};
|
|
6338
|
+
material.customProgramCacheKey = () => "breachMask" + (base.customProgramCacheKey?.() ?? "");
|
|
6339
|
+
const mesh = new THREE.Mesh(g, material);
|
|
6340
|
+
mesh.name = "breach-shell";
|
|
6341
|
+
mesh.userData.breachMask = mask;
|
|
6342
|
+
mesh.position.copy(it.mesh.position);
|
|
6343
|
+
mesh.quaternion.copy(it.mesh.quaternion);
|
|
6344
|
+
mesh.scale.copy(it.mesh.scale);
|
|
6345
|
+
mesh.castShadow = it.mesh.castShadow;
|
|
6346
|
+
mesh.receiveShadow = it.mesh.receiveShadow;
|
|
6347
|
+
mesh.renderOrder = it.mesh.renderOrder;
|
|
6348
|
+
mesh.onBeforeRender = () => {
|
|
6349
|
+
house.updateMatrixWorld();
|
|
6350
|
+
toHouse.value.copy(house.matrixWorld).invert().multiply(mesh.matrixWorld);
|
|
6351
|
+
};
|
|
6352
|
+
it.mesh.parent.add(mesh);
|
|
6353
|
+
mesh.updateMatrixWorld(true);
|
|
6354
|
+
toHouse.value.copy(house.matrixWorld).invert().multiply(mesh.matrixWorld);
|
|
6355
|
+
return mesh;
|
|
6356
|
+
}
|
|
6357
|
+
function maskOf(hostId, parts) {
|
|
6358
|
+
const host = structure.byId.get(hostId);
|
|
6359
|
+
const [u0, v0, u1, v1] = host.aabb;
|
|
6360
|
+
const alongU = u1 - u0 >= v1 - v0;
|
|
6361
|
+
const y0 = Math.min(...parts.map((id) => structure.byId.get(id).y0)), y1 = Math.max(...parts.map((id) => structure.byId.get(id).y1));
|
|
6362
|
+
const len = alongU ? u1 - u0 : v1 - v0;
|
|
6363
|
+
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)));
|
|
6364
|
+
const data = new Uint8Array(w * h);
|
|
6365
|
+
const texture = new THREE.DataTexture(data, w, h, THREE.RedFormat);
|
|
6366
|
+
texture.magFilter = THREE.LinearFilter;
|
|
6367
|
+
texture.minFilter = THREE.LinearFilter;
|
|
6368
|
+
texture.needsUpdate = true;
|
|
6369
|
+
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) };
|
|
6370
|
+
}
|
|
6371
|
+
const boxDistance = (s, y, a, b, c, d) => Math.hypot(Math.max(a - s, 0, s - b), Math.max(c - y, 0, y - d));
|
|
6372
|
+
function paint(view) {
|
|
6373
|
+
const { mask } = view;
|
|
6374
|
+
const along = (p) => (mask.alongU ? p[0] : p[2]) - mask.start;
|
|
6375
|
+
const holes = [];
|
|
6376
|
+
const discs = [];
|
|
6377
|
+
const shown = [];
|
|
6378
|
+
for (const id of view.parts) {
|
|
6379
|
+
const part = structure.byId.get(id), units2 = specOf(id).units, chips = view.chips.get(id), opening = view.openings.get(id);
|
|
6380
|
+
const all = cells(part, units2);
|
|
6381
|
+
const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
|
|
6382
|
+
const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
|
|
6383
|
+
if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
|
|
6384
|
+
const gone = (c) => chips.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;
|
|
6385
|
+
const kept = [];
|
|
6386
|
+
for (const c of all) (gone(c) ? holes : kept).push(c);
|
|
6387
|
+
for (const c of kept) shown.push({ c, part, units: units2, thick, mid });
|
|
6388
|
+
}
|
|
6389
|
+
const reach = SPALL_MIN + SPALL_RAGGED;
|
|
6390
|
+
mask.data.fill(0);
|
|
6391
|
+
const stamp = (s0, s1, y0, y1, inside) => {
|
|
6392
|
+
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));
|
|
6393
|
+
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));
|
|
6394
|
+
for (let j = j0; j <= j1; j++) {
|
|
6395
|
+
const y = mask.y0 + (j + 0.5) / mask.h * mask.height;
|
|
6396
|
+
for (let i = i0; i <= i1; i++) {
|
|
6397
|
+
const s = (i + 0.5) / mask.w * mask.len;
|
|
6398
|
+
const edge = SPALL_MIN + SPALL_RAGGED * (0.6 * noise2(s, y, 0.03) + 0.4 * noise2(s + 7.3, y - 3.1, 0.011));
|
|
6399
|
+
if (inside(s, y) <= edge) mask.data[j * mask.w + i] = 255;
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
6402
|
+
};
|
|
6403
|
+
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));
|
|
6404
|
+
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));
|
|
6405
|
+
mask.texture.needsUpdate = true;
|
|
6406
|
+
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);
|
|
6407
|
+
const list = [];
|
|
6408
|
+
const tone = (hex, k) => tmp.c.setHex(hex).multiplyScalar(k).getHex();
|
|
6409
|
+
for (const { c, part, units: units2, thick, mid } of shown) {
|
|
6410
|
+
if (!near(c)) continue;
|
|
6411
|
+
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;
|
|
6412
|
+
const at = (s, dy, d, colour) => list.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 });
|
|
6413
|
+
const base = fragmentMaterial(part.material).color.getHex();
|
|
6414
|
+
const n = noise2(centre * 3.1, y * 2.7, 0.07);
|
|
6415
|
+
if (units2.mortar === "concrete") {
|
|
6416
|
+
at(len, h, thick - 2 * COVER - 0.03 * n, tone(base, 0.86 + 0.2 * n));
|
|
6417
|
+
} else {
|
|
6418
|
+
at(len, h, thick - 2 * COVER - 0.012, 10327948);
|
|
6419
|
+
at(len - 0.012, h - 0.012, thick - 2 * COVER, tone(base, 0.8 + 0.35 * n));
|
|
6420
|
+
}
|
|
6421
|
+
}
|
|
6422
|
+
if (view.units) {
|
|
6423
|
+
view.units.removeFromParent();
|
|
6424
|
+
view.units.dispose();
|
|
6425
|
+
}
|
|
6426
|
+
const units = new THREE.InstancedMesh(unitGeometry, unitMaterial, Math.max(1, list.length));
|
|
6427
|
+
units.name = "breach:" + view.host;
|
|
6428
|
+
list.forEach((u, i) => {
|
|
6429
|
+
tmp.m.compose(tmp.p.set(u.x, u.y, u.z), tmp.q.identity(), tmp.s.set(u.sx, u.sy, u.sz));
|
|
6430
|
+
units.setMatrixAt(i, tmp.m);
|
|
6431
|
+
units.setColorAt(i, tmp.c.setHex(u.colour));
|
|
6432
|
+
});
|
|
6433
|
+
units.count = list.length;
|
|
6434
|
+
units.instanceMatrix.needsUpdate = true;
|
|
6435
|
+
if (units.instanceColor) units.instanceColor.needsUpdate = true;
|
|
6436
|
+
units.castShadow = true;
|
|
6437
|
+
units.receiveShadow = true;
|
|
6438
|
+
units.visible = lod === 0;
|
|
6439
|
+
house.add(units);
|
|
6440
|
+
view.units = units;
|
|
6441
|
+
view.gone = holes.length;
|
|
6308
6442
|
}
|
|
6309
6443
|
function draw2(partId, change) {
|
|
6310
6444
|
if (lod !== 0) return false;
|
|
6311
|
-
const
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
let view = views.get(partId);
|
|
6445
|
+
const hostId = hostOf(partId);
|
|
6446
|
+
if (!hostId || !specOf(partId)?.units) return false;
|
|
6447
|
+
let view = views.get(hostId);
|
|
6315
6448
|
if (!view) {
|
|
6316
|
-
const items = structure.meshesOf(
|
|
6449
|
+
const items = structure.meshesOf(hostId).filter((it) => it.mesh);
|
|
6317
6450
|
if (!items.length) return false;
|
|
6318
|
-
|
|
6451
|
+
const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
|
|
6452
|
+
if (!parts.includes(partId)) return false;
|
|
6453
|
+
const mask = maskOf(hostId, parts);
|
|
6454
|
+
view = { host: hostId, parts, items, mask, shells: [], saved: null, units: null, gone: 0, chips: new Map(parts.map((id) => [id, []])), openings: /* @__PURE__ */ new Map() };
|
|
6319
6455
|
}
|
|
6456
|
+
if (!view.chips.has(partId)) return false;
|
|
6320
6457
|
if (change.opening) {
|
|
6321
|
-
if (view.
|
|
6322
|
-
view.
|
|
6458
|
+
if (view.openings.has(partId)) return false;
|
|
6459
|
+
view.openings.set(partId, { center: local(change.opening.center), radius: change.opening.radius });
|
|
6323
6460
|
}
|
|
6324
|
-
if (change.chip) view.chips.push(local(change.chip));
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
view.
|
|
6328
|
-
view.mesh.dispose();
|
|
6461
|
+
if (change.chip) view.chips.get(partId).push(local(change.chip));
|
|
6462
|
+
if (!view.saved) {
|
|
6463
|
+
view.shells = view.items.map((it) => shell(it, view.mask));
|
|
6464
|
+
view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
|
|
6329
6465
|
}
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
list.forEach((u, i) => {
|
|
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));
|
|
6334
|
-
mesh.setMatrixAt(i, tmp.m);
|
|
6335
|
-
mesh.setColorAt(i, tmp.c.setHex(u.colour));
|
|
6336
|
-
});
|
|
6337
|
-
mesh.count = list.length;
|
|
6338
|
-
mesh.instanceMatrix.needsUpdate = true;
|
|
6339
|
-
if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
|
|
6340
|
-
mesh.castShadow = true;
|
|
6341
|
-
mesh.receiveShadow = true;
|
|
6342
|
-
house.add(mesh);
|
|
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);
|
|
6466
|
+
paint(view);
|
|
6467
|
+
views.set(hostId, view);
|
|
6347
6468
|
invalidate();
|
|
6348
6469
|
invalidateShadow();
|
|
6349
6470
|
return true;
|
|
@@ -6354,30 +6475,40 @@ function installDestructionBreach(K, _shared) {
|
|
|
6354
6475
|
else s.data = collapseRange(s.it.mesh, s.v0, s.it.v1);
|
|
6355
6476
|
}
|
|
6356
6477
|
}
|
|
6478
|
+
function dispose(view) {
|
|
6479
|
+
view.units.removeFromParent();
|
|
6480
|
+
view.units.dispose();
|
|
6481
|
+
for (const m of view.shells) {
|
|
6482
|
+
m.removeFromParent();
|
|
6483
|
+
m.geometry.dispose();
|
|
6484
|
+
m.material.dispose();
|
|
6485
|
+
}
|
|
6486
|
+
view.mask.texture.dispose();
|
|
6487
|
+
}
|
|
6357
6488
|
return {
|
|
6358
|
-
/** Handles a `stage` event of the house's object. Returns true when the wall was
|
|
6489
|
+
/** Handles a `stage` event of the house's object. Returns true when the wall was cut. */
|
|
6359
6490
|
stage(event) {
|
|
6360
6491
|
return event.stage === 2 && !!event.opening ? draw2(event.part, { opening: event.opening }) : false;
|
|
6361
6492
|
},
|
|
6362
|
-
/** Handles a `chip` event: the unit that came out leaves a
|
|
6493
|
+
/** 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. */
|
|
6363
6494
|
chip(event) {
|
|
6364
6495
|
return draw2(event.part, { chip: event.center });
|
|
6365
6496
|
},
|
|
6366
|
-
/** The part broke or fell:
|
|
6497
|
+
/** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
|
|
6367
6498
|
drop(partId) {
|
|
6368
6499
|
const v = views.get(partId);
|
|
6369
6500
|
if (!v) return;
|
|
6370
|
-
v
|
|
6371
|
-
v.mesh.dispose();
|
|
6501
|
+
dispose(v);
|
|
6372
6502
|
views.delete(partId);
|
|
6373
6503
|
},
|
|
6374
|
-
/** LOD 0 draws the
|
|
6504
|
+
/** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
|
|
6375
6505
|
setLod(level) {
|
|
6376
6506
|
const was = lod;
|
|
6377
6507
|
lod = level;
|
|
6378
6508
|
if (was === 0 === (level === 0)) return;
|
|
6379
6509
|
for (const v of views.values()) {
|
|
6380
|
-
v.
|
|
6510
|
+
v.units.visible = level === 0;
|
|
6511
|
+
for (const m of v.shells) m.visible = level === 0;
|
|
6381
6512
|
plain(v, level !== 0);
|
|
6382
6513
|
}
|
|
6383
6514
|
invalidate();
|
|
@@ -6386,17 +6517,21 @@ function installDestructionBreach(K, _shared) {
|
|
|
6386
6517
|
/** Everything back to what it was (after `world.reset`). */
|
|
6387
6518
|
restore() {
|
|
6388
6519
|
for (const v of views.values()) {
|
|
6389
|
-
v
|
|
6390
|
-
v.mesh.dispose();
|
|
6520
|
+
dispose(v);
|
|
6391
6521
|
if (lod === 0) plain(v, true);
|
|
6392
6522
|
}
|
|
6393
6523
|
views.clear();
|
|
6394
6524
|
},
|
|
6395
|
-
/** The wall
|
|
6525
|
+
/** The wall has damage drawn. */
|
|
6396
6526
|
has(partId) {
|
|
6397
6527
|
return views.has(partId);
|
|
6398
6528
|
},
|
|
6399
|
-
|
|
6529
|
+
/** Walls with damage drawn, units out of them, and units laid bare at the edge of the cuts. */
|
|
6530
|
+
statistics: () => ({
|
|
6531
|
+
walls: views.size,
|
|
6532
|
+
gone: [...views.values()].reduce((n, v) => n + v.gone, 0),
|
|
6533
|
+
units: [...views.values()].reduce((n, v) => n + v.units.count, 0)
|
|
6534
|
+
})
|
|
6400
6535
|
};
|
|
6401
6536
|
}
|
|
6402
6537
|
Object.assign(K, { createBreachView });
|
|
@@ -8515,6 +8650,7 @@ function createFloorplanTestModule(options = {}) {
|
|
|
8515
8650
|
return {
|
|
8516
8651
|
id: FLOORPLAN_AREA_ID,
|
|
8517
8652
|
name: text.name,
|
|
8653
|
+
ground: { surface: "lawn" },
|
|
8518
8654
|
group: text.group,
|
|
8519
8655
|
get houses() {
|
|
8520
8656
|
return placed;
|
|
@@ -8622,6 +8758,9 @@ import { inferJoints } from "@adatechnology/game-destruction";
|
|
|
8622
8758
|
import { markKitOrigin as markKitOrigin2 } from "@adatechnology/game-contracts";
|
|
8623
8759
|
import { residentialItems } from "@adatechnology/game-floorplan-residential";
|
|
8624
8760
|
var WOOD_THIN = 0.035;
|
|
8761
|
+
var BOARD_TOP_THICK = 0.07;
|
|
8762
|
+
var BOARD_TOP_WIDTH = 0.5;
|
|
8763
|
+
var BOARD_WIDTH = 0.16;
|
|
8625
8764
|
var SHELF_SPAN = 1.5;
|
|
8626
8765
|
var SHELVING = /shelf|bookcase|rack/;
|
|
8627
8766
|
function carcass(parts, drawn) {
|
|
@@ -8636,6 +8775,39 @@ function carcass(parts, drawn) {
|
|
|
8636
8775
|
} else if (y <= x && y <= z) part.structure = { role: "beam", maxSpan: SHELF_SPAN * Math.max(x, z) };
|
|
8637
8776
|
}
|
|
8638
8777
|
}
|
|
8778
|
+
function splitTopsIntoBoards(drawn, materialOf, carcassed) {
|
|
8779
|
+
const tops = /* @__PURE__ */ new Map();
|
|
8780
|
+
if (carcassed) return tops;
|
|
8781
|
+
for (let i = drawn.length - 1; i >= 0; i--) {
|
|
8782
|
+
const p = drawn[i];
|
|
8783
|
+
if (p.key !== "wood" || materialOf.wood !== "wood_hard" || p.shape.kind !== "box" || p.rotation) continue;
|
|
8784
|
+
const [sx, sy, sz] = p.shape.size;
|
|
8785
|
+
const across = sx < sz ? 0 : 2;
|
|
8786
|
+
const width = Math.min(sx, sz);
|
|
8787
|
+
if (sy < WOOD_THIN || sy > BOARD_TOP_THICK || sy >= width || width < BOARD_TOP_WIDTH) continue;
|
|
8788
|
+
const count = Math.round(width / BOARD_WIDTH);
|
|
8789
|
+
const boards = [];
|
|
8790
|
+
for (let b = 0; b < count; b++) {
|
|
8791
|
+
const size = [sx, sy, sz];
|
|
8792
|
+
size[across] = width / count;
|
|
8793
|
+
const position = [...p.position];
|
|
8794
|
+
position[across] += (b + 0.5) * size[across] - width / 2;
|
|
8795
|
+
const min = [...p.min];
|
|
8796
|
+
const max = [...p.max];
|
|
8797
|
+
min[across] = position[across] - size[across] / 2;
|
|
8798
|
+
max[across] = position[across] + size[across] / 2;
|
|
8799
|
+
boards.push({ ...p, id: `${p.id}_b${b}`, shape: { kind: "box", size }, position, min, max, color: shadeBoard(p.color, b) });
|
|
8800
|
+
}
|
|
8801
|
+
drawn.splice(i, 1, ...boards);
|
|
8802
|
+
tops.set(p.id, boards);
|
|
8803
|
+
}
|
|
8804
|
+
return tops;
|
|
8805
|
+
}
|
|
8806
|
+
function shadeBoard(color, board) {
|
|
8807
|
+
const k = [1, 0.94, 1.05, 0.97, 1.03][board % 5];
|
|
8808
|
+
const channel = (shift) => Math.min(255, Math.round((color >> shift & 255) * k)) << shift;
|
|
8809
|
+
return channel(16) | channel(8) | channel(0);
|
|
8810
|
+
}
|
|
8639
8811
|
var FLOOR_TOUCH = 5e-3;
|
|
8640
8812
|
var LOOSE = /* @__PURE__ */ new Set(["paper"]);
|
|
8641
8813
|
var BASE_MATERIAL = {
|
|
@@ -8713,10 +8885,12 @@ function recordSpec(THREE, name, root, run, opts) {
|
|
|
8713
8885
|
};
|
|
8714
8886
|
run(A);
|
|
8715
8887
|
if (!drawn.length) throw new Error(`game-floorplan-three: the builder of "${name}" drew no box or cylinder`);
|
|
8888
|
+
const boardsOf = splitTopsIntoBoards(drawn, materialOf, Boolean(opts.carcass));
|
|
8716
8889
|
const parts = drawn.map((p) => {
|
|
8717
8890
|
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]);
|
|
8718
8891
|
let material = materialOf[p.key] ?? "plastic";
|
|
8719
|
-
if (p.key === "wood" && material === "wood_hard" &&
|
|
8892
|
+
if (p.key === "wood" && material === "wood_hard" && opts.carcass && !opts.materials?.wood) material = "mdf";
|
|
8893
|
+
else if (p.key === "wood" && material === "wood_hard" && thin < WOOD_THIN) material = "wood_soft";
|
|
8720
8894
|
const part = { id: p.id, material, shape: p.shape, position: p.position };
|
|
8721
8895
|
if (p.rotation) part.rotation = p.rotation;
|
|
8722
8896
|
if (!LOOSE.has(material) && p.min[1] <= FLOOR_TOUCH) part.anchored = true;
|
|
@@ -8751,6 +8925,13 @@ function recordSpec(THREE, name, root, run, opts) {
|
|
|
8751
8925
|
if (area < 0.6 * footprint || p.max[1] < 0.2) continue;
|
|
8752
8926
|
if (!top || p.max[1] > top.y + 1e-6) top = { part: p.id, y: p.max[1] };
|
|
8753
8927
|
}
|
|
8928
|
+
for (const boards of boardsOf.values()) {
|
|
8929
|
+
if (top && boards[0].max[1] <= top.y - 1e-6) continue;
|
|
8930
|
+
const mid = boards[boards.length >> 1];
|
|
8931
|
+
const min = [Math.min(...boards.map((b) => b.min[0])), Math.min(...boards.map((b) => b.min[2]))];
|
|
8932
|
+
const max = [Math.max(...boards.map((b) => b.max[0])), Math.max(...boards.map((b) => b.max[2]))];
|
|
8933
|
+
if ((max[0] - min[0]) * (max[1] - min[1]) >= 0.6 * footprint && mid.max[1] >= 0.2) top = { part: mid.id, y: mid.max[1], parts: boards.map((b) => b.id) };
|
|
8934
|
+
}
|
|
8754
8935
|
if (opts.carcass) carcass(parts, byId);
|
|
8755
8936
|
const spec = { name, parts, joints, tags: ["furniture"] };
|
|
8756
8937
|
const colors = new Map(drawn.map((p) => [p.id, p.color]));
|