reze-engine 0.9.1 → 0.9.2
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 +18 -15
- package/dist/engine.d.ts +16 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/physics.d.ts +5 -3
- package/dist/physics.d.ts.map +1 -1
- package/dist/physics.js +9 -62
- package/package.json +1 -1
- package/src/engine.ts +8 -14
- package/src/index.ts +1 -0
- package/src/physics.ts +16 -76
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Reze Engine
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A minimal-dependency WebGPU engine for real-time MMD/PMX rendering. Only external dependency is Ammo.js for physics.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -10,13 +10,12 @@ npm install reze-engine
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- Blinn-Phong
|
|
14
|
-
- VMD animation
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- Multi-model (per-model materials, IK, physics)
|
|
13
|
+
- Blinn-Phong shading, alpha blending, rim lighting, outlines, MSAA 4x
|
|
14
|
+
- VMD animation with IK solver and Bullet physics
|
|
15
|
+
- Orbit camera with bone-follow mode
|
|
16
|
+
- GPU picking (double-click/tap)
|
|
17
|
+
- Ground plane with PCF shadow mapping
|
|
18
|
+
- Multi-model support
|
|
20
19
|
|
|
21
20
|
## Quick Start
|
|
22
21
|
|
|
@@ -25,7 +24,7 @@ import { Engine, Vec3 } from "reze-engine"
|
|
|
25
24
|
|
|
26
25
|
const engine = new Engine(canvas, {
|
|
27
26
|
ambientColor: new Vec3(0.88, 0.92, 0.99),
|
|
28
|
-
cameraDistance: 31.5,
|
|
27
|
+
cameraDistance: 31.5, // MMD units (1 unit = 8 cm)
|
|
29
28
|
})
|
|
30
29
|
await engine.init()
|
|
31
30
|
|
|
@@ -56,8 +55,7 @@ engine.runRenderLoop()
|
|
|
56
55
|
| `engine.toggleMaterialVisible(model, mat)` | Toggle material visibility |
|
|
57
56
|
| `engine.setIKEnabled(enabled)` | Enable/disable IK globally |
|
|
58
57
|
| `engine.setPhysicsEnabled(enabled)` | Enable/disable physics globally |
|
|
59
|
-
| `engine.
|
|
60
|
-
| `engine.setCameraFollow(model, bone?, offset?)` | Follow cam bound to bone |
|
|
58
|
+
| `engine.setCameraFollow(model, bone?, offset?)` | Orbit center tracks a bone |
|
|
61
59
|
| `engine.setCameraTarget(vec3)` | Static camera target |
|
|
62
60
|
| `engine.setCameraDistance(d)` | Set orbit radius |
|
|
63
61
|
| `engine.setCameraAlpha(a)` | Set horizontal orbit angle |
|
|
@@ -99,15 +97,20 @@ engine.runRenderLoop()
|
|
|
99
97
|
cameraTarget: Vec3,
|
|
100
98
|
cameraFov: number,
|
|
101
99
|
onRaycast: (modelName, material, screenX, screenY) => void,
|
|
100
|
+
physicsOptions: {
|
|
101
|
+
constraintSolverKeywords: string[],
|
|
102
|
+
},
|
|
102
103
|
}
|
|
103
104
|
```
|
|
104
105
|
|
|
106
|
+
`constraintSolverKeywords` — joints whose name contains any keyword use the Bullet 2.75 constraint solver; all others keep the stable Ammo 2.82+ default. See [babylon-mmd: Fix Constraint Behavior](https://noname0310.github.io/babylon-mmd/docs/reference/runtime/apply-physics-to-mmd-models/#fix-constraint-behavior) for details.
|
|
107
|
+
|
|
105
108
|
## Projects Using This Engine
|
|
106
109
|
|
|
107
|
-
- **[MiKaPo](https://mikapo.vercel.app)**
|
|
108
|
-
- **[Popo](https://popo.love)**
|
|
109
|
-
- **[MPL](https://mmd-mpl.vercel.app)**
|
|
110
|
-
- **[Mixamo-MMD](https://mixamo-mmd.vercel.app)**
|
|
110
|
+
- **[MiKaPo](https://mikapo.vercel.app)** — Real-time motion capture for MMD
|
|
111
|
+
- **[Popo](https://popo.love)** — LLM-generated MMD poses
|
|
112
|
+
- **[MPL](https://mmd-mpl.vercel.app)** — Motion programming language for MMD
|
|
113
|
+
- **[Mixamo-MMD](https://mixamo-mmd.vercel.app)** — Retarget Mixamo FBX to VMD
|
|
111
114
|
|
|
112
115
|
## Tutorial
|
|
113
116
|
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Vec3 } from "./math";
|
|
2
2
|
import { Model } from "./model";
|
|
3
|
+
import { type PhysicsOptions } from "./physics";
|
|
3
4
|
export type RaycastCallback = (modelName: string, material: string | null, screenX: number, screenY: number) => void;
|
|
4
5
|
export type EngineOptions = {
|
|
5
6
|
ambientColor?: Vec3;
|
|
@@ -10,9 +11,21 @@ export type EngineOptions = {
|
|
|
10
11
|
cameraTarget?: Vec3;
|
|
11
12
|
cameraFov?: number;
|
|
12
13
|
onRaycast?: RaycastCallback;
|
|
14
|
+
physicsOptions?: PhysicsOptions;
|
|
15
|
+
};
|
|
16
|
+
export declare const DEFAULT_ENGINE_OPTIONS: {
|
|
17
|
+
ambientColor: Vec3;
|
|
18
|
+
directionalLightIntensity: number;
|
|
19
|
+
minSpecularIntensity: number;
|
|
20
|
+
rimLightIntensity: number;
|
|
21
|
+
cameraDistance: number;
|
|
22
|
+
cameraTarget: Vec3;
|
|
23
|
+
cameraFov: number;
|
|
24
|
+
onRaycast: undefined;
|
|
25
|
+
physicsOptions: {
|
|
26
|
+
constraintSolverKeywords: string[];
|
|
27
|
+
};
|
|
13
28
|
};
|
|
14
|
-
export type RequiredEngineOptions = Required<Omit<EngineOptions, "onRaycast">> & Pick<EngineOptions, "onRaycast">;
|
|
15
|
-
export declare const DEFAULT_ENGINE_OPTIONS: RequiredEngineOptions;
|
|
16
29
|
export interface EngineStats {
|
|
17
30
|
fps: number;
|
|
18
31
|
frameTime: number;
|
|
@@ -69,6 +82,7 @@ export declare class Engine {
|
|
|
69
82
|
private shadowVPLightY;
|
|
70
83
|
private shadowVPLightZ;
|
|
71
84
|
private onRaycast?;
|
|
85
|
+
private physicsOptions;
|
|
72
86
|
private lastTouchTime;
|
|
73
87
|
private readonly DOUBLE_TAP_DELAY;
|
|
74
88
|
private pickPipeline;
|
|
@@ -148,7 +162,6 @@ export declare class Engine {
|
|
|
148
162
|
getIKEnabled(): boolean;
|
|
149
163
|
setPhysicsEnabled(enabled: boolean): void;
|
|
150
164
|
getPhysicsEnabled(): boolean;
|
|
151
|
-
resetPhysics(): void;
|
|
152
165
|
private forEachInstance;
|
|
153
166
|
private updateInstances;
|
|
154
167
|
private updateVertexBuffer;
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,IAAI,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.js
CHANGED
|
@@ -11,6 +11,7 @@ export const DEFAULT_ENGINE_OPTIONS = {
|
|
|
11
11
|
cameraTarget: new Vec3(0, 12.5, 0),
|
|
12
12
|
cameraFov: Math.PI / 4,
|
|
13
13
|
onRaycast: undefined,
|
|
14
|
+
physicsOptions: { constraintSolverKeywords: ["胸"] },
|
|
14
15
|
};
|
|
15
16
|
export class Engine {
|
|
16
17
|
static getInstance() {
|
|
@@ -30,6 +31,7 @@ export class Engine {
|
|
|
30
31
|
this.shadowVPLightX = Number.NaN;
|
|
31
32
|
this.shadowVPLightY = Number.NaN;
|
|
32
33
|
this.shadowVPLightZ = Number.NaN;
|
|
34
|
+
this.physicsOptions = DEFAULT_ENGINE_OPTIONS.physicsOptions;
|
|
33
35
|
this.lastTouchTime = 0;
|
|
34
36
|
this.DOUBLE_TAP_DELAY = 300;
|
|
35
37
|
this.pendingPick = null;
|
|
@@ -96,6 +98,7 @@ export class Engine {
|
|
|
96
98
|
this.cameraTarget = options.cameraTarget ?? DEFAULT_ENGINE_OPTIONS.cameraTarget;
|
|
97
99
|
this.cameraFov = options.cameraFov ?? DEFAULT_ENGINE_OPTIONS.cameraFov;
|
|
98
100
|
this.onRaycast = options.onRaycast;
|
|
101
|
+
this.physicsOptions = options.physicsOptions ?? DEFAULT_ENGINE_OPTIONS.physicsOptions;
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
// Step 1: Get WebGPU device and context
|
|
@@ -999,14 +1002,6 @@ export class Engine {
|
|
|
999
1002
|
getPhysicsEnabled() {
|
|
1000
1003
|
return this.physicsEnabled;
|
|
1001
1004
|
}
|
|
1002
|
-
resetPhysics() {
|
|
1003
|
-
this.forEachInstance((inst) => {
|
|
1004
|
-
if (!inst.physics)
|
|
1005
|
-
return;
|
|
1006
|
-
inst.model.computeWorldMatrices();
|
|
1007
|
-
inst.physics.reset(inst.model.getWorldMatrices(), inst.model.getBoneInverseBindMatrices());
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
1005
|
forEachInstance(fn) {
|
|
1011
1006
|
for (const inst of this.modelInstances.values())
|
|
1012
1007
|
fn(inst);
|
|
@@ -1069,7 +1064,7 @@ export class Engine {
|
|
|
1069
1064
|
});
|
|
1070
1065
|
this.device.queue.writeBuffer(indexBuffer, 0, indices);
|
|
1071
1066
|
const rbs = model.getRigidbodies();
|
|
1072
|
-
const physics = rbs.length > 0 ? new Physics(rbs, model.getJoints()) : null;
|
|
1067
|
+
const physics = rbs.length > 0 ? new Physics(rbs, model.getJoints(), this.physicsOptions) : null;
|
|
1073
1068
|
const shadowBindGroup = this.device.createBindGroup({
|
|
1074
1069
|
label: `${name}: shadow bind`,
|
|
1075
1070
|
layout: this.shadowDepthPipeline.getBindGroupLayout(0),
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { Engine, type EngineStats } from "./engine";
|
|
|
2
2
|
export { Model } from "./model";
|
|
3
3
|
export { Vec3, Quat, Mat4 } from "./math";
|
|
4
4
|
export { AnimationState, type AnimationClip, type BoneKeyframe, type MorphKeyframe, } from "./animation";
|
|
5
|
+
export { Physics, type PhysicsOptions } from "./physics";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/physics.d.ts
CHANGED
|
@@ -43,10 +43,14 @@ export interface Joint {
|
|
|
43
43
|
springPosition: Vec3;
|
|
44
44
|
springRotation: Vec3;
|
|
45
45
|
}
|
|
46
|
+
export interface PhysicsOptions {
|
|
47
|
+
constraintSolverKeywords?: string[];
|
|
48
|
+
}
|
|
46
49
|
export declare class Physics {
|
|
47
50
|
private rigidbodies;
|
|
48
51
|
private joints;
|
|
49
52
|
private gravity;
|
|
53
|
+
private constraintSolverPattern;
|
|
50
54
|
private ammoInitialized;
|
|
51
55
|
private ammoPromise;
|
|
52
56
|
private ammo;
|
|
@@ -56,9 +60,8 @@ export declare class Physics {
|
|
|
56
60
|
private rigidbodiesInitialized;
|
|
57
61
|
private jointsCreated;
|
|
58
62
|
private firstFrame;
|
|
59
|
-
private forceDisableOffsetForConstraintFrame;
|
|
60
63
|
private zeroVector;
|
|
61
|
-
constructor(rigidbodies: Rigidbody[], joints?: Joint[]);
|
|
64
|
+
constructor(rigidbodies: Rigidbody[], joints?: Joint[], options?: PhysicsOptions);
|
|
62
65
|
private initAmmo;
|
|
63
66
|
setGravity(gravity: Vec3): void;
|
|
64
67
|
getGravity(): Vec3;
|
|
@@ -72,7 +75,6 @@ export declare class Physics {
|
|
|
72
75
|
private createAmmoRigidbodies;
|
|
73
76
|
private createAmmoJoints;
|
|
74
77
|
private normalizeAngle;
|
|
75
|
-
reset(boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array): void;
|
|
76
78
|
step(dt: number, boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array): void;
|
|
77
79
|
private computeBodyOffsets;
|
|
78
80
|
private positionBodiesFromBones;
|
package/dist/physics.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../src/physics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAIzC,oBAAY,cAAc;IACxB,MAAM,IAAI;IACV,GAAG,IAAI;IACP,OAAO,IAAI;CACZ;AAED,oBAAY,aAAa;IACvB,MAAM,IAAI;IACV,OAAO,IAAI;IACX,SAAS,IAAI;CACd;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,cAAc,CAAA;IACrB,IAAI,EAAE,IAAI,CAAA;IACV,aAAa,EAAE,IAAI,CAAA;IACnB,aAAa,EAAE,IAAI,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,aAAa,CAAA;IACnB,uBAAuB,EAAE,IAAI,CAAA;IAC7B,gBAAgB,CAAC,EAAE,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,IAAI,CAAA;IACd,QAAQ,EAAE,IAAI,CAAA;IACd,WAAW,EAAE,IAAI,CAAA;IACjB,WAAW,EAAE,IAAI,CAAA;IACjB,WAAW,EAAE,IAAI,CAAA;IACjB,WAAW,EAAE,IAAI,CAAA;IACjB,cAAc,EAAE,IAAI,CAAA;IACpB,cAAc,EAAE,IAAI,CAAA;CACrB;AAED,qBAAa,OAAO;IAClB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,WAAW,CAAqC;IACxD,OAAO,CAAC,IAAI,CAA4B;IAExC,OAAO,CAAC,aAAa,CAAY;IAEjC,OAAO,CAAC,eAAe,CAAY;IAEnC,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,sBAAsB,CAAQ;IACtC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,UAAU,CAAO;
|
|
1
|
+
{"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../src/physics.ts"],"names":[],"mappings":"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"}
|
package/dist/physics.js
CHANGED
|
@@ -13,8 +13,9 @@ export var RigidbodyType;
|
|
|
13
13
|
RigidbodyType[RigidbodyType["Kinematic"] = 2] = "Kinematic";
|
|
14
14
|
})(RigidbodyType || (RigidbodyType = {}));
|
|
15
15
|
export class Physics {
|
|
16
|
-
constructor(rigidbodies, joints = []) {
|
|
16
|
+
constructor(rigidbodies, joints = [], options) {
|
|
17
17
|
this.gravity = new Vec3(0, -98, 0); // Gravity acceleration (cm/s²), MMD-style default
|
|
18
|
+
this.constraintSolverPattern = null;
|
|
18
19
|
this.ammoInitialized = false;
|
|
19
20
|
this.ammoPromise = null;
|
|
20
21
|
this.ammo = null;
|
|
@@ -27,11 +28,14 @@ export class Physics {
|
|
|
27
28
|
this.rigidbodiesInitialized = false; // bodyOffsetMatrixInverse computed and bodies positioned
|
|
28
29
|
this.jointsCreated = false; // Joints delayed until after rigidbodies are positioned
|
|
29
30
|
this.firstFrame = true; // Needed to reposition bodies before creating joints
|
|
30
|
-
this.forceDisableOffsetForConstraintFrame = true; // MMD compatibility (Bullet 2.75 behavior)
|
|
31
31
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
32
|
this.zeroVector = null; // Cached zero vector for velocity clearing
|
|
33
33
|
this.rigidbodies = rigidbodies;
|
|
34
34
|
this.joints = joints;
|
|
35
|
+
const keywords = options?.constraintSolverKeywords ?? [];
|
|
36
|
+
if (keywords.length > 0) {
|
|
37
|
+
this.constraintSolverPattern = new RegExp(keywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "i");
|
|
38
|
+
}
|
|
35
39
|
this.initAmmo();
|
|
36
40
|
}
|
|
37
41
|
async initAmmo() {
|
|
@@ -246,8 +250,9 @@ export class Physics {
|
|
|
246
250
|
frameInB.setRotation(quatB);
|
|
247
251
|
const useLinearReferenceFrameA = true;
|
|
248
252
|
const constraint = new Ammo.btGeneric6DofSpringConstraint(bodyA, bodyB, frameInA, frameInB, useLinearReferenceFrameA);
|
|
249
|
-
//
|
|
250
|
-
|
|
253
|
+
// Per-joint Bullet 2.75 constraint solver: disable m_useOffsetForConstraintFrame for
|
|
254
|
+
// joints whose name matches constraintSolverKeywords.
|
|
255
|
+
if (this.constraintSolverPattern && this.constraintSolverPattern.test(joint.name)) {
|
|
251
256
|
let jointPtr;
|
|
252
257
|
if (typeof Ammo.getPointer === "function") {
|
|
253
258
|
jointPtr = Ammo.getPointer(constraint);
|
|
@@ -258,7 +263,6 @@ export class Physics {
|
|
|
258
263
|
}
|
|
259
264
|
if (jointPtr !== undefined && Ammo.HEAP8) {
|
|
260
265
|
const heap8 = Ammo.HEAP8;
|
|
261
|
-
// jointPtr + 1300 = m_useLinearReferenceFrameA, jointPtr + 1301 = m_useOffsetForConstraintFrame
|
|
262
266
|
if (heap8[jointPtr + 1300] === (useLinearReferenceFrameA ? 1 : 0) && heap8[jointPtr + 1301] === 1) {
|
|
263
267
|
heap8[jointPtr + 1301] = 0;
|
|
264
268
|
}
|
|
@@ -329,63 +333,6 @@ export class Physics {
|
|
|
329
333
|
}
|
|
330
334
|
return angle;
|
|
331
335
|
}
|
|
332
|
-
// Reset physics state (reposition bodies, clear velocities)
|
|
333
|
-
// Following babylon-mmd pattern: initialize all rigid body positions from current bone poses
|
|
334
|
-
// Call this when starting a new animation to prevent physics instability from sudden pose changes
|
|
335
|
-
reset(boneWorldMatrices, boneInverseBindMatrices) {
|
|
336
|
-
if (!this.ammoInitialized || !this.ammo || !this.dynamicsWorld) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
const boneCount = boneWorldMatrices.length;
|
|
340
|
-
const Ammo = this.ammo;
|
|
341
|
-
// Ensure body offsets are computed
|
|
342
|
-
if (!this.rigidbodiesInitialized) {
|
|
343
|
-
this.computeBodyOffsets(boneInverseBindMatrices, boneCount);
|
|
344
|
-
this.rigidbodiesInitialized = true;
|
|
345
|
-
}
|
|
346
|
-
// Reposition ALL rigid bodies from current bone poses (like babylon-mmd initialize)
|
|
347
|
-
// This ensures all bodies are correctly positioned before physics starts
|
|
348
|
-
for (let i = 0; i < this.rigidbodies.length; i++) {
|
|
349
|
-
const rb = this.rigidbodies[i];
|
|
350
|
-
const ammoBody = this.ammoRigidbodies[i];
|
|
351
|
-
if (!ammoBody || rb.boneIndex < 0 || rb.boneIndex >= boneCount)
|
|
352
|
-
continue;
|
|
353
|
-
const boneIdx = rb.boneIndex;
|
|
354
|
-
// Get bone world matrix
|
|
355
|
-
const boneWorldMat = boneWorldMatrices[boneIdx];
|
|
356
|
-
// Compute body world matrix: bodyWorld = boneWorld × bodyOffsetMatrix
|
|
357
|
-
// (like babylon-mmd: bodyWorldMatrix = bodyOffsetMatrix.multiplyToRef(bodyWorldMatrix))
|
|
358
|
-
const bodyOffsetMatrix = rb.bodyOffsetMatrix || rb.bodyOffsetMatrixInverse.inverse();
|
|
359
|
-
const bodyWorldMatrix = boneWorldMat.multiply(bodyOffsetMatrix);
|
|
360
|
-
const worldPos = bodyWorldMatrix.getPosition();
|
|
361
|
-
const worldRot = bodyWorldMatrix.toQuat();
|
|
362
|
-
// Set transform matrix
|
|
363
|
-
const transform = new Ammo.btTransform();
|
|
364
|
-
const pos = new Ammo.btVector3(worldPos.x, worldPos.y, worldPos.z);
|
|
365
|
-
const quat = new Ammo.btQuaternion(worldRot.x, worldRot.y, worldRot.z, worldRot.w);
|
|
366
|
-
transform.setOrigin(pos);
|
|
367
|
-
transform.setRotation(quat);
|
|
368
|
-
ammoBody.setWorldTransform(transform);
|
|
369
|
-
ammoBody.getMotionState().setWorldTransform(transform);
|
|
370
|
-
// Clear velocities for all rigidbodies
|
|
371
|
-
if (!this.zeroVector) {
|
|
372
|
-
this.zeroVector = new Ammo.btVector3(0, 0, 0);
|
|
373
|
-
}
|
|
374
|
-
ammoBody.setLinearVelocity(this.zeroVector);
|
|
375
|
-
ammoBody.setAngularVelocity(this.zeroVector);
|
|
376
|
-
// Explicitly activate dynamic rigidbodies after reset (wake them up)
|
|
377
|
-
// This is critical for dress pieces and other dynamic bodies to prevent teleporting
|
|
378
|
-
if (rb.type === RigidbodyType.Dynamic) {
|
|
379
|
-
ammoBody.activate(true); // Wake up the body
|
|
380
|
-
}
|
|
381
|
-
Ammo.destroy(pos);
|
|
382
|
-
Ammo.destroy(quat);
|
|
383
|
-
}
|
|
384
|
-
// Step simulation once to stabilize (like babylon-mmd)
|
|
385
|
-
if (this.dynamicsWorld.stepSimulation) {
|
|
386
|
-
this.dynamicsWorld.stepSimulation(0, 0, 0);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
336
|
// Syncs bones to rigidbodies, simulates dynamics, solves constraints
|
|
390
337
|
// Modifies boneWorldMatrices in-place for dynamic rigidbodies that drive bones
|
|
391
338
|
step(dt, boneWorldMatrices, boneInverseBindMatrices) {
|
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Camera } from "./camera"
|
|
|
2
2
|
import { Mat4, Vec3 } from "./math"
|
|
3
3
|
import { Model } from "./model"
|
|
4
4
|
import { PmxLoader } from "./pmx-loader"
|
|
5
|
-
import { Physics } from "./physics"
|
|
5
|
+
import { Physics, type PhysicsOptions } from "./physics"
|
|
6
6
|
|
|
7
7
|
export type RaycastCallback = (modelName: string, material: string | null, screenX: number, screenY: number) => void
|
|
8
8
|
|
|
@@ -15,11 +15,10 @@ export type EngineOptions = {
|
|
|
15
15
|
cameraTarget?: Vec3
|
|
16
16
|
cameraFov?: number
|
|
17
17
|
onRaycast?: RaycastCallback
|
|
18
|
+
physicsOptions?: PhysicsOptions
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
export const DEFAULT_ENGINE_OPTIONS: RequiredEngineOptions = {
|
|
21
|
+
export const DEFAULT_ENGINE_OPTIONS = {
|
|
23
22
|
ambientColor: new Vec3(0.88, 0.88, 0.88),
|
|
24
23
|
directionalLightIntensity: 0.24,
|
|
25
24
|
minSpecularIntensity: 0.3,
|
|
@@ -28,6 +27,7 @@ export const DEFAULT_ENGINE_OPTIONS: RequiredEngineOptions = {
|
|
|
28
27
|
cameraTarget: new Vec3(0, 12.5, 0),
|
|
29
28
|
cameraFov: Math.PI / 4,
|
|
30
29
|
onRaycast: undefined,
|
|
30
|
+
physicsOptions: { constraintSolverKeywords: ["胸"] },
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface EngineStats {
|
|
@@ -141,6 +141,7 @@ export class Engine {
|
|
|
141
141
|
private shadowVPLightZ = Number.NaN
|
|
142
142
|
|
|
143
143
|
private onRaycast?: RaycastCallback
|
|
144
|
+
private physicsOptions: PhysicsOptions = DEFAULT_ENGINE_OPTIONS.physicsOptions
|
|
144
145
|
private lastTouchTime = 0
|
|
145
146
|
private readonly DOUBLE_TAP_DELAY = 300
|
|
146
147
|
// GPU picking
|
|
@@ -183,7 +184,7 @@ export class Engine {
|
|
|
183
184
|
constructor(canvas: HTMLCanvasElement, options?: EngineOptions) {
|
|
184
185
|
this.canvas = canvas
|
|
185
186
|
if (options) {
|
|
186
|
-
this.ambientColor = options.ambientColor ?? DEFAULT_ENGINE_OPTIONS.ambientColor
|
|
187
|
+
this.ambientColor = options.ambientColor ?? DEFAULT_ENGINE_OPTIONS.ambientColor
|
|
187
188
|
this.directionalLightIntensity =
|
|
188
189
|
options.directionalLightIntensity ?? DEFAULT_ENGINE_OPTIONS.directionalLightIntensity
|
|
189
190
|
this.minSpecularIntensity = options.minSpecularIntensity ?? DEFAULT_ENGINE_OPTIONS.minSpecularIntensity
|
|
@@ -192,6 +193,7 @@ export class Engine {
|
|
|
192
193
|
this.cameraTarget = options.cameraTarget ?? DEFAULT_ENGINE_OPTIONS.cameraTarget
|
|
193
194
|
this.cameraFov = options.cameraFov ?? DEFAULT_ENGINE_OPTIONS.cameraFov
|
|
194
195
|
this.onRaycast = options.onRaycast
|
|
196
|
+
this.physicsOptions = options.physicsOptions ?? DEFAULT_ENGINE_OPTIONS.physicsOptions
|
|
195
197
|
}
|
|
196
198
|
}
|
|
197
199
|
|
|
@@ -1188,14 +1190,6 @@ export class Engine {
|
|
|
1188
1190
|
return this.physicsEnabled
|
|
1189
1191
|
}
|
|
1190
1192
|
|
|
1191
|
-
public resetPhysics(): void {
|
|
1192
|
-
this.forEachInstance((inst) => {
|
|
1193
|
-
if (!inst.physics) return
|
|
1194
|
-
inst.model.computeWorldMatrices()
|
|
1195
|
-
inst.physics.reset(inst.model.getWorldMatrices(), inst.model.getBoneInverseBindMatrices())
|
|
1196
|
-
})
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
1193
|
private forEachInstance(fn: (inst: ModelInstance) => void): void {
|
|
1200
1194
|
for (const inst of this.modelInstances.values()) fn(inst)
|
|
1201
1195
|
}
|
|
@@ -1278,7 +1272,7 @@ export class Engine {
|
|
|
1278
1272
|
this.device.queue.writeBuffer(indexBuffer, 0, indices)
|
|
1279
1273
|
|
|
1280
1274
|
const rbs = model.getRigidbodies()
|
|
1281
|
-
const physics = rbs.length > 0 ? new Physics(rbs, model.getJoints()) : null
|
|
1275
|
+
const physics = rbs.length > 0 ? new Physics(rbs, model.getJoints(), this.physicsOptions) : null
|
|
1282
1276
|
|
|
1283
1277
|
const shadowBindGroup = this.device.createBindGroup({
|
|
1284
1278
|
label: `${name}: shadow bind`,
|
package/src/index.ts
CHANGED
package/src/physics.ts
CHANGED
|
@@ -50,10 +50,18 @@ export interface Joint {
|
|
|
50
50
|
springRotation: Vec3 // Spring stiffness values
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export interface PhysicsOptions {
|
|
54
|
+
// Joint name keywords for per-joint Bullet 2.75 constraint solver behavior.
|
|
55
|
+
// Joints whose name contains any keyword get m_useOffsetForConstraintFrame
|
|
56
|
+
// disabled (matching Bullet 2.75). All others keep the stable Ammo 2.82+ default.
|
|
57
|
+
constraintSolverKeywords?: string[]
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
export class Physics {
|
|
54
61
|
private rigidbodies: Rigidbody[]
|
|
55
62
|
private joints: Joint[]
|
|
56
63
|
private gravity: Vec3 = new Vec3(0, -98, 0) // Gravity acceleration (cm/s²), MMD-style default
|
|
64
|
+
private constraintSolverPattern: RegExp | null = null
|
|
57
65
|
private ammoInitialized = false
|
|
58
66
|
private ammoPromise: Promise<AmmoInstance> | null = null
|
|
59
67
|
private ammo: AmmoInstance | null = null
|
|
@@ -66,13 +74,16 @@ export class Physics {
|
|
|
66
74
|
private rigidbodiesInitialized = false // bodyOffsetMatrixInverse computed and bodies positioned
|
|
67
75
|
private jointsCreated = false // Joints delayed until after rigidbodies are positioned
|
|
68
76
|
private firstFrame = true // Needed to reposition bodies before creating joints
|
|
69
|
-
private forceDisableOffsetForConstraintFrame = true // MMD compatibility (Bullet 2.75 behavior)
|
|
70
77
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
78
|
private zeroVector: any = null // Cached zero vector for velocity clearing
|
|
72
79
|
|
|
73
|
-
constructor(rigidbodies: Rigidbody[], joints: Joint[] = []) {
|
|
80
|
+
constructor(rigidbodies: Rigidbody[], joints: Joint[] = [], options?: PhysicsOptions) {
|
|
74
81
|
this.rigidbodies = rigidbodies
|
|
75
82
|
this.joints = joints
|
|
83
|
+
const keywords = options?.constraintSolverKeywords ?? []
|
|
84
|
+
if (keywords.length > 0) {
|
|
85
|
+
this.constraintSolverPattern = new RegExp(keywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "i")
|
|
86
|
+
}
|
|
76
87
|
this.initAmmo()
|
|
77
88
|
}
|
|
78
89
|
|
|
@@ -361,8 +372,9 @@ export class Physics {
|
|
|
361
372
|
useLinearReferenceFrameA
|
|
362
373
|
)
|
|
363
374
|
|
|
364
|
-
//
|
|
365
|
-
|
|
375
|
+
// Per-joint Bullet 2.75 constraint solver: disable m_useOffsetForConstraintFrame for
|
|
376
|
+
// joints whose name matches constraintSolverKeywords.
|
|
377
|
+
if (this.constraintSolverPattern && this.constraintSolverPattern.test(joint.name)) {
|
|
366
378
|
let jointPtr: number | undefined
|
|
367
379
|
if (typeof Ammo.getPointer === "function") {
|
|
368
380
|
jointPtr = Ammo.getPointer(constraint)
|
|
@@ -373,7 +385,6 @@ export class Physics {
|
|
|
373
385
|
|
|
374
386
|
if (jointPtr !== undefined && Ammo.HEAP8) {
|
|
375
387
|
const heap8 = Ammo.HEAP8 as Uint8Array
|
|
376
|
-
// jointPtr + 1300 = m_useLinearReferenceFrameA, jointPtr + 1301 = m_useOffsetForConstraintFrame
|
|
377
388
|
if (heap8[jointPtr + 1300] === (useLinearReferenceFrameA ? 1 : 0) && heap8[jointPtr + 1301] === 1) {
|
|
378
389
|
heap8[jointPtr + 1301] = 0
|
|
379
390
|
}
|
|
@@ -457,77 +468,6 @@ export class Physics {
|
|
|
457
468
|
return angle
|
|
458
469
|
}
|
|
459
470
|
|
|
460
|
-
// Reset physics state (reposition bodies, clear velocities)
|
|
461
|
-
// Following babylon-mmd pattern: initialize all rigid body positions from current bone poses
|
|
462
|
-
// Call this when starting a new animation to prevent physics instability from sudden pose changes
|
|
463
|
-
reset(boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array): void {
|
|
464
|
-
if (!this.ammoInitialized || !this.ammo || !this.dynamicsWorld) {
|
|
465
|
-
return
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const boneCount = boneWorldMatrices.length
|
|
469
|
-
const Ammo = this.ammo
|
|
470
|
-
|
|
471
|
-
// Ensure body offsets are computed
|
|
472
|
-
if (!this.rigidbodiesInitialized) {
|
|
473
|
-
this.computeBodyOffsets(boneInverseBindMatrices, boneCount)
|
|
474
|
-
this.rigidbodiesInitialized = true
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Reposition ALL rigid bodies from current bone poses (like babylon-mmd initialize)
|
|
478
|
-
// This ensures all bodies are correctly positioned before physics starts
|
|
479
|
-
for (let i = 0; i < this.rigidbodies.length; i++) {
|
|
480
|
-
const rb = this.rigidbodies[i]
|
|
481
|
-
const ammoBody = this.ammoRigidbodies[i]
|
|
482
|
-
if (!ammoBody || rb.boneIndex < 0 || rb.boneIndex >= boneCount) continue
|
|
483
|
-
|
|
484
|
-
const boneIdx = rb.boneIndex
|
|
485
|
-
|
|
486
|
-
// Get bone world matrix
|
|
487
|
-
const boneWorldMat = boneWorldMatrices[boneIdx]
|
|
488
|
-
|
|
489
|
-
// Compute body world matrix: bodyWorld = boneWorld × bodyOffsetMatrix
|
|
490
|
-
// (like babylon-mmd: bodyWorldMatrix = bodyOffsetMatrix.multiplyToRef(bodyWorldMatrix))
|
|
491
|
-
const bodyOffsetMatrix = rb.bodyOffsetMatrix || rb.bodyOffsetMatrixInverse.inverse()
|
|
492
|
-
const bodyWorldMatrix = boneWorldMat.multiply(bodyOffsetMatrix)
|
|
493
|
-
|
|
494
|
-
const worldPos = bodyWorldMatrix.getPosition()
|
|
495
|
-
const worldRot = bodyWorldMatrix.toQuat()
|
|
496
|
-
|
|
497
|
-
// Set transform matrix
|
|
498
|
-
const transform = new Ammo.btTransform()
|
|
499
|
-
const pos = new Ammo.btVector3(worldPos.x, worldPos.y, worldPos.z)
|
|
500
|
-
const quat = new Ammo.btQuaternion(worldRot.x, worldRot.y, worldRot.z, worldRot.w)
|
|
501
|
-
|
|
502
|
-
transform.setOrigin(pos)
|
|
503
|
-
transform.setRotation(quat)
|
|
504
|
-
|
|
505
|
-
ammoBody.setWorldTransform(transform)
|
|
506
|
-
ammoBody.getMotionState().setWorldTransform(transform)
|
|
507
|
-
|
|
508
|
-
// Clear velocities for all rigidbodies
|
|
509
|
-
if (!this.zeroVector) {
|
|
510
|
-
this.zeroVector = new Ammo.btVector3(0, 0, 0)
|
|
511
|
-
}
|
|
512
|
-
ammoBody.setLinearVelocity(this.zeroVector)
|
|
513
|
-
ammoBody.setAngularVelocity(this.zeroVector)
|
|
514
|
-
|
|
515
|
-
// Explicitly activate dynamic rigidbodies after reset (wake them up)
|
|
516
|
-
// This is critical for dress pieces and other dynamic bodies to prevent teleporting
|
|
517
|
-
if (rb.type === RigidbodyType.Dynamic) {
|
|
518
|
-
ammoBody.activate(true) // Wake up the body
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
Ammo.destroy(pos)
|
|
522
|
-
Ammo.destroy(quat)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Step simulation once to stabilize (like babylon-mmd)
|
|
526
|
-
if (this.dynamicsWorld.stepSimulation) {
|
|
527
|
-
this.dynamicsWorld.stepSimulation(0, 0, 0)
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
471
|
// Syncs bones to rigidbodies, simulates dynamics, solves constraints
|
|
532
472
|
// Modifies boneWorldMatrices in-place for dynamic rigidbodies that drive bones
|
|
533
473
|
step(dt: number, boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array): void {
|