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()
相关文章:

ThreeJS-3D教学六-物体位移旋转
之前文章其实也有涉及到这方面的内容,比如在ThreeJS-3D教学三:平移缩放物体沿轨迹运动这篇中,通过获取轨迹点物体动起来,其它几篇文章也有旋转的效果,本篇我们来详细看下,另外加了tween.js知识点࿰…...

BC v1.2充电规范
1 JEITA Reference to https://www.mianbaoban.cn/blog/post/169964 符合 JEITA 规范的锂离子电池充电器解决方案 2 Battery Fuel Gauge 2.1 Cycle Count(充放电循环次数) 此指令回传一只读字段,代表电芯组已经历的完整充放电循环数。当放电容…...
判断一个整数是否回文
回文数字的定义:第一位和最后一位相等,第二位和倒数第二位相等...依次类推,比如1221,12321等等,也就是说一个数字如果是回文,那么将它反转之后,一定和原来的值相等 解法一:投机取巧,…...

【广州华锐互动】车辆零部件检修AR远程指导系统有效提高维修效率和准确性
在快速发展的科技时代,我们的生活和工作方式正在被重新定义。这种变化在许多领域都有所体现,尤其是在汽车维修行业。近年来,AR(增强现实)技术的进步为这个行业带来了前所未有的可能性。通过将AR技术与远程协助系统相结…...

简单实现接口自动化测试(基于python+unittest)
简介 本文通过从Postman获取基本的接口测试Code简单的接口测试入手,一步步调整优化接口调用,以及增加基本的结果判断,讲解Python自带的Unittest框架调用,期望各位可以通过本文对接口自动化测试有一个大致的了解。 引言 为什么要…...

【算法|双指针系列No.4】leetcode11. 盛最多水的容器
个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 🍔本专栏旨在提高自己算法能力的同时,记录一下自己的学习过程,希望…...
数据结构全集介绍
以下列举了部分常见的数据结构: 数组(Array):数组是一种线性数据结构,可以用来存储固定大小的数据集合。在数组中,每个元素都有一个对应的索引,可以通过索引直接访问和更新元素。数组的优点是访…...
力扣刷题-字符串-反转字符串
344 反转字符串 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 你可以假设数组中的所有字符都是 ASCII 码表中…...
【CCNP】第七章 动态路由协议-BGP
第一节 BGP的基本概念 BGP(Border Gateway Protocol),边界网关协议 是运行在网络和网络之间的协议,是一款EGP(外部网关协议) BGP基于TCP协议工作,目的端口号179。源端口随机,由路由…...
java学习--day24(stream流)
文章目录 今天的内容1.Stream【难点】1.1获取流的对象1.2Stream流对象下面1.2.1count和forEach1.2.2filter方法1.2.3limit1.2.4map方法1.2.5skip1.2.6concat 1.3收集流 1.基于接口和抽象类的匿名内部类的写法 abstract class Person {public abstract void eat(); } public sta…...

Multi-Grade Deep Learning for Partial Differential Equations
论文阅读:Multi-Grade Deep Learning for Partial Differential Equations with Applications to the Burgers Equation Multi-Grade Deep Learning for Partial Differential Equations with Applications to the Burgers Equation符号定义偏微分方程定义FNN定义PI…...
Docker部署rustdesk
查看镜像版本 https://hub.docker.com/r/rustdesk/rustdesk-server/tags 拉取镜像 docker pull rustdesk/rustdesk-server:1.1.8-2创建挂载目录 mkdir -p /opt/rustdesk/{hbbr,hbbs}/root运行hbbs –nethost 仅适用于 Linux,它让 hbbs/hbbr 可以看到对方真实的…...

win1011安装MG-SOFT+MIB+Browser+v10b
文章目录 安装MG-SOFTSNMP服务配置安装MG-SOFT启动MIB-Browser以及错误解决MIB Browser使用 安装MG-SOFT win10和win11安装基本一样,所以参照下面的操作即可! SNMP服务配置 打开设置,应用和功能,可选功能,选择添加功…...
PCL点云处理之Pcd文件读取、法线与曲率计算、多线程加速、属性字段合并 (二百零八)
PCL点云处理之Pcd文件读取、法线与曲率计算、多线程加速、属性字段合并(二百零八) 一、相关介绍二、算法实现1.代码一、相关介绍 (夜深人不静) 法线和曲率的计算是点云处理中常用的关键特征,PCL提供了特有的点类型PointNormal来记录这些信息,通过OMP多线程对相关的计算函…...

JavaEE-文件IO操作
构造方法 一般方法,有很多,我们以下只是列举几个经常使用的 注意在上述的操作过程中,无论是绝对路径下的这个文件还是相对路径下的这个文件,都是不存在的 Reader 使用 --> 文本文件 FileReader类所涉及到的一些方法 Fil…...

二蛋赠书四期:《Go编程进阶实战:开发命令行应用、HTTP应用和gRPC应用》
前言 大家好!我是二蛋,一个热爱技术、乐于分享的工程师。在过去的几年里,我一直通过各种渠道与大家分享技术知识和经验。我深知,每一位技术人员都对自己的技能提升和职业发展有着热切的期待。因此,我非常感激大家一直…...

MySQL数据库基本操作-DQL-排序查询
介绍 如果我们需要对读取的数据进行排序,我们就可以使用 MySQL 的 order by 子句来设定你想按哪个字段哪种方式来进行排序,再返回搜索结果。 语法 select 字段名1,字段名2,…… from 表名 order by 字段名1 [asc|desc]…...
这是一篇测试文章
这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章这是一篇测试文章…...
Ubuntu plt画图 新罗马字体网格marker刻度朝内
* 字体文件:坚果云下code包,新罗马字体 参考链接:Linux下Matplotlib画图New Times Roman字体设置 - 知乎 * 刻度朝内 plt.rcParams[font.sans-serif] [Times New Roman]plt.rcParams[xtick.direction]in#设置x轴刻度向内plt.rcParams[ytic…...
flutter布局中的一些细节
前言 记录flutter使用中遇到的一些细节和坑,希望能帮助到大家 Column中不能直接嵌套ListView, (需要指定ListView的高度或者加上shrinkWrap: true属性)需要限制button的大小,可以在外部嵌套一个Container或SizedBox来限制在List…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...