Three.js图片旋转木马:Quaternion.angleTo无法区分左右角度求解
Three.js图片旋转木马:精准旋转方向区分与平滑动画实现
问题背景
我正在用Three.js开发图片旋转木马,通过以下代码将图片(Plane平面)以圆形排列添加到组中:
function createObjects(){ let angleLambda = 360 / 6; for (let c = 1; c < 7; c++) { let geometry = new THREE.PlaneGeometry(1, 1.5); let texture1 = new THREE.TextureLoader().load("pic/0" + c + ".jpg"); let material = new THREE.MeshBasicMaterial({map: texture1, side: THREE.DoubleSide}); let plane = new THREE.Mesh(geometry, material); plane.rotation.y = -THREE.MathUtils.degToRad(angleLambda * (c-1)); // 负数表示顺时针排列 plane.translateZ(3.5); carousel.add(plane); } }
目前我用临时方法实现了旋转到指定图片后停止的功能:
if(carousel.rotation.y <= THREE.MathUtils.degToRad(360/6*5)){ carousel.rotation.y += 0.01; }
但尝试用四元数的方式实现时遇到问题:
if(carousel.rotation.y <= carousel.children[4].quaternion.angleTo(camera.quaternion)) { carousel.rotation.y += 0.01; }
该方法仅对前3张图片有效,目标为第5张时会停在对称的第2张——因为quaternion.angleTo()只返回0到π的角度,无法区分左右旋转方向。
提问
- 如何正确区分旋转木马左右侧图片的角度?必须用状态机记录位置吗?
- 每次累加0.01更新旋转并判断的方式太繁琐,有没有类似
rotateUntil(x)或rotate(45°)的平滑旋转方法?
解决方案
1. 区分旋转方向:无需状态机,用角度差值的最短路径计算
quaternion.angleTo()的局限性在于只返回无方向的夹角,解决思路是直接计算carousel需要旋转到的目标角度,并处理角度循环的差值,判断旋转方向:
步骤1:给每个Plane记录目标旋转角度
在创建Plane时,存储carousel需要转到的目标y轴角度(此时该Plane会正对相机):
function createObjects(){ let angleLambda = 360 / 6; for (let c = 1; c < 7; c++) { // ... 原有创建代码 plane.rotation.y = -THREE.MathUtils.degToRad(angleLambda * (c-1)); // 存储目标角度:carousel转到该值时,当前Plane正对相机 plane.targetCarouselRotY = -plane.rotation.y; plane.translateZ(3.5); carousel.add(plane); } }
步骤2:计算最短旋转路径与方向
在动画循环中,计算当前carousel角度与目标角度的差值,并将差值限制在[-π, π]范围内,通过差值的正负判断旋转方向:
// 假设目标是第5张图(索引4) const targetAngle = carousel.children[4].targetCarouselRotY; let angleDiff = targetAngle - carousel.rotation.y; // 处理角度循环,将差值转换为[-π, π]的最短路径 angleDiff = THREE.MathUtils.euclideanModulo(angleDiff + Math.PI, 2 * Math.PI) - Math.PI; // 设置阈值防止抖动,持续旋转直到接近目标 if (Math.abs(angleDiff) > 0.01) { // 按差值的正负决定旋转方向(正为逆时针,负为顺时针) carousel.rotation.y += Math.sign(angleDiff) * 0.01; } else { // 最终校准到目标角度 carousel.rotation.y = targetAngle; }
这种方法无需状态机,通过数学计算直接区分左右旋转方向,避免停在对称位置。
2. 平滑旋转:用插值或动画库实现
Three.js没有内置的rotateUntil方法,但可以通过以下两种方式实现平滑旋转:
方式1:用Three.js内置的MathUtils.lerp做线性插值
通过渐进式插值让旋转更平滑,factor值越小,动画越平缓:
const targetAngle = carousel.children[4].targetCarouselRotY; // 平滑插值,factor建议取0.03-0.1之间 carousel.rotation.y = THREE.MathUtils.lerp(carousel.rotation.y, targetAngle, 0.05); // 当角度差足够小时,锁定目标角度 const angleDiff = THREE.MathUtils.euclideanModulo(targetAngle - carousel.rotation.y + Math.PI, 2 * Math.PI) - Math.PI; if (Math.abs(angleDiff) < 0.001) { carousel.rotation.y = targetAngle; }
方式2:用Tween.js实现带缓动的动画
Three.js官方提供的Tween.js可以实现更丰富的缓动效果,比如先快后慢的平滑过渡:
// 引入Tween.js(Three.js自带,路径为three/addons/libs/tween.module.js) import { Tween, Easing } from 'three/addons/libs/tween.module.js'; // 创建补间动画,1秒内旋转到目标角度 new Tween(carousel.rotation) .to({ y: targetAngle }, 1000) .easing(Easing.Quadratic.InOut) // 缓动函数,可选线性、指数等 .onComplete(() => { // 动画结束后的回调 carousel.rotation.y = targetAngle; }) .start(); // 必须在动画循环中更新Tween function animate() { requestAnimationFrame(animate); Tween.update(); renderer.render(scene, camera); }
内容的提问来源于stack exchange,提问作者clockw0rk
相关产品推荐
相关产品推荐

