ThreeJS-3D教学七-交互

news/2024/11/15 6:18:49/

在threejs中想要选中一个物体,点击或者鼠标悬浮,又或者移动端的touch事件,核心都是通过new THREE.Raycaster完成的。这里用到了一个概念,即我们点击时的 屏幕坐标 转换为 three中的3D坐标。
先看效果图:
在这里插入图片描述
代码是:

<!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>
</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, temporaryKeep;
let camera, scene, renderer, controls;
const group = new THREE.Group();
let widthImg = 200;
let heightImg = 200;
const mouse = new THREE.Vector2();
init();
initHelp();
initLight();
axesHelperWord();
animate();
// 添加平面
addPlane();let geometry = new THREE.BoxGeometry(20, 20, 10);
let material = new THREE.MeshLambertMaterial({color: 0xcccccc});
let cube = new THREE.Mesh(geometry, material);
cube.position.y = 10;
cube.name = 'BoxGeometry';
scene.add(cube);/*** CylinderGeometry(radiusTop : Float, radiusBottom : Float, height : Float, radialSegments : Integer, heightSegments : Integer)radiusTop—顶部圆柱体的半径。默认值为1。radiusBottom—底部圆柱体的半径。默认值为1。height——圆柱体的高度。默认值为1。radialSegments—圆柱体圆周上的分段面数。默认值为32heightSegments—沿圆柱体高度的面行数。默认值为1。*/
let geometry1 = new THREE.CylinderGeometry(15, 15, 10, 32, 1);
let material1 = new THREE.MeshLambertMaterial({color: 0xffff00});
let cylinder = new THREE.Mesh(geometry1, material1);
cylinder.position.set(30, 5, -50);
cylinder.name = 'CylinderGeometry';
scene.add(cylinder);function onDocumentMouseMove(event) {event.preventDefault();// 将鼠标点击位置的屏幕坐标转成threejs中的标准坐标,具体解释见代码释义 如果 canvas有左边距 和 上边距 需要减去mouse.x = (event.clientX / window.innerWidth) * 2 - 1;mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;// 新建一个三维单位向量 假设z方向就是1// 根据照相机,把这个向量转换到视点坐标系const vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera);// 在视点坐标系中形成射线,射线的起点向量是照相机, 射线的方向向量是照相机到点击的点,这个向量应该归一标准化。const raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());//射线和模型求交,选中一系列直线const intersects = raycaster.intersectObjects(scene.children);console.log('imtersrcts=', intersects);/*** 不是所有的 Material 都有这个属性* emissive.getHex* emissive.setHex*/if (intersects.length > 0) {//选中第一个射线相交的物体if (temporaryKeep !== intersects[0].object) {if (temporaryKeep) temporaryKeep.material.emissive?.setHex(temporaryKeep.currentHex);temporaryKeep = intersects[0].object;temporaryKeep.currentHex = temporaryKeep.material.emissive?.getHex();temporaryKeep.material.emissive?.setHex(0xff0000);}} else {if (temporaryKeep) temporaryKeep.material.emissive?.setHex(temporaryKeep.currentHex);temporaryKeep = null;}
}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 );document.addEventListener('click', onDocumentMouseMove, false);
}function initLight() {const light = new THREE.DirectionalLight(new THREE.Color('rgb(253,253,253)'));light.position.set(100, 100, -10);light.intensity = 3; // 光线强度light.castShadow = true; // 是否有阴影light.shadow.mapSize.width = 2048; // 阴影像素light.shadow.mapSize.height = 2048;// 阴影范围const d = 80;light.shadow.camera.left = -d;light.shadow.camera.right = d;light.shadow.camera.top = d;light.shadow.camera.bottom = -d;light.shadow.bias = -0.0005; // 解决条纹阴影的出现// 最大可视距和最小可视距light.shadow.camera.near = 0.01;light.shadow.camera.far = 2000;const AmbientLight = new THREE.AmbientLight(new THREE.Color('rgb(255, 255, 255)'));scene.add( light );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 );stats.update();controls.update();labelRenderer.render( scene, camera );renderer.render( scene, camera );
}
</script>
</body>
</html>

具体思路代码中都有注释,实在不懂也没关系,函数一封装先用着就是,后面慢慢理解!


http://www.ppmy.cn/news/1143459.html

相关文章

TCP相关面试题

TCP相关面试题 题目1 介绍一下TCP三次握手的过程 介绍TCP三次握手应该从3个方面进行回答&#xff0c;分别是数据包名称&#xff0c;客户端与服务端的状态变化&#xff0c;数据包的序号变化。而不能只是简单回答发送的数据包名称。 TCP三次握手的过程如下&#xff1a; 从数据…

【深蓝学院】手写VIO第2章--IMU传感器--作业

这次作业坑很多&#xff0c;作业说明的不清楚&#xff0c;摸索了很长时间才将此次作业完成&#xff0c;在这里进行记录。 1. T1 1.1 题干 1.2 解答 1.2.1 法1&#xff0c;ros related方法 不知道为什么我的launch不了&#xff0c;在imu_utils目录下面建立build后&#xff0…

redis实战-实现用户签到UV统计

BitMap功能演示 我们针对签到功能完全可以通过mysql来完成&#xff0c;比如说以下这张表 用户一次签到&#xff0c;就是一条记录&#xff0c;假如有1000万用户&#xff0c;平均每人每年签到次数为10次&#xff0c;则这张表一年的数据量为 1亿条 每签到一次需要使用&#xff08…

面向对象设计-UML六种箭头含义

目录 UML概述UML语义UML表示法 六种常用关系标识方法泛化实现依赖关联聚合组合 本文参考文章 https://blog.csdn.net/qq_25091281/article/details/123801862 UML概述 UML (Unified Modeling Language)为面向对象软件设计提供统一的、标准的、可视化的建模语言。适用于描述以…

学生用的台灯护眼的哪种比较好?精选适合学生用的护眼台灯

现代小孩的学习压力确实很大&#xff0c;已经不能和我们以往那种“半大自然化学习”相提并论啦&#xff0c;如今各种学习PAD、电脑网课&#xff0c;成堆的学习资料与作业&#xff0c;恐怕是从小学甚至学前就已经是常态了。而且在平时我们路过学校的时候应该也不难发现&#xff…

Java基础面试,ArrayList和LinkedList的区别

ArrayList 基于动态数组&#xff0c;是一个连续的内存存储&#xff0c;适合进行下标访问扩容机制是&#xff0c;超出长度的时候需要重新建立数组&#xff0c;然后将老数组的数组拷贝到新的数组中。插入元素的时候性能消耗大&#xff0c;因为要移动其他的元素 LinkedList 基于…

流程引擎概述及组成

一、流程引擎概述 流程&#xff0c;可以理解为步骤&#xff0c;一个有序的活动或动作&#xff1b; 引擎&#xff0c;可以理解为驱动&#xff0c;是一个程序或者一套系统。 所以&#xff0c;字面意思可以理解为&#xff0c;流程引擎是一套&#xff08;或一个&#xff09;用来…

【Vue基础-数字大屏】地图

一、阿里云数据可视化平台 地图数据https://datav.aliyun.com/portal/school/atlas/area_selector 二、操作步骤 1、打开阿里云数据可视化平台&#xff0c;复制中国地图数据链接 2、在浏览器中打开中国地图数据链接&#xff0c;复制json数据 3、在assets静态目录下创建mapDa…