three.js一些细节功能的实现

three.js一些细节功能的实现

  • 1.three.js如何实现一键复原的功能
    • 说明
    • 实现
    • 完整demo
  • 2.three.js中,如何用一个新的变量复制另一个模型的坐标?是直接等于号赋值吗?
  • 3. 如何在three.js中随心所欲的添加或删除一个模型?(就是说,添加后我们才能选中他,删除后我们就不能选中他)
  • 4.鼠标左键移动three.js中某个独立的个体
  • 5.关于鼠标移动事件的碎碎念
  • 6.当你想获得一个模型的位置时,你应怎么做?
    • 题目背景 : 假如说,你想把mesh0的坐标复制到annex0上
    • 1)错误的想法
    • 2)正确的做法(具体为啥正确我也不知道,反正研究了很久,虽然还是不知道为什么!但是效果出来了!)

1.three.js如何实现一键复原的功能

说明

一键复原,即恢复模型位置,旋转角度,缩放

实现

  • 第一步,将原来的网格先复制一遍
// 假设 cube是你要复制的网格
   const newMesh = cube.clone();
  • 第二步,一键还原
cube.position.copy(newMesh.position);
cube.rotation.copy(newMesh.rotation);
cube.scale.copy(newMesh.scale);

完整demo

import { useState, useEffect, Component } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import * as THREE from 'three';
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 引入指针锁定控制器扩展库PointerLockControls.js
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js'
// 导入动画库
import gsap from "gsap";
//导入lil.gui
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";


// function App() {
//   useEffect(() => {


//     const scene = new THREE.Scene();
//     const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
//     //创建渲染器
//     const renderer = new THREE.WebGLRenderer();
//     renderer.setSize(window.innerWidth, window.innerHeight);
//     document.body.appendChild(renderer.domElement);
//     //创建几何体
//     const geometry = new THREE.BoxGeometry(1, 1, 1);
//     //创建材质
//     const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
//     //创建网格=几何体+材质
//     const cube = new THREE.Mesh(geometry, material);
//     //将网格添加到场景
//     scene.add(cube);

//     //正对着我们的是z轴
//     camera.position.z = 5;
//     //默认看向原点
//     camera.lookAt(0, 0, 0);

//     //创建轨道控制器
//     //const controls = new OrbitControls(camera, renderer.domElement);

//     function animate() {
//       //播放下一帧,继续调用animate函数
//       requestAnimationFrame(animate);
//       //旋转
//       cube.rotation.x += 0.01;
//       cube.rotation.y += 0.01;
//       //渲染
//       renderer.render(scene, camera);
//     }
//     //调用函数
//     animate();
//   }, [])

