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 相关文章推荐
phpwind放自动注册方法
Dec 02 Javascript
jQuery Flash/MP3/Video多媒体插件
Jan 18 Javascript
javascript中的正则表达式使用详解
Aug 30 Javascript
node.js插件nodeclipse安装图文教程
Oct 19 Javascript
浅谈js中的延迟执行和定时执行
May 31 Javascript
JS脚本实现动态给标签控件添加事件的方法
Jun 02 Javascript
浅谈js和css内联外联注意事项
Jun 30 Javascript
angular实现表单验证及提交功能
Feb 01 Javascript
JavaScript与Java正则表达式写法的区别介绍
Aug 15 Javascript
浅谈webpack SplitChunksPlugin实用指南
Sep 17 Javascript
Element ui 下拉多选时新增一个选择所有的选项
Aug 21 Javascript
Canvas跟随鼠标炫彩小球的实现
Apr 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/30 PHP
php将textarea数据提交到mysql出现很多空格的解决方法
2014/12/19 PHP
PHP实现二叉树的深度优先与广度优先遍历方法
2015/09/28 PHP
php多文件打包下载的实例代码
2017/07/12 PHP
jquery插件jquery倒计时插件分享
2013/12/27 Javascript
js怎么覆盖原有方法实现重写
2014/09/04 Javascript
js+css实现tab菜单切换效果的方法
2015/01/20 Javascript
微信小程序 五星评分(包括半颗星评分)实例代码
2016/12/14 Javascript
vue-dialog的弹出层组件
2020/05/25 Javascript
Vue.2.0.5过渡效果使用技巧
2017/03/16 Javascript
jQuery轻松实现无缝轮播效果
2017/03/22 jQuery
Angular2 父子组件数据通信实例
2017/06/22 Javascript
jQuery插件DataTables分页开发心得体会
2017/08/22 jQuery
微信小程序swiper组件用法实例分析【附源码下载】
2017/12/07 Javascript
JavaScript生成指定范围的时间列表
2018/03/19 Javascript
Python发送email的3种方法
2015/04/28 Python
Python中创建字典的几种方法总结(推荐)
2017/04/27 Python
Python编程求解二叉树中和为某一值的路径代码示例
2018/01/04 Python
Python使用combinations实现排列组合的方法
2018/11/13 Python
Python基础知识点 初识Python.md
2019/05/14 Python
关于不懂Chromedriver如何配置环境变量问题解决方法
2019/06/12 Python
python搜索包的路径的实现方法
2019/07/19 Python
解决Python3.7.0 SSL低版本导致Pip无法使用问题
2020/09/03 Python
Python爬虫爬取ts碎片视频+验证码登录功能
2021/02/22 Python
Html5应用程序缓存(Cache manifest)
2018/06/04 HTML / CSS
Woolworth官网:澳洲第一大超市
2017/06/25 全球购物
英国奢华护肤、美容和Spa品牌:Temple Spa
2019/11/02 全球购物
腾讯公司的一个sql题
2013/01/22 面试题
大学生毕业求职找工作的自我评价
2013/09/29 职场文书
学生处主任岗位职责
2013/12/01 职场文书
初中体育教学反思
2014/01/14 职场文书
高职教师先进事迹材料
2014/08/24 职场文书
个人四风问题整改措施
2014/10/24 职场文书
教师党员承诺书2015
2015/01/21 职场文书
2019学生会干事辞职信
2019/06/27 职场文书
使用SQL实现车流量的计算的示例代码
2022/02/28 SQL Server