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.hqwc.cn/news/130440.html

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

相关文章

【置顶】关于博客的一些公告

所谓 万事开头难&#xff0c;最开始的两个专栏 《微机》 和 《骨骼动作识别》 定价 29.9 &#xff0c;因为&#xff1a; 刚开始确实比较困难&#xff0c;要把自己学的知识彻底搞懂讲给别人&#xff0c;还要 码字排版&#xff0c;从 Markdown 语法开始学起&#xff08;这都是 花…

【C++】Stack Queue -- 详解

一、stack的介绍和使用 1、stack的介绍 https://cplusplus.com/reference/stack/stack/?kwstack 1. stack 是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。 2. stack 是作为容器适配器被…

bin-editor-next实现josn序列化

线上链接 BIN-EDITOR-NEXThttps://wangbin3162.gitee.io/bin-editor-next/#/editor gitee地址bin-editor-next: ace-editor 的vue3升级版本https://gitee.com/wangbin3162/bin-editor-next#https://gitee.com/link?targethttps%3A%2F%2Funpkg.com%2Fbin-editor-next%2F 实现…

如何在Apache和Resin环境中实现HTTP到HTTPS的自动跳转:一次全面的探讨与实践

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【SpringCloud】Ribbon负载均衡原理、负载均衡策略、饥饿加载

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Ribbon 一、 Ribbon负载均衡原理1.1 负载均…

Go 复合类型之字典类型介绍

Go 复合类型之字典类型介绍 文章目录 Go 复合类型之字典类型介绍一、map类型介绍1.1 什么是 map 类型&#xff1f;1.2 map 类型特性 二.map 变量的声明和初始化2.1 方法一&#xff1a;使用 make 函数声明和初始化&#xff08;推荐&#xff09;2.2 方法二&#xff1a;使用复合字…

[NewStarCTF 2023 公开赛道] week1

最近没什么正式比赛&#xff0c;都是入门赛&#xff0c;有moectf,newstar,SHCTF,0xGame都是漫长的比赛。一周一堆制。 这周newstar第1周结束了&#xff0c;据说py得很厉害&#xff0c;第2周延期了&#xff0c;什么时候开始还不一定&#xff0c;不过第一周已经结束提交了&#…

CDGA数据治理工程师考试心得

2023年8月初&#xff0c;准备备考CDGA。当时也是很迷茫&#xff0c;啥时候考试都不知道&#xff0c;也不知道该怎么做。写这篇文章的目的也只是记录一下。 1.什么是CDGA? CDGA就是数据治理工程师&#xff08;Certified Data Governance Associate&#xff09;&#xff0c;“D…

OpenSSL安装过程总结

1 OpenSSL是什么及怎么用 参考: openssl中文手册 2 下载源文件 Github&#xff1a; https://github.com/openssl/openssl 官网&#xff1a; https://www.openssl.org/source/ 3 安装 先查看README.md文档&#xff0c;根据描述找到自己对应平台的NOTES-*.md文档和INSTALL.m…

函数reshape(-1,)里的-1的意思

reshape函数是对narray的数据结构进行维度变换&#xff0c;由于变换遵循对象元素个数不变&#xff0c;在进行变换时&#xff0c;假设一个数据对象narray的总元素个数为N&#xff0c;如果我们给出一个维度为&#xff08;m&#xff0c;-1&#xff09;时&#xff0c;我们就理解为将…

【已解决】msvcp140.dll丢失怎样修复?msvcp140.dll重新安装的解决方法

今天我要和大家分享的是关于msvcp140.dll丢失的五种不同解决方法。我们知道&#xff0c;在运行一些软件或游戏的时候&#xff0c;经常会遇到“msvcp140.dll丢失”的问题&#xff0c;这可能会影响到我们的使用体验。那么&#xff0c;面对这个问题&#xff0c;我们应该如何应对呢…

Java Agent初探

1&#xff1a;Java Agent简介 Java Agent 这个技术出现在 JDK1.5 之后&#xff0c;对于大多数人来说都比较陌生&#xff0c;但是多多少少又接触过&#xff0c;实际上&#xff0c;我们平时用的很多工具&#xff0c;都是基于 Java Agent 实现的&#xff0c;例如常见的热部署 JRe…