reze-engine 0.3.0 → 0.3.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/dist/engine.d.ts +9 -8
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +97 -274
- package/dist/player.d.ts +100 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +409 -0
- package/package.json +1 -1
- package/src/engine.ts +112 -294
- package/src/player.ts +490 -0
package/src/engine.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { bezierInterpolate } from "./bezier-interpolate"
|
|
2
1
|
import { Camera } from "./camera"
|
|
3
|
-
import {
|
|
2
|
+
import { Quat, Vec3 } from "./math"
|
|
4
3
|
import { Model } from "./model"
|
|
5
4
|
import { PmxLoader } from "./pmx-loader"
|
|
6
5
|
import { Physics } from "./physics"
|
|
7
|
-
import {
|
|
6
|
+
import { AnimationPose, Player } from "./player"
|
|
8
7
|
|
|
9
8
|
export type EngineOptions = {
|
|
10
9
|
ambientColor?: Vec3
|
|
@@ -134,14 +133,9 @@ export class Engine {
|
|
|
134
133
|
private animationFrameId: number | null = null
|
|
135
134
|
private renderLoopCallback: (() => void) | null = null
|
|
136
135
|
|
|
137
|
-
private
|
|
138
|
-
private animationTimeouts: number[] = []
|
|
136
|
+
private player: Player = new Player()
|
|
139
137
|
private hasAnimation = false // Set to true when loadAnimation is called
|
|
140
|
-
private
|
|
141
|
-
private animationStartTime: number = 0 // When animation started playing
|
|
142
|
-
private animationDuration: number = 0 // Total animation duration in seconds
|
|
143
|
-
private boneTracks: Map<string, Array<{ boneFrame: BoneFrame; time: number }>> = new Map()
|
|
144
|
-
private morphTracks: Map<string, Array<{ morphFrame: MorphFrame; time: number }>> = new Map()
|
|
138
|
+
private animationStartTime: number = 0 // Track when animation first started (for A-pose prevention)
|
|
145
139
|
|
|
146
140
|
constructor(canvas: HTMLCanvasElement, options?: EngineOptions) {
|
|
147
141
|
this.canvas = canvas
|
|
@@ -1273,109 +1267,71 @@ export class Engine {
|
|
|
1273
1267
|
this.lightData[3] = 0.0 // Padding for vec3f alignment
|
|
1274
1268
|
}
|
|
1275
1269
|
|
|
1276
|
-
public async loadAnimation(url: string) {
|
|
1277
|
-
|
|
1278
|
-
this.animationFrames = frames
|
|
1270
|
+
public async loadAnimation(url: string, audioUrl?: string) {
|
|
1271
|
+
await this.player.loadVmd(url, audioUrl)
|
|
1279
1272
|
this.hasAnimation = true
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
public playAnimation() {
|
|
1283
|
-
if (this.animationFrames.length === 0) return
|
|
1284
1273
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1274
|
+
// Show first frame (time 0) immediately
|
|
1275
|
+
if (this.currentModel) {
|
|
1276
|
+
const initialPose = this.player.getPoseAtTime(0)
|
|
1277
|
+
this.applyPose(initialPose)
|
|
1288
1278
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
}
|
|
1279
|
+
// Reset bones without time 0 keyframes
|
|
1280
|
+
const skeleton = this.currentModel.getSkeleton()
|
|
1281
|
+
const bonesWithPose = new Set(initialPose.boneRotations.keys())
|
|
1282
|
+
const bonesToReset: string[] = []
|
|
1283
|
+
for (const bone of skeleton.bones) {
|
|
1284
|
+
if (!bonesWithPose.has(bone.name)) {
|
|
1285
|
+
bonesToReset.push(bone.name)
|
|
1286
|
+
}
|
|
1297
1287
|
}
|
|
1298
|
-
}
|
|
1299
1288
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1289
|
+
if (bonesToReset.length > 0) {
|
|
1290
|
+
const identityQuat = new Quat(0, 0, 0, 1)
|
|
1291
|
+
const identityQuats = new Array(bonesToReset.length).fill(identityQuat)
|
|
1292
|
+
this.rotateBones(bonesToReset, identityQuats, 0)
|
|
1304
1293
|
}
|
|
1305
|
-
boneKeyFramesByBone.get(boneFrame.boneName)!.push({ boneFrame, time })
|
|
1306
|
-
}
|
|
1307
1294
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
1295
|
+
// Update model pose and physics
|
|
1296
|
+
this.currentModel.evaluatePose()
|
|
1311
1297
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
for (const morphFrame of keyFrame.morphFrames) {
|
|
1316
|
-
allMorphKeyFrames.push({
|
|
1317
|
-
morphFrame,
|
|
1318
|
-
time: keyFrame.time,
|
|
1319
|
-
})
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1298
|
+
if (this.physics) {
|
|
1299
|
+
const worldMats = this.currentModel.getBoneWorldMatrices()
|
|
1300
|
+
this.physics.reset(worldMats, this.currentModel.getBoneInverseBindMatrices())
|
|
1322
1301
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1302
|
+
// Upload matrices immediately
|
|
1303
|
+
this.device.queue.writeBuffer(
|
|
1304
|
+
this.worldMatrixBuffer!,
|
|
1305
|
+
0,
|
|
1306
|
+
worldMats.buffer,
|
|
1307
|
+
worldMats.byteOffset,
|
|
1308
|
+
worldMats.byteLength
|
|
1309
|
+
)
|
|
1310
|
+
const encoder = this.device.createCommandEncoder()
|
|
1311
|
+
this.computeSkinMatrices(encoder)
|
|
1312
|
+
this.device.queue.submit([encoder.finish()])
|
|
1327
1313
|
}
|
|
1328
|
-
morphKeyFramesByMorph.get(morphFrame.morphName)!.push({ morphFrame, time })
|
|
1329
1314
|
}
|
|
1315
|
+
}
|
|
1330
1316
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
}
|
|
1317
|
+
public playAnimation() {
|
|
1318
|
+
if (!this.hasAnimation || !this.currentModel) return
|
|
1334
1319
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
this.morphTracks = morphKeyFramesByMorph
|
|
1338
|
-
|
|
1339
|
-
// Calculate animation duration from max frame time (already in seconds)
|
|
1340
|
-
let maxFrameTime = 0
|
|
1341
|
-
for (const keyFrames of this.boneTracks.values()) {
|
|
1342
|
-
if (keyFrames.length > 0) {
|
|
1343
|
-
const lastTime = keyFrames[keyFrames.length - 1].time
|
|
1344
|
-
if (lastTime > maxFrameTime) {
|
|
1345
|
-
maxFrameTime = lastTime
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
for (const keyFrames of this.morphTracks.values()) {
|
|
1350
|
-
if (keyFrames.length > 0) {
|
|
1351
|
-
const lastTime = keyFrames[keyFrames.length - 1].time
|
|
1352
|
-
if (lastTime > maxFrameTime) {
|
|
1353
|
-
maxFrameTime = lastTime
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
this.animationDuration = maxFrameTime > 0 ? maxFrameTime : 0
|
|
1358
|
-
this.animationStartTime = performance.now()
|
|
1320
|
+
const wasPaused = this.player.isPausedState()
|
|
1321
|
+
const wasPlaying = this.player.isPlayingState()
|
|
1359
1322
|
|
|
1360
|
-
//
|
|
1361
|
-
if (
|
|
1362
|
-
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
// Apply time 0 bone keyframes
|
|
1366
|
-
for (const [boneName, keyFrames] of this.boneTracks.entries()) {
|
|
1367
|
-
if (keyFrames.length > 0 && keyFrames[0].time === 0) {
|
|
1368
|
-
const boneFrame = keyFrames[0].boneFrame
|
|
1369
|
-
this.rotateBones([boneName], [boneFrame.rotation], 0)
|
|
1370
|
-
this.moveBones([boneName], [boneFrame.translation], 0)
|
|
1371
|
-
bonesWithTime0.add(boneName)
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1323
|
+
// Only reset pose and physics if starting from beginning (not resuming)
|
|
1324
|
+
if (!wasPlaying && !wasPaused) {
|
|
1325
|
+
// Get initial pose at time 0
|
|
1326
|
+
const initialPose = this.player.getPoseAtTime(0)
|
|
1327
|
+
this.applyPose(initialPose)
|
|
1374
1328
|
|
|
1375
1329
|
// Reset bones without time 0 keyframes
|
|
1330
|
+
const skeleton = this.currentModel.getSkeleton()
|
|
1331
|
+
const bonesWithPose = new Set(initialPose.boneRotations.keys())
|
|
1376
1332
|
const bonesToReset: string[] = []
|
|
1377
1333
|
for (const bone of skeleton.bones) {
|
|
1378
|
-
if (!
|
|
1334
|
+
if (!bonesWithPose.has(bone.name)) {
|
|
1379
1335
|
bonesToReset.push(bone.name)
|
|
1380
1336
|
}
|
|
1381
1337
|
}
|
|
@@ -1386,14 +1342,6 @@ export class Engine {
|
|
|
1386
1342
|
this.rotateBones(bonesToReset, identityQuats, 0)
|
|
1387
1343
|
}
|
|
1388
1344
|
|
|
1389
|
-
// Apply time 0 morph keyframes
|
|
1390
|
-
for (const [morphName, keyFrames] of this.morphTracks.entries()) {
|
|
1391
|
-
if (keyFrames.length > 0 && keyFrames[0].time === 0) {
|
|
1392
|
-
const morphFrame = keyFrames[0].morphFrame
|
|
1393
|
-
this.setMorphWeight(morphName, morphFrame.weight, 0)
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
1345
|
// Reset physics immediately and upload matrices to prevent A-pose flash
|
|
1398
1346
|
if (this.physics) {
|
|
1399
1347
|
this.currentModel.evaluatePose()
|
|
@@ -1414,202 +1362,79 @@ export class Engine {
|
|
|
1414
1362
|
this.device.queue.submit([encoder.finish()])
|
|
1415
1363
|
}
|
|
1416
1364
|
}
|
|
1417
|
-
}
|
|
1418
1365
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1366
|
+
// Start playback (or resume if paused)
|
|
1367
|
+
this.player.play()
|
|
1368
|
+
if (this.animationStartTime === 0) {
|
|
1369
|
+
this.animationStartTime = performance.now()
|
|
1422
1370
|
}
|
|
1423
|
-
this.animationTimeouts = []
|
|
1424
|
-
this.playingAnimation = false
|
|
1425
|
-
this.boneTracks.clear()
|
|
1426
|
-
this.morphTracks.clear()
|
|
1427
1371
|
}
|
|
1428
1372
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
private animate(frameTime: number): void {
|
|
1433
|
-
if (!this.currentModel) return
|
|
1434
|
-
|
|
1435
|
-
// Helper to find upper bound index (binary search)
|
|
1436
|
-
const upperBoundFrameIndex = (time: number, keyFrames: Array<{ boneFrame: BoneFrame; time: number }>): number => {
|
|
1437
|
-
let left = 0
|
|
1438
|
-
let right = keyFrames.length
|
|
1439
|
-
while (left < right) {
|
|
1440
|
-
const mid = Math.floor((left + right) / 2)
|
|
1441
|
-
if (keyFrames[mid].time <= time) {
|
|
1442
|
-
left = mid + 1
|
|
1443
|
-
} else {
|
|
1444
|
-
right = mid
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
return left
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
const boneNamesToRotate: string[] = []
|
|
1451
|
-
const rotationsToApply: Quat[] = []
|
|
1452
|
-
const boneNamesToMove: string[] = []
|
|
1453
|
-
const translationsToApply: Vec3[] = []
|
|
1454
|
-
const morphNamesToSet: string[] = []
|
|
1455
|
-
const morphWeightsToSet: number[] = []
|
|
1373
|
+
public stopAnimation() {
|
|
1374
|
+
this.player.stop()
|
|
1375
|
+
}
|
|
1456
1376
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1377
|
+
public pauseAnimation() {
|
|
1378
|
+
this.player.pause()
|
|
1379
|
+
}
|
|
1460
1380
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
const endTime = keyFrames[keyFrames.length - 1].time
|
|
1464
|
-
const clampedFrameTime = Math.max(startTime, Math.min(endTime, frameTime))
|
|
1381
|
+
public seekAnimation(time: number) {
|
|
1382
|
+
if (!this.currentModel || !this.hasAnimation) return
|
|
1465
1383
|
|
|
1466
|
-
|
|
1467
|
-
const upperBoundIndexMinusOne = upperBoundIndex - 1
|
|
1384
|
+
this.player.seek(time)
|
|
1468
1385
|
|
|
1469
|
-
|
|
1386
|
+
// Immediately apply pose at seeked time
|
|
1387
|
+
const pose = this.player.getPoseAtTime(time)
|
|
1388
|
+
this.applyPose(pose)
|
|
1470
1389
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1390
|
+
// Update model pose and physics
|
|
1391
|
+
this.currentModel.evaluatePose()
|
|
1473
1392
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
interp[0] / 127, // x1
|
|
1490
|
-
interp[1] / 127, // x2
|
|
1491
|
-
interp[2] / 127, // y1
|
|
1492
|
-
interp[3] / 127, // y2
|
|
1493
|
-
gradient
|
|
1494
|
-
)
|
|
1495
|
-
const interpolatedRotation = Quat.slerp(boneFrameA.rotation, boneFrameB.rotation, rotWeight)
|
|
1496
|
-
|
|
1497
|
-
// Interpolate translation using Bezier (separate curves for X, Y, Z)
|
|
1498
|
-
// VMD interpolation layout (from reference, 4x4 grid, row-major):
|
|
1499
|
-
// Row 0: X_x1, Y_x1, phy1, phy2,
|
|
1500
|
-
// Row 1: X_y1, Y_y1, Z_y1, R_y1,
|
|
1501
|
-
// Row 2: X_x2, Y_x2, Z_x2, R_x2,
|
|
1502
|
-
// Row 3: X_y2, Y_y2, Z_y2, R_y2,
|
|
1503
|
-
// Row 4: Y_x1, Z_x1, R_x1, X_y1,
|
|
1504
|
-
// Row 5: Y_y1, Z_y1, R_y1, X_x2,
|
|
1505
|
-
// Row 6: Y_x2, Z_x2, R_x2, X_y2,
|
|
1506
|
-
// Row 7: Y_y2, Z_y2, R_y2, 00,
|
|
1507
|
-
// Row 8: Z_x1, R_x1, X_y1, Y_y1,
|
|
1508
|
-
// Row 9: Z_y1, R_y1, X_x2, Y_x2,
|
|
1509
|
-
// Row 10: Z_x2, R_x2, X_y2, Y_y2,
|
|
1510
|
-
// Row 11: Z_y2, R_y2, 00, 00,
|
|
1511
|
-
// Row 12: R_x1, X_y1, Y_y1, Z_y1,
|
|
1512
|
-
// Row 13: R_y1, X_x2, Y_x2, Z_x2,
|
|
1513
|
-
// Row 14: R_x2, X_y2, Y_y2, Z_y2,
|
|
1514
|
-
// Row 15: R_y2, 00, 00, 00
|
|
1515
|
-
// For rotation: R_x1=16, R_y1=20, R_x2=24, R_y2=28
|
|
1516
|
-
// For position X: X_x1=0, X_y1=4, X_x2=8, X_y2=12
|
|
1517
|
-
// For position Y: Y_x1=16, Y_y1=20, Y_x2=24, Y_y2=28
|
|
1518
|
-
// For position Z: Z_x1=32, Z_y1=36, Z_x2=40, Z_y2=44
|
|
1519
|
-
const xWeight = bezierInterpolate(
|
|
1520
|
-
interp[0] / 127, // X_x1
|
|
1521
|
-
interp[8] / 127, // X_x2
|
|
1522
|
-
interp[4] / 127, // X_y1
|
|
1523
|
-
interp[12] / 127, // X_y2
|
|
1524
|
-
gradient
|
|
1525
|
-
)
|
|
1526
|
-
const yWeight = bezierInterpolate(
|
|
1527
|
-
interp[16] / 127, // Y_x1
|
|
1528
|
-
interp[24] / 127, // Y_x2
|
|
1529
|
-
interp[20] / 127, // Y_y1
|
|
1530
|
-
interp[28] / 127, // Y_y2
|
|
1531
|
-
gradient
|
|
1532
|
-
)
|
|
1533
|
-
const zWeight = bezierInterpolate(
|
|
1534
|
-
interp[32] / 127, // Z_x1
|
|
1535
|
-
interp[40] / 127, // Z_x2
|
|
1536
|
-
interp[36] / 127, // Z_y1
|
|
1537
|
-
interp[44] / 127, // Z_y2
|
|
1538
|
-
gradient
|
|
1539
|
-
)
|
|
1540
|
-
|
|
1541
|
-
const interpolatedTranslation = new Vec3(
|
|
1542
|
-
boneFrameA.translation.x + (boneFrameB.translation.x - boneFrameA.translation.x) * xWeight,
|
|
1543
|
-
boneFrameA.translation.y + (boneFrameB.translation.y - boneFrameA.translation.y) * yWeight,
|
|
1544
|
-
boneFrameA.translation.z + (boneFrameB.translation.z - boneFrameA.translation.z) * zWeight
|
|
1545
|
-
)
|
|
1546
|
-
|
|
1547
|
-
boneNamesToRotate.push(boneName)
|
|
1548
|
-
rotationsToApply.push(interpolatedRotation)
|
|
1549
|
-
boneNamesToMove.push(boneName)
|
|
1550
|
-
translationsToApply.push(interpolatedTranslation)
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
// Helper to find upper bound index for morph frames
|
|
1555
|
-
const upperBoundMorphIndex = (time: number, keyFrames: Array<{ morphFrame: MorphFrame; time: number }>): number => {
|
|
1556
|
-
let left = 0
|
|
1557
|
-
let right = keyFrames.length
|
|
1558
|
-
while (left < right) {
|
|
1559
|
-
const mid = Math.floor((left + right) / 2)
|
|
1560
|
-
if (keyFrames[mid].time <= time) {
|
|
1561
|
-
left = mid + 1
|
|
1562
|
-
} else {
|
|
1563
|
-
right = mid
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
return left
|
|
1393
|
+
if (this.physics) {
|
|
1394
|
+
const worldMats = this.currentModel.getBoneWorldMatrices()
|
|
1395
|
+
this.physics.reset(worldMats, this.currentModel.getBoneInverseBindMatrices())
|
|
1396
|
+
|
|
1397
|
+
// Upload matrices immediately
|
|
1398
|
+
this.device.queue.writeBuffer(
|
|
1399
|
+
this.worldMatrixBuffer!,
|
|
1400
|
+
0,
|
|
1401
|
+
worldMats.buffer,
|
|
1402
|
+
worldMats.byteOffset,
|
|
1403
|
+
worldMats.byteLength
|
|
1404
|
+
)
|
|
1405
|
+
const encoder = this.device.createCommandEncoder()
|
|
1406
|
+
this.computeSkinMatrices(encoder)
|
|
1407
|
+
this.device.queue.submit([encoder.finish()])
|
|
1567
1408
|
}
|
|
1409
|
+
}
|
|
1568
1410
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
// Clamp frame time to track range
|
|
1574
|
-
const startTime = keyFrames[0].time
|
|
1575
|
-
const endTime = keyFrames[keyFrames.length - 1].time
|
|
1576
|
-
const clampedFrameTime = Math.max(startTime, Math.min(endTime, frameTime))
|
|
1577
|
-
|
|
1578
|
-
const upperBoundIndex = upperBoundMorphIndex(clampedFrameTime, keyFrames)
|
|
1579
|
-
const upperBoundIndexMinusOne = upperBoundIndex - 1
|
|
1580
|
-
|
|
1581
|
-
if (upperBoundIndexMinusOne < 0) continue
|
|
1411
|
+
public getAnimationProgress() {
|
|
1412
|
+
return this.player.getProgress()
|
|
1413
|
+
}
|
|
1582
1414
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1415
|
+
/**
|
|
1416
|
+
* Apply animation pose to model
|
|
1417
|
+
*/
|
|
1418
|
+
private applyPose(pose: AnimationPose): void {
|
|
1419
|
+
if (!this.currentModel) return
|
|
1585
1420
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
// Linear interpolation between two keyframes
|
|
1592
|
-
const timeA = keyFrames[upperBoundIndexMinusOne].time
|
|
1593
|
-
const morphFrameB = keyFrames[upperBoundIndex].morphFrame
|
|
1594
|
-
const gradient = (clampedFrameTime - timeA) / (timeB - timeA)
|
|
1595
|
-
const interpolatedWeight = morphFrameA.weight + (morphFrameB.weight - morphFrameA.weight) * gradient
|
|
1596
|
-
|
|
1597
|
-
morphNamesToSet.push(morphName)
|
|
1598
|
-
morphWeightsToSet.push(interpolatedWeight)
|
|
1599
|
-
}
|
|
1421
|
+
// Apply bone rotations
|
|
1422
|
+
if (pose.boneRotations.size > 0) {
|
|
1423
|
+
const boneNames = Array.from(pose.boneRotations.keys())
|
|
1424
|
+
const rotations = Array.from(pose.boneRotations.values())
|
|
1425
|
+
this.rotateBones(boneNames, rotations, 0)
|
|
1600
1426
|
}
|
|
1601
1427
|
|
|
1602
|
-
// Apply
|
|
1603
|
-
if (
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
this.moveBones(boneNamesToMove, translationsToApply, 0)
|
|
1428
|
+
// Apply bone translations
|
|
1429
|
+
if (pose.boneTranslations.size > 0) {
|
|
1430
|
+
const boneNames = Array.from(pose.boneTranslations.keys())
|
|
1431
|
+
const translations = Array.from(pose.boneTranslations.values())
|
|
1432
|
+
this.moveBones(boneNames, translations, 0)
|
|
1608
1433
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1434
|
+
|
|
1435
|
+
// Apply morph weights
|
|
1436
|
+
for (const [morphName, weight] of pose.morphWeights.entries()) {
|
|
1437
|
+
this.setMorphWeight(morphName, weight, 0)
|
|
1613
1438
|
}
|
|
1614
1439
|
}
|
|
1615
1440
|
|
|
@@ -2104,18 +1929,11 @@ export class Engine {
|
|
|
2104
1929
|
this.updateRenderTarget()
|
|
2105
1930
|
|
|
2106
1931
|
// Animate VMD animation if playing
|
|
2107
|
-
if (this.
|
|
2108
|
-
const
|
|
2109
|
-
if (
|
|
2110
|
-
|
|
2111
|
-
this.stopAnimation()
|
|
2112
|
-
} else {
|
|
2113
|
-
const frameTime = elapsedSeconds
|
|
2114
|
-
this.animate(frameTime)
|
|
1932
|
+
if (this.hasAnimation && this.currentModel) {
|
|
1933
|
+
const pose = this.player.update(currentTime)
|
|
1934
|
+
if (pose) {
|
|
1935
|
+
this.applyPose(pose)
|
|
2115
1936
|
}
|
|
2116
|
-
} else if (this.playingAnimation && this.animationDuration <= 0) {
|
|
2117
|
-
// Animation has no duration or invalid, stop it immediately
|
|
2118
|
-
this.stopAnimation()
|
|
2119
1937
|
}
|
|
2120
1938
|
|
|
2121
1939
|
// Update model pose first (this may update morph weights via tweens)
|
|
@@ -2141,7 +1959,7 @@ export class Engine {
|
|
|
2141
1959
|
// Hide model if animation is loaded but hasn't started playing yet (prevents A-pose flash)
|
|
2142
1960
|
// Once animation has played (even if it stopped), continue rendering normally
|
|
2143
1961
|
// Still update physics and poses, just don't render visually before first play
|
|
2144
|
-
if (this.hasAnimation && !this.
|
|
1962
|
+
if (this.hasAnimation && !this.player.isPlayingState() && this.animationStartTime === 0) {
|
|
2145
1963
|
// Submit encoder to ensure matrices are uploaded and physics initializes
|
|
2146
1964
|
this.device.queue.submit([encoder.finish()])
|
|
2147
1965
|
return
|