reze-engine 0.9.1 → 0.9.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Reze Engine
2
2
 
3
- A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering, built with TypeScript.
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 lighting, alpha blending, rim lighting, outlines, MSAA 4x
14
- - VMD animation (multiple named, non-interruptible), IK solver, Ammo/Bullet physics
15
- - GPU picking (double-click/tap returns model + material name)
16
- - Souls-style follow cam (orbit center bound to model bone)
17
- - Optimized bind groups (per-frame / per-instance / per-material)
18
- - Ground shadow mapping with PCF
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.resetPhysics()` | Reset physics to current pose |
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)** - Real-time motion capture for MMD
108
- - **[Popo](https://popo.love)** - LLM-generated MMD poses
109
- - **[MPL](https://mmd-mpl.vercel.app)** - Motion programming language for MMD
110
- - **[Mixamo-MMD](https://mixamo-mmd.vercel.app)** - Retarget Mixamo FBX to VMD
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,23 @@ export type EngineOptions = {
10
11
  cameraTarget?: Vec3;
11
12
  cameraFov?: number;
12
13
  onRaycast?: RaycastCallback;
14
+ physicsOptions?: PhysicsOptions;
15
+ shadowLightDirection?: Vec3;
16
+ };
17
+ export declare const DEFAULT_ENGINE_OPTIONS: {
18
+ ambientColor: Vec3;
19
+ directionalLightIntensity: number;
20
+ minSpecularIntensity: number;
21
+ rimLightIntensity: number;
22
+ cameraDistance: number;
23
+ cameraTarget: Vec3;
24
+ cameraFov: number;
25
+ onRaycast: undefined;
26
+ physicsOptions: {
27
+ constraintSolverKeywords: string[];
28
+ };
29
+ shadowLightDirection: Vec3;
13
30
  };
14
- export type RequiredEngineOptions = Required<Omit<EngineOptions, "onRaycast">> & Pick<EngineOptions, "onRaycast">;
15
- export declare const DEFAULT_ENGINE_OPTIONS: RequiredEngineOptions;
16
31
  export interface EngineStats {
17
32
  fps: number;
18
33
  frameTime: number;
@@ -65,10 +80,9 @@ export declare class Engine {
65
80
  private shadowComparisonSampler;
66
81
  private groundShadowMaterialBuffer?;
67
82
  private groundDrawCall;
68
- private shadowVPLightX;
69
- private shadowVPLightY;
70
- private shadowVPLightZ;
71
83
  private onRaycast?;
84
+ private physicsOptions;
85
+ private shadowLightDirection;
72
86
  private lastTouchTime;
73
87
  private readonly DOUBLE_TAP_DELAY;
74
88
  private pickPipeline;
@@ -148,13 +162,13 @@ 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;
155
168
  private setupModelInstance;
156
169
  private createGroundGeometry;
157
170
  private createShadowGroundResources;
171
+ private shadowLightVPDirty;
158
172
  private updateShadowLightVP;
159
173
  private setupMaterialsForInstance;
160
174
  private createMaterialUniformBuffer;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
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,8 @@ 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: ["胸"] },
15
+ shadowLightDirection: new Vec3(0.12, -1, 0.16),
14
16
  };
15
17
  export class Engine {
16
18
  static getInstance() {
@@ -27,9 +29,8 @@ export class Engine {
27
29
  this.hasGround = false;
28
30
  this.shadowLightVPMatrix = new Float32Array(16);
29
31
  this.groundDrawCall = null;
30
- this.shadowVPLightX = Number.NaN;
31
- this.shadowVPLightY = Number.NaN;
32
- this.shadowVPLightZ = Number.NaN;
32
+ this.physicsOptions = DEFAULT_ENGINE_OPTIONS.physicsOptions;
33
+ this.shadowLightDirection = DEFAULT_ENGINE_OPTIONS.shadowLightDirection;
33
34
  this.lastTouchTime = 0;
34
35
  this.DOUBLE_TAP_DELAY = 300;
35
36
  this.pendingPick = null;
@@ -54,6 +55,8 @@ export class Engine {
54
55
  };
55
56
  this.animationFrameId = null;
56
57
  this.renderLoopCallback = null;
58
+ // Shadow uses a fixed orthographic projection, independent of the visible light direction
59
+ this.shadowLightVPDirty = true;
57
60
  this.handleCanvasDoubleClick = (event) => {
58
61
  if (!this.onRaycast || this.modelInstances.size === 0)
59
62
  return;
@@ -96,6 +99,8 @@ export class Engine {
96
99
  this.cameraTarget = options.cameraTarget ?? DEFAULT_ENGINE_OPTIONS.cameraTarget;
97
100
  this.cameraFov = options.cameraFov ?? DEFAULT_ENGINE_OPTIONS.cameraFov;
98
101
  this.onRaycast = options.onRaycast;
102
+ this.physicsOptions = options.physicsOptions ?? DEFAULT_ENGINE_OPTIONS.physicsOptions;
103
+ this.shadowLightDirection = options.shadowLightDirection ?? DEFAULT_ENGINE_OPTIONS.shadowLightDirection;
99
104
  }
100
105
  }
101
106
  // Step 1: Get WebGPU device and context
@@ -999,14 +1004,6 @@ export class Engine {
999
1004
  getPhysicsEnabled() {
1000
1005
  return this.physicsEnabled;
1001
1006
  }
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
1007
  forEachInstance(fn) {
1011
1008
  for (const inst of this.modelInstances.values())
1012
1009
  fn(inst);
@@ -1069,7 +1066,7 @@ export class Engine {
1069
1066
  });
1070
1067
  this.device.queue.writeBuffer(indexBuffer, 0, indices);
1071
1068
  const rbs = model.getRigidbodies();
1072
- const physics = rbs.length > 0 ? new Physics(rbs, model.getJoints()) : null;
1069
+ const physics = rbs.length > 0 ? new Physics(rbs, model.getJoints(), this.physicsOptions) : null;
1073
1070
  const shadowBindGroup = this.device.createBindGroup({
1074
1071
  label: `${name}: shadow bind`,
1075
1072
  layout: this.shadowDepthPipeline.getBindGroupLayout(0),
@@ -1211,25 +1208,15 @@ export class Engine {
1211
1208
  });
1212
1209
  }
1213
1210
  updateShadowLightVP() {
1214
- const lx = this.lightData[4];
1215
- const ly = this.lightData[5];
1216
- const lz = this.lightData[6];
1217
- if (lx === this.shadowVPLightX && ly === this.shadowVPLightY && lz === this.shadowVPLightZ)
1211
+ if (!this.shadowLightVPDirty)
1218
1212
  return;
1219
- this.shadowVPLightX = lx;
1220
- this.shadowVPLightY = ly;
1221
- this.shadowVPLightZ = lz;
1222
- const dir = new Vec3(lx, ly, lz);
1223
- if (dir.length() < 1e-6) {
1224
- dir.x = 0.35;
1225
- dir.y = -1;
1226
- dir.z = 0.2;
1227
- }
1228
- else
1229
- dir.normalize();
1213
+ this.shadowLightVPDirty = false;
1214
+ const dir = new Vec3(this.shadowLightDirection.x, this.shadowLightDirection.y, this.shadowLightDirection.z);
1215
+ dir.normalize();
1230
1216
  const target = new Vec3(0, 11, 0);
1231
1217
  const eye = new Vec3(target.x - dir.x * 72, target.y - dir.y * 72, target.z - dir.z * 72);
1232
- const view = Mat4.lookAt(eye, target, new Vec3(0, 1, 0));
1218
+ const up = Math.abs(dir.y) > 0.99 ? new Vec3(0, 0, -1) : new Vec3(0, 1, 0);
1219
+ const view = Mat4.lookAt(eye, target, up);
1233
1220
  const proj = Mat4.orthographicLh(-72, 72, -72, 72, 1, 140);
1234
1221
  const vp = proj.multiply(view);
1235
1222
  this.shadowLightVPMatrix.set(vp.values);
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
@@ -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
@@ -2,3 +2,4 @@ export { Engine } from "./engine";
2
2
  export { Model } from "./model";
3
3
  export { Vec3, Quat, Mat4 } from "./math";
4
4
  export { AnimationState, } from "./animation";
5
+ export { Physics } from "./physics";
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;
@@ -1 +1 @@
1
- {"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../src/physics.ts"],"names":[],"mappings":"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"}
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
- // Disable offset for constraint frame for MMD compatibility (Bullet 2.75 behavior)
250
- if (this.forceDisableOffsetForConstraintFrame) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
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,11 @@ export type EngineOptions = {
15
15
  cameraTarget?: Vec3
16
16
  cameraFov?: number
17
17
  onRaycast?: RaycastCallback
18
+ physicsOptions?: PhysicsOptions
19
+ shadowLightDirection?: Vec3
18
20
  }
19
21
 
20
- export type RequiredEngineOptions = Required<Omit<EngineOptions, "onRaycast">> & Pick<EngineOptions, "onRaycast">
21
-
22
- export const DEFAULT_ENGINE_OPTIONS: RequiredEngineOptions = {
22
+ export const DEFAULT_ENGINE_OPTIONS = {
23
23
  ambientColor: new Vec3(0.88, 0.88, 0.88),
24
24
  directionalLightIntensity: 0.24,
25
25
  minSpecularIntensity: 0.3,
@@ -28,6 +28,8 @@ export const DEFAULT_ENGINE_OPTIONS: RequiredEngineOptions = {
28
28
  cameraTarget: new Vec3(0, 12.5, 0),
29
29
  cameraFov: Math.PI / 4,
30
30
  onRaycast: undefined,
31
+ physicsOptions: { constraintSolverKeywords: ["胸"] },
32
+ shadowLightDirection: new Vec3(0.12, -1, 0.16),
31
33
  }
32
34
 
33
35
  export interface EngineStats {
@@ -136,11 +138,10 @@ export class Engine {
136
138
  private shadowComparisonSampler!: GPUSampler
137
139
  private groundShadowMaterialBuffer?: GPUBuffer
138
140
  private groundDrawCall: DrawCall | null = null
139
- private shadowVPLightX = Number.NaN
140
- private shadowVPLightY = Number.NaN
141
- private shadowVPLightZ = Number.NaN
142
141
 
143
142
  private onRaycast?: RaycastCallback
143
+ private physicsOptions: PhysicsOptions = DEFAULT_ENGINE_OPTIONS.physicsOptions
144
+ private shadowLightDirection: Vec3 = DEFAULT_ENGINE_OPTIONS.shadowLightDirection
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,8 @@ 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
197
+ this.shadowLightDirection = options.shadowLightDirection ?? DEFAULT_ENGINE_OPTIONS.shadowLightDirection
195
198
  }
196
199
  }
197
200
 
@@ -1188,14 +1191,6 @@ export class Engine {
1188
1191
  return this.physicsEnabled
1189
1192
  }
1190
1193
 
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
1194
  private forEachInstance(fn: (inst: ModelInstance) => void): void {
1200
1195
  for (const inst of this.modelInstances.values()) fn(inst)
1201
1196
  }
@@ -1278,7 +1273,7 @@ export class Engine {
1278
1273
  this.device.queue.writeBuffer(indexBuffer, 0, indices)
1279
1274
 
1280
1275
  const rbs = model.getRigidbodies()
1281
- const physics = rbs.length > 0 ? new Physics(rbs, model.getJoints()) : null
1276
+ const physics = rbs.length > 0 ? new Physics(rbs, model.getJoints(), this.physicsOptions) : null
1282
1277
 
1283
1278
  const shadowBindGroup = this.device.createBindGroup({
1284
1279
  label: `${name}: shadow bind`,
@@ -1439,23 +1434,17 @@ export class Engine {
1439
1434
  })
1440
1435
  }
1441
1436
 
1437
+ // Shadow uses a fixed orthographic projection, independent of the visible light direction
1438
+ private shadowLightVPDirty = true
1442
1439
  private updateShadowLightVP() {
1443
- const lx = this.lightData[4]
1444
- const ly = this.lightData[5]
1445
- const lz = this.lightData[6]
1446
- if (lx === this.shadowVPLightX && ly === this.shadowVPLightY && lz === this.shadowVPLightZ) return
1447
- this.shadowVPLightX = lx
1448
- this.shadowVPLightY = ly
1449
- this.shadowVPLightZ = lz
1450
- const dir = new Vec3(lx, ly, lz)
1451
- if (dir.length() < 1e-6) {
1452
- dir.x = 0.35
1453
- dir.y = -1
1454
- dir.z = 0.2
1455
- } else dir.normalize()
1440
+ if (!this.shadowLightVPDirty) return
1441
+ this.shadowLightVPDirty = false
1442
+ const dir = new Vec3(this.shadowLightDirection.x, this.shadowLightDirection.y, this.shadowLightDirection.z)
1443
+ dir.normalize()
1456
1444
  const target = new Vec3(0, 11, 0)
1457
1445
  const eye = new Vec3(target.x - dir.x * 72, target.y - dir.y * 72, target.z - dir.z * 72)
1458
- const view = Mat4.lookAt(eye, target, new Vec3(0, 1, 0))
1446
+ const up = Math.abs(dir.y) > 0.99 ? new Vec3(0, 0, -1) : new Vec3(0, 1, 0)
1447
+ const view = Mat4.lookAt(eye, target, up)
1459
1448
  const proj = Mat4.orthographicLh(-72, 72, -72, 72, 1, 140)
1460
1449
  const vp = proj.multiply(view)
1461
1450
  this.shadowLightVPMatrix.set(vp.values)
package/src/index.ts CHANGED
@@ -7,3 +7,4 @@ export {
7
7
  type BoneKeyframe,
8
8
  type MorphKeyframe,
9
9
  } from "./animation"
10
+ export { Physics, type PhysicsOptions } from "./physics"
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
- // Disable offset for constraint frame for MMD compatibility (Bullet 2.75 behavior)
365
- if (this.forceDisableOffsetForConstraintFrame) {
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 {