ThreeJS-3D教学六-物体位移旋转

news2024/11/24 6:01:50

之前文章其实也有涉及到这方面的内容,比如在ThreeJS-3D教学三:平移缩放+物体沿轨迹运动这篇中,通过获取轨迹点物体动起来,其它几篇文章也有旋转的效果,本篇我们来详细看下,另外加了tween.js知识点,tween可以很好的协助 three 做动画,与之相似的还有 gsap.js方法类似。

1、物体位移两种方式

mesh.position.set(x, y, z);
mesh.position.x = 10;
mesh.position.y = 10;
mesh.position.z = 10;

2、物体旋转两种方式

// 绕局部空间的轴旋转这个物体
mesh.rotateX = 0.1; // 以弧度为单位旋转的角度
mesh.rotateY = 0.1;
mesh.rotateZ = 0.1;
// 物体的局部旋转,以弧度来表示
mesh.rotation.x = MathUtils.degToRad(90)
// 在局部空间中绕着该物体的轴来旋转一个物体
mesh.rotateOnAxis(new Vector3(1,0,0), 3.14);

注意,这里设置的数值是弧度,需要和角度区分开
角度 转 弧度 THREE.MathUtils.degToRad(deg)
弧度 转 角度 THREE.MathUtils.radToDeg (rad)
弧度 = 角度 / 180 * Math.PI
角度 = 弧度 * 180 / Math.PI
π(弧度) = 180°(角度)

3、tween.js

Tween.js 是一个 过渡计算工具包 ,理论上来说只是一个工具包,可以用到各种需要有过渡效果的场景,一般情况下,需要通过环境里面提供的辅助时间工具来实现。比如在网页上就是使用 window 下的 requestAnimationFrame 方法,低端浏览器只能使用 setInterval 方法来配合了。

1)简单使用
例如:把一个元素的 x 坐标在 1秒内由100 增加到 200, 通过以下代码可以实现:

// 1. 定义元素
const position = { x: 100, y: 0 };
// 2. new 一个 Tween对象,构造参数传入需要变化的元素
const tween = new TWEEN.Tween(position);
// 3. 设置目标
tween.to({ x: 200 }, 1000);
// 4. 启动,通常情况下,2,3,4步一般习惯性写在一起,定义命名也省了
//    例如 new TWEEN.Tween(position).to({ x: 200 }, 1000).start();
tween.start();
// 5. (可选:)如果在变化过程中想自定义事件,则可以通过以下事件按需使用
tween1.onStart(() => {
  console.log('onStart');
});
tween1.onStop(() => {
  console.log('onStop');
});
tween1.onComplete(() => {
 console.log('onComplete');
});
tween.onUpdate(function(pos) {
 console.log(pos.x);
});
// 6. 设置 requestAnimationFrame,在方法里面调用全局的 `TWEEN.update()`
//    在这个方法里面也可以加入其它的代码:d3, threejs 里面经常会用到。
//	  比如THREEJS里面用到的变化,如 `renderer.render(scene, camera);` 等
function animate() {
    requestAnimationFrame(animate);
    TWEEN.update();
}
// 6. 启动全局的 animate
animate();

2、连续型变化
一般情况下,设置起点和终点时两个数时,认为是连续型的,里面有无数的可能变化到的值(算上小数)。
连续型变化使用 easing 做为时间变化函数,默认使用的是 Liner 纯性过渡函数,easing过渡 的种类很多,每种还区分 IN, OUT, INOUT 算法。引用时 使用 TWEEN.Easing前缀,例如 :

tween.easing(TWEEN.Easing.Quadratic.Out)

目前内置的过渡函数下图:
在这里插入图片描述
3、主动控制

start([time]) , stop()
开始,结束,start 方法还接受一个时间参数,如果传了的话,就直接从传入的时间开始

update([time])
更新,会触发更新事件,如果有监听,则可以执行监听相关代码。
更新方法接受一个时间参数,如果传了的话,就更新到指定的时间

chain(tweenObject,[tweenObject])
如果是多个 Tween 对像 如果 tweenA 需要等到 tweenB 结束后,才能 start, 那么可以使用

tweenA.chain(tweenB);
//另外,chain方法也可以接收多个参数,如果 A对象 需要等待多个对像时,依次传入
tweenA.chain(tweenB, tweenC, tweenD);