//   return (
//     <>
//       
// // ) // } class App extends Component { render() { return <div></div> } componentDidMount() { // const scene = new THREE.Scene(); // const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); // //创建渲染器 // const renderer = new THREE.WebGLRenderer(); // renderer.setSize(window.innerWidth, window.innerHeight); // document.body.appendChild(renderer.domElement); // //创建几何体 // const geometry = new THREE.BoxGeometry(1, 1, 1); // //创建材质 // const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // //创建网格=几何体+材质 // const cube = new THREE.Mesh(geometry, material); // //将网格添加到场景 // scene.add(cube); // //正对着我们的是z轴 // camera.position.z = 5; // //默认看向原点 // camera.lookAt(0, 0, 0); // //创建轨道控制器 // //const controls = new OrbitControls(camera, renderer.domElement); // function animate() { // //播放下一帧,继续调用animate函数 // requestAnimationFrame(animate); // //旋转 // cube.rotation.x += 0.01; // cube.rotation.y += 0.01; // //渲染 // renderer.render(scene, camera); // } // //调用函数 // animate(); // console.log(THREE); // 设置场景、相机 const scene = new THREE.Scene(); // 设置场景颜色为蓝色 scene.background = new THREE.Color(0x0000ff); //透视投影(透视投影会使得远离中心的物体看起来扭曲或拉伸,这是一个不可避免的数学事实) //const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); //解决办法是使用正交投影: const camera = new THREE.OrthographicCamera(window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, 1, 1000); //设置相机的缩放比例(正交投影会使得物体变得非常小,所以你需要设置一下缩放比例,比如放大一百倍) camera.zoom = 100; // 更新相机的投影矩阵(这句话好像不用加) camera.updateProjectionMatrix(); // 设置相机位置 //camera.position.set(0, 0, 10); //camera.position.set(0, 15, 70); //camera.position.z = -6; camera.position.y = 6; camera.position.x = -6; scene.add(camera); //渲染器 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 监听窗口大小变化事件 window.addEventListener('resize', function () { var width = window.innerWidth; var height = window.innerHeight; //1. 更新渲染器大小 renderer.setSize(width, height); //2.更新相机的宽高比 camera.aspect = width / height; // 设置渲染器的像素比(这个好像可有可无,不过加上确实更好一点,拉伸更自然了) renderer.setPixelRatio(window.devicePixelRatio); // 3.更新相机的投影矩阵 camera.updateProjectionMatrix(); }); /********************************************/ //1.创建纹理加载器 let textureLoader = new THREE.TextureLoader(); //2.加载纹理 //let texture = textureLoader.load("./picture/52ec610d2c388ddc9c07f02027231f1.png", // let texture = textureLoader.load( // '/root/myspace/three/react-three-app/vite-project/src/picture/52ec610d2c388ddc9c07f02027231f1.png', let texture = textureLoader.load( './src/picture/2.png', // onLoad回调 function (texture) { // in this example we create the material when the texture is loaded console.log('Texture loaded successfully.'); }, // 目前暂不支持 onProgress 的回调 undefined, // onError回调 function (err) { console.log('An error happened while loading the texture.'); console.log('Error details:{{{[', err, ']}}}'); } ); /********************************************/ // 创建一个立方体 const geometry = new THREE.BoxGeometry(); //材质 const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, map: texture, }); //设置材质为线框模式 material.wireframe = false; const cube = new THREE.Mesh(geometry, material); //cube.position.x = 2; //等价于 cube.position.set(0, 0, 0); // 假设 mesh0 是你要复制的网格 const newMesh = cube.clone(); scene.add(cube); /**************/ // 创建轨道控制器(此时你可以使用右键让模型动起来) const controls = new OrbitControls(camera, renderer.domElement); // 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。 controls.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, // 左键旋转 MIDDLE: THREE.MOUSE.PAN, // 中键平移 RIGHT: THREE.MOUSE.DOLLY // 右键缩放 }; controls.enableDamping = true; //设置阻尼的系数 controls.dampingFactor = 0.05; //让其主动的旋转 //controls.autoRotate = true; // 添加坐标轴辅助器(相当于一个线段,看成一个物件) const axesHelper = new THREE.AxesHelper(5);//线段的长度为5 scene.add(axesHelper);//添加到场景中 // 设置时钟 const clock = new THREE.Clock(); /**************/ // 鼠标交互 let isDragging = false; let previousMousePosition = { x: 0, y: 0 }; function onDocumentMouseDown(event) { isDragging = true; } function onDocumentMouseMove(event) { if (isDragging) { var deltaMove = { x: event.clientX - previousMousePosition.x, y: event.clientY - previousMousePosition.y }; const deltaRotationQuaternion = new THREE.Quaternion() .setFromEuler(new THREE.Euler( toRadians(deltaMove.y * 1), toRadians(deltaMove.x * 1), 0, 'XYZ' )); cube.quaternion.multiplyQuaternions(deltaRotationQuaternion, cube.quaternion); } previousMousePosition = { x: event.clientX, y: event.clientY }; } function onDocumentMouseUp(event) { isDragging = false; } // 将鼠标事件监听器添加到渲染器的DOM元素 // renderer.domElement.addEventListener('mousedown', onDocumentMouseDown, false); // renderer.domElement.addEventListener('mousemove', onDocumentMouseMove, false); // renderer.domElement.addEventListener('mouseup', onDocumentMouseUp, false); // 动画循环渲染 function animate() { // 如果没有鼠标交互,立方体会自动旋转 if (!isDragging) { // cube.rotation.x += 0.01; // cube.rotation.y += 0.01; } controls.update(); renderer.render(scene, camera); requestAnimationFrame(animate); } animate(); // 开始动画循环 // 辅助函数:将角度转换为弧度 function toRadians(angle) { return angle * (Math.PI / 180); } //lil-gui let eventObj = { Fullscreen: function () { renderer.domElement.requestFullscreen(); gui.show(); }, ExitFullscreen: function () { document.exitFullscreen(); gui.show(); } }; //创建GUI const gui = new GUI(); //改变交互界面style属性 gui.domElement.style.right = '0px'; gui.domElement.style.width = '300px'; gui.close(); // 创建一个折叠的文件夹 const folder = gui.addFolder('全屏设置'); // 添加按钮到折叠文件夹 folder.add(eventObj, 'Fullscreen').name('全屏'); folder.add(eventObj, 'ExitFullscreen').name('退出全屏'); // 关闭折叠面板 folder.close(); const folder1 = gui.addFolder('立方体位置设置'); folder1.add(cube.position, "x", -5, 5).name("立方体x轴位置").onChange((val) => { console.log("cube's x position:", val); }); folder1.add(cube.position, "y", -5, 5).name("立方体y轴位置"); folder1.add(cube.position, "z", -5, 5).name("立方体z轴位置"); //.min(-10).max(10).step(1) folder1.close(); const folder2 = gui.addFolder('线框模式设置'); folder2.add(material, "wireframe").name("线框模式"); folder2.close(); const folder3 = gui.addFolder('颜色设置'); let colorParams = { cubeColor: "#ff0000", }; folder3.addColor(colorParams, "cubeColor") .name("颜色变化") .onChange((val) => { cube.material.color.set(val); }); folder3.close(); window.addEventListener('mouseup', function () { console.log('鼠标按下'); cube.position.x += 1; cube.rotateY(Math.PI / 3); cube.scale.x = 2; // 在 x 轴方向上缩放为原来的两倍 cube.scale.y = 0.5; // 在 y 轴方向上缩放为原来的一半 cube.scale.z = 1.5; // 在 z 轴方向上缩放为原来的1.5倍 renderer.render(scene, camera); }); // 添加HTML按钮 const button = document.getElementById('myButton'); button.addEventListener('click', function () { console.log('Button Clicked!'); // // 将新网格对象添加到场景中 // scene.add(newMesh); // // 从场景中删除要替换的网格对象 // scene.remove(cube); cube.position.copy(newMesh.position); cube.rotation.copy(newMesh.rotation); cube.scale.copy(newMesh.scale); // 渲染场景 renderer.render(scene, camera); }); } } export default App

