魔方对于大多数人都不陌生,也是个立方体的玩意儿。
这里就简单用three.js实现一下,复杂的还是定位,毕竟是3d的还能乱转。
看代码前还是先来说明:基本框架还是一样----舞台,摄像头和渲染器。
之后用faceMaterial写一个6面颜色不一样的cube,并用27个这样的cube组成魔方的基本样子。
trackballControls是摄像头控制函数,加入可以用鼠标控制其中的摄像头。
监听鼠标按下事件,按下时获取点击的三维坐标,获取在最前端的cube的name。
通过坐标计算旋转方向(这里容易脑壳疼),通过name计算同一平面的其他cube。
通过方向与平面以矩阵旋转平面内9个cube,并把旋转做成动画。
听说好文章结尾都有彩蛋~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>test3d</title>
<style type="text/css">
body{
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="webgl"></div>
<script type="text/javascript" src="http://test.ganjiacheng.cn/3d/learning-threejs/libs/three.js"></script>
<script type="text/javascript" src="http://test.ganjiacheng.cn/3d/learning-threejs/libs/TrackballControls.js"></script>
<script type="text/javascript">
function init(){
var scene=new THREE.Scene();
var camera=new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000);
camera.position.set(-20,20,20);
camera.lookAt(scene.position);
var renderer=new THREE.WebGLRenderer();
renderer.setClearColor(0xdadada);
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.shadowMapEnabled=true;
var axes=new THREE.AxisHelper(2);
scene.add(axes);
var group=new THREE.Mesh();
var mats=[];
mats.push(new THREE.MeshBasicMaterial({color:0x009e60}));//g
mats.push(new THREE.MeshBasicMaterial({color:0x0051ba}));//b
mats.push(new THREE.MeshBasicMaterial({color:0xffd500}));//y
mats.push(new THREE.MeshBasicMaterial({color:0xff5800}));//j
mats.push(new THREE.MeshBasicMaterial({color:0xC41E3A}));//r
mats.push(new THREE.MeshBasicMaterial({color:0xffffff}));//w
var faceMaterial=new THREE.MeshFaceMaterial(mats);
for(var x=0;x<3;x++){
for(var y=0;y<3;y++){
for(var z=0;z<3;z++){
var cubeGeom=new THREE.BoxGeometry(2.9,2.9,2.9);
var cube=new THREE.Mesh(cubeGeom,faceMaterial);
cube.position.set(x*3-3,y*3-3,z*3-3);
cube.name=z+3*y+9*x;
group.add(cube);
}
}
}
scene.add(group);
var trackballControls=new THREE.TrackballControls(camera);
trackballControls.rotateSpeed=1.0;
trackballControls.zoomSpeed=1.0;
trackballControls.panSpeed=1.0;
document.addEventListener('mousedown',onMouseDown,false);
var clock=new THREE.Clock();
document.getElementById("webgl").appendChild(renderer.domElement);
renderer.render(scene,camera);
var test=new THREE.MeshBasicMaterial({color:0x000000});
var startMove=-1;
var moveList=[];
var rotateDirection;
var DirectionLR=true;
var j=0;
function onMouseDown(event){
if(startMove!=-1){
return;
}
var vector=new THREE.Vector3((event.clientX/window.innerWidth)*2-1,-(event.clientY/window.innerHeight)*2+1,0.5);
vector=vector.unproject(camera);
var raycaster=new THREE.Raycaster(camera.position,vector.sub(camera.position).normalize());
var intersects=raycaster.intersectObjects(group.children);
if(intersects.length>0){
j=0;
moveList=[];
startMove=intersects[0].object.name;
var y=scene.children[1].children[startMove].position.y;
getRotateDirection(intersects[0].point.x,intersects[0].point.y,intersects[0].point.z);
if(rotateDirection==1){
for(var i=0;i<27;i++){
if(xround(scene.children[1].children[i].position.y,2)==xround(y,2)){
moveList.push(i);
}
}
}else if(rotateDirection==2){
for(var i=0;i<27;i++){
if(xround(scene.children[1].children[i].position.x,2)==xround(scene.children[1].children[startMove].position.x,2)){
moveList.push(i);
}
}
}else if(rotateDirection=3){
for(var i=0;i<27;i++){
if(xround(scene.children[1].children[i].position.z,2)==xround(scene.children[1].children[startMove].position.z,2)){
moveList.push(i);
}
}
}
}
}
function reset(){
startMove=-1;
}
function rotationMF(moveList){
var rotationV=DirectionLR?Math.PI/100:-Math.PI/100;
if(rotateDirection==1){
if(j<50){
for(var i in moveList){
var rotation = new THREE.Matrix4().makeRotationY(rotationV);
scene.children[1].children[moveList[i]].applyMatrix(rotation);
}
j++;
}else{
reset()
}
}else if(rotateDirection==2){
if(j<50){
for(var i in moveList){
var rotation = new THREE.Matrix4().makeRotationX(rotationV);
scene.children[1].children[moveList[i]].applyMatrix(rotation);
}
j++;
}else{
reset()
}
}else if(rotateDirection==3){
if(j<50){
for(var i in moveList){
var rotation = new THREE.Matrix4().makeRotationZ(rotationV);
scene.children[1].children[moveList[i]].applyMatrix(rotation);
}
j++;
}else{
reset()
}
}
}
function xround(x, num){
return Math.round(x * Math.pow(10, num)) / Math.pow(10, num);
}
function getRotateDirection(x,y,z){
function dealxyz(axis){
for(var i=0;i<3;i++){
if(xround(axis[i],2)==-4.45 || xround(axis[i],2)==4.45){
var fl=xround(axis.splice(i,1),2)==-4.45;
axis[0]=axis[0]>1.5?axis[0]-3:axis[0];
axis[0]=axis[0]<-1.5?axis[0]+3:axis[0];
axis[1]=axis[1]>1.5?axis[1]-3:axis[1];
axis[1]=axis[1]<-1.5?axis[1]+3:axis[1];
var judge;
if(i==0 && Math.abs(axis[0])<Math.abs(axis[1])){
rotateDirection=1;
judge=fl?(Math.abs(axis[0])<Math.abs(axis[1]) && axis[1]<0):(Math.abs(axis[0])<Math.abs(axis[1]) && axis[1]>0);
}else if(i==0 && Math.abs(axis[0])>Math.abs(axis[1])){
rotateDirection=3;
judge=!fl?(Math.abs(axis[1])<Math.abs(axis[0]) && axis[0]<0):(Math.abs(axis[1])<Math.abs(axis[0]) && axis[0]>0);
}else if(i==1 && Math.abs(axis[0])<Math.abs(axis[1])){
rotateDirection=2;
judge=!fl?(Math.abs(axis[0])<Math.abs(axis[1]) && axis[1]<0):(Math.abs(axis[0])<Math.abs(axis[1]) && axis[1]>0);
}else if(i==1 && Math.abs(axis[0])>Math.abs(axis[1])){
rotateDirection=3;
judge=fl?(Math.abs(axis[1])<Math.abs(axis[0]) && axis[0]<0):(Math.abs(axis[1])<Math.abs(axis[0]) && axis[0]>0);
}else if(i==2 && Math.abs(axis[0])>Math.abs(axis[1])){
rotateDirection=1;
judge=fl?(Math.abs(axis[1])<Math.abs(axis[0]) && axis[0]>0):(Math.abs(axis[1])<Math.abs(axis[0]) && axis[0]<0);
console.log(judge);
}else if(i==2 && Math.abs(axis[0])<Math.abs(axis[1])){
rotateDirection=2;
judge=!fl?(Math.abs(axis[0])<Math.abs(axis[1]) && axis[1]>0):(Math.abs(axis[0])<Math.abs(axis[1]) && axis[1]<0);
}
return judge;
}
}
}
DirectionLR=!dealxyz([x,y,z]);
}
function renderScene(){
var delta=clock.getDelta();
if(startMove!=-1){rotationMF(moveList);}
trackballControls.update(delta);
requestAnimationFrame(renderScene);
renderer.render(scene,camera);
}
renderScene();
}
window.onload=init;
</script>
</body>
</html>
这里不预览啦,主要注明的一点就是看起来比写起来真是两码事,
一开始纠结在rotation的旋转会连带转自己的坐标轴。后来慢慢发现他转的是他的children子元素,并可以创造矩阵来旋转。
本来想的很好做一个沿y轴转四个面,然后推广到x,z,只要写一套就行。现实还是安心的做完了6个面以及每个面里9个小块的分析。
本来还想着怎么能写的系统一点,可以轻松调InOut,这样就可以做多元的魔方,说不定还能研究个魔方的随机打乱和复原,好吧继续想着吧。
还有点感悟就是three.js文档虽然齐全不过问的问题确实不多,有点难搜到相似问题。搜到的时候讲的都是欧拉角,旋转矩阵,四元数这种画风。。。
three.js完结篇,,
才怪
版权声明:本文为原创文章,转载请注明出处和作者,不得用于商业用途,请遵守
CC BY-NC-SA 4.0协议。
赞赏一下
支付宝打赏
微信打赏