reze-engine 0.33.2 → 0.35.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 +13 -8
- package/dist/engine.d.ts +22 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +40 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/physics/contact.d.ts +3 -0
- package/dist/physics/contact.d.ts.map +1 -1
- package/dist/physics/contact.js +386 -19
- package/dist/physics/manifold.d.ts +13 -0
- package/dist/physics/manifold.d.ts.map +1 -0
- package/dist/physics/manifold.js +68 -0
- package/dist/physics/physics.d.ts +4 -0
- package/dist/physics/physics.d.ts.map +1 -1
- package/dist/physics/physics.js +7 -0
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +64 -4
- package/dist/physics/world.d.ts +33 -0
- package/dist/physics/world.d.ts.map +1 -1
- package/dist/physics/world.js +57 -8
- package/package.json +1 -1
- package/src/engine.ts +44 -0
- package/src/index.ts +2 -1
- package/src/physics/contact.ts +410 -15
- package/src/physics/physics.ts +8 -1
- package/src/physics/solver.ts +64 -3
- package/src/physics/world.ts +84 -3
package/dist/physics/solver.js
CHANGED
|
@@ -13,6 +13,29 @@
|
|
|
13
13
|
import { Mat4 } from "../math";
|
|
14
14
|
import { STOP_ERP } from "./constraint";
|
|
15
15
|
const BOUNCE_THRESHOLD = 2.0;
|
|
16
|
+
// Successive over-relaxation factor on CONTACT rows only (joints untouched).
|
|
17
|
+
// Each contact row independently drives the relative velocity at its own point
|
|
18
|
+
// to zero; when a dress panel carries up to 43 of them at once, they all
|
|
19
|
+
// correct the same motion and the body is over-braked, differently every
|
|
20
|
+
// substep. Scaling each row's step damps that without changing the fixed
|
|
21
|
+
// point — the accumulated impulse still converges to the same answer, just
|
|
22
|
+
// approached rather than overshot.
|
|
23
|
+
//
|
|
24
|
+
// The gain is PER CONTACT, scaled by how contended its two bodies are, not a
|
|
25
|
+
// flat constant. A flat factor was measured first and is wrong: it also slows
|
|
26
|
+
// the well-conditioned rows, and a body resting on a SINGLE contact can then
|
|
27
|
+
// no longer cancel its approach velocity within the iteration budget, so it
|
|
28
|
+
// creeps forever instead of settling (托特 peak speed at 15 s: 0.11 at gain
|
|
29
|
+
// 1.0, 0.46 at 0.5, 0.72 at 0.3 — a permanent limit cycle on a one-contact
|
|
30
|
+
// body). Over-constraint is a local property, so the remedy has to be local.
|
|
31
|
+
//
|
|
32
|
+
// gain = 1 / max(rows on A, rows on B), floored. One contact → 1.0, exact and
|
|
33
|
+
// settling preserved; a dress panel sharing 20 rows → 0.05, damped. This is
|
|
34
|
+
// the standard mass-splitting blend toward Jacobi in the contended cluster.
|
|
35
|
+
const CONTACT_SOR_MIN = 0.12;
|
|
36
|
+
// Per-body contact-row counts for the gain above; grown on demand, refilled
|
|
37
|
+
// each substep. Module-level so the solve allocates nothing.
|
|
38
|
+
let _rowCount = new Int32Array(0);
|
|
16
39
|
// Ceilings on limit-correction velocity. In normal operation limit errors are
|
|
17
40
|
// tiny; a large error only appears after a discontinuity (teleport, stall,
|
|
18
41
|
// deep penetration), and feeding err·ERP/dt to the solver unclamped then
|
|
@@ -74,8 +97,27 @@ export function solveConstraints(store, constraints, cache, contacts, dt, iterat
|
|
|
74
97
|
for (let c = 0; c < constraints.length; c++) {
|
|
75
98
|
setupConstraint(constraints[c], c, cache, store, dt, invDt);
|
|
76
99
|
}
|
|
100
|
+
// How many contact rows each body carries this substep — the per-contact
|
|
101
|
+
// relaxation gain below is a function of the more contended of its two
|
|
102
|
+
// bodies. Counting is O(contacts), done once, before any setup.
|
|
103
|
+
if (_rowCount.length < store.count)
|
|
104
|
+
_rowCount = new Int32Array(store.count);
|
|
105
|
+
else
|
|
106
|
+
_rowCount.fill(0, 0, store.count);
|
|
107
|
+
// Only DYNAMIC bodies are counted. A static body — above all the ground —
|
|
108
|
+
// collects a row from every body resting on it, and letting that inflate the
|
|
109
|
+
// count crushes the gain of each of those contacts to the floor, so nothing
|
|
110
|
+
// resting on the ground can build enough impulse and it creeps instead of
|
|
111
|
+
// settling. A body with invMass 0 cannot be over-braked in the first place.
|
|
112
|
+
for (let ci = 0; ci < contacts.count; ci++) {
|
|
113
|
+
const c = contacts.get(ci);
|
|
114
|
+
if (invMass[c.bodyA] > 0)
|
|
115
|
+
_rowCount[c.bodyA]++;
|
|
116
|
+
if (invMass[c.bodyB] > 0)
|
|
117
|
+
_rowCount[c.bodyB]++;
|
|
118
|
+
}
|
|
77
119
|
for (let ci = 0; ci < contacts.count; ci++) {
|
|
78
|
-
setupContactRow(contacts.get(ci), lv, av, invMass, W);
|
|
120
|
+
setupContactRow(contacts.get(ci), lv, av, invMass, W, invDt);
|
|
79
121
|
}
|
|
80
122
|
for (let iter = 0; iter < iterations; iter++) {
|
|
81
123
|
for (let c = 0; c < constraints.length; c++) {
|
|
@@ -692,7 +734,7 @@ function iterateConstraint(ci, cache, lv, av, invMass) {
|
|
|
692
734
|
// SETUP: pre-compute Jacobians, friction basis, and the bounce reference
|
|
693
735
|
// from the *initial* closing velocity (Bullet's pattern — captures restitution
|
|
694
736
|
// before iter 1 zeroes out the approach).
|
|
695
|
-
function setupContactRow(c, lv, av, invMass, W) {
|
|
737
|
+
function setupContactRow(c, lv, av, invMass, W, invDt) {
|
|
696
738
|
const ai = c.bodyA * 3;
|
|
697
739
|
const bi = c.bodyB * 3;
|
|
698
740
|
const a9 = c.bodyA * 9;
|
|
@@ -736,6 +778,24 @@ function setupContactRow(c, lv, av, invMass, W) {
|
|
|
736
778
|
c.bounceVel = c.restitution > 0 && relVelN0 < -BOUNCE_THRESHOLD
|
|
737
779
|
? -c.restitution * relVelN0
|
|
738
780
|
: 0;
|
|
781
|
+
// Speculative rows (depth < 0 — the shapes are inside the margin band but
|
|
782
|
+
// NOT touching) must not brake a body that hasn't arrived yet. Their whole
|
|
783
|
+
// job is to stop it crossing the surface within this substep, so the
|
|
784
|
+
// approach speed they leave alone is exactly the one that closes the
|
|
785
|
+
// remaining gap in dt; only the excess above that is cancelled.
|
|
786
|
+
//
|
|
787
|
+
// Without this the row targets relVelN = 0 like a touching contact and
|
|
788
|
+
// stops approaching bodies dead up to CONTACT_MARGIN away from anything.
|
|
789
|
+
// The push-only clamp does NOT prevent that — it only forbids a negative
|
|
790
|
+
// (pulling) impulse, not a large positive one on a body in mid-air. On a
|
|
791
|
+
// dress rig half of all contact rows are speculative and 88% of them fire,
|
|
792
|
+
// which is the field of invisible brakes the cloth was shaking against.
|
|
793
|
+
c.allowedApproachVel = c.depth < 0 ? -c.depth * invDt : 0;
|
|
794
|
+
// Relaxation gain, from the more contended of the two bodies (see
|
|
795
|
+
// CONTACT_SOR_MIN). A lone contact keeps gain 1.0 and stays exact.
|
|
796
|
+
const contended = _rowCount[c.bodyA] > _rowCount[c.bodyB] ? _rowCount[c.bodyA] : _rowCount[c.bodyB];
|
|
797
|
+
const gain = contended > 1 ? 1 / contended : 1;
|
|
798
|
+
c.sorGain = gain < CONTACT_SOR_MIN ? CONTACT_SOR_MIN : gain;
|
|
739
799
|
// Friction tangent basis. Pick the axis least aligned with n.
|
|
740
800
|
let t1x, t1y, t1z;
|
|
741
801
|
if (Math.abs(nx) < 0.7071) {
|
|
@@ -840,7 +900,7 @@ function iterateContactRow(c, lv, av, invMass) {
|
|
|
840
900
|
if (jacInvN > 0) {
|
|
841
901
|
const nx = c.nx, ny = c.ny, nz = c.nz;
|
|
842
902
|
const relVelN = dvx * nx + dvy * ny + dvz * nz;
|
|
843
|
-
let dImpN = (c.bounceVel - relVelN) * jacInvN;
|
|
903
|
+
let dImpN = (c.bounceVel - c.allowedApproachVel - relVelN) * jacInvN * c.sorGain;
|
|
844
904
|
const oldN = c.appliedNormalImpulse;
|
|
845
905
|
let newN = oldN + dImpN;
|
|
846
906
|
if (newN < 0) {
|
|
@@ -890,7 +950,7 @@ function applyFrictionTangent(c, ai, bi, dvx, dvy, dvz, tx, ty, tz, cAx, cAy, cA
|
|
|
890
950
|
if (jacInv <= 0)
|
|
891
951
|
return;
|
|
892
952
|
const relVel = dvx * tx + dvy * ty + dvz * tz;
|
|
893
|
-
let dImp = -relVel * jacInv;
|
|
953
|
+
let dImp = -relVel * jacInv * c.sorGain;
|
|
894
954
|
const old = slot === 1 ? c.appliedFrictionImpulse1 : c.appliedFrictionImpulse2;
|
|
895
955
|
let next = old + dImp;
|
|
896
956
|
if (next < -muNormal) {
|
package/dist/physics/world.d.ts
CHANGED
|
@@ -3,14 +3,47 @@ import type { RigidBodyStore } from "./body";
|
|
|
3
3
|
import type { SixDofSpringConstraint } from "./constraint";
|
|
4
4
|
import { type SolverCache } from "./solver";
|
|
5
5
|
import { type ContactPool } from "./contact";
|
|
6
|
+
/**
|
|
7
|
+
* Air movement across the whole world.
|
|
8
|
+
*
|
|
9
|
+
* Applied as an acceleration alongside gravity, which is the same shape MMD's
|
|
10
|
+
* own wind plugins take and keeps it free: gravity and wind are summed once per
|
|
11
|
+
* substep, so the per-body predict loop is untouched. Terminal velocity comes
|
|
12
|
+
* out of the existing per-body damping rather than a drag model — PMX authors
|
|
13
|
+
* already tune that damping to get the hang they want, and a second, hidden
|
|
14
|
+
* drag term would fight it.
|
|
15
|
+
*/
|
|
16
|
+
export interface WindOptions {
|
|
17
|
+
/** Direction the air travels. Normalised on assignment; a zero vector is off. */
|
|
18
|
+
direction: Vec3;
|
|
19
|
+
/** Acceleration along it, in gravity's units — the built-in gravity is 98. */
|
|
20
|
+
strength: number;
|
|
21
|
+
/** Gust depth, clamped to 0–1. 0 is a steady breeze; 1 swings between still
|
|
22
|
+
* and double. It is a clamp rather than a free scalar because past 1 the
|
|
23
|
+
* swing goes negative and the wind blows backwards on every other beat,
|
|
24
|
+
* which is never what a caller meant by "more turbulent". */
|
|
25
|
+
turbulence?: number;
|
|
26
|
+
/** Gusts per second. */
|
|
27
|
+
frequency?: number;
|
|
28
|
+
}
|
|
6
29
|
export declare class World {
|
|
7
30
|
readonly gravity: Vec3;
|
|
8
31
|
solverIterations: number;
|
|
9
32
|
private dampCacheDt;
|
|
10
33
|
private linDampFactor;
|
|
11
34
|
private angDampFactor;
|
|
35
|
+
private windX;
|
|
36
|
+
private windY;
|
|
37
|
+
private windZ;
|
|
38
|
+
private windTurbulence;
|
|
39
|
+
private windFrequency;
|
|
40
|
+
/** Advances with simulated time, not wall time, so a scrubbed or exported
|
|
41
|
+
* take gusts identically to a live one. */
|
|
42
|
+
private windClock;
|
|
12
43
|
constructor(gravity: Vec3);
|
|
13
44
|
setGravity(g: Vec3): void;
|
|
45
|
+
setWind(wind: WindOptions | null): void;
|
|
46
|
+
getWind(): WindOptions | null;
|
|
14
47
|
step(store: RigidBodyStore, constraints: SixDofSpringConstraint[], cache: SolverCache, contacts: ContactPool, dt: number): void;
|
|
15
48
|
}
|
|
16
49
|
//# sourceMappingURL=world.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/physics/world.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAE5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAC1D,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AAC7D,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAO1D,qBAAa,KAAK;IAChB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,gBAAgB,SAAK;IAIrB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,aAAa,CAA4B;
|
|
1
|
+
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/physics/world.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAE5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAC1D,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AAC7D,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAO1D;;;;;;;;;GASG;AACH,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,SAAS,EAAE,IAAI,CAAA;IACf,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;IAChB;;;kEAG8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,qBAAa,KAAK;IAChB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,gBAAgB,SAAK;IAIrB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,aAAa,CAA4B;IAIjD,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,aAAa,CAAO;IAC5B;gDAC4C;IAC5C,OAAO,CAAC,SAAS,CAAI;gBAET,OAAO,EAAE,IAAI;IAIzB,UAAU,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI;IAMzB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;IAoBvC,OAAO,IAAI,WAAW,GAAG,IAAI;IAW7B,IAAI,CACF,KAAK,EAAE,cAAc,EACrB,WAAW,EAAE,sBAAsB,EAAE,EACrC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,MAAM,GACT,IAAI;CA0KR"}
|
package/dist/physics/world.js
CHANGED
|
@@ -2,11 +2,6 @@ import { Vec3 } from "../math";
|
|
|
2
2
|
import { RigidbodyType } from "./types";
|
|
3
3
|
import { solveConstraints } from "./solver";
|
|
4
4
|
import { findContacts } from "./contact";
|
|
5
|
-
// World step: predict velocities → collide → solve → position correction →
|
|
6
|
-
// integrate. Static and kinematic bodies are skipped during predict and
|
|
7
|
-
// integrate; the parent class syncs them from bones around the step. The
|
|
8
|
-
// solver pass runs on all bodies — kinematic ones have invMass = 0 and
|
|
9
|
-
// act as anchors.
|
|
10
5
|
export class World {
|
|
11
6
|
constructor(gravity) {
|
|
12
7
|
this.solverIterations = 10;
|
|
@@ -15,6 +10,16 @@ export class World {
|
|
|
15
10
|
this.dampCacheDt = -1;
|
|
16
11
|
this.linDampFactor = null;
|
|
17
12
|
this.angDampFactor = null;
|
|
13
|
+
// Wind, resolved to a direction × strength at construction of the options so
|
|
14
|
+
// the step loop only multiplies by a gust scalar.
|
|
15
|
+
this.windX = 0;
|
|
16
|
+
this.windY = 0;
|
|
17
|
+
this.windZ = 0;
|
|
18
|
+
this.windTurbulence = 0;
|
|
19
|
+
this.windFrequency = 0.35;
|
|
20
|
+
/** Advances with simulated time, not wall time, so a scrubbed or exported
|
|
21
|
+
* take gusts identically to a live one. */
|
|
22
|
+
this.windClock = 0;
|
|
18
23
|
this.gravity = new Vec3(gravity.x, gravity.y, gravity.z);
|
|
19
24
|
}
|
|
20
25
|
setGravity(g) {
|
|
@@ -22,6 +27,35 @@ export class World {
|
|
|
22
27
|
this.gravity.y = g.y;
|
|
23
28
|
this.gravity.z = g.z;
|
|
24
29
|
}
|
|
30
|
+
setWind(wind) {
|
|
31
|
+
// Shape first, and unconditionally: bailing out early on a zero strength
|
|
32
|
+
// used to leave turbulence and frequency holding whatever a previous call
|
|
33
|
+
// set, so raising the strength again resurrected settings the caller had
|
|
34
|
+
// since replaced.
|
|
35
|
+
this.windTurbulence = Math.min(1, Math.max(0, wind?.turbulence ?? 0));
|
|
36
|
+
this.windFrequency = Math.max(0, wind?.frequency ?? 0.35);
|
|
37
|
+
const d = wind?.direction;
|
|
38
|
+
const len = d ? Math.hypot(d.x, d.y, d.z) : 0;
|
|
39
|
+
if (!wind || !d || len < 1e-9 || wind.strength === 0) {
|
|
40
|
+
this.windX = this.windY = this.windZ = 0;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const s = wind.strength / len;
|
|
44
|
+
this.windX = d.x * s;
|
|
45
|
+
this.windY = d.y * s;
|
|
46
|
+
this.windZ = d.z * s;
|
|
47
|
+
}
|
|
48
|
+
getWind() {
|
|
49
|
+
const strength = Math.hypot(this.windX, this.windY, this.windZ);
|
|
50
|
+
if (strength === 0)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
direction: new Vec3(this.windX / strength, this.windY / strength, this.windZ / strength),
|
|
54
|
+
strength,
|
|
55
|
+
turbulence: this.windTurbulence,
|
|
56
|
+
frequency: this.windFrequency,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
25
59
|
step(store, constraints, cache, contacts, dt) {
|
|
26
60
|
if (dt <= 0)
|
|
27
61
|
return;
|
|
@@ -34,9 +68,24 @@ export class World {
|
|
|
34
68
|
const ldamp = store.linearDamping;
|
|
35
69
|
const adamp = store.angularDamping;
|
|
36
70
|
const invMass = store.invMass;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
71
|
+
// Gravity and wind are the same kind of term, so they are summed here and
|
|
72
|
+
// the predict loop below never learns wind exists.
|
|
73
|
+
let gx = this.gravity.x;
|
|
74
|
+
let gy = this.gravity.y;
|
|
75
|
+
let gz = this.gravity.z;
|
|
76
|
+
if (this.windX !== 0 || this.windY !== 0 || this.windZ !== 0) {
|
|
77
|
+
this.windClock += dt;
|
|
78
|
+
let gust = 1;
|
|
79
|
+
if (this.windTurbulence > 0 && this.windFrequency > 0) {
|
|
80
|
+
// Two incommensurate sines: a single one is a metronome, and real gusts
|
|
81
|
+
// do not repeat on a bar line. Stays within 1 ± turbulence.
|
|
82
|
+
const t = this.windClock * this.windFrequency * Math.PI * 2;
|
|
83
|
+
gust = 1 + this.windTurbulence * 0.5 * (Math.sin(t) + Math.sin(t * 0.37 + 1.3));
|
|
84
|
+
}
|
|
85
|
+
gx += this.windX * gust;
|
|
86
|
+
gy += this.windY * gust;
|
|
87
|
+
gz += this.windZ * gust;
|
|
88
|
+
}
|
|
40
89
|
// 1. Predict — gravity + damping. The pow form (vs the linear
|
|
41
90
|
// 1−damping·dt approximation) stays stable at high PMX damping
|
|
42
91
|
// values like 0.99. Factors are cached (damping and dt are constant).
|
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { VMDLoader } from "./vmd-loader"
|
|
|
7
7
|
import { CameraAnimation } from "./camera-animation"
|
|
8
8
|
import { PmxLoader } from "./pmx-loader"
|
|
9
9
|
import { RezePhysics } from "./physics"
|
|
10
|
+
import type { WindOptions } from "./physics/world"
|
|
10
11
|
import {
|
|
11
12
|
createFetchAssetReader,
|
|
12
13
|
createFileMapAssetReader,
|
|
@@ -799,6 +800,10 @@ export class Engine {
|
|
|
799
800
|
// IK and physics enabled at engine level (same for all models)
|
|
800
801
|
private ikEnabled = true
|
|
801
802
|
private physicsEnabled = true
|
|
803
|
+
// World-wide, not per-model: MMD treats gravity and wind as properties of the
|
|
804
|
+
// scene, and a model loaded later must arrive into the same air as the rest.
|
|
805
|
+
private gravity = new Vec3(0, -98, 0)
|
|
806
|
+
private wind: WindOptions | null = null
|
|
802
807
|
// GPU vertex-morph path. Set false BEFORE loadModel to fall back to the CPU path (A/B).
|
|
803
808
|
private useGpuMorphs = true
|
|
804
809
|
|
|
@@ -3249,6 +3254,39 @@ export class Engine {
|
|
|
3249
3254
|
return this.physicsEnabled
|
|
3250
3255
|
}
|
|
3251
3256
|
|
|
3257
|
+
/**
|
|
3258
|
+
* Scene gravity, applied to every model's cloth and hair. The default is
|
|
3259
|
+
* (0, -98, 0) — MMD's own scale, where a character stands about 20 units
|
|
3260
|
+
* tall. Lower magnitudes float; tilting it sideways hangs everything on a
|
|
3261
|
+
* slant, which is the cheap way to fake a strong draught.
|
|
3262
|
+
*/
|
|
3263
|
+
setGravity(gravity: Vec3): void {
|
|
3264
|
+
this.gravity = new Vec3(gravity.x, gravity.y, gravity.z)
|
|
3265
|
+
this.forEachInstance((inst) => inst.physics?.setGravity(this.gravity))
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
getGravity(): Vec3 {
|
|
3269
|
+
return new Vec3(this.gravity.x, this.gravity.y, this.gravity.z)
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
/**
|
|
3273
|
+
* Air movement across the scene — null is still air.
|
|
3274
|
+
*
|
|
3275
|
+
* Applied as an acceleration alongside gravity, so `strength` is in the same
|
|
3276
|
+
* units: against the default gravity of 98, a strength of 10-30 reads as a
|
|
3277
|
+
* breeze through hair and a skirt without lifting them off the body. Gusting
|
|
3278
|
+
* is driven by simulated time rather than wall time, so an exported take
|
|
3279
|
+
* gusts exactly as the preview did.
|
|
3280
|
+
*/
|
|
3281
|
+
setWind(wind: WindOptions | null): void {
|
|
3282
|
+
this.wind = wind ? { ...wind, direction: new Vec3(wind.direction.x, wind.direction.y, wind.direction.z) } : null
|
|
3283
|
+
this.forEachInstance((inst) => inst.physics?.setWind(this.wind))
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
getWind(): WindOptions | null {
|
|
3287
|
+
return this.wind ? { ...this.wind, direction: new Vec3(this.wind.direction.x, this.wind.direction.y, this.wind.direction.z) } : null
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3252
3290
|
resetPhysics(): void {
|
|
3253
3291
|
this.forEachInstance((inst) => {
|
|
3254
3292
|
if (!inst.physics) return
|
|
@@ -3421,6 +3459,12 @@ export class Engine {
|
|
|
3421
3459
|
|
|
3422
3460
|
const rbs = model.getRigidbodies()
|
|
3423
3461
|
const physics = rbs.length > 0 ? new RezePhysics(rbs, model.getJoints()) : null
|
|
3462
|
+
// Adopt the scene's air, or a model added mid-session would fall under
|
|
3463
|
+
// different gravity from the ones already on stage.
|
|
3464
|
+
if (physics) {
|
|
3465
|
+
physics.setGravity(this.gravity)
|
|
3466
|
+
if (this.wind) physics.setWind(this.wind)
|
|
3467
|
+
}
|
|
3424
3468
|
|
|
3425
3469
|
const shadowBindGroup = this.device.createBindGroup({
|
|
3426
3470
|
label: `${name}: shadow bind`,
|
package/src/index.ts
CHANGED
|
@@ -86,4 +86,5 @@ export { VMDLoader, type CameraKeyframe, type IkFrame } from "./vmd-loader"
|
|
|
86
86
|
export { VMDWriter } from "./vmd-writer"
|
|
87
87
|
export { PmxLoader } from "./pmx-loader"
|
|
88
88
|
export { CameraAnimation, type CameraPose } from "./camera-animation"
|
|
89
|
-
export { RezePhysics } from "./physics"
|
|
89
|
+
export { RezePhysics } from "./physics"
|
|
90
|
+
export type { WindOptions } from "./physics/world"
|