修复播放完动画,嘴角下垂的问题
This commit is contained in:
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user