reze-engine 0.9.0 → 0.9.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 CHANGED
@@ -1,44 +1,114 @@
1
1
  # Reze Engine
2
2
 
3
- A lightweight engine built with WebGPU and TypeScript for real-time 3D anime character MMD model rendering.
3
+ A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering, built with TypeScript.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install reze-engine
9
+ ```
4
10
 
5
11
  ## Features
6
12
 
7
13
  - Blinn-Phong lighting, alpha blending, rim lighting, outlines, MSAA 4x
8
- - Post alpha eye rendering (see-through eyes)
9
- - Bone and morph API, VMD animation (multiple named, non-interruptible), IK solver, Ammo/Bullet physics
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
10
19
  - Multi-model (per-model materials, IK, physics)
11
20
 
12
- ## Usage
21
+ ## Quick Start
13
22
 
14
23
  ```javascript
15
- import { Engine, Model } from "reze-engine"
24
+ import { Engine, Vec3 } from "reze-engine"
16
25
 
17
- const engine = new Engine(canvas, {})
26
+ const engine = new Engine(canvas, {
27
+ ambientColor: new Vec3(0.88, 0.92, 0.99),
28
+ cameraDistance: 31.5,
29
+ })
18
30
  await engine.init()
19
- const model = await engine.loadModel("/models/reze/reze.pmx")
20
- await model.loadAnimation("default", "/animations/dance.vmd")
21
- model.show("default")
31
+
32
+ const model = await engine.loadModel("hero", "/models/hero/hero.pmx")
33
+ await model.loadAnimation("idle", "/animations/idle.vmd")
34
+ model.show("idle")
22
35
  model.play()
23
- engine.runRenderLoop(() => {})
36
+
37
+ engine.setCameraFollow(model, "センター", new Vec3(0, 3.5, 0))
38
+ engine.addGround({ width: 160, height: 160 })
39
+ engine.runRenderLoop()
24
40
  ```
25
41
 
26
- ## API (summary)
42
+ ## API
43
+
44
+ ### Engine
45
+
46
+ | Method | Description |
47
+ |--------|-------------|
48
+ | `new Engine(canvas, options?)` | Create engine with optional config |
49
+ | `engine.init()` | Initialize WebGPU device and context |
50
+ | `engine.loadModel(path)` | Load PMX model (auto-named) |
51
+ | `engine.loadModel(name, path)` | Load PMX model with name |
52
+ | `engine.getModel(name)` | Get model by name |
53
+ | `engine.getModelNames()` | List all model names |
54
+ | `engine.removeModel(name)` | Remove model |
55
+ | `engine.setMaterialVisible(model, mat, visible)` | Show/hide material |
56
+ | `engine.toggleMaterialVisible(model, mat)` | Toggle material visibility |
57
+ | `engine.setIKEnabled(enabled)` | Enable/disable IK globally |
58
+ | `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 |
61
+ | `engine.setCameraTarget(vec3)` | Static camera target |
62
+ | `engine.setCameraDistance(d)` | Set orbit radius |
63
+ | `engine.setCameraAlpha(a)` | Set horizontal orbit angle |
64
+ | `engine.setCameraBeta(b)` | Set vertical orbit angle |
65
+ | `engine.addGround(options?)` | Add ground plane with shadows |
66
+ | `engine.runRenderLoop(callback?)` | Start render loop |
67
+ | `engine.stopRenderLoop()` | Stop render loop |
68
+ | `engine.getStats()` | Returns `{ fps, frameTime }` |
69
+ | `engine.dispose()` | Clean up all resources |
27
70
 