2.three.js中,如何用一个新的变量复制另一个模型的坐标?是直接等于号赋值吗?

答:不是,需要先建立一个THREE.Vector3对象,然后再调用copy函数!

var startPoint = new THREE.Vector3();
startPoint.copy(axesHelper1.position);
console.log("axesHelper1:", axesHelper1.position);
console.log("startPoint:", startPoint);

3. 如何在three.js中随心所欲的添加或删除一个模型?(就是说,添加后我们才能选中他,删除后我们就不能选中他)

  • 天才第一步,在代码的第一行添加射线选中的元素有哪些,我们用一个空数组表示,你最好写在第一行代码,不然你后面用的时候,你根本找不到到该数组,就会报错,说系统也找不到
var objects = [];
  • 天才第二步
//创建射线
const raycaster = new THREE.Raycaster();
//创建鼠标向量
const mouse = new THREE.Vector2();
//存放射线可射的范围
let intersects;


/********************************
* 上面两行放全局变量
* 下面两行放在mousedown鼠标事件中
*********************************/


//通过摄像机和鼠标为位置,更新射线
raycaster.setFromCamera(mouse, camera);
//计算物体和射线的焦点
intersects = raycaster.intersectObjects(objects);      
  • 天才第n步,在你需要的位置将模型显示并可以选中
scene.add(axesHelper1);//将箭头加入到场景中

//将箭头加入到我们的选中范围
objects.push(axesHelper1);
// 射线检测
intersects = raycaster.intersectObjects(objects);
  • 天才第n+1步,在你需要的位置将模型隐藏并不可以选中
scene.remove(ZTubeMesh);//将箭头"隐藏"到场景中
console.log('删掉了,兄弟们!');//调试代码
console.log('objects:', objects);//调试代码



objects.pop(ZTubeMesh);//将管道Z取消在我们的选中范围
console.log('objects:', objects);//调试代码
// 射线检测
intersects = raycaster.intersectObjects(objects);

注意,如果你不写下面这两行,你就会发现该模型在场景中消失了,但是还能点到(这就是误触!)

objects.pop(ZTubeMesh);//将管道Z取消在我们的选中范围
// 射线检测
intersects = raycaster.intersectObjects(objects);

4.鼠标左键移动three.js中某个独立的个体

下面这种方法算是初步实现,具体细节还需进行优化

