张春成
2023/03/06阅读:24主题:默认主题
THREEJS 的三阶魔方
THREEJS 的三阶魔方
春去江花红胜火,春来江水绿如蓝,能不忆江南。这个周末气温回暖,草木发芽,于是在家有前端做了个虚拟化的三阶魔方。本文的开源代码可见我的前端笔记本
Rolling magic cube with THREE.js[1]
-
THREEJS 的三阶魔方[2] -
圆角方块[3] -
方块分组[4] -
定轴旋转[5] -
操作链表[6] -
平滑曲线[7]
-
圆角方块
圆角方块类是继承自 THREEJS 的 BoxGeometry,与基类的区别在于它是圆角的。通过简单地在每个面上贴上对应颜色的贴纸,我们就可以生成一个仿真性挺强的魔方。


方块分组
三阶魔方的旋转是一种挺复杂的东西。它包括 27 个块,从三个方向(axis)来看,它们分别组成三个面(level),每个面都有两个可能的旋转方向(dir),因此我们可以认为它的旋转自由度是 。对于如此大量的旋转动作来说,每个旋转动作都对应九个块。由于魔方的排列组合数量过多,因此这九个块是随机的。这就导致了一个问题,这个问题来自 THREE.js 的运行逻辑。
对于 THREE.js 来说,它的旋转操作是针对 Mesh 或 Mesh 的组合(Group),而为了提高执行效率起见,这些操作的对象都是事先定义好的。也就是说,如果要对某个面执行旋转动作,需要将它操作的九个块合并成一个组,再对这个组做旋转。而这样做显然是不经济的,因为每次旋转的块和面的关系无法事先确定。
因此,为了解决面的旋转问题,就需要程序根据要旋转的面“找到”它对应的九个块,再将这九个块进行同步旋转。找到九个块可以做到,而让它们进行同步旋转时就遇到另一个问题,那就是旋转轴不是块的中心。
定轴旋转
接下来要解决的问题是对块进行旋转,并且旋转轴不是块的中心,它甚至都不在块的内部。为了解决这个问题,我将全部块的转动轴都设置在魔方的中心,再将转动轴作为虚拟成员与每个块进行绑定,由于核心代码很短,因此誊写如下。接下来,只要将这些块所在的组进行旋转,就可以实现块沿着转动轴而不是自己的中心进行旋转。从使用者的角度来看,就是某个平面的九个块进行整体旋转的效果。
/**
* Generate pivot and bound it with the cube,
* the combination is a group.
*/
const pivot = new THREE.Group();
// The sub-cube
const material = new THREE.MeshNormalMaterial(),
size = 0.95,
geometry = new RoundedBoxGeometry(size, size, size, 20, 0.1),
cube = new THREE.Mesh(geometry, material);
Object.assign(cube.position, position);
pivot.add(cube);
操作链表
对于魔方来说,我们希望它旋转,但不希望它自由旋转。也就是说,每个面的每次旋转经过的弧度必须是 ,否则就会出现物理干涉的情况。另外,我们还需要实现一定的动效,让魔方看上去像是正常旋转的样子。这就要求它每次都精确地旋转一定角度,而某次旋转完成之前不应进行其他旋转。
为了满足上述要求,我建立了一个“操作链表”,它当然是 FIFO 的,链表中的元素是每个时间切片的旋转操作。在每个时间切片中,魔方的某个面只旋转一个较小的角度。而一次完整的 旋转则由多次时间切片构成。由于链表具有 FIFO 特性,因此它总能保证旋转操作的完整性。
平滑曲线
最后,为了使得旋转过程更像真实的物理旋转场景,我将每个时间切片的旋转角度进行映射,映射函数如下图所示。它代表开始和结束时的变化率较小,而中段的变化率较大。体现在旋转动作上,它代表开始和结束转动时的速度变化较小,而中段的旋转速度变化较大,即角速度呈现先增大后减小的规律性变化。

参考资料
Rolling magic cube with THREE.js: https://observablehq.com/@listenzcc/rolling-magic-cube-with-three-js
[2]THREEJS 的三阶魔方: #threejs-的三阶魔方
[3]圆角方块: #圆角方块
[4]方块分组: #方块分组
[5]定轴旋转: #定轴旋转
[6]操作链表: #操作链表
[7]平滑曲线: #平滑曲线
作者介绍