reze-engine 0.3.5 → 0.3.6
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/engine.d.ts +2 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +44 -100
- package/dist/ik-solver.d.ts +6 -0
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/ik-solver.js +98 -101
- package/dist/ik.d.ts +32 -0
- package/dist/ik.d.ts.map +1 -0
- package/dist/ik.js +337 -0
- package/dist/math.d.ts +0 -5
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +0 -55
- package/dist/model.d.ts +1 -3
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +11 -41
- package/dist/player.d.ts +6 -20
- package/dist/player.d.ts.map +1 -1
- package/dist/player.js +88 -191
- package/dist/pool-scene.d.ts +52 -0
- package/dist/pool-scene.d.ts.map +1 -0
- package/dist/pool-scene.js +1122 -0
- package/dist/pool.d.ts +38 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +422 -0
- package/package.json +1 -1
- package/src/engine.ts +54 -101
- package/src/ik-solver.ts +106 -124
- package/src/math.ts +0 -74
- package/src/model.ts +12 -44
- package/src/player.ts +115 -210
package/src/model.ts
CHANGED
|
@@ -279,35 +279,24 @@ export class Model {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
private initializeRotTweenBuffers(): void {
|
|
282
|
-
|
|
283
|
-
this.rotTweenState = {
|
|
284
|
-
active: new Uint8Array(n),
|
|
285
|
-
startQuat: new Float32Array(n * 4),
|
|
286
|
-
targetQuat: new Float32Array(n * 4),
|
|
287
|
-
startTimeMs: new Float32Array(n),
|
|
288
|
-
durationMs: new Float32Array(n),
|
|
289
|
-
}
|
|
282
|
+
this.rotTweenState = this.createTweenState(this.skeleton.bones.length, 4, 4)
|
|
290
283
|
}
|
|
291
284
|
|
|
292
285
|
private initializeTransTweenBuffers(): void {
|
|
293
|
-
|
|
294
|
-
this.transTweenState = {
|
|
295
|
-
active: new Uint8Array(n),
|
|
296
|
-
startVec: new Float32Array(n * 3),
|
|
297
|
-
targetVec: new Float32Array(n * 3),
|
|
298
|
-
startTimeMs: new Float32Array(n),
|
|
299
|
-
durationMs: new Float32Array(n),
|
|
300
|
-
}
|
|
286
|
+
this.transTweenState = this.createTweenState(this.skeleton.bones.length, 3, 3)
|
|
301
287
|
}
|
|
302
288
|
|
|
303
289
|
private initializeMorphTweenBuffers(): void {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
290
|
+
this.morphTweenState = this.createTweenState(this.morphing.morphs.length, 1, 1)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private createTweenState(count: number, startSize: number, targetSize: number): any {
|
|
294
|
+
return {
|
|
295
|
+
active: new Uint8Array(count),
|
|
296
|
+
startQuat: new Float32Array(count * startSize),
|
|
297
|
+
targetQuat: new Float32Array(count * targetSize),
|
|
298
|
+
startTimeMs: new Float32Array(count),
|
|
299
|
+
durationMs: new Float32Array(count),
|
|
311
300
|
}
|
|
312
301
|
}
|
|
313
302
|
|
|
@@ -410,33 +399,22 @@ export class Model {
|
|
|
410
399
|
return hasActiveTweens
|
|
411
400
|
}
|
|
412
401
|
|
|
413
|
-
// Get interleaved vertex data for GPU upload
|
|
414
|
-
// Format: [x,y,z, nx,ny,nz, u,v, x,y,z, nx,ny,nz, u,v, ...]
|
|
415
402
|
getVertices(): Float32Array<ArrayBuffer> {
|
|
416
403
|
return this.vertexData
|
|
417
404
|
}
|
|
418
405
|
|
|
419
|
-
// Get texture information
|
|
420
406
|
getTextures(): Texture[] {
|
|
421
407
|
return this.textures
|
|
422
408
|
}
|
|
423
409
|
|
|
424
|
-
// Get material information
|
|
425
410
|
getMaterials(): Material[] {
|
|
426
411
|
return this.materials
|
|
427
412
|
}
|
|
428
413
|
|
|
429
|
-
// Get vertex count
|
|
430
|
-
getVertexCount(): number {
|
|
431
|
-
return this.vertexCount
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Get index data for GPU upload
|
|
435
414
|
getIndices(): Uint32Array<ArrayBuffer> {
|
|
436
415
|
return this.indexData
|
|
437
416
|
}
|
|
438
417
|
|
|
439
|
-
// Accessors for skeleton/skinning
|
|
440
418
|
getSkeleton(): Skeleton {
|
|
441
419
|
return this.skeleton
|
|
442
420
|
}
|
|
@@ -445,7 +423,6 @@ export class Model {
|
|
|
445
423
|
return this.skinning
|
|
446
424
|
}
|
|
447
425
|
|
|
448
|
-
// Accessors for physics data
|
|
449
426
|
getRigidbodies(): Rigidbody[] {
|
|
450
427
|
return this.rigidbodies
|
|
451
428
|
}
|
|
@@ -454,7 +431,6 @@ export class Model {
|
|
|
454
431
|
return this.joints
|
|
455
432
|
}
|
|
456
433
|
|
|
457
|
-
// Accessors for morphing
|
|
458
434
|
getMorphing(): Morphing {
|
|
459
435
|
return this.morphing
|
|
460
436
|
}
|
|
@@ -465,10 +441,6 @@ export class Model {
|
|
|
465
441
|
|
|
466
442
|
// ------- Bone helpers (public API) -------
|
|
467
443
|
|
|
468
|
-
getBoneNames(): string[] {
|
|
469
|
-
return this.skeleton.bones.map((b) => b.name)
|
|
470
|
-
}
|
|
471
|
-
|
|
472
444
|
rotateBones(names: string[], quats: Quat[], durationMs?: number): void {
|
|
473
445
|
const state = this.rotTweenState
|
|
474
446
|
const normalized = quats.map((q) => q.normalize())
|
|
@@ -646,10 +618,6 @@ export class Model {
|
|
|
646
618
|
return this.skeleton.inverseBindMatrices
|
|
647
619
|
}
|
|
648
620
|
|
|
649
|
-
getMorphNames(): string[] {
|
|
650
|
-
return this.morphing.morphs.map((m) => m.name)
|
|
651
|
-
}
|
|
652
|
-
|
|
653
621
|
setMorphWeight(name: string, weight: number, durationMs?: number): void {
|
|
654
622
|
const idx = this.runtimeMorph.nameIndex[name] ?? -1
|
|
655
623
|
if (idx < 0 || idx >= this.runtimeMorph.weights.length) return
|
package/src/player.ts
CHANGED
|
@@ -18,17 +18,15 @@ export class Player {
|
|
|
18
18
|
private frames: VMDKeyFrame[] = []
|
|
19
19
|
private boneTracks: Map<string, Array<{ boneFrame: BoneFrame; time: number }>> = new Map()
|
|
20
20
|
private morphTracks: Map<string, Array<{ morphFrame: MorphFrame; time: number }>> = new Map()
|
|
21
|
-
private
|
|
21
|
+
private _duration: number = 0
|
|
22
22
|
|
|
23
23
|
// Playback state
|
|
24
24
|
private isPlaying: boolean = false
|
|
25
25
|
private isPaused: boolean = false
|
|
26
|
-
private
|
|
26
|
+
private _currentTime: number = 0
|
|
27
27
|
|
|
28
28
|
// Timing
|
|
29
29
|
private startTime: number = 0 // Real-time when playback started
|
|
30
|
-
private pausedTime: number = 0 // Accumulated paused duration
|
|
31
|
-
private pauseStartTime: number = 0
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
32
|
* Load VMD animation file
|
|
@@ -43,75 +41,57 @@ export class Player {
|
|
|
43
41
|
* Process frames into tracks
|
|
44
42
|
*/
|
|
45
43
|
private processFrames(): void {
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
})
|
|
44
|
+
// Helper to group frames by name and sort by time
|
|
45
|
+
const groupFrames = <T>(
|
|
46
|
+
items: Array<{ item: T; name: string; time: number }>
|
|
47
|
+
): Map<string, Array<{ item: T; time: number }>> => {
|
|
48
|
+
const tracks = new Map<string, Array<{ item: T; time: number }>>()
|
|
49
|
+
for (const { item, name, time } of items) {
|
|
50
|
+
if (!tracks.has(name)) tracks.set(name, [])
|
|
51
|
+
tracks.get(name)!.push({ item, time })
|
|
54
52
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const boneKeyFramesByBone = new Map<string, Array<{ boneFrame: BoneFrame; time: number }>>()
|
|
58
|
-
for (const { boneFrame, time } of allBoneKeyFrames) {
|
|
59
|
-
if (!boneKeyFramesByBone.has(boneFrame.boneName)) {
|
|
60
|
-
boneKeyFramesByBone.set(boneFrame.boneName, [])
|
|
53
|
+
for (const keyFrames of tracks.values()) {
|
|
54
|
+
keyFrames.sort((a, b) => a.time - b.time)
|
|
61
55
|
}
|
|
62
|
-
|
|
56
|
+
return tracks
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
59
|
+
// Collect all bone and morph frames
|
|
60
|
+
const boneItems: Array<{ item: BoneFrame; name: string; time: number }> = []
|
|
61
|
+
const morphItems: Array<{ item: MorphFrame; name: string; time: number }> = []
|
|
68
62
|
|
|
69
|
-
// Process morph frames
|
|
70
|
-
const allMorphKeyFrames: Array<{ morphFrame: MorphFrame; time: number }> = []
|
|
71
63
|
for (const keyFrame of this.frames) {
|
|
64
|
+
for (const boneFrame of keyFrame.boneFrames) {
|
|
65
|
+
boneItems.push({ item: boneFrame, name: boneFrame.boneName, time: keyFrame.time })
|
|
66
|
+
}
|
|
72
67
|
for (const morphFrame of keyFrame.morphFrames) {
|
|
73
|
-
|
|
74
|
-
morphFrame,
|
|
75
|
-
time: keyFrame.time,
|
|
76
|
-
})
|
|
68
|
+
morphItems.push({ item: morphFrame, name: morphFrame.morphName, time: keyFrame.time })
|
|
77
69
|
}
|
|
78
70
|
}
|
|
79
71
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
72
|
+
// Transform to expected format
|
|
73
|
+
this.boneTracks = new Map()
|
|
74
|
+
for (const [name, frames] of groupFrames(boneItems).entries()) {
|
|
75
|
+
this.boneTracks.set(
|
|
76
|
+
name,
|
|
77
|
+
frames.map((f) => ({ boneFrame: f.item, time: f.time }))
|
|
78
|
+
)
|
|
86
79
|
}
|
|
87
80
|
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
this.morphTracks = new Map()
|
|
82
|
+
for (const [name, frames] of groupFrames(morphItems).entries()) {
|
|
83
|
+
this.morphTracks.set(
|
|
84
|
+
name,
|
|
85
|
+
frames.map((f) => ({ morphFrame: f.item, time: f.time }))
|
|
86
|
+
)
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
//
|
|
93
|
-
this.boneTracks
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
for (const keyFrames of this.boneTracks.values()) {
|
|
99
|
-
if (keyFrames.length > 0) {
|
|
100
|
-
const lastTime = keyFrames[keyFrames.length - 1].time
|
|
101
|
-
if (lastTime > maxFrameTime) {
|
|
102
|
-
maxFrameTime = lastTime
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
for (const keyFrames of this.morphTracks.values()) {
|
|
107
|
-
if (keyFrames.length > 0) {
|
|
108
|
-
const lastTime = keyFrames[keyFrames.length - 1].time
|
|
109
|
-
if (lastTime > maxFrameTime) {
|
|
110
|
-
maxFrameTime = lastTime
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
this.duration = maxFrameTime > 0 ? maxFrameTime : 0
|
|
89
|
+
// Calculate duration from all tracks
|
|
90
|
+
const allTracks = [...this.boneTracks.values(), ...this.morphTracks.values()]
|
|
91
|
+
this._duration = allTracks.reduce((max, keyFrames) => {
|
|
92
|
+
const lastTime = keyFrames[keyFrames.length - 1]?.time ?? 0
|
|
93
|
+
return Math.max(max, lastTime)
|
|
94
|
+
}, 0)
|
|
115
95
|
}
|
|
116
96
|
|
|
117
97
|
/**
|
|
@@ -121,16 +101,8 @@ export class Player {
|
|
|
121
101
|
play(): void {
|
|
122
102
|
if (this.frames.length === 0) return
|
|
123
103
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.isPaused = false
|
|
127
|
-
// Adjust start time so current time calculation continues smoothly
|
|
128
|
-
this.startTime = performance.now() - this.currentTime * 1000
|
|
129
|
-
} else {
|
|
130
|
-
// Start from beginning or current seek position
|
|
131
|
-
this.startTime = performance.now() - this.currentTime * 1000
|
|
132
|
-
this.pausedTime = 0
|
|
133
|
-
}
|
|
104
|
+
this.isPaused = false
|
|
105
|
+
this.startTime = performance.now() - this._currentTime * 1000
|
|
134
106
|
|
|
135
107
|
this.isPlaying = true
|
|
136
108
|
}
|
|
@@ -140,9 +112,7 @@ export class Player {
|
|
|
140
112
|
*/
|
|
141
113
|
pause(): void {
|
|
142
114
|
if (!this.isPlaying || this.isPaused) return
|
|
143
|
-
|
|
144
115
|
this.isPaused = true
|
|
145
|
-
this.pauseStartTime = performance.now()
|
|
146
116
|
}
|
|
147
117
|
|
|
148
118
|
/**
|
|
@@ -151,22 +121,19 @@ export class Player {
|
|
|
151
121
|
stop(): void {
|
|
152
122
|
this.isPlaying = false
|
|
153
123
|
this.isPaused = false
|
|
154
|
-
this.
|
|
124
|
+
this._currentTime = 0
|
|
155
125
|
this.startTime = 0
|
|
156
|
-
this.pausedTime = 0
|
|
157
126
|
}
|
|
158
127
|
|
|
159
128
|
/**
|
|
160
129
|
* Seek to specific time
|
|
161
130
|
*/
|
|
162
131
|
seek(time: number): void {
|
|
163
|
-
const clampedTime = Math.max(0, Math.min(time, this.
|
|
164
|
-
this.
|
|
132
|
+
const clampedTime = Math.max(0, Math.min(time, this._duration))
|
|
133
|
+
this._currentTime = clampedTime
|
|
165
134
|
|
|
166
|
-
// Adjust start time if playing
|
|
167
135
|
if (this.isPlaying && !this.isPaused) {
|
|
168
136
|
this.startTime = performance.now() - clampedTime * 1000
|
|
169
|
-
this.pausedTime = 0
|
|
170
137
|
}
|
|
171
138
|
}
|
|
172
139
|
|
|
@@ -181,21 +148,21 @@ export class Player {
|
|
|
181
148
|
|
|
182
149
|
// If paused, return current pose at paused time (no time update)
|
|
183
150
|
if (this.isPaused) {
|
|
184
|
-
return this.getPoseAtTime(this.
|
|
151
|
+
return this.getPoseAtTime(this._currentTime)
|
|
185
152
|
}
|
|
186
153
|
|
|
187
154
|
// Calculate current animation time
|
|
188
155
|
const elapsedSeconds = (currentRealTime - this.startTime) / 1000
|
|
189
|
-
this.
|
|
156
|
+
this._currentTime = elapsedSeconds
|
|
190
157
|
|
|
191
158
|
// Check if animation ended
|
|
192
|
-
if (this.
|
|
193
|
-
this.
|
|
159
|
+
if (this._currentTime >= this._duration) {
|
|
160
|
+
this._currentTime = this._duration
|
|
194
161
|
this.pause() // Auto-pause at end
|
|
195
|
-
return this.getPoseAtTime(this.
|
|
162
|
+
return this.getPoseAtTime(this._currentTime)
|
|
196
163
|
}
|
|
197
164
|
|
|
198
|
-
return this.getPoseAtTime(this.
|
|
165
|
+
return this.getPoseAtTime(this._currentTime)
|
|
199
166
|
}
|
|
200
167
|
|
|
201
168
|
/**
|
|
@@ -208,136 +175,86 @@ export class Player {
|
|
|
208
175
|
morphWeights: new Map(),
|
|
209
176
|
}
|
|
210
177
|
|
|
211
|
-
//
|
|
212
|
-
const
|
|
213
|
-
let left = 0
|
|
214
|
-
|
|
178
|
+
// Generic binary search for upper bound
|
|
179
|
+
const upperBound = <T extends { time: number }>(time: number, keyFrames: T[]): number => {
|
|
180
|
+
let left = 0,
|
|
181
|
+
right = keyFrames.length
|
|
215
182
|
while (left < right) {
|
|
216
183
|
const mid = Math.floor((left + right) / 2)
|
|
217
|
-
if (keyFrames[mid].time <= time)
|
|
218
|
-
|
|
219
|
-
} else {
|
|
220
|
-
right = mid
|
|
221
|
-
}
|
|
184
|
+
if (keyFrames[mid].time <= time) left = mid + 1
|
|
185
|
+
else right = mid
|
|
222
186
|
}
|
|
223
187
|
return left
|
|
224
188
|
}
|
|
225
189
|
|
|
226
|
-
// Process
|
|
190
|
+
// Process bone tracks
|
|
227
191
|
for (const [boneName, keyFrames] of this.boneTracks.entries()) {
|
|
228
192
|
if (keyFrames.length === 0) continue
|
|
229
193
|
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
const clampedFrameTime = Math.max(startTime, Math.min(endTime, time))
|
|
234
|
-
|
|
235
|
-
const upperBoundIndex = upperBoundFrameIndex(clampedFrameTime, keyFrames)
|
|
236
|
-
const upperBoundIndexMinusOne = upperBoundIndex - 1
|
|
237
|
-
|
|
238
|
-
if (upperBoundIndexMinusOne < 0) continue
|
|
194
|
+
const clampedTime = Math.max(keyFrames[0].time, Math.min(keyFrames[keyFrames.length - 1].time, time))
|
|
195
|
+
const idx = upperBound(clampedTime, keyFrames) - 1
|
|
196
|
+
if (idx < 0) continue
|
|
239
197
|
|
|
240
|
-
const
|
|
241
|
-
const
|
|
198
|
+
const frameA = keyFrames[idx].boneFrame
|
|
199
|
+
const frameB = keyFrames[idx + 1]?.boneFrame
|
|
242
200
|
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
pose.
|
|
246
|
-
pose.boneTranslations.set(boneName, boneFrameA.translation)
|
|
201
|
+
if (!frameB) {
|
|
202
|
+
pose.boneRotations.set(boneName, frameA.rotation)
|
|
203
|
+
pose.boneTranslations.set(boneName, frameA.translation)
|
|
247
204
|
} else {
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
gradient
|
|
261
|
-
)
|
|
262
|
-
const interpolatedRotation = Quat.slerp(boneFrameA.rotation, boneFrameB.rotation, rotWeight)
|
|
263
|
-
|
|
264
|
-
// Interpolate translation using Bezier (separate curves for X, Y, Z)
|
|
265
|
-
const xWeight = bezierInterpolate(
|
|
266
|
-
interp[0] / 127, // X_x1
|
|
267
|
-
interp[8] / 127, // X_x2
|
|
268
|
-
interp[4] / 127, // X_y1
|
|
269
|
-
interp[12] / 127, // X_y2
|
|
270
|
-
gradient
|
|
271
|
-
)
|
|
272
|
-
const yWeight = bezierInterpolate(
|
|
273
|
-
interp[16] / 127, // Y_x1
|
|
274
|
-
interp[24] / 127, // Y_x2
|
|
275
|
-
interp[20] / 127, // Y_y1
|
|
276
|
-
interp[28] / 127, // Y_y2
|
|
277
|
-
gradient
|
|
278
|
-
)
|
|
279
|
-
const zWeight = bezierInterpolate(
|
|
280
|
-
interp[32] / 127, // Z_x1
|
|
281
|
-
interp[40] / 127, // Z_x2
|
|
282
|
-
interp[36] / 127, // Z_y1
|
|
283
|
-
interp[44] / 127, // Z_y2
|
|
284
|
-
gradient
|
|
205
|
+
const timeA = keyFrames[idx].time
|
|
206
|
+
const timeB = keyFrames[idx + 1].time
|
|
207
|
+
const gradient = (clampedTime - timeA) / (timeB - timeA)
|
|
208
|
+
const interp = frameB.interpolation
|
|
209
|
+
|
|
210
|
+
pose.boneRotations.set(
|
|
211
|
+
boneName,
|
|
212
|
+
Quat.slerp(
|
|
213
|
+
frameA.rotation,
|
|
214
|
+
frameB.rotation,
|
|
215
|
+
bezierInterpolate(interp[0] / 127, interp[1] / 127, interp[2] / 127, interp[3] / 127, gradient)
|
|
216
|
+
)
|
|
285
217
|
)
|
|
286
218
|
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
219
|
+
const lerp = (a: number, b: number, w: number) => a + (b - a) * w
|
|
220
|
+
const getWeight = (offset: number) =>
|
|
221
|
+
bezierInterpolate(
|
|
222
|
+
interp[offset] / 127,
|
|
223
|
+
interp[offset + 8] / 127,
|
|
224
|
+
interp[offset + 4] / 127,
|
|
225
|
+
interp[offset + 12] / 127,
|
|
226
|
+
gradient
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
pose.boneTranslations.set(
|
|
230
|
+
boneName,
|
|
231
|
+
new Vec3(
|
|
232
|
+
lerp(frameA.translation.x, frameB.translation.x, getWeight(0)),
|
|
233
|
+
lerp(frameA.translation.y, frameB.translation.y, getWeight(16)),
|
|
234
|
+
lerp(frameA.translation.z, frameB.translation.z, getWeight(32))
|
|
235
|
+
)
|
|
291
236
|
)
|
|
292
|
-
|
|
293
|
-
pose.boneRotations.set(boneName, interpolatedRotation)
|
|
294
|
-
pose.boneTranslations.set(boneName, interpolatedTranslation)
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Helper to find upper bound index for morph frames
|
|
299
|
-
const upperBoundMorphIndex = (time: number, keyFrames: Array<{ morphFrame: MorphFrame; time: number }>): number => {
|
|
300
|
-
let left = 0
|
|
301
|
-
let right = keyFrames.length
|
|
302
|
-
while (left < right) {
|
|
303
|
-
const mid = Math.floor((left + right) / 2)
|
|
304
|
-
if (keyFrames[mid].time <= time) {
|
|
305
|
-
left = mid + 1
|
|
306
|
-
} else {
|
|
307
|
-
right = mid
|
|
308
|
-
}
|
|
309
237
|
}
|
|
310
|
-
return left
|
|
311
238
|
}
|
|
312
239
|
|
|
313
|
-
// Process
|
|
240
|
+
// Process morph tracks
|
|
314
241
|
for (const [morphName, keyFrames] of this.morphTracks.entries()) {
|
|
315
242
|
if (keyFrames.length === 0) continue
|
|
316
243
|
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
const clampedFrameTime = Math.max(startTime, Math.min(endTime, time))
|
|
321
|
-
|
|
322
|
-
const upperBoundIndex = upperBoundMorphIndex(clampedFrameTime, keyFrames)
|
|
323
|
-
const upperBoundIndexMinusOne = upperBoundIndex - 1
|
|
324
|
-
|
|
325
|
-
if (upperBoundIndexMinusOne < 0) continue
|
|
244
|
+
const clampedTime = Math.max(keyFrames[0].time, Math.min(keyFrames[keyFrames.length - 1].time, time))
|
|
245
|
+
const idx = upperBound(clampedTime, keyFrames) - 1
|
|
246
|
+
if (idx < 0) continue
|
|
326
247
|
|
|
327
|
-
const
|
|
328
|
-
const
|
|
248
|
+
const frameA = keyFrames[idx].morphFrame
|
|
249
|
+
const frameB = keyFrames[idx + 1]?.morphFrame
|
|
329
250
|
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
pose.morphWeights.set(morphName, morphFrameA.weight)
|
|
251
|
+
if (!frameB) {
|
|
252
|
+
pose.morphWeights.set(morphName, frameA.weight)
|
|
333
253
|
} else {
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
const interpolatedWeight = morphFrameA.weight + (morphFrameB.weight - morphFrameA.weight) * gradient
|
|
339
|
-
|
|
340
|
-
pose.morphWeights.set(morphName, interpolatedWeight)
|
|
254
|
+
const timeA = keyFrames[idx].time
|
|
255
|
+
const timeB = keyFrames[idx + 1].time
|
|
256
|
+
const gradient = (clampedTime - timeA) / (timeB - timeA)
|
|
257
|
+
pose.morphWeights.set(morphName, frameA.weight + (frameB.weight - frameA.weight) * gradient)
|
|
341
258
|
}
|
|
342
259
|
}
|
|
343
260
|
|
|
@@ -349,37 +266,25 @@ export class Player {
|
|
|
349
266
|
*/
|
|
350
267
|
getProgress(): AnimationProgress {
|
|
351
268
|
return {
|
|
352
|
-
current: this.
|
|
353
|
-
duration: this.
|
|
354
|
-
percentage: this.
|
|
269
|
+
current: this._currentTime,
|
|
270
|
+
duration: this._duration,
|
|
271
|
+
percentage: this._duration > 0 ? (this._currentTime / this._duration) * 100 : 0,
|
|
355
272
|
}
|
|
356
273
|
}
|
|
357
274
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
*/
|
|
361
|
-
getCurrentTime(): number {
|
|
362
|
-
return this.currentTime
|
|
275
|
+
get currentTime(): number {
|
|
276
|
+
return this._currentTime
|
|
363
277
|
}
|
|
364
278
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
*/
|
|
368
|
-
getDuration(): number {
|
|
369
|
-
return this.duration
|
|
279
|
+
get duration(): number {
|
|
280
|
+
return this._duration
|
|
370
281
|
}
|
|
371
282
|
|
|
372
|
-
|
|
373
|
-
* Check if playing
|
|
374
|
-
*/
|
|
375
|
-
isPlayingState(): boolean {
|
|
283
|
+
get isPlayingState(): boolean {
|
|
376
284
|
return this.isPlaying && !this.isPaused
|
|
377
285
|
}
|
|
378
286
|
|
|
379
|
-
|
|
380
|
-
* Check if paused
|
|
381
|
-
*/
|
|
382
|
-
isPausedState(): boolean {
|
|
287
|
+
get isPausedState(): boolean {
|
|
383
288
|
return this.isPaused
|
|
384
289
|
}
|
|
385
290
|
}
|