repeat(number)
循环的次数,默认是 1 所以不定义的话,只过渡一次就没了,可以使用上面刚说的无限循环的方法,便只是一个对象无限循环时,就使用 :

tween.repeat(Infinity); //无限循环
tween.repeat(5); //循环5次

delay(time)
delay 是指延时多久才开始。比如以下代码:

tween.delay(1000);
tween.start();

虽然已经调用 start 了,但过渡动作要等 1秒 后才开始执行!

大致讲这些算是给大家一个初步的认知,感兴趣的可以自行查询文档,下面回到咱们本次的案例中,先看效果图:
在这里插入图片描述
代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    body {
      width: 100%;
      height: 100%;
    }
    * {
      margin: 0;
      padding: 0;
    }
    .label {
      font-size: 20px;
      color: #000;
      font-weight: 700;
    }
  </style>
  <script src="../tween.js/dist/tween.umd.js"></script>
</head>
<body>
<div id="container"></div>
<script type="importmap">
  {
    "imports": {
      "three": "../three-155/build/three.module.js",
      "three/addons/": "../three-155/examples/jsm/"
    }
  }
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
let stats, labelRenderer, gpuPanel, colors = [], indices = [];
let camera, scene, renderer, controls, group1, mesh2, mesh3, mesh4;
const group = new THREE.Group();
let widthImg = 200;
let heightImg = 200;
let time = 0;
init();
initHelp();
initLight();
axesHelperWord();
animate();
// 添加平面
addPlane();

addBox1();
addBox2();
addBox3();
addBox4();

function addBox1() {
  // BoxGeometry(width : Float, height : Float, depth : Float)
  let geo1 = new THREE.BoxGeometry(6, 12, 8);
  let material1 = new THREE.MeshLambertMaterial({
    color: 0x2194ce * Math.random(),
    side: THREE.DoubleSide,
    vertexColors: false
  });
  // 以图形一边为中心旋转
  let mesh1 = new THREE.Mesh(geo1, material1);
  mesh1.position.x = -3;
  mesh1.position.z = 4;
  // 改变物体旋转的中心点  将物体放入new THREE.Group 组中,通过位移实现
  // Group 的中心点在原点
  group1 = new THREE.Group();
  group1.position.x = 3;
  group1.position.z = -4;
  group1.add(mesh1);
  scene.add(group1);

  // 一个包围盒子的线框
  scene.add(new THREE.BoxHelper(mesh1, 0xff0000));
}

function addBox2() {
  let geo2 = new THREE.BoxGeometry(6, 12, 8);
  let material2 = new THREE.MeshLambertMaterial({
    color: 0x2194ce * Math.random(),
    side: THREE.DoubleSide,
    vertexColors: false
  });
  mesh2 = new THREE.Mesh(geo2, material2);
  mesh2.position.z = 30;
  scene.add(mesh2);

  // 获取 物体的最大最小 区间
  let box4 = new THREE.Box3().setFromObject(mesh2);
  console.log(box4);
  // 将物体的中心点 给到 mesh的位置
  // box4.getCenter(mesh.position);
}

function addBox3() {
  let geo3 = new THREE.BoxGeometry(10, 10, 5);

  // geo.faces 废除
  const colorsAttr = geo3.attributes.position.clone();
  // 面将由顶点颜色着色
  const color = new THREE.Color();
  const n = 800, n2 = n / 2;
  for (let i = 0; i < colorsAttr.count; i++) {
    const x = Math.random() * n - n2;
    const y = Math.random() * n - n2;
    const z = Math.random() * n - n2;
    const vx = ( x / n ) + 1.5;
    const vy = ( y / n ) + 0.5;
    const vz = ( z / n ) + 0.5;
    color.setRGB( vx, vy, vz );
    colors.push( color.r, color.g, color.b );
  }

  geo3.setAttribute('color', new THREE.Float32BufferAttribute( colors, 3 ));

  let material = new THREE.MeshLambertMaterial({
    // color: 0x2194ce * Math.random(),
    side: THREE.DoubleSide,
    vertexColors: true
  });
  mesh3 = new THREE.Mesh(geo3, material);
  mesh3.position.y = 10;
  mesh3.position.x = 40;

  // 将盒子的 坐标点变为反向
  // mesh3.position.multiplyScalar(-1);
  scene.add(mesh3);
}

