因为之前的几节讲了一些数学方法,例如Vector3、Matrix4、Euler还有Quaternion的知识。所以这篇郭先生就来说说用three.js怎么制作一个魔方。在线案例请点击博客原文

制作魔方主要运用坐标变换的知识,制作魔方的方法有很多,建议你先在大脑中构思,然后试着做一做,下面我将一种比较简单的方法。

  1. 制作出魔方各个方块的位置坐标(27个)的数组,然后制作出魔方各个面的材质(6个)
  2. 根据坐标和材质制作魔方的方块,并添加到一个组group
  3. 制作一个标志被选面的几何体(我是用球体),然后隐藏
  4. 使用THREE.Raycaster,给模型绑定事件,并记录选定的一些数据,将标志显示并放到合适的位置
  5. 点击模拟方向键盘,根据之前记录的数据,转动魔方(重点)

以上是主要的步骤,但是对于新同学,需要注意的仍然很多,下面上主要代码,按照方法说

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的向量

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];
}

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";
}

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;
}
}
})
}

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);
}

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();
}

郭先生制作魔方的主要思路就是先做出27个方块添加到组A,6个面分别添加不同颜色的贴图(自己p的哦),然后使用raycaster选择点击的面,并确定当时的up方向和法向量方向以备后用,点击上下左右并结合u方向和法向量方向计算出旋转矩阵,根据已有条件计算出是那一排方块改变,并将这9个块添加到组B中,从组A中删除这9个,根据旋转矩阵旋转组B,并且在旋转完之后将组B中的方块添加到组A中,旋转完毕(这里比较难的就是根据上下左右判断旋转轴向量)。

以上就是一种制作魔方的方法,综合了很多矩阵向量四元数欧拉角和平面的知识,希望对新来的同游有些帮助

转载请注明地址:郭先生的博客

three.js 制作魔方的更多相关文章

  1. js制作带有遮罩弹出层实现登录小窗口

    要实现的效果如下 点击“登录”按钮后,弹出登录小窗口,并且有遮罩层(这个名词还是百度知道的,以前只知道效果,却不知道名字) 在没有点击“登录”按钮之前登录小窗口不显示,点击“登录”按钮后小窗口显示,并 ...

  2. 基于node.js制作爬虫教程

    前言:最近想学习node.js,突然在网上看到基于node的爬虫制作教程,所以简单学习了一下,把这篇文章分享给同样初学node.js的朋友. 目标:爬取 http://tweixin.yueyishu ...

  3. 用JS制作一个信息管理平台完整版

      前  言 JRedu 在之前的文章中,介绍了如何用JS制作一个实用的信息管理平台. 但是那样的平台功能过于简陋了,我们今天来继续完善一下. 首先我们回顾一下之前的内容.   1.JSON的基础知识 ...

  4. 利用css+原生js制作简易钟表

    利用css+原生js制作简单的钟表.效果如下所示 实现该效果,分三大块:html.javascript.css html部分html部分比较简单,定义一个clock的div,内部有原点.时分秒针.日期 ...

  5. JS制作图片切换

    <!DOCTYPE html> <html> <head> <title>纯JS制作简单的图片切换</title> <meta cha ...

  6. 使用Vue.js制作仿Metronic高级表格(一)静态设计

    Metronic高级表格是Metonic框架中自行实现的表格,其底层是Datatables.本教程将主要使用Vue实现交互部分,使用Bootstrap做样式库.jQuery做部分用户交互(弹窗). 使 ...

  7. 使用 原生js 制作插件 (javaScript音乐播放器)

    1.引用页面 index.html <!DOCTYPE html> <html lang="en"> <head> <meta chars ...

  8. D3.js 制作中国地图 .net 公共基础类

    D3.js 制作中国地图 from:  http://d3.decembercafe.org/pages/map/index.html GeoJSON is a format for encoding ...

  9. CSS3 制作魔方 - 玩转魔方

    在上一篇<CSS3 制作魔方 - 形成魔方>中介绍了一个完整魔方的绘制实现,本文将介绍魔方的玩转,支持上下左右每一层独立地旋转.先来一睹玩转的风采. 1.一个问题 由于魔方格的位置与转动的 ...

随机推荐

  1. Python干货整理之数据结构篇

    1 stack的实现 实现接口: init() 用于初始化stack,数据类型为list size() 用于获得stack的大小 push() 用于往栈中添加元素,添加的元素类型可以是int或者lis ...

  2. Linux下nginx反向代理服务器安装与配置实操

    1.我们只要实现访问nginx服务器能跳转到不同的服务器即可,我本地测试是这样的, 在nginx服务器里面搭建了2个tomcat,2个tomcat端口分别是8080和8081,当我输入我nginx服务 ...

  3. JavaScript笔记- 函数声明和函数表达式(001)

    function 是 Javascript 中的第一类对象,这就意味着函数可以像其他值一样被传递.一个最常见的用法就是将一个匿名函数作为回调函数传递到另外一个异步函数中. 函数声明 ? 1 funct ...

  4. igate(因特网网关)

    网关:Gateway 又称网间连接器.协议转换器.-----复杂的网络互连设备. 网关在网络层以上实现网络互连,是复杂的网络互连设备,仅用于两个高层协议不同的网络互连.网关既可以用于广域网互连,也可以 ...

  5. Python3笔记018 - 4.3 元组

    第4章 序列的应用 python的数据类型分为:空类型.布尔类型.数字类型.字节类型.字符串类型.元组类型.列表类型.字典类型.集合类型 在python中序列是一块用于存放多个值的连续内存空间. py ...

  6. Synchronized锁的是什么?

    Synchronized锁的是什么? 临界区与锁 并发编程中不可避免的会出现多个线程共享同一个资源的情况,为了防止出现数据不一致情况的发生,人们引入了临界区的概念.临界区是一个用来访问共享资源的代码块 ...

  7. flex-direction和flex-wrap

    当外层容器使用flex布局,并且把flex-direction设置成colum的时候,内层容器的宽度会跟外层容器的宽度保持一致. 在浏览器上的效果如下: 当把外层容器的纵向布局不适用flex-dire ...

  8. 如何排查CPU占用太高

    线上项目运行时,出现问题不像在本地那么容易排查,经常需要借助日志.或者一些工具来找出问题.cpu被占满我们经常会遇到.比如我们有这样一段代码: public Class Demo1_16 { publ ...

  9. Python 的print报错SyntaxError: invalid syntax

    1. #!/usr/bin/python print "hello world!" print报错:SyntaxError: Missing parentheses in call ...

  10. 介绍下 npm 模块安装机制,为什么输入 npm install 就可以自动安装对应的模块?

    1. npm 模块安装机制: 发出npm install命令 查询node_modules目录之中是否已经存在指定模块 若存在,不再重新安装 若不存在 npm 向 registry 查询模块压缩包的网 ...