reze-engine 0.3.3 → 0.3.5
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/dist/ammo-loader.d.ts +0 -1
- package/dist/ammo-loader.d.ts.map +1 -1
- package/dist/ammo-loader.js +0 -3
- package/dist/engine.d.ts +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +2 -2
- package/dist/ik-solver.d.ts +1 -2
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/ik-solver.js +13 -37
- package/dist/math.d.ts +11 -1
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +39 -7
- package/dist/model.d.ts +0 -16
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +13 -52
- package/dist/player.d.ts +3 -25
- package/dist/player.d.ts.map +1 -1
- package/dist/player.js +4 -117
- package/package.json +1 -1
- package/src/ammo-loader.ts +0 -4
- package/src/engine.ts +2 -2
- package/src/ik-solver.ts +16 -55
- package/src/math.ts +47 -8
- package/src/model.ts +14 -57
- package/src/player.ts +4 -141
- package/src/bezier-interpolate.ts +0 -47
package/src/player.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { bezierInterpolate } from "./
|
|
2
|
-
import { Quat, Vec3 } from "./math"
|
|
1
|
+
import { Quat, Vec3, bezierInterpolate } from "./math"
|
|
3
2
|
import { BoneFrame, MorphFrame, VMDKeyFrame, VMDLoader } from "./vmd-loader"
|
|
4
3
|
|
|
5
4
|
export interface AnimationPose {
|
|
@@ -31,72 +30,13 @@ export class Player {
|
|
|
31
30
|
private pausedTime: number = 0 // Accumulated paused duration
|
|
32
31
|
private pauseStartTime: number = 0
|
|
33
32
|
|
|
34
|
-
// Audio
|
|
35
|
-
private audioElement?: HTMLAudioElement
|
|
36
|
-
private audioUrl?: string
|
|
37
|
-
private audioLoaded: boolean = false
|
|
38
|
-
|
|
39
33
|
/**
|
|
40
|
-
* Load VMD animation file
|
|
34
|
+
* Load VMD animation file
|
|
41
35
|
*/
|
|
42
|
-
async loadVmd(vmdUrl: string
|
|
36
|
+
async loadVmd(vmdUrl: string): Promise<void> {
|
|
43
37
|
// Load animation
|
|
44
38
|
this.frames = await VMDLoader.load(vmdUrl)
|
|
45
39
|
this.processFrames()
|
|
46
|
-
|
|
47
|
-
// Load audio if provided
|
|
48
|
-
if (audioUrl) {
|
|
49
|
-
await this.loadAudio(audioUrl)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Load audio file
|
|
55
|
-
*/
|
|
56
|
-
async loadAudio(url: string): Promise<void> {
|
|
57
|
-
this.audioUrl = url
|
|
58
|
-
this.audioLoaded = false
|
|
59
|
-
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
const audio = new Audio(url)
|
|
62
|
-
audio.preload = "auto"
|
|
63
|
-
|
|
64
|
-
// iOS Safari requires playsinline attribute for inline playback
|
|
65
|
-
// This must be set before loading
|
|
66
|
-
audio.setAttribute("playsinline", "true")
|
|
67
|
-
audio.setAttribute("webkit-playsinline", "true")
|
|
68
|
-
|
|
69
|
-
// Set volume to ensure audio is ready
|
|
70
|
-
audio.volume = 1.0
|
|
71
|
-
|
|
72
|
-
// iOS sometimes requires audio element to be in DOM
|
|
73
|
-
// Add it hidden to the document body
|
|
74
|
-
audio.style.display = "none"
|
|
75
|
-
audio.style.position = "absolute"
|
|
76
|
-
audio.style.visibility = "hidden"
|
|
77
|
-
audio.style.width = "0"
|
|
78
|
-
audio.style.height = "0"
|
|
79
|
-
document.body.appendChild(audio)
|
|
80
|
-
|
|
81
|
-
audio.addEventListener("loadeddata", () => {
|
|
82
|
-
this.audioElement = audio
|
|
83
|
-
this.audioLoaded = true
|
|
84
|
-
resolve()
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
audio.addEventListener("error", (e) => {
|
|
88
|
-
console.warn("Failed to load audio:", url, e)
|
|
89
|
-
this.audioLoaded = false
|
|
90
|
-
// Remove from DOM on error
|
|
91
|
-
if (audio.parentNode) {
|
|
92
|
-
audio.parentNode.removeChild(audio)
|
|
93
|
-
}
|
|
94
|
-
// Don't reject - animation should still work without audio
|
|
95
|
-
resolve()
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
audio.load()
|
|
99
|
-
})
|
|
100
40
|
}
|
|
101
41
|
|
|
102
42
|
/**
|
|
@@ -176,6 +116,7 @@ export class Player {
|
|
|
176
116
|
|
|
177
117
|
/**
|
|
178
118
|
* Start or resume playback
|
|
119
|
+
* Note: For iOS, this should be called synchronously from a user interaction event
|
|
179
120
|
*/
|
|
180
121
|
play(): void {
|
|
181
122
|
if (this.frames.length === 0) return
|
|
@@ -192,25 +133,6 @@ export class Player {
|
|
|
192
133
|
}
|
|
193
134
|
|
|
194
135
|
this.isPlaying = true
|
|
195
|
-
|
|
196
|
-
// Play audio if available
|
|
197
|
-
if (this.audioElement && this.audioLoaded) {
|
|
198
|
-
// Ensure audio is ready for iOS
|
|
199
|
-
this.audioElement.currentTime = this.currentTime
|
|
200
|
-
this.audioElement.muted = false
|
|
201
|
-
this.audioElement.volume = 1.0
|
|
202
|
-
|
|
203
|
-
// iOS requires play() to be called synchronously during user interaction
|
|
204
|
-
// This must happen directly from the user's click/touch event
|
|
205
|
-
const playPromise = this.audioElement.play()
|
|
206
|
-
|
|
207
|
-
if (playPromise !== undefined) {
|
|
208
|
-
playPromise.catch((error) => {
|
|
209
|
-
// Log error but don't block animation playback
|
|
210
|
-
console.warn("Audio play failed:", error, error.name)
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
136
|
}
|
|
215
137
|
|
|
216
138
|
/**
|
|
@@ -221,11 +143,6 @@ export class Player {
|
|
|
221
143
|
|
|
222
144
|
this.isPaused = true
|
|
223
145
|
this.pauseStartTime = performance.now()
|
|
224
|
-
|
|
225
|
-
// Pause audio if available
|
|
226
|
-
if (this.audioElement) {
|
|
227
|
-
this.audioElement.pause()
|
|
228
|
-
}
|
|
229
146
|
}
|
|
230
147
|
|
|
231
148
|
/**
|
|
@@ -237,12 +154,6 @@ export class Player {
|
|
|
237
154
|
this.currentTime = 0
|
|
238
155
|
this.startTime = 0
|
|
239
156
|
this.pausedTime = 0
|
|
240
|
-
|
|
241
|
-
// Stop audio if available
|
|
242
|
-
if (this.audioElement) {
|
|
243
|
-
this.audioElement.pause()
|
|
244
|
-
this.audioElement.currentTime = 0
|
|
245
|
-
}
|
|
246
157
|
}
|
|
247
158
|
|
|
248
159
|
/**
|
|
@@ -257,11 +168,6 @@ export class Player {
|
|
|
257
168
|
this.startTime = performance.now() - clampedTime * 1000
|
|
258
169
|
this.pausedTime = 0
|
|
259
170
|
}
|
|
260
|
-
|
|
261
|
-
// Seek audio if available
|
|
262
|
-
if (this.audioElement && this.audioLoaded) {
|
|
263
|
-
this.audioElement.currentTime = clampedTime
|
|
264
|
-
}
|
|
265
171
|
}
|
|
266
172
|
|
|
267
173
|
/**
|
|
@@ -289,15 +195,6 @@ export class Player {
|
|
|
289
195
|
return this.getPoseAtTime(this.currentTime)
|
|
290
196
|
}
|
|
291
197
|
|
|
292
|
-
// Sync audio if present (with tolerance)
|
|
293
|
-
if (this.audioElement && this.audioLoaded) {
|
|
294
|
-
const audioTime = this.audioElement.currentTime
|
|
295
|
-
const syncTolerance = 0.1 // 100ms tolerance
|
|
296
|
-
if (Math.abs(audioTime - this.currentTime) > syncTolerance) {
|
|
297
|
-
this.audioElement.currentTime = this.currentTime
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
198
|
return this.getPoseAtTime(this.currentTime)
|
|
302
199
|
}
|
|
303
200
|
|
|
@@ -485,38 +382,4 @@ export class Player {
|
|
|
485
382
|
isPausedState(): boolean {
|
|
486
383
|
return this.isPaused
|
|
487
384
|
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Check if has audio
|
|
491
|
-
*/
|
|
492
|
-
hasAudio(): boolean {
|
|
493
|
-
return this.audioElement !== undefined && this.audioLoaded
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Set audio volume (0.0 to 1.0)
|
|
498
|
-
*/
|
|
499
|
-
setVolume(volume: number): void {
|
|
500
|
-
if (this.audioElement) {
|
|
501
|
-
this.audioElement.volume = Math.max(0, Math.min(1, volume))
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Mute audio
|
|
507
|
-
*/
|
|
508
|
-
mute(): void {
|
|
509
|
-
if (this.audioElement) {
|
|
510
|
-
this.audioElement.muted = true
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* Unmute audio
|
|
516
|
-
*/
|
|
517
|
-
unmute(): void {
|
|
518
|
-
if (this.audioElement) {
|
|
519
|
-
this.audioElement.muted = false
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
385
|
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bezier interpolation for VMD animations
|
|
3
|
-
* Based on the reference implementation from babylon-mmd
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Bezier interpolation function
|
|
8
|
-
* @param x1 First control point X (0-127, normalized to 0-1)
|
|
9
|
-
* @param x2 Second control point X (0-127, normalized to 0-1)
|
|
10
|
-
* @param y1 First control point Y (0-127, normalized to 0-1)
|
|
11
|
-
* @param y2 Second control point Y (0-127, normalized to 0-1)
|
|
12
|
-
* @param t Interpolation parameter (0-1)
|
|
13
|
-
* @returns Interpolated value (0-1)
|
|
14
|
-
*/
|
|
15
|
-
export function bezierInterpolate(x1: number, x2: number, y1: number, y2: number, t: number): number {
|
|
16
|
-
// Clamp t to [0, 1]
|
|
17
|
-
t = Math.max(0, Math.min(1, t))
|
|
18
|
-
|
|
19
|
-
// Binary search for the t value that gives us the desired x
|
|
20
|
-
// We're solving for t in the Bezier curve: x(t) = 3*(1-t)^2*t*x1 + 3*(1-t)*t^2*x2 + t^3
|
|
21
|
-
let start = 0
|
|
22
|
-
let end = 1
|
|
23
|
-
let mid = 0.5
|
|
24
|
-
|
|
25
|
-
// Iterate until we find the t value that gives us the desired x
|
|
26
|
-
for (let i = 0; i < 15; i++) {
|
|
27
|
-
// Evaluate Bezier curve at mid point
|
|
28
|
-
const x = 3 * (1 - mid) * (1 - mid) * mid * x1 + 3 * (1 - mid) * mid * mid * x2 + mid * mid * mid
|
|
29
|
-
|
|
30
|
-
if (Math.abs(x - t) < 0.0001) {
|
|
31
|
-
break
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (x < t) {
|
|
35
|
-
start = mid
|
|
36
|
-
} else {
|
|
37
|
-
end = mid
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
mid = (start + end) / 2
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Now evaluate the y value at this t
|
|
44
|
-
const y = 3 * (1 - mid) * (1 - mid) * mid * y1 + 3 * (1 - mid) * mid * mid * y2 + mid * mid * mid
|
|
45
|
-
|
|
46
|
-
return y
|
|
47
|
-
}
|