function addBox4() {
  let geo4 = new THREE.BoxGeometry(6, 12, 8);
  let material4 = new THREE.MeshLambertMaterial({
    color: 0x2194ce * Math.random(),
    side: THREE.DoubleSide,
    vertexColors: false
  });
  mesh4 = new THREE.Mesh(geo4, material4);
  mesh4.position.z = -80;
  scene.add(mesh4);

  const tween1 = new TWEEN.Tween(mesh4.position)
    .to({
        x: 100,
        y: 20,
        z: -100
      },
      5000);
  tween1.start();
  // 无限循环 Infinity
  // tween1.repeat(4);
  // 这个方法,只在使用了repeat方法后,才能起作用,可以从移动的终点,返回到起点,就像悠悠球一样!
  // .yoyo方法在一些旧的版本中会报错 新的版本已经修复
  // tween1.yoyo(true);
  tween1.onStart(() => {
    console.log(111);
  });
  tween1.onComplete(() => {
    console.log(222);
  });

  // 添加第二个动画
  // 这个通过chain()方法可以将这两个补间衔接起来,
  // 这样当动画启动之后,程序就会在这两个补间循环。
  // 例如:一个动画在另一个动画结束后开始。可以通过chain方法来使实现。
  const tween2 = new TWEEN.Tween(mesh4.position)
    .to({
        x: 100,
        y: 20,
        z: 100
      },
      5000);
  tween2.chain(tween1);

  tween1.chain(tween2);
}

function addPlane() {
  // 创建一个平面 PlaneGeometry(width, height, widthSegments, heightSegments)
  const planeGeometry = new THREE.PlaneGeometry(widthImg, heightImg, 1, 1);
  // 创建 Lambert 材质:会对场景中的光源作出反应,但表现为暗淡,而不光亮。
  const planeMaterial = new THREE.MeshPhongMaterial({
    color: 0xb2d3e6,
    side: THREE.DoubleSide
  });
  const plane = new THREE.Mesh(planeGeometry, planeMaterial);
  // 以自身中心为旋转轴,绕 x 轴顺时针旋转 45 度
  plane.rotation.x = -0.5 * Math.PI;
  plane.position.set(0, -4, 0);
  scene.add(plane);
}

function init() {

  camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 10, 2000 );
  camera.up.set(0, 1, 0);
  camera.position.set(60, 40, 60);
  camera.lookAt(0, 0, 0);

  scene = new THREE.Scene();
  scene.background = new THREE.Color( '#ccc' );

  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

  labelRenderer = new CSS2DRenderer();
  labelRenderer.setSize( window.innerWidth, window.innerHeight );
  labelRenderer.domElement.style.position = 'absolute';
  labelRenderer.domElement.style.top = '0px';
  labelRenderer.domElement.style.pointerEvents = 'none';
  document.getElementById( 'container' ).appendChild( labelRenderer.domElement );

  controls = new OrbitControls( camera, renderer.domElement );
  controls.mouseButtons = {
    LEFT: THREE.MOUSE.PAN,
    MIDDLE: THREE.MOUSE.DOLLY,
    RIGHT: THREE.MOUSE.ROTATE
  };
  controls.enablePan = true;
  // 设置最大最小视距
  controls.minDistance = 20;
  controls.maxDistance = 1000;

  window.addEventListener( 'resize', onWindowResize );

  stats = new Stats();
  stats.setMode(1); // 0: fps, 1: ms
  document.body.appendChild( stats.dom );

  gpuPanel = new GPUStatsPanel( renderer.getContext() );
  stats.addPanel( gpuPanel );
  stats.showPanel( 0 );

  scene.add( group );
}

function initLight() {
  const AmbientLight = new THREE.AmbientLight(new THREE.Color('rgb(255, 255, 255)'));
  scene.add( AmbientLight );
}

function initHelp() {
  // const size = 100;
  // const divisions = 5;
  // const gridHelper = new THREE.GridHelper( size, divisions );
  // scene.add( gridHelper );

  // The X axis is red. The Y axis is green. The Z axis is blue.
  const axesHelper = new THREE.AxesHelper( 100 );
  scene.add( axesHelper );
}

function axesHelperWord() {
  let xP = addWord('X轴');
  let yP = addWord('Y轴');
  let zP = addWord('Z轴');
  xP.position.set(50, 0, 0);
  yP.position.set(0, 50, 0);
  zP.position.set(0, 0, 50);
}

