因为之前的几节讲了一些数学方法,例如Vector3、Matrix4、Euler还有Quaternion的知识。所以这篇郭先生就来说说用three.js怎么制作一个魔方。制作魔方主要运用坐标变换的知识,制作魔方的方法有很多,建议你先在大脑中构思,然后试着做一做,下面我将一种比较简单的方法。

制作出魔方各个方块的位置坐标(27个)的数组,然后制作出魔方各个面的材质(6个)

根据坐标和材质制作魔方的方块,并添加到一个组group

制作一个标志被选面的几何体(我是用球体),然后隐藏

使用THREE.Raycaster,给模型绑定事件,并记录选定的一些数据,将标志显示并放到合适的位置

点击模拟方向键盘,根据之前记录的数据,转动魔方(重点)
制作出魔方各个方块的位置坐标(27个)的数组,然后制作出魔方各个面的材质(6个)根据坐标和材质制作魔方的方块,并添加到一个组group制作一个标志被选面的几何体(我是用球体),然后隐藏使用THREE.Raycaster,给模型绑定事件,并记录选定的一些数据,将标志显示并放到合适的位置点击模拟方向键盘,根据之前记录的数据,转动魔方(重点)以上是主要的步骤,但是对于新同学,需要注意的仍然很多,下面上主要代码,按照方法说1. 定义的变量1. 定义的变量
posArr = [

[100,100,100],[100,100,0],[100,100,-100],[100,0,100],[100,0,0],[100,0,-100],[100,-100,100],[100,-100,0],[100,-100,-100],

[0,100,100],[0,100,0],[0,100,-100],[0,0,100],[0,0,0],[0,0,-100],[0,-100,100],[0,-100,0],[0,-100,-100],

[-100,100,100],[-100,100,0],[-100,100,-100],[-100,0,100],[-100,0,0],[-100,0,-100],[-100,-100,100],[-100,-100,0],[-100,-100,-100]
],//方块位置坐标
materials,//材质数组
mouse = new THREE.Vector2(),//通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1.
raycaster,//射线对象
group,//存放魔方方块的数组
groupTemp,//魔方转动时临时数组
object3d,//魔方被选择面的标志物对象
currentPos,//魔方被点击小块的位置
currentNor,//魔方被点击小块面的法向量
currentUp,//魔方被点击时,相机up的向量
posArr = [

[100,100,100],[100,100,0],[100,100,-100],[100,0,100],[100,0,0],[100,0,-100],[100,-100,100],[100,-100,0],[100,-100,-100],

[0,100,100],[0,100,0],[0,100,-100],[0,0,100],[0,0,0],[0,0,-100],[0,-100,100],[0,-100,0],[0,-100,-100],

[-100,100,100],[-100,100,0],[-100,100,-100],[-100,0,100],[-100,0,0],[-100,0,-100],[-100,-100,100],[-100,-100,0],[-100,-100,-100]
],//方块位置坐标
materials,//材质数组
mouse = new THREE.Vector2(),//通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1.
raycaster,//射线对象
group,//存放魔方方块的数组
groupTemp,//魔方转动时临时数组
object3d,//魔方被选择面的标志物对象
currentPos,//魔方被点击小块的位置
currentNor,//魔方被点击小块面的法向量
currentUp,//魔方被点击时,相机up的向量2. 定义材质数组2. 定义材质数组
initMaterial() {

var map_red = new THREE.TextureLoader().load('/static/images/color/m_red.jpg', () => this.loadover --);

var map_orange = new THREE.TextureLoader().load('/static/images/color/m_orange.jpg', () => this.loadover --);

var map_yellow = new THREE.TextureLoader().load('/static/images/color/m_yellow.jpg', () => this.loadover --);

var map_blue = new THREE.TextureLoader().load('/static/images/color/m_blue.jpg', () => this.loadover --);

var map_green = new THREE.TextureLoader().load('/static/images/color/m_green.jpg', () => this.loadover --);

var map_white = new THREE.TextureLoader().load('/static/images/color/m_white.jpg', () => this.loadover --);

var map_sprite = new THREE.TextureLoader().load('/static/images/base/direction.png', () => this.loadover --);


let mater_red = new THREE.MeshBasicMaterial({map: map_red, side: THREE.DoubleSide});

let mater_orange = new THREE.MeshBasicMaterial({map: map_orange, side: THREE.DoubleSide});

let mater_yellow = new THREE.MeshBasicMaterial({map: map_yellow, side: THREE.DoubleSide});

let mater_white = new THREE.MeshBasicMaterial({map: map_white, side: THREE.DoubleSide});

let mater_blue = new THREE.MeshBasicMaterial({map: map_blue, side: THREE.DoubleSide});

let mater_green = new THREE.MeshBasicMaterial({map: map_green, side: THREE.DoubleSide});


materials = [mater_red, mater_orange, mater_yellow,mater_white, mater_blue, mater_green];
}
initMaterial() {

var map_red = new THREE.TextureLoader().load('/static/images/color/m_red.jpg', () => this.loadover --);

var map_orange = new THREE.TextureLoader().load('/static/images/color/m_orange.jpg', () => this.loadover --);

var map_yellow = new THREE.TextureLoader().load('/static/images/color/m_yellow.jpg', () => this.loadover --);

var map_blue = new THREE.TextureLoader().load('/static/images/color/m_blue.jpg', () => this.loadover --);

var map_green = new THREE.TextureLoader().load('/static/images/color/m_green.jpg', () => this.loadover --);

var map_white = new THREE.TextureLoader().load('/static/images/color/m_white.jpg', () => this.loadover --);

var map_sprite = new THREE.TextureLoader().load('/static/images/base/direction.png', () => this.loadover --);


let mater_red = new THREE.MeshBasicMaterial({map: map_red, side: THREE.DoubleSide});

let mater_orange = new THREE.MeshBasicMaterial({map: map_orange, side: THREE.DoubleSide});

let mater_yellow = new THREE.MeshBasicMaterial({map: map_yellow, side: THREE.DoubleSide});

let mater_white = new THREE.MeshBasicMaterial({map: map_white, side: THREE.DoubleSide});

let mater_blue = new THREE.MeshBasicMaterial({map: map_blue, side: THREE.DoubleSide});

let mater_green = new THREE.MeshBasicMaterial({map: map_green, side: THREE.DoubleSide});


materials = [mater_red, mater_orange, mater_yellow,mater_white, mater_blue, mater_green];
}3. 绘制小方块并绘制标志物(先隐藏)3. 绘制小方块并绘制标志物(先隐藏)
initsquare() {

var sphereGeom = new THREE.SphereGeometry(10, 30, 20);

var sphereMate = new THREE.MeshPhongMaterial({color: 0x333333});

object3d = new THREE.Mesh(sphereGeom, sphereMate);

object3d.name = 'object3d';

object3d.visible = false;

scene.add(object3d);


group = new THREE.Group();

group.name = 'group';


var geometry = new THREE.BoxGeometry(100, 100, 100);

var mesh = new THREE.Mesh(geometry, materials);

posArr.forEach((d,i) => {

let meshCopy = mesh.clone();

meshCopy.position.set(d[0], d[1], d[2])

meshCopy.name = 'box-' + i;

group.add(meshCopy);

})


scene.add(group);


this.render();

document.getElementById("loading").style.display = "none";
}
initsquare() {

var sphereGeom = new THREE.SphereGeometry(10, 30, 20);

var sphereMate = new THREE.MeshPhongMaterial({color: 0x333333});

object3d = new THREE.Mesh(sphereGeom, sphereMate);

object3d.name = 'object3d';

object3d.visible = false;

scene.add(object3d);


group = new THREE.Group();

group.name = 'group';


var geometry = new THREE.BoxGeometry(100, 100, 100);

var mesh = new THREE.Mesh(geometry, materials);

posArr.forEach((d,i) => {

let meshCopy = mesh.clone();

meshCopy.position.set(d[0], d[1], d[2])

meshCopy.name = 'box-' + i;

group.add(meshCopy);

})


scene.add(group);


this.render();

document.getElementById("loading").style.display = "none";
}4. 监听模型的点击事件4. 监听模型的点击事件
initEventListener() {

raycaster = new THREE.Raycaster();

document.addEventListener('mousemove', function (event) {

event.preventDefault();

mouse.x = (event.clientX / window.innerWidth) * 2 - 1;

mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;

}, false)

document.addEventListener('mousedown', () => {

if (scene.children && scene.getObjectByName('group')) {

raycaster.setFromCamera(mouse, camera);

let intersects = raycaster.intersectObjects(scene.getObjectByName('group').children);

if (intersects[0] && intersects[0].object.name != 'object3d') {

let index = intersects[0].faceIndex;

let point = intersects[0].point;

currentUp = this.computedUp(camera);

currentNor = this.computedNor(point)

currentPos = intersects[0].object.position;

let pos = this.computedPos(point);

object3d.position.copy(pos);

object3d.visible = true;

}

}

})
}
initEventListener() {

raycaster = new THREE.Raycaster();

document.addEventListener('mousemove', function (event) {

event.preventDefault();

mouse.x = (event.clientX / window.innerWidth) * 2 - 1;

mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;

}, false)

document.addEventListener('mousedown', () => {

if (scene.children && scene.getObjectByName('group')) {

raycaster.setFromCamera(mouse, camera);

let intersects = raycaster.intersectObjects(scene.getObjectByName('group').children);

if (intersects[0] && intersects[0].object.name != 'object3d') {

let index = intersects[0].faceIndex;

let point = intersects[0].point;

currentUp = this.computedUp(camera);

currentNor = this.computedNor(point)

currentPos = intersects[0].object.position;

let pos = this.computedPos(point);

object3d.position.copy(pos);

object3d.visible = true;

}

}

})
}5. 监听方向软键盘的点击,根据点击键的不同,生成旋转轴5. 监听方向软键盘的点击,根据点击键的不同,生成旋转轴
handleRotate(num) {

if(!rotateFlag || !currentPos) return ;

rotateFlag = false;

groupTemp = new THREE.Group();

groupTemp.name = 'group-temp';

let axis;

let tempMeshArr = [];

switch (num) {

case 1:

axis = currentNor.clone().cross(currentUp);

break;

case 2:

axis = currentNor.clone().cross(currentUp).negate();

break;

case 3:

axis = currentUp.clone().negate();

break;

case 4:

axis = currentUp;

break;

}

let plane = new THREE.Plane().setFromNormalAndCoplanarPoint(axis, currentPos);

scene.getObjectByName('group').children.forEach(d => Math.abs(plane.distanceToPoint(d.position)) < 1 && tempMeshArr.push(d))

tempMeshArr.forEach(d => {

group.remove(d);

groupTemp.add(d);

})

// object3d.visible = false;

scene.remove(scene.getObjectByName('group-temp'));

scene.add(groupTemp);

this.handleTween(axis);
}
handleRotate(num) {

if(!rotateFlag || !currentPos) return ;

rotateFlag = false;

groupTemp = new THREE.Group();

groupTemp.name = 'group-temp';

let axis;

let tempMeshArr = [];

switch (num) {

case 1:

axis = currentNor.clone().cross(currentUp);

break;

case 2:

axis = currentNor.clone().cross(currentUp).negate();

break;

case 3:

axis = currentUp.clone().negate();

break;

case 4:

axis = currentUp;

break;

}

let plane = new THREE.Plane().setFromNormalAndCoplanarPoint(axis, currentPos);

scene.getObjectByName('group').children.forEach(d => Math.abs(plane.distanceToPoint(d.position)) < 1 && tempMeshArr.push(d))

tempMeshArr.forEach(d => {

group.remove(d);

groupTemp.add(d);

})

// object3d.visible = false;

scene.remove(scene.getObjectByName('group-temp'));

scene.add(groupTemp);

this.handleTween(axis);
}6. 加一点tween的补间动画,效果更好哦6. 加一点tween的补间动画,效果更好哦
handleTween(axis) {

let start = {angle: 0, axis};

let end = {angle: Math.PI/2, axis};

tween = new TWEEN.Tween(start).to(end, 500);

tween.easing(TWEEN.Easing.Linear.None);

tween.onUpdate(function () {

let quaternion = new THREE.Quaternion().setFromAxisAngle(axis, this._object.angle);

groupTemp.rotation.setFromQuaternion(quaternion);

});

tween.onComplete(() => {

let matrix = this.standerMatrix(groupTemp.matrix);

groupTemp.children.forEach(d => {

let mesh = d.clone();

mesh.applyMatrix4(matrix)

mesh.position.copy(this.standarPos(mesh.position))

group.add(mesh);

})

scene.remove(groupTemp)

rotateFlag = true;

})

tween.start();
}
handleTween(axis) {

let start = {angle: 0, axis};

let end = {angle: Math.PI/2, axis};

tween = new TWEEN.Tween(start).to(end, 500);

tween.easing(TWEEN.Easing.Linear.None);

tween.onUpdate(function () {

let quaternion = new THREE.Quaternion().setFromAxisAngle(axis, this._object.angle);

groupTemp.rotation.setFromQuaternion(quaternion);

});

tween.onComplete(() => {

let matrix = this.standerMatrix(groupTemp.matrix);

groupTemp.children.forEach(d => {

let mesh = d.clone();

mesh.applyMatrix4(matrix)

mesh.position.copy(this.standarPos(mesh.position))

group.add(mesh);

})

scene.remove(groupTemp)

rotateFlag = true;

})

tween.start();
}郭先生制作魔方的主要思路就是先做出27个方块添加到组A,6个面分别添加不同颜色的贴图(自己p的哦),然后使用raycaster选择点击的面,并确定当时的up方向和法向量方向以备后用,点击上下左右并结合u方向和法向量方向计算出旋转矩阵,根据已有条件计算出是那一排方块改变,并将这9个块添加到组B中,从组A中删除这9个,根据旋转矩阵旋转组B,并且在旋转完之后将组B中的方块添加到组A中,旋转完毕(这里比较难的就是根据上下左右判断旋转轴向量)。以上就是一种制作魔方的方法,综合了很多矩阵向量四元数欧拉角和平面的知识,希望对新来的同游有些帮助