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

发布时间:2024年01月13日

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 (
//     <>
//       <div className='App'></div>

//     </>
//   )
// }

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);//加上自身中心坐标,现在就回到了原来的点
文章来源:https://blog.csdn.net/weixin_52668597/article/details/135492648
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。