diff --git a/examples/3d/blendshapeAnimator.js b/examples/3d/blendshapeAnimator.js index 41d399e..94da996 100644 --- a/examples/3d/blendshapeAnimator.js +++ b/examples/3d/blendshapeAnimator.js @@ -3,6 +3,7 @@ class BlendShapeAnimator { constructor(config = {}) { this.morphTargetAdapter = null; this.animationFrames = []; + this.animationShapeNames = []; this.isPlaying = false; this.currentFrameIndex = 0; this.animationStartTime = 0; @@ -58,7 +59,8 @@ class BlendShapeAnimator { // 加载动画帧数据 loadAnimationFrames(frames) { - this.animationFrames = frames; + this.animationFrames = frames || []; + this.animationShapeNames = this._collectAnimationShapeNames(this.animationFrames); } // 播放动画 @@ -78,7 +80,7 @@ class BlendShapeAnimator { window.ExpressionLibrary.randomPlayer.stop(); } - this.stopAnimation(); + this.stopAnimation(false); this.isPlaying = true; this.currentFrameIndex = 0; this.animationStartTime = performance.now(); @@ -88,17 +90,41 @@ class BlendShapeAnimator { } // 停止动画 - stopAnimation() { + stopAnimation(resumeExpressions = true) { this.isPlaying = false; + this._resetAnimationInfluences(); // 恢复随机表情 - if (this.isExpressionEnabled && window.ExpressionLibrary) { + if (resumeExpressions && this.isExpressionEnabled && window.ExpressionLibrary) { window.ExpressionLibrary.randomPlayer.start(); } 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() { if (!this.isPlaying) return; @@ -116,12 +142,18 @@ class BlendShapeAnimator { const currentFrame = this.animationFrames[targetFrameIndex]; 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 smoothProgress = this._easeOutQuad(frameProgress); - for (const key in currentFrame.blendShapes) { - const currentValue = currentFrame.blendShapes[key] || 0; - const nextValue = nextFrame.blendShapes[key] || 0; + const shapeNames = this.animationShapeNames.length > 0 + ? this.animationShapeNames + : 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 scaledValue = interpolatedValue * this.blendShapeScale; diff --git a/examples/3d/index.html b/examples/3d/index.html index 5671c35..051f729 100644 --- a/examples/3d/index.html +++ b/examples/3d/index.html @@ -37,6 +37,13 @@ style="width: 100%; cursor: pointer;"> +
+ + +
+ diff --git a/examples/3d/main.js b/examples/3d/main.js index 2f5a7d4..88bcea4 100644 --- a/examples/3d/main.js +++ b/examples/3d/main.js @@ -104,6 +104,11 @@ function updateScale(value) { document.getElementById('scaleValue').textContent = value; } +function updateFps(value) { + animator.updateConfig('dataFps', parseInt(value, 10)); + document.getElementById('fpsValue').textContent = value; +} + function testBlendShape() { if (morphAdapter.getCacheSize() === 0) { showStatus("模型未加载", "error");