function addWord(word) {
  let name = `<span>${word}</span>`;
  let moonDiv = document.createElement( 'div' );
  moonDiv.className = 'label';
  // moonDiv.textContent = 'Moon';
  // moonDiv.style.marginTop = '-1em';
  moonDiv.innerHTML = name;
  const label = new CSS2DObject( moonDiv );
  group.add( label );
  return label;
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
}

function animate() {
  requestAnimationFrame( animate );

  if (mesh2) {
    // 围绕 x y z轴旋转 中心点是自身中心
    mesh2.rotateX(0.01);
    mesh2.rotateY(0.01);
    mesh2.rotateZ(0.01);
  }

  if (group1) {
    group1.rotateY(0.01);
  }

  if (mesh3) {
    let v3 = new THREE.Vector3(1, 1, 0);
    mesh3.rotateOnAxis(v3, 0.01);
  }

  if (TWEEN) {
    TWEEN.update();
  }

  stats.update();
  controls.update();
  labelRenderer.render( scene, camera );
  renderer.render( scene, camera );
}
</script>
</body>
</html>

这里有几个小知识点:
1、改变物体旋转的中心点,我们通过将物体放入new THREE.Group 组中,通过位移实现;
2、我们通过new THREE.Box3().setFromObject(mesh2)可以获取物体的最大最小 区间;
3、tween.yoyo(true) 方法在一些旧的版本中会报错,新的版本已经修复,请大家使用最新版本
4、最重要的一点 不要忘记加 TWEEN.update()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1068698.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot的靓车汽车销售网站

目录 前言 一、技术栈 二、系统功能介绍 用户信息管理 车辆展示管理 车辆品牌管理 用户交流管理 购物车 用户交流 我的订单管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的…

除静电离子风刀的工作原理及应用

除静电离子风刀是一种能够产生高速气流并带有离子的设备&#xff0c;主要用于去除物体表面的静电。它的工作原理是通过离子产生器产生大量负离子&#xff0c;并通过高压电场将离子加速&#xff0c;使其成为一股高速气流&#xff0c;从而将静电荷从物体表面中除去。 除静电离子…

阿里云 linux tomcat 无法访问方法

1、阿里云放行tomcat端口 例如7077端口号 2、linux 命令行防火墙 设置端口打开 以下命令查看是否开启指定端口 firewall-cmd --list-ports以下命令添加指定端口让防火墙放行 firewall-cmd --zonepublic --add-port3306/tcp --permanent以下命令重新启动防火墙 systemctl re…

聊一下读完“优势成长”这本书后感

&#xff08;优势成长上&#xff09; (优势成长下) 最近读完了一本个人觉得还可以的书,这本书是一位新东方老师,帅键翔老师写的 整本书概括起来,最重要一点就是找到自己的优势,然后利用自己的优势,去挖掘自己的潜力,发现新大陆 能适应时代变化的&#xff0c;是“新木桶原理”&a…

JAVA中解析package、import、class、this关键字

一、前言 代码写的多了有时候我们就慢慢忽视了最简单&#xff0c;最基本的东西。比如一个类中最常见出现的package、import、class、this关键字。我们平时很少追究它的含义或者从来不会深究为什么需要这些关键字。不需要这些关键字&#xff0c;又会怎样。这边博文就简单介绍一下…

设计模式 - 观察者模式

目录 一. 前言 二. 实现 三. 优缺点 一. 前言 观察者模式属于行为型模式。在程序设计中&#xff0c;观察者模式通常由两个对象组成&#xff1a;观察者和被观察者。当被观察者状态发生改变时&#xff0c;它会通知所有的观察者对象&#xff0c;使他们能够及时做出响应&#xf…

攻防世界 Web_python_template_injection SSTI printer方法