28
- - **Multi-model:** `engine.loadModel(path)` / `engine.loadModel(name, path)`, `engine.addModel(model, pmxPath, name?)`, `getModel(name)`, `getModelNames()`, `removeModel(name)`, `setMaterialVisible(modelName, materialName, visible)`, `setIKEnabled(enabled)`, `setPhysicsEnabled(enabled)`, `getIKEnabled()`, `getPhysicsEnabled()`, `resetPhysics()`, `markVertexBufferDirty(modelName?)`
29
- - **Animation:** `model.loadAnimation(name, vmdUrl)`; `model.show(name)`; `model.play()` / `model.play(name)`; `model.pause()`; `model.stop()`; `model.seek(t)`; `model.getAnimationProgress()`. Animations are non-interruptible (next is queued).
30
- - **Bones / morphs:** `model.rotateBones()`, `model.moveBones()`, `model.setMorphWeight()`, `model.resetAllBones()`, `model.resetAllMorphs()`
31
- - **Engine:** `runRenderLoop()`, `addGround({ mode: "reflection" | "shadow", ... })`, `onRaycast: (modelName, material, screenX, screenY) => {}`
71
+ ### Model
72
+
73
+ | Method | Description |
74
+ |--------|-------------|
75
+ | `model.loadAnimation(name, url)` | Load VMD animation |
76
+ | `model.show(name)` | Set pose at time 0 |
77
+ | `model.play(name?)` | Play animation (queued if busy) |
78
+ | `model.pause()` | Pause playback |
79
+ | `model.stop()` | Stop playback |
80
+ | `model.seek(time)` | Seek to time |
81
+ | `model.getAnimationProgress()` | `{ current, duration, percentage, animationName }` |
82
+ | `model.getAnimationState()` | Access animation controller |
83
+ | `model.rotateBones(rotations, ms?)` | Tween bone rotations |
84
+ | `model.moveBones(translations, ms?)` | Tween bone translations |
85
+ | `model.setMorphWeight(name, weight, ms?)` | Tween morph weight |
86
+ | `model.resetAllBones()` | Reset to bind pose |
87
+ | `model.resetAllMorphs()` | Reset all morph weights |
88
+ | `model.getBoneWorldPosition(name)` | World position of bone |
89
+
90
+ ### Engine Options
91
+
92
+ ```javascript
93
+ {
94
+ ambientColor: Vec3,
95
+ directionalLightIntensity: number,
96
+ minSpecularIntensity: number,
97
+ rimLightIntensity: number,
98
+ cameraDistance: number,
99
+ cameraTarget: Vec3,
100
+ cameraFov: number,
101
+ onRaycast: (modelName, material, screenX, screenY) => void,
102
+ }
103
+ ```
32
104
 
33
105
  ## Projects Using This Engine
34
106
 