// 初始化鼠标坐标
var mouseX = 0;
var mouseY = 0;
var m_firstMouse = true;
window.addEventListener('mousemove', function (event) {
	console.log('鼠标移动');
	console.log('objects:', objects);
	/****************************
	* //2024-01-09
	*/
	
	if (hasTeethSelected && mouseDown) {
	 //有牙齿被选中了,且鼠标已经按下了
	
	 if (event.buttons === 1)//左键旋转
	 {
	   //如果是第一次进来则记录一下当前位置
	   if (m_firstMouse) {
	     // 更新鼠标坐标
	     mouseX = event.clientX;
	     mouseY = event.clientY;
	     m_firstMouse = false;
	   }
	   // 计算鼠标坐标差值
	   var deltaX = event.clientX - mouseX;
	   var deltaY = event.clientY - mouseY;
	   console.log('x:', deltaX, 'y:', deltaY);
	
	   /***
	    * 得到模型的中心 center
	    */
	   const boundingBox = new THREE.Box3().setFromObject(selectedObject);
	   const center = new THREE.Vector3();
	   boundingBox.getCenter(center);
	
	   // 更新鼠标坐标
	   mouseX = event.clientX;
	   mouseY = event.clientY;
	
	   // 创建矩阵进行旋转
	   var matrix = new THREE.Matrix4();
	
	   // 根据鼠标坐标差值进行旋转
	   matrix.makeRotationY(deltaX * 0.005); // Y轴旋转//左右拖动
	   matrix.multiply(new THREE.Matrix4().makeRotationX(deltaY * 0.005)); // X轴旋转//上下拖动
	
	   selectedObject.position.sub(center);//减去自身中心坐标,现在就回到了原点
	   // 应用变换矩阵到立方体
	   selectedObject.applyMatrix4(matrix);
	   selectedObject.position.add(center);//加上自身中心坐标,现在就回到了原来的点
	 }
	}
});

这里我要强掉一下,只要你是移动场景中的"某一个物体",你就要像下面这个逻辑一样,先减去自身中心点,再添加自身中心点

  • 逻辑
  1. 减去中心点
  2. 矩阵变换
  3. 加上中心点
  • demo
selectedObject.position.sub(center);//减去自身中心坐标,现在就回到了原点
// 应用变换矩阵到立方体
selectedObject.applyMatrix4(matrix);
selectedObject.position.add(center);//加上自身中心坐标,现在就回到了原来的点

5.关于鼠标移动事件的碎碎念

鼠标移动事件,是每更新一帧就执行就执行一次.
帧数越多,鼠标移动事件就执行的次数越多,画面就越流畅.

所以,不管还是qt还是three.js,里面的函数代码都是对图像的每一帧进行的操作!!!

6.当你想获得一个模型的位置时,你应怎么做?

题目背景 : 假如说,你想把mesh0的坐标复制到annex0上

1)错误的想法

相信你读到这里,你已经有了自己的想法:

annex0.position = mesh0.position;
或者
annex0.position.copy(mesh0.position);

没错,我一开始也是这么想的!
然而,事实很打脸!是错的:

  • 首先,annex0.position = mesh0.position;在语法上就是错的,three.js的三维坐标不能直接用等于号;
  • 其次,annex0.position.copy(mesh0.position);在copy之后是:(0,0,0),具体是为什么?我不太清楚(跪求大佬解答!!!)
    你用console.log(annex0.position)console.log(mesh0.position)都是 (0,0,0) , 这就无法满足我们的需求.

2)正确的做法(具体为啥正确我也不知道,反正研究了很久,虽然还是不知道为什么!但是效果出来了!)

  • 方法:你要计算BoundingBox
const modelCenter = new THREE.Vector3();//用来计算局部坐标中心点
mesh0.geometry.computeBoundingBox();//该行与下面这行是计算模型的中心点
mesh0.geometry.boundingBox.getCenter(modelCenter);//现在模型的中心点就出来了!!!
//你不能直接annex0.position.copy(mesh0.position);具体为啥,我也不知道
annex0.position.copy(modelCenter);//拿到模型的坐标,大功告成!!!

你再试着去打印一下:

console.log('mesh0:', annex0.position);

输出结果 : nice!终于拿到模型的坐标了!!!

{
    "x": 26.112296104431152,
    "y": -1.6030312776565552,
    "z": 16.00271224975586
}

你可能感兴趣的:(opengl,javascript,开发语言,ecmascript)