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 相关文章推荐
JQuery.get提交页面不跳转的解决方法
Jan 13 Javascript
JavaScript实现拖拽网页内元素的方法
Apr 15 Javascript
JavaScript修改作用域外变量的方法
Mar 25 Javascript
基于JS代码实现当鼠标悬停表格上显示这一格的全部内容
Jun 12 Javascript
使用Node.js实现ORM的一种思路详解(图文)
Oct 24 Javascript
Bootstrap实现下拉菜单多级联动
Nov 23 Javascript
Element-UI踩坑之Pagination组件的使用
Oct 29 Javascript
JavaScript封闭函数及常用内置对象示例
May 13 Javascript
create-react-app中添加less支持的实现
Nov 15 Javascript
JavaScript 严格模式(use strict)用法实例分析
Mar 04 Javascript
vue addRoutes路由动态加载操作
Aug 04 Javascript
快速解决element的autofocus失效问题
Sep 08 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中获取变量的变量名的一段代码的bug分析
2011/07/07 PHP
Laravel 中使用简单的方法跟踪用户是否在线(推荐)
2019/10/30 PHP
javascript 写类方式之七
2009/07/05 Javascript
jQuery 阴影插件代码分享
2012/01/09 Javascript
JavaScript高级程序设计 阅读笔记(十二) js内置对象Math
2012/08/14 Javascript
document.addEventListener使用介绍
2014/03/07 Javascript
JQuery表单验证插件EasyValidator用法分析
2014/11/15 Javascript
jQuery创建自定义的选择器用以选择高度大于100的超链接实例
2015/03/18 Javascript
jQuery选择器源码解读(五):tokenize的解析过程
2015/03/31 Javascript
基于jQuery实现点击列表加载更多效果
2016/05/31 Javascript
聊一聊JS中this的指向问题
2016/06/17 Javascript
微信小程序开发教程-手势解锁实例
2017/01/06 Javascript
jQuery图片切换动画效果
2017/02/28 Javascript
JavaScript事件方法(实例讲解)
2017/06/27 Javascript
小程序中英文混合排序问题解决
2019/08/02 Javascript
使用next.js开发网址缩短服务的方法
2020/06/17 Javascript
Vue环境搭建+VSCode+Win10的详细教程
2020/08/19 Javascript
详解Vue的mixin策略
2020/11/19 Vue.js
[01:04:20]完美世界DOTA2联赛PWL S2 LBZS vs Forest 第一场 11.29
2020/12/02 DOTA
Python中的特殊语法:filter、map、reduce、lambda介绍
2015/04/14 Python
python结合shell查询google关键词排名的实现代码
2016/02/27 Python
Appium+Python自动化测试之运行App程序示例
2019/01/23 Python
详解Django配置JWT认证方式
2020/05/09 Python
Python基于time模块表示时间常用方法
2020/06/18 Python
浅谈tensorflow 中的图片读取和裁剪方式
2020/06/30 Python
HTML5 Canvas绘制圆点虚线实例
2015/01/01 HTML / CSS
Luxplus丹麦:香水和个人护理折扣
2018/04/23 全球购物
师范生自荐信范文
2013/10/06 职场文书
祖国在我心中演讲稿
2014/01/15 职场文书
护理学专业求职信
2014/06/29 职场文书
有子女的离婚协议书怎么写(范本)
2014/09/29 职场文书
评职称个人总结
2015/03/05 职场文书
解放思想大讨论活动总结
2015/05/09 职场文书
python tkinter实现定时关机
2021/04/21 Python
磁贴还没死, 微软Win11可修改注册表找回Win10开始菜单
2021/11/21 数码科技
Apache Hudi的多版本清理服务彻底讲解
2022/03/31 Servers