three.js 如何制作魔方


Posted in Javascript onJuly 31, 2020

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

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 如何制作魔方的文章就介绍到这了,更多相关three.js 制作魔方内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
input、button的不同type值在ajax提交表单时导致的陷阱
Feb 24 Javascript
javascript获取元素CSS样式代码示例
Nov 28 Javascript
动态加载jQuery的方法
Jun 16 Javascript
bootstrap提示标签、提示框实现代码
Dec 28 Javascript
js中编码函数:escape,encodeURI与encodeURIComponent详解
Mar 21 Javascript
angularjs中ng-bind-html的用法总结
May 23 Javascript
JavaScript之promise_动力节点Java学院整理
Jul 03 Javascript
Vue-cli-webpack搭建斗鱼直播步骤详解
Nov 17 Javascript
js如何找出字符串中的最长回文串
Jun 04 Javascript
JS封装的模仿qq右下角消息弹窗功能示例
Aug 22 Javascript
angularJs提交文本框数据到后台的方法
Oct 08 Javascript
vue 子组件和父组件传值的示例
Sep 11 Javascript
Vue ElementUI实现:限制输入框只能输入正整数的问题
Jul 31 #Javascript
js实现贪吃蛇小游戏(加墙)
Jul 31 #Javascript
vue实现简易图片左右旋转,上一张,下一张组件案例
Jul 31 #Javascript
vue实现给div绑定keyup的enter事件
Jul 31 #Javascript
简单了解JavaScript作用域
Jul 31 #Javascript
基于vue--key值的特殊用处详解
Jul 31 #Javascript
javascript开发实现贪吃蛇游戏
Jul 31 #Javascript
You might like
php文件上传的例子及参数详解
2013/12/12 PHP
PHP、Java des加密解密实例
2015/04/27 PHP
CI框架集成Smarty的方法分析
2016/05/17 PHP
js类后台管理菜单类-MenuSwitch
2007/09/12 Javascript
jQuery 选择器、DOM操作、事件、动画
2010/11/25 Javascript
js模拟点击事件实现代码
2012/11/06 Javascript
JS 添加网页桌面快捷方式的代码详细整理
2012/12/27 Javascript
完善的jquery处理机制
2016/02/21 Javascript
scroll事件实现监控滚动条并分页显示(zepto.js)
2016/12/18 Javascript
jQuery表单插件ajaxForm实例详解
2017/01/17 Javascript
Vue-router 切换组件页面时进入进出动画方法
2018/09/01 Javascript
vue解决使用$http获取数据时报错的问题
2019/10/30 Javascript
详解vue实现坐标拾取器功能示例
2020/11/18 Vue.js
vue实现树状表格效果
2020/12/29 Vue.js
[07:40]DOTA2每周TOP10 精彩击杀集锦vol.4
2014/06/25 DOTA
[03:40]DOTA2亚洲邀请赛小组赛第二日 赛事回顾
2015/01/31 DOTA
在Python中使用itertools模块中的组合函数的教程
2015/04/13 Python
python模块简介之有序字典(OrderedDict)
2016/12/01 Python
python递归删除指定目录及其所有内容的方法
2017/01/13 Python
Pandas读取MySQL数据到DataFrame的方法
2018/07/25 Python
python 实现查找文件并输出满足某一条件的数据项方法
2019/06/12 Python
windows中安装Python3.8.0的实现方法
2019/11/19 Python
Django 如何使用日期时间选择器规范用户的时间输入示例代码详解
2020/05/22 Python
Scrapy 配置动态代理IP的实现
2020/09/28 Python
Pycharm配置autopep8实现流程解析
2020/11/28 Python
AmazeUI 平滑滚动效果的示例代码
2020/08/20 HTML / CSS
Puritan’s Pride(普丽普莱)官方网站:美国最大最全的保健品公司之一
2016/10/23 全球购物
捷克鲜花配送:Florea.cz
2018/10/29 全球购物
Public Desire美国/加拿大:全球性的在线鞋类品牌
2018/12/17 全球购物
2014年社团工作总结范文
2014/11/27 职场文书
认真学习保证书
2015/02/26 职场文书
会计专业求职信范文
2015/03/19 职场文书
放假通知
2015/04/14 职场文书
安全生产培训心得体会
2016/01/18 职场文书
postgres之jsonb属性的使用操作
2021/06/23 PostgreSQL
MySQL索引失效场景及解决方案
2022/07/23 MySQL