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/src/player.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { bezierInterpolate } from "./bezier-interpolate"
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 and optionally audio
34
+ * Load VMD animation file
41
35
  */
42
- async loadVmd(vmdUrl: string, audioUrl?: string): Promise<void> {
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
- }