修复播放完动画,嘴角下垂的问题

This commit is contained in:
yinsx
2025-12-24 15:52:06 +08:00
parent de7c26da35
commit 48d587c1ff
3 changed files with 51 additions and 7 deletions

View File

@ -3,6 +3,7 @@ class BlendShapeAnimator {
constructor(config = {}) { constructor(config = {}) {
this.morphTargetAdapter = null; this.morphTargetAdapter = null;
this.animationFrames = []; this.animationFrames = [];
this.animationShapeNames = [];
this.isPlaying = false; this.isPlaying = false;
this.currentFrameIndex = 0; this.currentFrameIndex = 0;
this.animationStartTime = 0; this.animationStartTime = 0;
@ -58,7 +59,8 @@ class BlendShapeAnimator {
// 加载动画帧数据 // 加载动画帧数据
loadAnimationFrames(frames) { loadAnimationFrames(frames) {
this.animationFrames = frames; this.animationFrames = frames || [];
this.animationShapeNames = this._collectAnimationShapeNames(this.animationFrames);
} }
// 播放动画 // 播放动画
@ -78,7 +80,7 @@ class BlendShapeAnimator {
window.ExpressionLibrary.randomPlayer.stop(); window.ExpressionLibrary.randomPlayer.stop();
} }
this.stopAnimation(); this.stopAnimation(false);
this.isPlaying = true; this.isPlaying = true;
this.currentFrameIndex = 0; this.currentFrameIndex = 0;
this.animationStartTime = performance.now(); this.animationStartTime = performance.now();
@ -88,17 +90,41 @@ class BlendShapeAnimator {
} }
// 停止动画 // 停止动画
stopAnimation() { stopAnimation(resumeExpressions = true) {
this.isPlaying = false; this.isPlaying = false;
this._resetAnimationInfluences();
// 恢复随机表情 // 恢复随机表情
if (this.isExpressionEnabled && window.ExpressionLibrary) { if (resumeExpressions && this.isExpressionEnabled && window.ExpressionLibrary) {
window.ExpressionLibrary.randomPlayer.start(); window.ExpressionLibrary.randomPlayer.start();
} }
this.onStatusChange('info', '已停止'); this.onStatusChange('info', '已停止');
} }
_collectAnimationShapeNames(frames) {
const names = new Set();
frames.forEach(frame => {
const blendShapes = frame?.blendShapes;
if (!blendShapes) return;
Object.keys(blendShapes).forEach(name => names.add(name));
});
return Array.from(names);
}
_resetAnimationInfluences() {
if (!this.morphTargetAdapter || this.animationShapeNames.length === 0) {
return;
}
this.animationShapeNames.forEach(name => {
this.morphTargetAdapter.setInfluence(name, 0);
});
}
// 内部动画帧处理 // 内部动画帧处理
_animateFrame() { _animateFrame() {
if (!this.isPlaying) return; if (!this.isPlaying) return;
@ -116,12 +142,18 @@ class BlendShapeAnimator {
const currentFrame = this.animationFrames[targetFrameIndex]; const currentFrame = this.animationFrames[targetFrameIndex];
const nextFrame = this.animationFrames[Math.min(targetFrameIndex + 1, this.animationFrames.length - 1)]; const nextFrame = this.animationFrames[Math.min(targetFrameIndex + 1, this.animationFrames.length - 1)];
const currentBlendShapes = currentFrame?.blendShapes || {};
const nextBlendShapes = nextFrame?.blendShapes || {};
const frameProgress = exactFrame - targetFrameIndex; const frameProgress = exactFrame - targetFrameIndex;
const smoothProgress = this._easeOutQuad(frameProgress); const smoothProgress = this._easeOutQuad(frameProgress);
for (const key in currentFrame.blendShapes) { const shapeNames = this.animationShapeNames.length > 0
const currentValue = currentFrame.blendShapes[key] || 0; ? this.animationShapeNames
const nextValue = nextFrame.blendShapes[key] || 0; : Object.keys(currentBlendShapes);
for (const key of shapeNames) {
const currentValue = currentBlendShapes[key] || 0;
const nextValue = nextBlendShapes[key] || 0;
const interpolatedValue = this._lerp(currentValue, nextValue, smoothProgress); const interpolatedValue = this._lerp(currentValue, nextValue, smoothProgress);
const scaledValue = interpolatedValue * this.blendShapeScale; const scaledValue = interpolatedValue * this.blendShapeScale;

View File

@ -37,6 +37,13 @@
style="width: 100%; cursor: pointer;"> style="width: 100%; cursor: pointer;">
</div> </div>
<div class="input-group">
<label>动画速度 (FPS): <span id="fpsValue">30</span></label>
<input type="range" id="fpsSlider" min="10" max="60" step="1" value="30"
oninput="updateFps(this.value)"
style="width: 100%; cursor: pointer;">
</div>
<button id="generateBtn" onclick="generateAnimation()">生成动画</button> <button id="generateBtn" onclick="generateAnimation()">生成动画</button>
<button onclick="playAnimation()">播放动画</button> <button onclick="playAnimation()">播放动画</button>
<button onclick="stopAnimation()">停止动画</button> <button onclick="stopAnimation()">停止动画</button>

View File

@ -104,6 +104,11 @@ function updateScale(value) {
document.getElementById('scaleValue').textContent = value; document.getElementById('scaleValue').textContent = value;
} }
function updateFps(value) {
animator.updateConfig('dataFps', parseInt(value, 10));
document.getElementById('fpsValue').textContent = value;
}
function testBlendShape() { function testBlendShape() {
if (morphAdapter.getCacheSize() === 0) { if (morphAdapter.getCacheSize() === 0) {
showStatus("模型未加载", "error"); showStatus("模型未加载", "error");