@adatechnology/game-destruction 0.2.0 → 0.3.1
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 +13 -3
- package/dist/index.d.ts +22 -1
- package/dist/index.global.js +51 -5
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +51 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -80,6 +80,9 @@ makes a through hole, through 5 cm concrete it only cracks and lodges. Grazing h
|
|
|
80
80
|
| `fire` | `temperature`, `radius`, `duration` | heats towards the flame temperature and ignites at the material's ignition point |
|
|
81
81
|
| `heat` | same | heat without flame (cutting torch, hot plate); melts what it can reach |
|
|
82
82
|
|
|
83
|
+
Brittle materials that `shatter` (glass, ceramic) take blunt and ballistic damage multiplied by `fragility(ductility, thickness)` (x1 from 2 cm up, to x118
|
|
84
|
+
for 3 mm glass), so a 9 mm shatters a window pane in one shot while a 5 cm block of glass takes several.
|
|
85
|
+
|
|
83
86
|
`hit(objectId, hit)` finds the part along `direction`; `hitPart` targets a known part; `shoot` and `raycast` work in world space.
|
|
84
87
|
|
|
85
88
|
## Fire, melting and leaks
|
|
@@ -125,13 +128,20 @@ without any `structure` behave exactly as before.
|
|
|
125
128
|
horizontal length for walls. A 0.3 m concrete column carries 40 t, a 0.2 m brick wall 8 t per metre, a 0.1 x 0.2 m softwood beam 2 t.
|
|
126
129
|
- **Joints** carry load when `bears` is true (default when both parts have a role). `bears: 'a_on_b'` means part `a` rests on `b`: load flows only
|
|
127
130
|
that way. A `partition` is supported but supports nothing, so breaking it never drops anything.
|
|
128
|
-
- **Overload**: above stress 1 a part
|
|
131
|
+
- **Overload**: above stress 1 a part loses `min(0.6, 0.12 + 0.8 * (stress - 1))` of its `maxHp` per second (stress 1.02 gives way in ~7 s, 1.1 in ~5 s,
|
|
132
|
+
1.5 in ~2 s, anything above 1.7 in ~1.7 s; deterministic), emits `stress` (once per 0.5 s) and cascades; brittle
|
|
129
133
|
parts `crack` from stress 0.8. Metal never fractures, so overloaded steel only reports stress.
|
|
130
134
|
- **Collapse**: a part with no support path to an anchored part falls: it is cut from the supported pieces into its own group (index 1 or more) and
|
|
131
|
-
one `collapse` event lists the group
|
|
135
|
+
one `collapse` event lists the group, and every part in it that is not already lost gets `PartState.status === 'collapsed'` (kept through
|
|
136
|
+
`serialize`/`load`, cleared by `reset`; a part that later breaks becomes `broken`). Non-structural parts that hang only on collapsed parts fall with them. Make the group its own object with
|
|
132
137
|
`splitGroup`.
|
|
133
138
|
- `buildFrame({ spanX, spanZ, columnsX, columnsZ, height, floors, walls, roof, columnCapacity, ... })` returns a test structure: columns on
|
|
134
139
|
foundations (`rebar`), beams, a slab per floor, optional brick walls (`mortar`) with a door opening, and a roof that rests (`resting`) on the top beams.
|
|
140
|
+
- `sizeStructure(spec, { safety?, redundancy? })` returns a copy of the spec with `structure.capacity` set from the loads the world itself solves
|
|
141
|
+
for the intact spec, so every structural part rests at stress `safety` (default 0.65). With `redundancy` (e.g. 0.97) the capacities are also
|
|
142
|
+
raised where needed so that losing any single `column` keeps every other part at or below that stress: the structure stands with one column
|
|
143
|
+
gone and, without help, falls with two neighbours gone. Raw material capacities are rarely a sensible design (a 54 t roof on 20 cm columns is
|
|
144
|
+
already over 1 at rest); size the structure instead of hand-tuning. Parts without `structure` or with role `none` are untouched.
|
|
135
145
|
|
|
136
146
|
## Contracts: implement your own or use the default world
|
|
137
147
|
|
|
@@ -182,7 +192,7 @@ world continues with the same events as the original. The loading world must use
|
|
|
182
192
|
`raycast`, `shoot`, `disassemble`, `workableJoints`, `nearestJoint`, `extinguish`, `setLiters`, `update`, `state`, `part`, `reset`, `on`,
|
|
183
193
|
`register`, `serialize`, `load`, `time`. `state()` and `part()` return live views; do not mutate them.
|
|
184
194
|
|
|
185
|
-
Also exported: `buildFrame`, `partCapacity`, `checkDestructible`, `checkHit`, `IMPACT_CONTRACT`, `MATERIALS`, `JOINT_DEFAULTS`, `JOINT_BY_MATERIALS`, `inferJoints`, `guessMaterial`, `partVolume`, `partMass`, `partThickness`, the
|
|
195
|
+
Also exported: `buildFrame`, `sizeStructure`, `partCapacity`, `checkDestructible`, `checkHit`, `IMPACT_CONTRACT`, `MATERIALS`, `JOINT_DEFAULTS`, `JOINT_BY_MATERIALS`, `inferJoints`, `guessMaterial`, `partVolume`, `partMass`, `partThickness`, the
|
|
186
196
|
helpers `vec`, `quat`, `quatFromEuler`, `rotateVec`, `toLocal`, `toWorld`, the locale (`locales`, `resolveStrings`, `DestructionStrings`) and all types.
|
|
187
197
|
|
|
188
198
|
The locale table has `en` and `pt-BR` strings for material names, part statuses, joint kinds, tools, damage types, event labels and fracture styles:
|
package/dist/index.d.ts
CHANGED
|
@@ -96,6 +96,18 @@ interface DestructionOptions {
|
|
|
96
96
|
/** Distance at which a burning part heats parts of other objects, in metres (default 0.6). */
|
|
97
97
|
fireSpread?: number;
|
|
98
98
|
}
|
|
99
|
+
interface SizeStructureOptions {
|
|
100
|
+
/** Stress every structural part has at rest after sizing (`load / capacity`), 0..1 (default 0.65). */
|
|
101
|
+
safety?: number;
|
|
102
|
+
/**
|
|
103
|
+
* Redundancy target: when set, the capacity of a part is raised (never lowered) until losing any single `column` leaves every other
|
|
104
|
+
* structural part at this stress or below, e.g. 0.97 (an N-1 design: the building stands with one column gone). Omit to skip the check.
|
|
105
|
+
* Should be above `safety` and, to let the structure fall when a second column goes, below 1.
|
|
106
|
+
*/
|
|
107
|
+
redundancy?: number;
|
|
108
|
+
/** Extra or overriding materials, the same list given to `createDestruction`. */
|
|
109
|
+
materials?: MaterialDef[];
|
|
110
|
+
}
|
|
99
111
|
interface AddOptions {
|
|
100
112
|
/** Object id (default a generated `obj_1`, `obj_2`...). */
|
|
101
113
|
id?: string;
|
|
@@ -299,6 +311,15 @@ interface FrameOptions {
|
|
|
299
311
|
*/
|
|
300
312
|
declare const buildFrame: (o?: FrameOptions) => DestructibleSpec;
|
|
301
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Sizes the load capacity of a structure the way an engineer would, from the loads the world itself computes. The intact spec goes through the
|
|
316
|
+
* same load solver `createDestruction` uses, and each structural part (`structure.role` other than `none`) gets `structure.capacity = load / safety`,
|
|
317
|
+
* so its at-rest stress is exactly `safety`. With `redundancy` set, every `column` is also removed in turn and the capacity of the parts that
|
|
318
|
+
* would carry more is raised so they stay at or below `redundancy`. Parts without `structure`, with role `none`, or with no support path to an
|
|
319
|
+
* anchored part are left as they are. The spec is not modified; a copy is returned.
|
|
320
|
+
*/
|
|
321
|
+
declare const sizeStructure: (spec: DestructibleSpec, options?: SizeStructureOptions) => DestructibleSpec;
|
|
322
|
+
|
|
302
323
|
/** Small vector helpers (arrays of three numbers). Pure functions; they allocate a new array. */
|
|
303
324
|
declare const vec: {
|
|
304
325
|
add: (a: Vec3, b: Vec3) => Vec3;
|
|
@@ -366,4 +387,4 @@ declare const checkDestructible: (impl: DestructibleImpl, options?: CheckOptions
|
|
|
366
387
|
problems: string[];
|
|
367
388
|
};
|
|
368
389
|
|
|
369
|
-
export { type AddOptions, type CheckOptions, type DestructionLocale, type DestructionOptions, type DestructionSnapshot, type DestructionStrings, type DestructionWorld, type FrameOptions, JOINT_BY_MATERIALS, JOINT_DEFAULTS, MATERIALS, type MaterialDef, type MaterialThermal, type ObjectRuntime, type PartRuntime, buildFrame, checkDestructible, createDestruction, fragility, guessMaterial, inferJoints, locales, partCapacity, partMass, partThickness, partVolume, quat, quatFromEuler, resolveStrings, rotateVec, toLocal, toWorld, vec };
|
|
390
|
+
export { type AddOptions, type CheckOptions, type DestructionLocale, type DestructionOptions, type DestructionSnapshot, type DestructionStrings, type DestructionWorld, type FrameOptions, JOINT_BY_MATERIALS, JOINT_DEFAULTS, MATERIALS, type MaterialDef, type MaterialThermal, type ObjectRuntime, type PartRuntime, type SizeStructureOptions, buildFrame, checkDestructible, createDestruction, fragility, guessMaterial, inferJoints, locales, partCapacity, partMass, partThickness, partVolume, quat, quatFromEuler, resolveStrings, rotateVec, sizeStructure, toLocal, toWorld, vec };
|
package/dist/index.global.js
CHANGED
|
@@ -41,6 +41,7 @@ var GameDestruction = (() => {
|
|
|
41
41
|
quatFromEuler: () => quatFromEuler,
|
|
42
42
|
resolveStrings: () => resolveStrings,
|
|
43
43
|
rotateVec: () => rotateVec,
|
|
44
|
+
sizeStructure: () => sizeStructure,
|
|
44
45
|
toLocal: () => toLocal,
|
|
45
46
|
toWorld: () => toWorld,
|
|
46
47
|
vec: () => vec
|
|
@@ -1461,6 +1462,7 @@ var GameDestruction = (() => {
|
|
|
1461
1462
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
1462
1463
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
1463
1464
|
var clone = (v) => structuredClone(v);
|
|
1465
|
+
var overloadRate = (stress) => Math.min(0.6, 0.12 + 0.8 * (stress - 1));
|
|
1464
1466
|
var updateBound = (o) => {
|
|
1465
1467
|
if (!o.parts.length) return;
|
|
1466
1468
|
const c = [0, 0, 0];
|
|
@@ -1771,7 +1773,10 @@ var GameDestruction = (() => {
|
|
|
1771
1773
|
for (const g of o.st.groups) {
|
|
1772
1774
|
const ps = g.map((id) => o.byId.get(id)).filter((p) => !isLost(p) && !o.collapsed.has(p.spec.id));
|
|
1773
1775
|
if (ps.length === 0 || g.some((id) => o.byId.get(id).anchored) || !ps.some((p) => p.structural)) continue;
|
|
1774
|
-
for (const p of ps)
|
|
1776
|
+
for (const p of ps) {
|
|
1777
|
+
o.collapsed.add(p.spec.id);
|
|
1778
|
+
p.st.status = "collapsed";
|
|
1779
|
+
}
|
|
1775
1780
|
emit({ ...base(o, src), type: "collapse", parts: ps.map((p) => p.spec.id), mass: ps.reduce((sum, p) => sum + p.st.mass, 0) });
|
|
1776
1781
|
}
|
|
1777
1782
|
}
|
|
@@ -2071,7 +2076,7 @@ var GameDestruction = (() => {
|
|
|
2071
2076
|
const effM = p.thickness / Math.max(sin, 0.3);
|
|
2072
2077
|
const thr = mat.penetration * effM * 1e3;
|
|
2073
2078
|
const penetrated = E > thr;
|
|
2074
|
-
const scale = mat.damageScale?.ballistic ?? 1;
|
|
2079
|
+
const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2075
2080
|
const id = p.spec.id;
|
|
2076
2081
|
if (!penetrated && mat.ricochetDeg && angle < mat.ricochetDeg) {
|
|
2077
2082
|
const r = vec.sub(d, vec.scale(n, 2 * vec.dot(d, n)));
|
|
@@ -2421,7 +2426,7 @@ var GameDestruction = (() => {
|
|
|
2421
2426
|
p.rt.stressT = 0.5;
|
|
2422
2427
|
emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
|
|
2423
2428
|
}
|
|
2424
|
-
applyDamage(o, p, (stress
|
|
2429
|
+
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
2425
2430
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
2426
2431
|
p.rt.stressT = 0.5;
|
|
2427
2432
|
const { c, q } = partXf(o, p);
|
|
@@ -2883,6 +2888,47 @@ var GameDestruction = (() => {
|
|
|
2883
2888
|
return { name: "frame", parts, joints, tags: ["structure"] };
|
|
2884
2889
|
};
|
|
2885
2890
|
|
|
2891
|
+
// src/size.ts
|
|
2892
|
+
var sizeStructure = (spec, options = {}) => {
|
|
2893
|
+
const safety = options.safety ?? 0.65;
|
|
2894
|
+
const redundancy = options.redundancy;
|
|
2895
|
+
const loads = (s) => {
|
|
2896
|
+
const w = createDestruction(options.materials ? { materials: options.materials } : {});
|
|
2897
|
+
const id = w.add(s);
|
|
2898
|
+
return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0 }]));
|
|
2899
|
+
};
|
|
2900
|
+
const out = structuredClone(spec);
|
|
2901
|
+
const base = loads(out);
|
|
2902
|
+
const need = /* @__PURE__ */ new Map();
|
|
2903
|
+
for (const p of out.parts) {
|
|
2904
|
+
if (!p.structure || p.structure.role === "none") continue;
|
|
2905
|
+
const b = base.get(p.id);
|
|
2906
|
+
if (!b || b.stress <= 0) continue;
|
|
2907
|
+
need.set(p.id, b.load / safety);
|
|
2908
|
+
}
|
|
2909
|
+
if (redundancy !== void 0) {
|
|
2910
|
+
for (const col of out.parts) {
|
|
2911
|
+
if (col.structure?.role !== "column") continue;
|
|
2912
|
+
const reduced = {
|
|
2913
|
+
...out,
|
|
2914
|
+
parts: out.parts.filter((p) => p.id !== col.id),
|
|
2915
|
+
joints: (out.joints ?? []).filter((j) => j.a !== col.id && j.b !== col.id)
|
|
2916
|
+
};
|
|
2917
|
+
if (!out.joints) delete reduced.joints;
|
|
2918
|
+
const l = loads(reduced);
|
|
2919
|
+
for (const [id, n] of need) {
|
|
2920
|
+
const r = l.get(id);
|
|
2921
|
+
if (r && r.stress > 0) need.set(id, Math.max(n, r.load / redundancy));
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
for (const p of out.parts) {
|
|
2926
|
+
const n = need.get(p.id);
|
|
2927
|
+
if (n !== void 0) p.structure = { ...p.structure, capacity: n };
|
|
2928
|
+
}
|
|
2929
|
+
return out;
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2886
2932
|
// src/locale.ts
|
|
2887
2933
|
var locales = {
|
|
2888
2934
|
en: {
|
|
@@ -2915,7 +2961,7 @@ var GameDestruction = (() => {
|
|
|
2915
2961
|
wax: "wax",
|
|
2916
2962
|
soil: "soil"
|
|
2917
2963
|
},
|
|
2918
|
-
partStatus: { intact: "intact", damaged: "damaged", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
2964
|
+
partStatus: { intact: "intact", damaged: "damaged", collapsed: "collapsed", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
2919
2965
|
jointKinds: {
|
|
2920
2966
|
screw: "screw",
|
|
2921
2967
|
bolt: "nut and bolt",
|
|
@@ -2996,7 +3042,7 @@ var GameDestruction = (() => {
|
|
|
2996
3042
|
wax: "cera",
|
|
2997
3043
|
soil: "terra"
|
|
2998
3044
|
},
|
|
2999
|
-
partStatus: { intact: "intacto", damaged: "danificado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
3045
|
+
partStatus: { intact: "intacto", damaged: "danificado", collapsed: "desabado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
3000
3046
|
jointKinds: {
|
|
3001
3047
|
screw: "parafuso",
|
|
3002
3048
|
bolt: "porca e parafuso",
|