35
- - **[MiKaPo](https://mikapo.vercel.app)** - Online real-time motion capture for MMD using webcam and MediaPipe
36
- - **[Popo](https://popo.love)** - Fine-tuned LLM that generates MMD poses from natural language descriptions
37
- - **[MPL](https://mmd-mpl.vercel.app)** - Semantic motion programming language for scripting MMD animations with intuitive syntax
38
- - **[Mixamo-MMD](https://mixamo-mmd.vercel.app)** - Retarget Mixamo FBX animation to VMD in one click
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
39
111
 
40
112
  ## Tutorial
41
113
 
42
- Learn WebGPU from scratch by building an anime character renderer in incremental steps. The tutorial covers the complete rendering pipeline from a simple triangle to fully textured, skeletal-animated characters.
43
-
44
114
  [How to Render an Anime Character with WebGPU](https://reze.one/tutorial)
package/dist/camera.d.ts CHANGED
@@ -31,6 +31,8 @@ export declare class Camera {
31
31
  getViewMatrix(): Mat4;
32
32
  private getCameraVectors;
33
33
  private panCamera;
34
+ /** Far plane grows with zoom-out so big floors / distant geometry stay visible */
35
+ private updateFarFromRadius;
34
36
  getProjectionMatrix(): Mat4;
35
37
  attachControl(canvas: HTMLCanvasElement): void;
36
38
  detachControl(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAInC,qBAAa,MAAM;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAI;IAClB,IAAI,EAAE,MAAM,CAAO;IACnB,GAAG,EAAE,MAAM,CAAM;IAGjB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,iBAAiB,CAAY;IACrC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,oBAAoB,CAAY;IAGxC,kBAAkB,EAAE,MAAM,CAAQ;IAClC,cAAc,EAAE,MAAM,CAAS;IAC/B,cAAc,EAAE,MAAM,CAAO;IAC7B,cAAc,EAAE,MAAM,CAAO;IAC7B,IAAI,EAAE,MAAM,CAAO;IACnB,IAAI,EAAE,MAAM,CAAM;IAClB,cAAc,EAAE,MAAM,CAAQ;IAC9B,cAAc,EAAE,MAAM,CAAkB;gBAE5B,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAE,MAAoB;IAkBhG,WAAW,IAAI,IAAI;IAQnB,aAAa,IAAI,IAAI;IAQrB,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,SAAS;IAiBjB,mBAAmB,IAAI,IAAI;IAI3B,aAAa,CAAC,MAAM,EAAE,iBAAiB;IAiBvC,aAAa;IAkBb,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,YAAY;IA6BpB,OAAO,CAAC,WAAW;IAiFnB,OAAO,CAAC,UAAU;CA+BnB"}
1
+ {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAMnC,qBAAa,MAAM;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAI;IAClB,IAAI,EAAE,MAAM,CAAO;IACnB,GAAG,EAAE,MAAM,CAAU;IAGrB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,iBAAiB,CAAY;IACrC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,oBAAoB,CAAY;IAGxC,kBAAkB,EAAE,MAAM,CAAQ;IAClC,cAAc,EAAE,MAAM,CAAS;IAC/B,cAAc,EAAE,MAAM,CAAO;IAC7B,cAAc,EAAE,MAAM,CAAO;IAC7B,IAAI,EAAE,MAAM,CAAO;IACnB,IAAI,EAAE,MAAM,CAAU;IACtB,cAAc,EAAE,MAAM,CAAQ;IAC9B,cAAc,EAAE,MAAM,CAAkB;gBAE5B,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAE,MAAoB;IAmBhG,WAAW,IAAI,IAAI;IAQnB,aAAa,IAAI,IAAI;IAQrB,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,SAAS;IAiBjB,kFAAkF;IAClF,OAAO,CAAC,mBAAmB;IAK3B,mBAAmB,IAAI,IAAI;IAK3B,aAAa,CAAC,MAAM,EAAE,iBAAiB;IAiBvC,aAAa;IAkBb,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,YAAY;IA6BpB,OAAO,CAAC,WAAW;IAgFnB,OAAO,CAAC,UAAU;CA+BnB"}
package/dist/camera.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { Mat4, Vec3 } from "./math";
2
- const FAR = 2000;
2
+ /** Far cap / zoom limit; large enough for wide shots without clipping distant ground */
3
+ const FAR_CAP = 8000;
4
+ const FAR_MIN = 200;
3
5
  export class Camera {
4
6
  constructor(alpha, beta, radius, target, fov = Math.PI / 4) {
5
7
  this.aspect = 1;
6
8
  this.near = 0.05;
7
- this.far = FAR;
9
+ this.far = FAR_CAP;
8
10
  // Input state
9
11
  this.canvas = null;
10
12
  this.isDragging = false;
@@ -22,7 +24,7 @@ export class Camera {
22
24
  this.wheelPrecision = 0.01;
23
25
  this.pinchPrecision = 0.05;
24
26
  this.minZ = 0.05;
25
- this.maxZ = FAR;
27
+ this.maxZ = FAR_CAP;
26
28
  this.lowerBetaLimit = 0.001;
27
29
  this.upperBetaLimit = Math.PI - 0.001;
28
30
  this.alpha = alpha;
@@ -30,6 +32,7 @@ export class Camera {
30
32
  this.radius = radius;
31
33
  this.target = target;
32
34
  this.fov = fov;
35
+ this.updateFarFromRadius();
33
36
  // Bind event handlers
34
37
  this.onMouseDown = this.onMouseDown.bind(this);
35
38
  this.onMouseMove = this.onMouseMove.bind(this);
@@ -103,7 +106,13 @@ export class Camera {
103
106
  // Update target position smoothly
104
107
  this.target = this.target.add(panRight).add(panUp);
105
108
  }
109
+ /** Far plane grows with zoom-out so big floors / distant geometry stay visible */
110
+ updateFarFromRadius() {
111
+ const margin = 600;
112
+ this.far = Math.min(FAR_CAP, Math.max(FAR_MIN, this.radius * 12 + margin));
113
+ }
106
114
  getProjectionMatrix() {
115
+ this.updateFarFromRadius();
107
116
  return Mat4.perspective(this.fov, this.aspect, this.near, this.far);
108
117
  }
109
118
  attachControl(canvas) {
@@ -168,8 +177,7 @@ export class Camera {
168
177
  this.radius += e.deltaY * this.wheelPrecision;
169
178
  // Clamp radius to reasonable bounds
170
179
  this.radius = Math.max(this.minZ, Math.min(this.maxZ, this.radius));
171
- // Expand far plane to keep scene visible when zooming out
172
- this.far = Math.max(FAR, this.radius * 4);
180
+ this.updateFarFromRadius();
173
181
  }
174
182
  onContextMenu(e) {
175
183
  e.preventDefault();
@@ -235,8 +243,7 @@ export class Camera {
235
243
  this.radius += delta * this.pinchPrecision;
236
244
  // Clamp radius to reasonable bounds
237
245
  this.radius = Math.max(this.minZ, Math.min(this.maxZ, this.radius));
238
- // Expand far plane for pinch zoom as well
239
- this.far = Math.max(FAR, this.radius * 4);
246
+ this.updateFarFromRadius();
240
247
  }
241
248
  if (isPanGesture) {
242
249
  // Primary gesture is pan (two-finger drag)
@@ -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
@@ -570,9 +570,11 @@ export class Engine {
570
570
  }
571
571
  let worldPos = skinnedPos.xyz;
572
572
  let worldNormal = normalize(skinnedNrm);
573
-
574
- let scaleFactor = 0.01;
575
- let expandedPos = worldPos + worldNormal * material.edgeSize * scaleFactor;
573
+ // Screen-stable edgeline: extrusion ∝ camera distance (same idea as MMD viewers / babylon-mmd-style scaling)
574
+ let camDist = max(length(camera.viewPos - worldPos), 0.25);
575
+ let refDist = 30.0;
576
+ let edgeScale = 0.03;
577
+ let expandedPos = worldPos + worldNormal * material.edgeSize * edgeScale * (camDist / refDist);
576
578
  output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
577
579
  return output;
578
580
  }
@@ -591,7 +593,8 @@ export class Engine {
591
593
  cullMode: "back",
592
594
  depthStencil: {
593
595
  format: "depth24plus-stencil8",
594
- depthWriteEnabled: true,
596
+ // Don’t write outline into depth buffer — stops z-fighting / black cracks vs body (MMD-style; body depth stays authoritative)
597
+ depthWriteEnabled: false,
595
598
  depthCompare: "less-equal",
596
599
  },
597
600
  });
@@ -217,8 +217,8 @@ export class PmxLoader {
217
217
  this.getFloat32(),
218
218
  this.getFloat32(),
219
219
  ];
220
- // edgeSize float
221
- const edgeSize = this.getFloat32() * 2; // double the size for better visibility
220
+ // edgeSize float (outline width in PMX units; engine scales by camera distance for stable screen thickness)
221
+ const edgeSize = this.getFloat32();
222
222
  const textureIndex = this.getNonVertexIndex(this.textureIndexSize);
223
223
  const sphereTextureIndex = this.getNonVertexIndex(this.textureIndexSize);
224
224
  const sphereTextureMode = this.getUint8();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.9.0",
4
- "description": "A WebGPU game engine for MMD/PMX models",
3
+ "version": "0.9.1",
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",
7
7
  "type": "module",
package/src/camera.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Mat4, Vec3 } from "./math"
2
2
 
3
- const FAR = 2000
3
+ /** Far cap / zoom limit; large enough for wide shots without clipping distant ground */
4
+ const FAR_CAP = 8000
5
+ const FAR_MIN = 200
4
6
 
5
7
  export class Camera {
6
8
  alpha: number
@@ -10,7 +12,7 @@ export class Camera {
10
12
  fov: number
11
13
  aspect: number = 1
12
14
  near: number = 0.05
13
- far: number = FAR
15
+ far: number = FAR_CAP
14
16
 
15
17
  // Input state
16
18
  private canvas: HTMLCanvasElement | null = null
@@ -30,7 +32,7 @@ export class Camera {
30
32
  wheelPrecision: number = 0.01
31
33
  pinchPrecision: number = 0.05
32
34
  minZ: number = 0.05
33
- maxZ: number = FAR
35
+ maxZ: number = FAR_CAP
34
36
  lowerBetaLimit: number = 0.001
35
37
  upperBetaLimit: number = Math.PI - 0.001
36
38
 
@@ -40,6 +42,7 @@ export class Camera {
40
42
  this.radius = radius
41
43
  this.target = target
42
44
  this.fov = fov
45
+ this.updateFarFromRadius()
43
46
 
44
47
  // Bind event handlers
45
48
  this.onMouseDown = this.onMouseDown.bind(this)
@@ -127,7 +130,14 @@ export class Camera {
127
130
  this.target = this.target.add(panRight).add(panUp)
128
131
  }
129
132
 
133
+ /** Far plane grows with zoom-out so big floors / distant geometry stay visible */
134
+ private updateFarFromRadius(): void {
135
+ const margin = 600
136
+ this.far = Math.min(FAR_CAP, Math.max(FAR_MIN, this.radius * 12 + margin))
137
+ }
138
+
130
139
  getProjectionMatrix(): Mat4 {
140
+ this.updateFarFromRadius()
131
141
  return Mat4.perspective(this.fov, this.aspect, this.near, this.far)
132
142
  }
133
143
 
@@ -206,8 +216,7 @@ export class Camera {
206
216
 
207
217
  // Clamp radius to reasonable bounds
208
218
  this.radius = Math.max(this.minZ, Math.min(this.maxZ, this.radius))
209
- // Expand far plane to keep scene visible when zooming out
210
- this.far = Math.max(FAR, this.radius * 4)
219
+ this.updateFarFromRadius()
211
220
  }
212
221
 
213
222
  private onContextMenu(e: Event) {
@@ -285,8 +294,7 @@ export class Camera {
285
294
 
286
295
  // Clamp radius to reasonable bounds
287
296
  this.radius = Math.max(this.minZ, Math.min(this.maxZ, this.radius))
288
- // Expand far plane for pinch zoom as well
289
- this.far = Math.max(FAR, this.radius * 4)
297
+ this.updateFarFromRadius()
290
298
  }
291
299
 
292
300
  if (isPanGesture) {
package/src/engine.ts CHANGED
@@ -696,9 +696,11 @@ export class Engine {
696
696
  }
697
697
  let worldPos = skinnedPos.xyz;
698
698
  let worldNormal = normalize(skinnedNrm);
699
-
700
- let scaleFactor = 0.01;
701
- let expandedPos = worldPos + worldNormal * material.edgeSize * scaleFactor;
699
+ // Screen-stable edgeline: extrusion ∝ camera distance (same idea as MMD viewers / babylon-mmd-style scaling)
700
+ let camDist = max(length(camera.viewPos - worldPos), 0.25);
701
+ let refDist = 30.0;
702
+ let edgeScale = 0.03;
703
+ let expandedPos = worldPos + worldNormal * material.edgeSize * edgeScale * (camDist / refDist);
702
704
  output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
703
705
  return output;
704
706
  }
@@ -718,7 +720,8 @@ export class Engine {
718
720
  cullMode: "back",
719
721
  depthStencil: {
720
722
  format: "depth24plus-stencil8",
721
- depthWriteEnabled: true,
723
+ // Don’t write outline into depth buffer — stops z-fighting / black cracks vs body (MMD-style; body depth stays authoritative)
724
+ depthWriteEnabled: false,
722
725
  depthCompare: "less-equal",
723
726
  },
724
727
  })
package/src/pmx-loader.ts CHANGED
@@ -261,8 +261,8 @@ export class PmxLoader {
261
261
  this.getFloat32(),
262
262
  this.getFloat32(),
263
263
  ]
264
- // edgeSize float
265
- const edgeSize = this.getFloat32()*2 // double the size for better visibility
264
+ // edgeSize float (outline width in PMX units; engine scales by camera distance for stable screen thickness)
265
+ const edgeSize = this.getFloat32()
266
266
 
267
267
  const textureIndex = this.getNonVertexIndex(this.textureIndexSize)
268
268
  const sphereTextureIndex = this.getNonVertexIndex(this.textureIndexSize)