reze-engine 0.8.2 → 0.8.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 +4 -3
- package/dist/animation.d.ts +0 -17
- package/dist/animation.d.ts.map +1 -1
- package/dist/animation.js +1 -13
- package/dist/engine.d.ts +0 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -2
- package/dist/model.d.ts +2 -14
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +11 -28
- package/package.json +1 -1
- package/src/animation.ts +1 -21
- package/src/engine.ts +1 -2
- package/src/model.ts +13 -33
package/README.md
CHANGED
|
@@ -16,8 +16,9 @@ import { Engine, Model } from "reze-engine"
|
|
|
16
16
|
|
|
17
17
|
const engine = new Engine(canvas, {})
|
|
18
18
|
await engine.init()
|
|
19
|
-
const model = await Model.
|
|
20
|
-
await model.
|
|
19
|
+
const model = await Model.loadFrom("/models/reze/reze.pmx")
|
|
20
|
+
await model.loadAnimation("default", "/animations/dance.vmd")
|
|
21
|
+
model.show("default")
|
|
21
22
|
model.playAnimation()
|
|
22
23
|
engine.runRenderLoop(() => {})
|
|
23
24
|
```
|
|
@@ -25,7 +26,7 @@ engine.runRenderLoop(() => {})
|
|
|
25
26
|
## API (summary)
|
|
26
27
|
|
|
27
28
|
- **Multi-model:** `engine.addModel(model, pmxPath, name?)`, `getModel(name)`, `getModelNames()`, `removeModel(name)`, `setMaterialVisible(modelName, materialName, visible)`, `setModelIKEnabled(modelName, enabled)`, `setModelPhysicsEnabled(modelName, enabled)`, `resetPhysics()`, `markVertexBufferDirty(modelName?)`
|
|
28
|
-
- **Animation:** `model.
|
|
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).
|
|
29
30
|
- **Bones / morphs:** `model.rotateBones()`, `model.moveBones()`, `model.setMorphWeight()`, `model.resetAllBones()`, `model.resetAllMorphs()`
|
|
30
31
|
- **Engine:** `runRenderLoop()`, `addGround({ mode: "reflection" | "shadow", ... })`, `onRaycast: (modelName, material, screenX, screenY) => {}`
|
|
31
32
|
|
package/dist/animation.d.ts
CHANGED
|
@@ -23,39 +23,24 @@ export interface MorphKeyframe {
|
|
|
23
23
|
weight: number;
|
|
24
24
|
time: number;
|
|
25
25
|
}
|
|
26
|
-
/** Immutable clip data for one animation (e.g. one VMD). */
|
|
27
26
|
export interface AnimationClip {
|
|
28
27
|
boneTracks: Map<string, BoneKeyframe[]>;
|
|
29
28
|
morphTracks: Map<string, MorphKeyframe[]>;
|
|
30
29
|
duration: number;
|
|
31
|
-
/** When true, clip loops at end. When false, playback stops and onEnd fires. Default false. */
|
|
32
30
|
loop?: boolean;
|
|
33
31
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Per-model animation state: multiple animations, non-interruptible playback.
|
|
36
|
-
* While one is playing, play(name) queues it to start when the current one finishes.
|
|
37
|
-
*/
|
|
38
32
|
export declare class AnimationState {
|
|
39
33
|
private animations;
|
|
40
34
|
private currentAnimationName;
|
|
41
35
|
private currentTime;
|
|
42
36
|
private isPlaying;
|
|
43
37
|
private isPaused;
|
|
44
|
-
/** When current (non-loop) ends, play this next. Cleared when started. */
|
|
45
38
|
private nextAnimationName;
|
|
46
39
|
private onEnd;
|
|
47
|
-
/** Add or replace an animation by name. Does not start playback. */
|
|
48
40
|
loadAnimation(name: string, clip: AnimationClip): void;
|
|
49
|
-
/** Remove an animation. If it was current, state is cleared. */
|
|
50
41
|
removeAnimation(name: string): void;
|
|
51
|
-
/**
|
|
52
|
-
* Start playing an animation by name. Non-interruptible: if one is already playing,
|
|
53
|
-
* this animation is queued to start when the current one finishes.
|
|
54
|
-
*/
|
|
55
42
|
play(name: string): boolean;
|
|
56
|
-
/** Resume current animation (no-op if none). */
|
|
57
43
|
play(): void;
|
|
58
|
-
/** Advance time. When a non-loop clip ends, starts nextAnimationName if set. */
|
|
59
44
|
update(deltaTime: number): {
|
|
60
45
|
ended: boolean;
|
|
61
46
|
animationName: string | null;
|
|
@@ -67,7 +52,6 @@ export declare class AnimationState {
|
|
|
67
52
|
getCurrentAnimation(): string | null;
|
|
68
53
|
getCurrentTime(): number;
|
|
69
54
|
getDuration(): number;
|
|
70
|
-
/** Progress of the current animation (time, duration, percentage). */
|
|
71
55
|
getProgress(): {
|
|
72
56
|
animationName: string | null;
|
|
73
57
|
current: number;
|
|
@@ -76,7 +60,6 @@ export declare class AnimationState {
|
|
|
76
60
|
};
|
|
77
61
|
getAnimationNames(): string[];
|
|
78
62
|
hasAnimation(name: string): boolean;
|
|
79
|
-
/** Show animation at time 0 without playing. Use after load when you want to play later (e.g. dance visualization). */
|
|
80
63
|
show(name: string): void;
|
|
81
64
|
setOnEnd(callback: ((animationName: string) => void) | null): void;
|
|
82
65
|
getPlaying(): boolean;
|
package/dist/animation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../src/animation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAEnC,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,YAAY,EAAE,YAAY,EAAE,CAAA;IAC5B,YAAY,EAAE,YAAY,EAAE,CAAA;IAC5B,YAAY,EAAE,YAAY,EAAE,CAAA;CAC7B;
|
|
1
|
+
{"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../src/animation.ts"],"names":[],"mappings":"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"}
|
package/dist/animation.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Per-model animation state: multiple animations, non-interruptible playback.
|
|
3
|
-
* While one is playing, play(name) queues it to start when the current one finishes.
|
|
4
|
-
*/
|
|
1
|
+
// Non-interruptible playback; play(name) while playing queues one next.
|
|
5
2
|
export class AnimationState {
|
|
6
3
|
constructor() {
|
|
7
4
|
this.animations = new Map();
|
|
@@ -9,15 +6,12 @@ export class AnimationState {
|
|
|
9
6
|
this.currentTime = 0;
|
|
10
7
|
this.isPlaying = false;
|
|
11
8
|
this.isPaused = false;
|
|
12
|
-
/** When current (non-loop) ends, play this next. Cleared when started. */
|
|
13
9
|
this.nextAnimationName = null;
|
|
14
10
|
this.onEnd = null;
|
|
15
11
|
}
|
|
16
|
-
/** Add or replace an animation by name. Does not start playback. */
|
|
17
12
|
loadAnimation(name, clip) {
|
|
18
13
|
this.animations.set(name, clip);
|
|
19
14
|
}
|
|
20
|
-
/** Remove an animation. If it was current, state is cleared. */
|
|
21
15
|
removeAnimation(name) {
|
|
22
16
|
this.animations.delete(name);
|
|
23
17
|
if (this.currentAnimationName === name) {
|
|
@@ -51,7 +45,6 @@ export class AnimationState {
|
|
|
51
45
|
this.nextAnimationName = null;
|
|
52
46
|
return true;
|
|
53
47
|
}
|
|
54
|
-
/** Advance time. When a non-loop clip ends, starts nextAnimationName if set. */
|
|
55
48
|
update(deltaTime) {
|
|
56
49
|
if (!this.isPlaying || this.isPaused || this.currentAnimationName === null) {
|
|
57
50
|
return { ended: false, animationName: this.currentAnimationName };
|
|
@@ -111,7 +104,6 @@ export class AnimationState {
|
|
|
111
104
|
const clip = this.getCurrentClip();
|
|
112
105
|
return clip ? clip.duration : 0;
|
|
113
106
|
}
|
|
114
|
-
/** Progress of the current animation (time, duration, percentage). */
|
|
115
107
|
getProgress() {
|
|
116
108
|
const clip = this.getCurrentClip();
|
|
117
109
|
const duration = clip ? clip.duration : 0;
|
|
@@ -129,7 +121,6 @@ export class AnimationState {
|
|
|
129
121
|
hasAnimation(name) {
|
|
130
122
|
return this.animations.has(name);
|
|
131
123
|
}
|
|
132
|
-
/** Show animation at time 0 without playing. Use after load when you want to play later (e.g. dance visualization). */
|
|
133
124
|
show(name) {
|
|
134
125
|
if (!this.animations.has(name))
|
|
135
126
|
return;
|
|
@@ -149,7 +140,6 @@ export class AnimationState {
|
|
|
149
140
|
return this.isPaused;
|
|
150
141
|
}
|
|
151
142
|
}
|
|
152
|
-
// Cubic bezier in normalized 0–1 space (binary search on x)
|
|
153
143
|
export function bezierInterpolate(x1, x2, y1, y2, t) {
|
|
154
144
|
t = Math.max(0, Math.min(1, t));
|
|
155
145
|
let start = 0;
|
|
@@ -172,7 +162,6 @@ export function bezierInterpolate(x1, x2, y1, y2, t) {
|
|
|
172
162
|
return y;
|
|
173
163
|
}
|
|
174
164
|
const INV_127 = 1 / 127;
|
|
175
|
-
// VMD 64-byte interpolation blob → BoneInterpolation
|
|
176
165
|
export function rawInterpolationToBoneInterpolation(raw) {
|
|
177
166
|
return {
|
|
178
167
|
rotation: [
|
|
@@ -193,7 +182,6 @@ export function rawInterpolationToBoneInterpolation(raw) {
|
|
|
193
182
|
],
|
|
194
183
|
};
|
|
195
184
|
}
|
|
196
|
-
// Control points are 0–127 VMD bytes
|
|
197
185
|
export function interpolateControlPoints(cp, t) {
|
|
198
186
|
return bezierInterpolate(cp[0].x * INV_127, cp[1].x * INV_127, cp[0].y * INV_127, cp[1].y * INV_127, t);
|
|
199
187
|
}
|
package/dist/engine.d.ts
CHANGED
|
@@ -85,7 +85,6 @@ export declare class Engine {
|
|
|
85
85
|
private modelInstances;
|
|
86
86
|
private materialSampler;
|
|
87
87
|
private textureCache;
|
|
88
|
-
/** Reusable buffer for raycast skinning to avoid per-instance allocations (Three.js/Babylon.js style). */
|
|
89
88
|
private raycastVertexBuffer;
|
|
90
89
|
private lastFpsUpdate;
|
|
91
90
|
private framesSinceLastUpdate;
|
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,EAAc,IAAI,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAG/B,MAAM,MAAM,eAAe,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;AAEpH,MAAM,MAAM,aAAa,GAAG;IAC1B,YAAY,CAAC,EAAE,IAAI,CAAA;IACnB,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,IAAI,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,gBAAgB,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,GAAG,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAA;AAEjH,eAAO,MAAM,sBAAsB,EAAE,qBAUpC,CAAA;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;CAClB;AAuCD,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAsB;WAE/B,WAAW,IAAI,MAAM;IAOnC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,kBAAkB,CAAmB;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,mBAAmB,CAAY;IACvC,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,kBAAkB,CAAY;IACtC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,YAAY,CAAa;IAEjC,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,oBAAoB,CAAoB;IAChD,OAAO,CAAC,uBAAuB,CAAoB;IACnD,OAAO,CAAC,iBAAiB,CAAoB;IAE7C,OAAO,CAAC,cAAc,CAAoB;IAC1C,OAAO,CAAC,qBAAqB,CAAqB;IAClD,OAAO,CAAC,kBAAkB,CAAoB;IAE9C,OAAO,CAAC,eAAe,CAAoB;IAC3C,OAAO,CAAC,mBAAmB,CAAoB;IAC/C,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,sBAAsB,CAAqB;IACnD,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,WAAW,CAAW;IAC9B,OAAO,CAAC,oBAAoB,CAA0B;IAEtD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAI;IAGtC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,yBAAyB,CAAS;IAC1C,OAAO,CAAC,oBAAoB,CAAS;IAErC,OAAO,CAAC,iBAAiB,CAAS;IAGlC,OAAO,CAAC,kBAAkB,CAAC,CAAW;IACtC,OAAO,CAAC,iBAAiB,CAAC,CAAW;IACrC,OAAO,CAAC,uBAAuB,CAAC,CAAY;IAC5C,OAAO,CAAC,8BAA8B,CAAC,CAAY;IACnD,OAAO,CAAC,4BAA4B,CAAC,CAAY;IACjD,OAAO,CAAC,yBAAyB,CAAC,CAAc;IAChD,OAAO,CAAC,2BAA2B,CAAC,CAAW;IAC/C,OAAO,CAAC,oBAAoB,CAAQ;IACpC,OAAO,CAAC,UAAU,CAAwC;IAC1D,OAAO,CAAC,gBAAgB,CAAC,CAAY;IACrC,OAAO,CAAC,kBAAkB,CAAC,CAAgB;IAC3C,OAAO,CAAC,mBAAmB,CAAoB;IAC/C,OAAO,CAAC,mBAAmB,CAAY;IACvC,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,oBAAoB,CAAoB;IAChD,OAAO,CAAC,2BAA2B,CAAqB;IACxD,OAAO,CAAC,qBAAqB,CAAC,CAAc;IAC5C,OAAO,CAAC,uBAAuB,CAAa;IAC5C,OAAO,CAAC,0BAA0B,CAAC,CAAW;IAC9C,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,cAAc,CAAa;IAEnC,OAAO,CAAC,SAAS,CAAC,CAAiB;IAEnC,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAEvC,OAAO,CAAC,cAAc,CAAmC;IACzD,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,YAAY,CAAgC;IACpD,
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.js
CHANGED
|
@@ -15,7 +15,7 @@ export const DEFAULT_ENGINE_OPTIONS = {
|
|
|
15
15
|
export class Engine {
|
|
16
16
|
static getInstance() {
|
|
17
17
|
if (!Engine.instance) {
|
|
18
|
-
throw new Error("Engine not ready: create Engine, await init(), then load models via Model.
|
|
18
|
+
throw new Error("Engine not ready: create Engine, await init(), then load models via Model.loadFrom().");
|
|
19
19
|
}
|
|
20
20
|
return Engine.instance;
|
|
21
21
|
}
|
|
@@ -39,7 +39,6 @@ export class Engine {
|
|
|
39
39
|
this.DOUBLE_TAP_DELAY = 300; // ms
|
|
40
40
|
this.modelInstances = new Map();
|
|
41
41
|
this.textureCache = new Map();
|
|
42
|
-
/** Reusable buffer for raycast skinning to avoid per-instance allocations (Three.js/Babylon.js style). */
|
|
43
42
|
this.raycastVertexBuffer = null;
|
|
44
43
|
this.lastFpsUpdate = performance.now();
|
|
45
44
|
this.framesSinceLastUpdate = 0;
|
package/dist/model.d.ts
CHANGED
|
@@ -97,7 +97,8 @@ export interface MorphRuntime {
|
|
|
97
97
|
export declare class Model {
|
|
98
98
|
private static _nextId;
|
|
99
99
|
private static nextDefaultName;
|
|
100
|
-
static
|
|
100
|
+
static loadFrom(path: string): Promise<Model>;
|
|
101
|
+
static loadFrom(name: string, path: string): Promise<Model>;
|
|
101
102
|
private _name;
|
|
102
103
|
get name(): string;
|
|
103
104
|
setName(value: string): void;
|
|
@@ -152,35 +153,23 @@ export declare class Model {
|
|
|
152
153
|
getSkinMatrices(): Float32Array;
|
|
153
154
|
setMorphWeight(name: string, weight: number, durationMs?: number): void;
|
|
154
155
|
private applyMorphs;
|
|
155
|
-
/** Build an AnimationClip from VMD keyframes (used by loadVmd and loadAnimation). */
|
|
156
156
|
private buildClipFromVmdKeyFrames;
|
|
157
|
-
/** Load one VMD as the "default" animation and show first frame. Does not auto-play; call playAnimation() when needed. */
|
|
158
|
-
loadVmd(vmdUrl: string): Promise<void>;
|
|
159
|
-
/** Load a VMD as a named animation (e.g. "idle", "walk", "attack"). Does not start playback. */
|
|
160
157
|
loadAnimation(animationName: string, vmdUrl: string): Promise<void>;
|
|
161
158
|
resetAllBones(): void;
|
|
162
159
|
resetAllMorphs(): void;
|
|
163
160
|
setIKEnabled(enabled: boolean): void;
|
|
164
161
|
setPhysicsEnabled(enabled: boolean): void;
|
|
165
162
|
getPhysicsEnabled(): boolean;
|
|
166
|
-
/** Low-level access to animation state when needed. Prefer model.play(), model.show(), etc. */
|
|
167
163
|
getAnimationState(): AnimationState;
|
|
168
|
-
/** Resume current animation (no-op if none). */
|
|
169
164
|
play(): void;
|
|
170
|
-
/** Play named animation; if one is already playing, it is queued. Returns false if name not loaded. */
|
|
171
165
|
play(name: string): boolean;
|
|
172
|
-
/** Show named animation at time 0 without playing. Use after load when you want to play later. */
|
|
173
166
|
show(name: string): void;
|
|
174
|
-
/** @deprecated Use model.play() */
|
|
175
167
|
playAnimation(): void;
|
|
176
168
|
pause(): void;
|
|
177
|
-
/** @deprecated Use model.pause() */
|
|
178
169
|
pauseAnimation(): void;
|
|
179
170
|
stop(): void;
|
|
180
|
-
/** @deprecated Use model.stop() */
|
|
181
171
|
stopAnimation(): void;
|
|
182
172
|
seek(time: number): void;
|
|
183
|
-
/** @deprecated Use model.seek() */
|
|
184
173
|
seekAnimation(time: number): void;
|
|
185
174
|
getAnimationProgress(): {
|
|
186
175
|
current: number;
|
|
@@ -190,7 +179,6 @@ export declare class Model {
|
|
|
190
179
|
};
|
|
191
180
|
private static upperBound;
|
|
192
181
|
private findKeyframeIndex;
|
|
193
|
-
/** Apply pose from a clip at the given time. No-op if clip is null. */
|
|
194
182
|
private applyPoseFromClip;
|
|
195
183
|
update(deltaTime: number): boolean;
|
|
196
184
|
private solveIKChains;
|
package/dist/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
package/dist/model.js
CHANGED
|
@@ -10,10 +10,12 @@ export class Model {
|
|
|
10
10
|
static nextDefaultName() {
|
|
11
11
|
return "model_" + Model._nextId++;
|
|
12
12
|
}
|
|
13
|
-
static async
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
await
|
|
13
|
+
static async loadFrom(nameOrPath, path) {
|
|
14
|
+
const name = path === undefined ? Model.nextDefaultName() : nameOrPath;
|
|
15
|
+
const pmxPath = path === undefined ? nameOrPath : path;
|
|
16
|
+
const model = await PmxLoader.load(pmxPath);
|
|
17
|
+
model.setName(name);
|
|
18
|
+
await Engine.getInstance().registerModel(model, pmxPath);
|
|
17
19
|
return model;
|
|
18
20
|
}
|
|
19
21
|
get name() {
|
|
@@ -455,7 +457,7 @@ export class Model {
|
|
|
455
457
|
Engine.getInstance().markVertexBufferDirty(this);
|
|
456
458
|
}
|
|
457
459
|
catch {
|
|
458
|
-
|
|
460
|
+
// not registered yet
|
|
459
461
|
}
|
|
460
462
|
return;
|
|
461
463
|
}
|
|
@@ -537,7 +539,6 @@ export class Model {
|
|
|
537
539
|
}
|
|
538
540
|
}
|
|
539
541
|
}
|
|
540
|
-
/** Build an AnimationClip from VMD keyframes (used by loadVmd and loadAnimation). */
|
|
541
542
|
buildClipFromVmdKeyFrames(vmdKeyFrames) {
|
|
542
543
|
const boneTracksByBone = {};
|
|
543
544
|
for (const keyFrame of vmdKeyFrames) {
|
|
@@ -595,17 +596,6 @@ export class Model {
|
|
|
595
596
|
}
|
|
596
597
|
return { boneTracks, morphTracks, duration: maxTime };
|
|
597
598
|
}
|
|
598
|
-
/** Load one VMD as the "default" animation and show first frame. Does not auto-play; call playAnimation() when needed. */
|
|
599
|
-
async loadVmd(vmdUrl) {
|
|
600
|
-
const vmdKeyFrames = await VMDLoader.load(vmdUrl);
|
|
601
|
-
this.resetAllBones();
|
|
602
|
-
this.resetAllMorphs();
|
|
603
|
-
const clip = this.buildClipFromVmdKeyFrames(vmdKeyFrames);
|
|
604
|
-
this.animationState.loadAnimation("default", clip);
|
|
605
|
-
this.animationState.show("default");
|
|
606
|
-
this.applyPoseFromClip(this.animationState.getCurrentClip(), 0);
|
|
607
|
-
}
|
|
608
|
-
/** Load a VMD as a named animation (e.g. "idle", "walk", "attack"). Does not start playback. */
|
|
609
599
|
async loadAnimation(animationName, vmdUrl) {
|
|
610
600
|
const vmdKeyFrames = await VMDLoader.load(vmdUrl);
|
|
611
601
|
const clip = this.buildClipFromVmdKeyFrames(vmdKeyFrames);
|
|
@@ -615,7 +605,6 @@ export class Model {
|
|
|
615
605
|
for (let boneIdx = 0; boneIdx < this.skeleton.bones.length; boneIdx++) {
|
|
616
606
|
const localRot = this.runtimeSkeleton.localRotations[boneIdx];
|
|
617
607
|
const localTrans = this.runtimeSkeleton.localTranslations[boneIdx];
|
|
618
|
-
// Reset to default pose: identity rotation and zero translation (like initial PMX state)
|
|
619
608
|
localRot.set(Quat.identity());
|
|
620
609
|
localTrans.set(Vec3.zeros());
|
|
621
610
|
}
|
|
@@ -638,7 +627,6 @@ export class Model {
|
|
|
638
627
|
getPhysicsEnabled() {
|
|
639
628
|
return this.physicsEnabled;
|
|
640
629
|
}
|
|
641
|
-
/** Low-level access to animation state when needed. Prefer model.play(), model.show(), etc. */
|
|
642
630
|
getAnimationState() {
|
|
643
631
|
return this.animationState;
|
|
644
632
|
}
|
|
@@ -649,32 +637,31 @@ export class Model {
|
|
|
649
637
|
}
|
|
650
638
|
return this.animationState.play(name);
|
|
651
639
|
}
|
|
652
|
-
/** Show named animation at time 0 without playing. Use after load when you want to play later. */
|
|
653
640
|
show(name) {
|
|
654
641
|
this.animationState.show(name);
|
|
655
642
|
}
|
|
656
|
-
|
|
643
|
+
// @deprecated Use model.play()
|
|
657
644
|
playAnimation() {
|
|
658
645
|
this.animationState.play();
|
|
659
646
|
}
|
|
660
647
|
pause() {
|
|
661
648
|
this.animationState.pause();
|
|
662
649
|
}
|
|
663
|
-
|
|
650
|
+
// @deprecated Use model.pause()
|
|
664
651
|
pauseAnimation() {
|
|
665
652
|
this.animationState.pause();
|
|
666
653
|
}
|
|
667
654
|
stop() {
|
|
668
655
|
this.animationState.stop();
|
|
669
656
|
}
|
|
670
|
-
|
|
657
|
+
// @deprecated Use model.stop()
|
|
671
658
|
stopAnimation() {
|
|
672
659
|
this.animationState.stop();
|
|
673
660
|
}
|
|
674
661
|
seek(time) {
|
|
675
662
|
this.animationState.seek(time);
|
|
676
663
|
}
|
|
677
|
-
|
|
664
|
+
// @deprecated Use model.seek()
|
|
678
665
|
seekAnimation(time) {
|
|
679
666
|
this.animationState.seek(time);
|
|
680
667
|
}
|
|
@@ -696,20 +683,16 @@ export class Model {
|
|
|
696
683
|
findKeyframeIndex(time, keyFrames, cachedIdx) {
|
|
697
684
|
if (keyFrames.length === 0)
|
|
698
685
|
return -1;
|
|
699
|
-
// Check if cached index is still valid (time is within the cached frame range)
|
|
700
686
|
if (cachedIdx >= 0 && cachedIdx < keyFrames.length) {
|
|
701
687
|
const frameTime = keyFrames[cachedIdx].time;
|
|
702
688
|
const nextFrameTime = cachedIdx + 1 < keyFrames.length ? keyFrames[cachedIdx + 1].time : Infinity;
|
|
703
|
-
// If time is within [frameTime, nextFrameTime), use cached index
|
|
704
689
|
if (time >= frameTime && time < nextFrameTime) {
|
|
705
690
|
return cachedIdx;
|
|
706
691
|
}
|
|
707
692
|
}
|
|
708
|
-
// Fall back to binary search
|
|
709
693
|
const idx = Model.upperBound(time, keyFrames, 0) - 1;
|
|
710
694
|
return idx;
|
|
711
695
|
}
|
|
712
|
-
/** Apply pose from a clip at the given time. No-op if clip is null. */
|
|
713
696
|
applyPoseFromClip(clip, time) {
|
|
714
697
|
if (!clip)
|
|
715
698
|
return;
|
package/package.json
CHANGED
package/src/animation.ts
CHANGED
|
@@ -12,7 +12,6 @@ export interface BoneInterpolation {
|
|
|
12
12
|
translationZ: ControlPoint[]
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
// Keyframe types for animation clips (used by Model and AnimationState)
|
|
16
15
|
export interface BoneKeyframe {
|
|
17
16
|
boneName: string
|
|
18
17
|
frame: number
|
|
@@ -29,35 +28,27 @@ export interface MorphKeyframe {
|
|
|
29
28
|
time: number
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
/** Immutable clip data for one animation (e.g. one VMD). */
|
|
33
31
|
export interface AnimationClip {
|
|
34
32
|
boneTracks: Map<string, BoneKeyframe[]>
|
|
35
33
|
morphTracks: Map<string, MorphKeyframe[]>
|
|
36
34
|
duration: number
|
|
37
|
-
/** When true, clip loops at end. When false, playback stops and onEnd fires. Default false. */
|
|
38
35
|
loop?: boolean
|
|
39
36
|
}
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
* Per-model animation state: multiple animations, non-interruptible playback.
|
|
43
|
-
* While one is playing, play(name) queues it to start when the current one finishes.
|
|
44
|
-
*/
|
|
38
|
+
// Non-interruptible playback; play(name) while playing queues one next.
|
|
45
39
|
export class AnimationState {
|
|
46
40
|
private animations = new Map<string, AnimationClip>()
|
|
47
41
|
private currentAnimationName: string | null = null
|
|
48
42
|
private currentTime = 0
|
|
49
43
|
private isPlaying = false
|
|
50
44
|
private isPaused = false
|
|
51
|
-
/** When current (non-loop) ends, play this next. Cleared when started. */
|
|
52
45
|
private nextAnimationName: string | null = null
|
|
53
46
|
private onEnd: ((animationName: string) => void) | null = null
|
|
54
47
|
|
|
55
|
-
/** Add or replace an animation by name. Does not start playback. */
|
|
56
48
|
loadAnimation(name: string, clip: AnimationClip): void {
|
|
57
49
|
this.animations.set(name, clip)
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
/** Remove an animation. If it was current, state is cleared. */
|
|
61
52
|
removeAnimation(name: string): void {
|
|
62
53
|
this.animations.delete(name)
|
|
63
54
|
if (this.currentAnimationName === name) {
|
|
@@ -70,12 +61,7 @@ export class AnimationState {
|
|
|
70
61
|
}
|
|
71
62
|
}
|
|
72
63
|
|
|
73
|
-
/**
|
|
74
|
-
* Start playing an animation by name. Non-interruptible: if one is already playing,
|
|
75
|
-
* this animation is queued to start when the current one finishes.
|
|
76
|
-
*/
|
|
77
64
|
play(name: string): boolean
|
|
78
|
-
/** Resume current animation (no-op if none). */
|
|
79
65
|
play(): void
|
|
80
66
|
play(name?: string): boolean | void {
|
|
81
67
|
if (name === undefined) {
|
|
@@ -98,7 +84,6 @@ export class AnimationState {
|
|
|
98
84
|
return true
|
|
99
85
|
}
|
|
100
86
|
|
|
101
|
-
/** Advance time. When a non-loop clip ends, starts nextAnimationName if set. */
|
|
102
87
|
update(deltaTime: number): { ended: boolean; animationName: string | null } {
|
|
103
88
|
if (!this.isPlaying || this.isPaused || this.currentAnimationName === null) {
|
|
104
89
|
return { ended: false, animationName: this.currentAnimationName }
|
|
@@ -166,7 +151,6 @@ export class AnimationState {
|
|
|
166
151
|
return clip ? clip.duration : 0
|
|
167
152
|
}
|
|
168
153
|
|
|
169
|
-
/** Progress of the current animation (time, duration, percentage). */
|
|
170
154
|
getProgress(): { animationName: string | null; current: number; duration: number; percentage: number } {
|
|
171
155
|
const clip = this.getCurrentClip()
|
|
172
156
|
const duration = clip ? clip.duration : 0
|
|
@@ -187,7 +171,6 @@ export class AnimationState {
|
|
|
187
171
|
return this.animations.has(name)
|
|
188
172
|
}
|
|
189
173
|
|
|
190
|
-
/** Show animation at time 0 without playing. Use after load when you want to play later (e.g. dance visualization). */
|
|
191
174
|
show(name: string): void {
|
|
192
175
|
if (!this.animations.has(name)) return
|
|
193
176
|
this.currentAnimationName = name
|
|
@@ -210,7 +193,6 @@ export class AnimationState {
|
|
|
210
193
|
}
|
|
211
194
|
}
|
|
212
195
|
|
|
213
|
-
// Cubic bezier in normalized 0–1 space (binary search on x)
|
|
214
196
|
export function bezierInterpolate(x1: number, x2: number, y1: number, y2: number, t: number): number {
|
|
215
197
|
t = Math.max(0, Math.min(1, t))
|
|
216
198
|
|
|
@@ -241,7 +223,6 @@ export function bezierInterpolate(x1: number, x2: number, y1: number, y2: number
|
|
|
241
223
|
|
|
242
224
|
const INV_127 = 1 / 127
|
|
243
225
|
|
|
244
|
-
// VMD 64-byte interpolation blob → BoneInterpolation
|
|
245
226
|
export function rawInterpolationToBoneInterpolation(raw: Uint8Array): BoneInterpolation {
|
|
246
227
|
return {
|
|
247
228
|
rotation: [
|
|
@@ -263,7 +244,6 @@ export function rawInterpolationToBoneInterpolation(raw: Uint8Array): BoneInterp
|
|
|
263
244
|
}
|
|
264
245
|
}
|
|
265
246
|
|
|
266
|
-
// Control points are 0–127 VMD bytes
|
|
267
247
|
export function interpolateControlPoints(cp: ControlPoint[], t: number): number {
|
|
268
248
|
return bezierInterpolate(
|
|
269
249
|
cp[0].x * INV_127,
|
package/src/engine.ts
CHANGED
|
@@ -78,7 +78,7 @@ export class Engine {
|
|
|
78
78
|
|
|
79
79
|
public static getInstance(): Engine {
|
|
80
80
|
if (!Engine.instance) {
|
|
81
|
-
throw new Error("Engine not ready: create Engine, await init(), then load models via Model.
|
|
81
|
+
throw new Error("Engine not ready: create Engine, await init(), then load models via Model.loadFrom().")
|
|
82
82
|
}
|
|
83
83
|
return Engine.instance
|
|
84
84
|
}
|
|
@@ -159,7 +159,6 @@ export class Engine {
|
|
|
159
159
|
private modelInstances = new Map<string, ModelInstance>()
|
|
160
160
|
private materialSampler!: GPUSampler
|
|
161
161
|
private textureCache = new Map<string, GPUTexture>()
|
|
162
|
-
/** Reusable buffer for raycast skinning to avoid per-instance allocations (Three.js/Babylon.js style). */
|
|
163
162
|
private raycastVertexBuffer: Float32Array | null = null
|
|
164
163
|
|
|
165
164
|
private lastFpsUpdate = performance.now()
|
package/src/model.ts
CHANGED
|
@@ -163,10 +163,14 @@ export class Model {
|
|
|
163
163
|
return "model_" + Model._nextId++
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
static async
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
static async loadFrom(path: string): Promise<Model>
|
|
167
|
+
static async loadFrom(name: string, path: string): Promise<Model>
|
|
168
|
+
static async loadFrom(nameOrPath: string, path?: string): Promise<Model> {
|
|
169
|
+
const name = path === undefined ? Model.nextDefaultName() : nameOrPath
|
|
170
|
+
const pmxPath = path === undefined ? nameOrPath : path
|
|
171
|
+
const model = await PmxLoader.load(pmxPath)
|
|
172
|
+
model.setName(name)
|
|
173
|
+
await Engine.getInstance().registerModel(model, pmxPath)
|
|
170
174
|
return model
|
|
171
175
|
}
|
|
172
176
|
|
|
@@ -721,7 +725,7 @@ export class Model {
|
|
|
721
725
|
try {
|
|
722
726
|
Engine.getInstance().markVertexBufferDirty(this)
|
|
723
727
|
} catch {
|
|
724
|
-
|
|
728
|
+
// not registered yet
|
|
725
729
|
}
|
|
726
730
|
return
|
|
727
731
|
}
|
|
@@ -816,7 +820,6 @@ export class Model {
|
|
|
816
820
|
}
|
|
817
821
|
}
|
|
818
822
|
|
|
819
|
-
/** Build an AnimationClip from VMD keyframes (used by loadVmd and loadAnimation). */
|
|
820
823
|
private buildClipFromVmdKeyFrames(vmdKeyFrames: VMDKeyFrame[]): AnimationClip {
|
|
821
824
|
const boneTracksByBone: Record<string, Array<{ frame: number; rotation: Quat; translation: Vec3; interpolation: BoneInterpolation }>> = {}
|
|
822
825
|
for (const keyFrame of vmdKeyFrames) {
|
|
@@ -877,18 +880,6 @@ export class Model {
|
|
|
877
880
|
return { boneTracks, morphTracks, duration: maxTime }
|
|
878
881
|
}
|
|
879
882
|
|
|
880
|
-
/** Load one VMD as the "default" animation and show first frame. Does not auto-play; call playAnimation() when needed. */
|
|
881
|
-
async loadVmd(vmdUrl: string): Promise<void> {
|
|
882
|
-
const vmdKeyFrames = await VMDLoader.load(vmdUrl)
|
|
883
|
-
this.resetAllBones()
|
|
884
|
-
this.resetAllMorphs()
|
|
885
|
-
const clip = this.buildClipFromVmdKeyFrames(vmdKeyFrames)
|
|
886
|
-
this.animationState.loadAnimation("default", clip)
|
|
887
|
-
this.animationState.show("default")
|
|
888
|
-
this.applyPoseFromClip(this.animationState.getCurrentClip(), 0)
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
/** Load a VMD as a named animation (e.g. "idle", "walk", "attack"). Does not start playback. */
|
|
892
883
|
async loadAnimation(animationName: string, vmdUrl: string): Promise<void> {
|
|
893
884
|
const vmdKeyFrames = await VMDLoader.load(vmdUrl)
|
|
894
885
|
const clip = this.buildClipFromVmdKeyFrames(vmdKeyFrames)
|
|
@@ -900,7 +891,6 @@ export class Model {
|
|
|
900
891
|
const localRot = this.runtimeSkeleton.localRotations[boneIdx]
|
|
901
892
|
const localTrans = this.runtimeSkeleton.localTranslations[boneIdx]
|
|
902
893
|
|
|
903
|
-
// Reset to default pose: identity rotation and zero translation (like initial PMX state)
|
|
904
894
|
localRot.set(Quat.identity())
|
|
905
895
|
localTrans.set(Vec3.zeros())
|
|
906
896
|
}
|
|
@@ -928,14 +918,11 @@ export class Model {
|
|
|
928
918
|
return this.physicsEnabled
|
|
929
919
|
}
|
|
930
920
|
|
|
931
|
-
/** Low-level access to animation state when needed. Prefer model.play(), model.show(), etc. */
|
|
932
921
|
getAnimationState(): AnimationState {
|
|
933
922
|
return this.animationState
|
|
934
923
|
}
|
|
935
924
|
|
|
936
|
-
/** Resume current animation (no-op if none). */
|
|
937
925
|
play(): void
|
|
938
|
-
/** Play named animation; if one is already playing, it is queued. Returns false if name not loaded. */
|
|
939
926
|
play(name: string): boolean
|
|
940
927
|
play(name?: string): void | boolean {
|
|
941
928
|
if (name === undefined) {
|
|
@@ -945,12 +932,11 @@ export class Model {
|
|
|
945
932
|
return this.animationState.play(name)
|
|
946
933
|
}
|
|
947
934
|
|
|
948
|
-
/** Show named animation at time 0 without playing. Use after load when you want to play later. */
|
|
949
935
|
show(name: string): void {
|
|
950
936
|
this.animationState.show(name)
|
|
951
937
|
}
|
|
952
938
|
|
|
953
|
-
|
|
939
|
+
// @deprecated Use model.play()
|
|
954
940
|
playAnimation(): void {
|
|
955
941
|
this.animationState.play()
|
|
956
942
|
}
|
|
@@ -959,7 +945,7 @@ export class Model {
|
|
|
959
945
|
this.animationState.pause()
|
|
960
946
|
}
|
|
961
947
|
|
|
962
|
-
|
|
948
|
+
// @deprecated Use model.pause()
|
|
963
949
|
pauseAnimation(): void {
|
|
964
950
|
this.animationState.pause()
|
|
965
951
|
}
|
|
@@ -968,7 +954,7 @@ export class Model {
|
|
|
968
954
|
this.animationState.stop()
|
|
969
955
|
}
|
|
970
956
|
|
|
971
|
-
|
|
957
|
+
// @deprecated Use model.stop()
|
|
972
958
|
stopAnimation(): void {
|
|
973
959
|
this.animationState.stop()
|
|
974
960
|
}
|
|
@@ -977,7 +963,7 @@ export class Model {
|
|
|
977
963
|
this.animationState.seek(time)
|
|
978
964
|
}
|
|
979
965
|
|
|
980
|
-
|
|
966
|
+
// @deprecated Use model.seek()
|
|
981
967
|
seekAnimation(time: number): void {
|
|
982
968
|
this.animationState.seek(time)
|
|
983
969
|
}
|
|
@@ -1001,23 +987,17 @@ export class Model {
|
|
|
1001
987
|
private findKeyframeIndex<T extends { time: number }>(time: number, keyFrames: T[], cachedIdx: number): number {
|
|
1002
988
|
if (keyFrames.length === 0) return -1
|
|
1003
989
|
|
|
1004
|
-
// Check if cached index is still valid (time is within the cached frame range)
|
|
1005
990
|
if (cachedIdx >= 0 && cachedIdx < keyFrames.length) {
|
|
1006
991
|
const frameTime = keyFrames[cachedIdx].time
|
|
1007
992
|
const nextFrameTime = cachedIdx + 1 < keyFrames.length ? keyFrames[cachedIdx + 1].time : Infinity
|
|
1008
|
-
|
|
1009
|
-
// If time is within [frameTime, nextFrameTime), use cached index
|
|
1010
993
|
if (time >= frameTime && time < nextFrameTime) {
|
|
1011
994
|
return cachedIdx
|
|
1012
995
|
}
|
|
1013
996
|
}
|
|
1014
|
-
|
|
1015
|
-
// Fall back to binary search
|
|
1016
997
|
const idx = Model.upperBound(time, keyFrames, 0) - 1
|
|
1017
998
|
return idx
|
|
1018
999
|
}
|
|
1019
1000
|
|
|
1020
|
-
/** Apply pose from a clip at the given time. No-op if clip is null. */
|
|
1021
1001
|
private applyPoseFromClip(clip: AnimationClip | null, time: number): void {
|
|
1022
1002
|
if (!clip) return
|
|
1023
1003
|
if (clip !== this.lastAppliedClip) {
|