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

之前文章其实也有涉及到这方面的内容,比如在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: msdocument.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.rhkb.cn/news/150934.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

类 ChatGPT 模型存在的局限性

尽管类ChatGPT模型经过数月的迭代和完善&#xff0c;已经初步融入了部分领域以及人们的日常生活&#xff0c;但目前市面上的产品和相关技术仍然存在一些问题&#xff0c;以下列出一些局限性进行详细说明与成因分析&#xff1a; 1&#xff09;互联网上高质量、大规模、经过清洗…

【SQL Server】表死锁/解锁和sql语句分析

文章目录 表死锁查询锁的进程解锁 sql语句分析来源 表死锁 查询锁的进程 1 首先创建一个测试用的表&#xff1a; CREATE TABLE Test ( TID INT IDENTITY(1,1) ) 2 执行下面的SQL语句将此表锁住&#xff1a; SELECT * FROM Test WITH (TABLOCKX) 3 通过下面的语句可以查看…

[极客大挑战 2019]BabySQL 1

#做题方法# 进去之后做了简单的注入发现有错误回显&#xff0c;就进行注入发现过滤了sql语 后面进行了双写and payload&#xff1a; ?usernameadmin%27%20aandnd%20updatexml(1,concat(0x7e,dAtabase(),0x7e,version()),1)%20--&passwordadmi 接下来又 ?usernameadm…

零基础Linux_13(基础IO_文件)文件系统接口+文件描述符fd+dup2函数

目录 1. C语言的文件操作 1.1 C语言文件的写入 1.2 当前路径 1.3 文件操作模式 1.4 文件的读取和cat 2. 文件系统接口 2.1 系统调用与封装 2.2 open打开文件 2.2.1 flags标记位 2.2.2 open用法演示 2.3 close关闭文件和write写入文件和rede读取文件 2.3.1 O_TRUNC…

学习记忆——数学篇——算术——无理数

谐音记忆法 2 \sqrt{2} 2 ​≈1.41421&#xff1a;意思意思而已&#xff1b;意思意思&#xff1b; 3 \sqrt{3} 3 ​≈1.7320&#xff1a;—起生鹅蛋&#xff1b;一起生儿&#xff1b; 5 \sqrt{5} 5 ​≈2.2360679&#xff1a;两鹅生六蛋(送)六妻舅&#xff1b;儿儿生&#xf…

k8s全栈-笔记6-Prometheus+Alertmanager构建监控系统

k8s全栈-笔记6-PrometheusAlertmanager构建监控系统 实验环境: Pormetheusgrafanaalertmanager安装在k8s集群,k8s环境如下 K8S集群角色IP主机名安装的组件控制节点(master)172.20.252.181k8s-master01apiserver,controller-manager,schedule,kubelet,etcd,kube-proxy,容器运…

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

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

使用hugo+github搭建免费个人博客

使用hugogithub搭建免费个人博客 前提条件 win11电脑一台电脑安装了git电脑安装了hugogithub账号一个 个人博客本地搭建 初始化一个博客 打开cmd窗口&#xff0c;使用hugo新建一个博客工程 hugo new site blogtest下载主题 主题官网&#xff1a;themes.gohugo.io 在上面…

【kubernetes的三种网络】

kubernetes的三种网络 一、三种网络service网络&#xff08;service是虚拟IP地址&#xff09;pod网络&#xff08;pod的IP地址 docker容器的IP&#xff09;节点网络&#xff08;网络服务器上的物理网卡IP&#xff09; 二、其他网络flannel一、vxlan(隧道方案)1.定义2.优势3.工作…

【Java每日一题】— —第二十四题:编程定义一个长方形类Rectangle(2023.10.08)

&#x1f578;️Hollow&#xff0c;各位小伙伴&#xff0c;今天我们要做的是第二十四题。 &#x1f3af;问题&#xff1a; &#xff08;1&#xff09;定义成员变量&#xff1a;长&#xff08;int height&#xff09;&#xff0c;宽&#xff08;int width&#xff09;&#xf…

uniapp uni.showToast 一闪而过的问题

问题&#xff1a;在页面跳转uni.navigateBack()等操作的前或后&#xff0c;执行uni.showToast&#xff0c;即使代码中设置2000ms的显示时间&#xff0c;也会一闪而过。 解决&#xff1a;用setTimeout延后navigateBack的执行。

论文笔记 A theory of learning from different domains

domain adaptation 领域理论方向的重要论文. 这篇笔记主要是推导文章中的定理, 还有分析定理的直观解释. 笔记中的章节号与论文中的保持一致. 1. Introduction domain adaptation 的设定介绍: 有两个域, source domain 与 target domain. source domain: 一组从 source dist.…

软件工程与计算总结(五)软件需求基础

本帖介绍软件需求涉及的诸多基本概念&#xff0c;通过对这些概念的阐述&#xff0c;剖析软件需求的来源、层次、类别、作用等重要知识~ 目录 ​编辑 一.引言 二.需求工程基础 1.简介 2.活动 3.需求获取 4.需求分析 5.需求规格说明 6.需求验证 7.需求管理 三.需求基…

JAVA在线电子病历编辑器源码 B/S架构

电子病历在线制作、管理和使用的一体化电子病历解决方案&#xff0c;通过一体化的设计&#xff0c;提供对住院病人的电子病历书写、保存、修改、打印等功能。电子病历系统将临床医护需要的诊疗资料以符合临床思维的方法展示。建立以病人为中心&#xff0c;以临床诊疗信息为主线…

FPGA project :HDMI

实验目标&#xff1a;驱动HdMI显示十色等宽彩条。 本实验的重点是&#xff1a; 1掌握TMDS通信协议。 2rgb565转rgb888。 3编写HDMI驱动程序。 4学会看流程图编写代码。 值得注意的事情 1注意数据与解析数据的信号&#xff08;比如传入的数据中0或者1的个数&#xff09;&…

OpenCV实现人脸检测(Haar特征)

学习目标 原理 实现 import cv2 as cv print(cv.__file__) 路径&#xff1a;E:\Anaconda3\envs\test_py3.6\Lib\site-packages\cv2\data 代码实现 import cv2 as cv import matplotlib.pyplot as plt from pylab import mplmpl.rcParams[font.sans-serif] [SimHei] #1&#x…

语义分割,实例分割,全景分割梳理

语义分割&#xff08;semantic segmentation&#xff09; 实例分割&#xff08;instance segmentation&#xff09; 全景分割&#xff08;Panoptic Segmentation&#xff09; 下面基于《Panoptic Segmentation 》这篇论文进行这几个概念的梳理 论文链接&#xff1a;https:/…

【算法刷题】【反转链表】给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

题目 解决&#xff1a; import java.util.*;/** public class ListNode {* int val;* ListNode next null;* public ListNode(int val) {* this.val val;* }* }*/public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#x…

gin路由相关方法

c.Request.URL.Path 拿到请求的路径 package mainimport ( "fmt" "github.com/gin-gonic/gin" "net/http")//路由重定向&#xff0c;请求转发&#xff0c;ANY &#xff0c;NoRoute&#xff0c;路由组func main() { r : gin.Default() // -------…

DVWA-内容安全策略绕过

内容安全策略绕过 ​ 内容安全策略&#xff08;Content Security Policy&#xff0c;简称CSP&#xff09;是一种以可信白名单作机制&#xff0c;来限制网站是否可以包含某些来源内容&#xff0c;缓解广泛的内容注入漏洞&#xff0c;是一种用于增强网页的安全性的安全策略机制。…