这题挺简单的 就是记录一下不同方法的rce python_template_injection ssti了 {{.__class__.__mro__[2].__subclasses__()}} 然后用脚本跑可以知道是 71 {{.__class__.__mro__[2].__subclasses__()[71]}} 然后直接 init {{.__class__.__mro__[2].__subclasses__()[71].__i…

18373-2013 印制板用E玻璃纤维布 知识梳理

声明 本文是学习GB-T 18373-2013 印制板用E玻璃纤维布.pdf而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了印制板用E 玻璃纤维布的定义、代号与规格、要求、试验方法、检验规则、标志、包装、 运输和贮存。 本标准适用于以E 玻璃…

移远通信:探索智慧支付新方式,创新智慧金融新格局

回顾支付工具的演变历程&#xff0c;从刷卡支付到扫码支付&#xff0c;再到 “即拿即走”的自动支付模式创新&#xff0c;货币的电子化程度一直在层层深入。这其中&#xff0c;物联网技术的作用不可言喻。 当下&#xff0c;伴随着物联网行业自身的变革与创新&#xff0c;以连接…

MySql8.0 驱动编译和使用 - Qt mingw73_32

一、开发背景 现在已经有 MySql8.0.33 是 64 位的数据库&#xff0c;仅支持 64 位的程序&#xff0c;但是当前 Qt 程序编译环境是 mingw73_32&#xff0c;所以需要编译 32 位的 MySql 驱动库 二、开发环境 QtCreator4.8.2 Qt5.12.2 MySql8.0.33 三、实现步骤 1、下载 MySql…

halcon 算子set_display_font

set_display_font 算子&#xff1a;set_display_font( : : WindowHandle, Size, Font, Bold, Slant : ) 示例&#xff1a;set_display_font (200000, 24, ‘mono’, ‘true’, ‘false’) 200000&#xff08;输入参数1&#xff09;&#xff1a;输入窗口句柄 24&#xff08;…

Git从本地库撤销已经添加的文件或目录

场景 在提交时, 误将一个目录添加到了暂存区, 而且commit 了本地库,同批次commit 的还有其他需要提交的文件。 commit 之后发现这个目录下所有的文件都不需要提交, 现在需要撤销这个提交, 使这个目录不被push到远端库。 这里以远端服务器github 为例,在Git GUI下看到的…

stm32之freeRTOS驱动小车

该文章记录将stm32之智能小车总结移植到freeRTOS上&#xff0c;期间也遇到了好些问题&#xff0c;这里做下记录。也是对freeRTOS的一个应用实践。 一、freeRTOS工程的创建 工程是利用CubeMX进行创建的&#xff0c;挺简单的&#xff0c;有空再试下手动移植freeRTOS。 启用软件定…

mysql中的各种日志

错误日志 错误日志是MySQL中最重要的日志之一,它记录了当mysqld启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。 该日志是默认开启的&#xff0c;默认存放目录/var/log/,默认的日志文件名为my…

300.最长递增子序列

贪心二分查找 贪心&#xff1a;上升子序列尽可能长&#xff0c;序列上升尽可能慢&#xff0c;每次在上升子序列后加上的那个数尽可能小数组d&#xff0c;长度为len的最长上升子序列&#xff0c;d[i]为长度为i的最长上升子序列的末尾元素最小值&#xff0c;起始len1&#xff0c…

Python--入门

标识符 标识符由字母&#xff0c;数字&#xff0c;下划线_组成 第一个字符不能是数字&#xff0c;必须是字母或下划线 标识符区分大小写 关键字 关键字即保留字&#xff0c;定义标识符时不能使用关键字&#xff0c;python中的关键字如下图 注释 python中的单行注释用 # 多行注…

口袋参谋:0销量宝贝,如何快速获的流量曝光?

​如今的淘宝天猫&#xff0c;新品前期无销量前提下&#xff0c;是无法获得任何流量的&#xff0c;而唯一快速破零的方法&#xff1a;只有S单&#xff01; 常规的搜索S单&#xff0c;是无法找到宝贝的&#xff0c;如果使用全标题、半标题&#xff0c;直接就会触发搜索降权&…

ssm+vue的公司人力资源管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的公司人力资源管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结…

LLMs 生成式人工智能项目生命周期备忘单Generative AI Project Lifecycle Cheat Sheet

到目前为止&#xff0c;在本课程中&#xff0c;从选择模型到微调模型&#xff0c;再到将其与人类偏好对齐&#xff0c;这一切都将在您部署应用程序之前发生。为了帮助您规划生成式AI项目生命周期的各个阶段&#xff0c;这个速查表提供了每个工作阶段所需的时间和精力的一些指示…

python学习之5个让日常编码更方便简单的库

今天为大家分享 5 个让日常编码更简单的 Python 库&#xff0c;全文3900字&#xff0c;阅读15分钟。 一、sh https://github.com/amoffat/sh 如果曾经在 Python 中使用过 subprocess 库&#xff0c;那么我们很有可能对它感到失望&#xff0c;它不是最直观的库&#xff0c;可…