ThreeJS:光线投射与3D场景交互
光线投射Raycaster
光线投射详细介绍可参考:https://en.wikipedia.org/wiki/Ray_casting,
ThreeJS中,提供了Raycaster类,用于进行鼠标拾取,即:当三维场景中鼠标移动时,利用光线投射,可计算出鼠标划过了哪些物体。
ThreeJS官方案例webgl_interactive_cubes演示了光线投射的具体应用,
通过Raycaster类实例的intersectObjects方法,可以获取到与射线Ray相交的一组物体;
通过Raycaster类实例的intersectObject方法,可以获取到与Ray射线相交的第一个物体,
此外,在鼠标点击或移动时,我们是需要不断去更新Ray射线的,这样才能选中正确的3D物体,可以通过setFromCamera方法,来更新射线,
接着,还有一个要解决的问题,就是这里的标准化设备坐标中的二维坐标,如何进行处理才是符合ThreeJS的接口规范的?
核心代码如下,
const mousePosition = new THREE.Vector2(); //记录鼠标位置
//TODO:监听鼠标移动事件
function onPointerMove( event ) {mousePosition.x = ( event.clientX / window.innerWidth ) * 2 - 1;mousePosition.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
document.addEventListener("mousemove",onPointerMove);
注意到:计算mousePosition.y的计算结果,相比mousePosition.x多乘了一个-1,这是因为屏幕坐标系和ThreeJS的XYZ空间直角坐标系,两者的Y轴是相反的。
3D场景鼠标交互案例
下面来实现一个利用光线投射控制鼠标选中物体高亮显示的案例,主要效果如下:
①鼠标选中物体,高亮显示,
②鼠标离开物体,取消高亮显示,
完整示例代码如下,
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js"; //轨道控制器
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js"; //lil-gui调试工具
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; //HDR加载器
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; //GLTF加载器
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";const gui = new GUI();//TODO:打印版本
console.warn("threejs版本:", THREE.REVISION);
//TODO:创建时钟
const clock = new THREE.Clock();
//TODO:创建场景
const scene = new THREE.Scene();
//TODO:创建透视相机
const camera = new THREE.PerspectiveCamera(45, //视角window.innerWidth / window.innerHeight,0.1, //近平面1000.0 //远平面
);//TODO:创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);//TODO:轨道控制器
const orbitControls = new OrbitControls(camera, renderer.domElement);
//设置阻尼效果
// orbitControls.enableDamping = true;
orbitControls.update();document.body.appendChild(renderer.domElement);//TODO:添加光源
const light = new THREE.PointLight(0xffffff, 1000, 1000);
light.position.set(15, 15, 15);
scene.add(light);scene.background = new THREE.Color(0xbfe3dd);//TODO:添加3个球体
const ball1 = new THREE.SphereGeometry(1);
const ballMaterial = new THREE.MeshBasicMaterial({color:0xffff00});
const ballMesh1 = new THREE.Mesh(ball1,ballMaterial);
ballMesh1.geometry.scale(0.5,0.5,0.5);
ballMesh1.position.set(-1.0,-1.0,-1.0);const ballMesh2 = new THREE.Mesh(ball1,ballMaterial);
ballMesh2.geometry.scale(0.5,0.5,0.5);
ballMesh2.position.set(1.0,-1.0,-1.0);const ballMesh3 = new THREE.Mesh(ball1,ballMaterial);
ballMesh3.geometry.scale(0.5,0.5,0.5);
ballMesh3.position.set(1.0,1.0,-1.0);const ballMesh4 = new THREE.Mesh(ball1,ballMaterial);
ballMesh4.geometry.scale(0.5,0.5,0.5);
ballMesh4.position.set(1.0,1.0,1.0);scene.add(ballMesh1);
scene.add(ballMesh2);
scene.add(ballMesh3);
scene.add(ballMesh4);//TODO:设置相机位置
camera.position.z = 5.0;
camera.position.y = 2.0;
camera.position.x = 2.0;
camera.lookAt(0, 0, 0);//TODO:利用光线投射,使用鼠标选中Mesh
const rayCaster = new THREE.Raycaster();
const mousePosition = new THREE.Vector2(); //记录鼠标位置
const hightLightColor = new THREE.Color(0xff0000);//高亮颜色
const highlightMaterial = new THREE.MeshBasicMaterial({color:hightLightColor});
const lastSelected = {originMaterial:null,//被选中的3D物体的materialinstance:null,//被选中的3D物体
}
//TODO:监听鼠标移动事件
function onPointerMove( event ) {mousePosition.x = ( event.clientX / window.innerWidth ) * 2 - 1;mousePosition.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
document.addEventListener("mousemove",onPointerMove);//TODO:还原上一次被选中的3D物体
function resetLastSelected(){if(lastSelected.instance && lastSelected.originMaterial){lastSelected.instance.material = lastSelected.originMaterial;lastSelected.instance = null;lastSelected.originMaterial = null;}
}function pick3DObject(){// 通过摄像机和鼠标位置更新射线_[使用一个新的原点和方向来更新射线。]rayCaster.setFromCamera(mousePosition,camera);/*** objects —— 检测和射线相交的一组物体。recursive —— 若为true,则同时也会检测所有物体的后代。否则将只会检测对象本身的相交部分。默认值为true。*/const interscets = rayCaster.intersectObjects(scene.children,false);if(interscets.length>0){const interscetObject = interscets[ 0 ].object;console.log("interscets::",interscetObject);//TODO:还原上一次选中的3D物体resetLastSelected();if(interscetObject){//TODO:鼠标选中3D物体,设置高亮效果//记录当前被选中的物体lastSelected.instance = interscetObject;lastSelected.originMaterial = interscetObject.material;//设置高亮interscetObject.material = highlightMaterial;}}else{//TODO:鼠标离开3D物体,还原MaterialresetLastSelected();}
}//TODO:创建坐标辅助器
// const axesHelper = new THREE.AxesHelper(5);
// scene.add(axesHelper);//TODO:渲染函数
function animate() {requestAnimationFrame(animate);//TODO:旋转立方体// mesh.rotation.x += 0.01;// mesh.rotation.y += 0.01;pick3DObject();//TODO:更新轨道控制器orbitControls.update();//TODO:渲染renderer.render(scene, camera);
}
window.onresize = function () {//TODO:重置渲染器宽高比renderer.setSize(window.innerWidth, window.innerHeight);//TODO:重置相机宽高比camera.aspect = window.innerWidth / window.innerHeight;//TODO:更新相机投影矩阵camera.updateProjectionMatrix();
};animate();//TODO:lil-gui添加调试按钮
const myObject = {// myBoolean: true,fullScreenBtnFunction: function () {document.body.requestFullscreen();},exitFullScreenBtnFunction: function () {document.exitFullscreen();},wireframeMode: false,renderMode: 1,colorSpace: 0,
};gui.add(myObject, "fullScreenBtnFunction").name("全屏显示"); // Button
gui.add(myObject, "exitFullScreenBtnFunction").name("退出全屏"); // Button
//TODO:开启/关闭线框模式
gui.add(myObject, "wireframeMode").name("线框模式").onChange(function (value) {console.log(value);mesh.material.wireframe = value;});
// gui.add(myObject, "myString"); // Text Field
// gui.add(myObject, "myNumber"); // Number Field//TODO:控制Mesh网格正反面显示模式
gui.add(myObject, "renderMode", { 双面模式: 0, 正面模式: 1, 背面模式: 2 }).name("三角形正反面显示模式").onChange(function (value) {console.log(value);switch (value) {case 0: {triangleMesh.material.side = THREE.DoubleSide;break;}case 1: {triangleMesh.material.side = THREE.FrontSide;break;}case 2: {triangleMesh.material.side = THREE.BackSide;break;}}});//TODO:控制颜色空间
gui.add(myObject, "colorSpace", {NoColorSpace: 0,SRGBColorSpace: 1,LinearSRGBColorSpace: 2,}).onChange((value) => {console.log("修改颜色空间::", value);switch (value) {case 0: {diffuseMap.colorSpace = THREE.NoColorSpace;break;}case 1: {diffuseMap.colorSpace = THREE.SRGBColorSpace;break;}case 2: {diffuseMap.colorSpace = THREE.LinearSRGBColorSpace;break;}}});
相关文章:
ThreeJS:光线投射与3D场景交互
光线投射Raycaster 光线投射详细介绍可参考:https://en.wikipedia.org/wiki/Ray_casting, ThreeJS中,提供了Raycaster类,用于进行鼠标拾取,即:当三维场景中鼠标移动时,利用光线投射,…...
docker挂载数据卷-以nginx为例
目录 一、什么是数据卷 二、数据卷的作用 三、如何挂载数据卷 1、创建nginx容器挂载数据卷 2、查看数据卷 3、查看数据卷详情 4、尝试在宿主机修改数据卷 5、查看容器内对应的数据卷目录 6、 访问nginx查看效果 一、什么是数据卷 挂载数据卷本质上就是实…...
Docker-compose部署Fastapi项目
Docker-compose部署Fastapi、postgres、Redis、Nginx) 之前有写过使用容器部署的方式,这次尝试使用Docker-compose试一次大胆的尝试 使用容器的方式部署只是掌握这项技能的基础,在使用Docker-compose的过程中会有些稍许的不同。毕竟踩过的坑才算是跨过去…...
Eigen求解线性方程组
1、线性方程组的应用 线性方程组可以用来解决各种涉及线性关系的问题。以下是一些通常可以用线性方程组来解决的问题: 在实际工程和科学计算中,求解多项式方程的根有着广泛的应用。 在控制系统的设计中,我们经常需要求解特征方程的根来分析…...
7、Java基本数据类型的使用细节探讨(超详细版本)
Java基本数据类型的使用细节探讨 一、整数类型二、浮点数三、字符型四、布尔型 我觉得基本数据类型大家学计算机的应该都懂,但是韩顺平老师讲的基本类型的使用细节我觉得有必要记录一下,重新学的时候才发现有了新的感悟! 一、整数类型 使用细…...
MFC实现点击列表头进行排序
MFC实现点击列表头排序 1、添加消息处理函数 在列表窗口右键,类向导。选择 IDC_LIST1(我的列表控件的ID),消息选择LVN_COLUMNCLICK。 2、消息映射如下 然后会在 cpp 文件中生成以下函数 void CFLashSearchDlg::OnLvnColumnclic…...
用龙梦迷你电脑福珑2.0做web服务器
用龙梦迷你电脑福珑2.0上做web服务器是可行的。已将一个网站源码放到该电脑,在局域网里可以访问网站网页。另外通过在同一局域网内的一台windows10电脑上安装花生壳软件,也可以在外网访问该内网服务器网站网页。该电脑的操作系统属于LAMP。在该电脑上安装…...
秋招后端开发面试题 - JVM类加载机制
目录 JVM类加载机制前言面试题能说一下类的生命周期吗?类加载的过程知道吗?类加载器有哪些?什么是双亲委派机制?为什么要用双亲委派机制?如何破坏双亲委派机制?如何判断一个类是无用的类? JVM类…...
OceanBase 分布式数据库【信创/国产化】- OceanBase 配置项和系统变量概述
本心、输入输出、结果 文章目录 OceanBase 分布式数据库【信创/国产化】- OceanBase 配置项和系统变量概述前言OceanBase 数据更新架构OceanBase 配置项和系统变量概述配置项配置项分类配置项查询系统变量系统变量分类系统变量查询配置项与系统变量的区分OceanBase 分布式数据库…...
单单单单单の刁队列
在数据结构的学习中,队列是一种常用的线性数据结构,它遵循先进先出(FIFO)的原则。而单调队列是队列的一种变体,它在特定条件下保证了队列中的元素具有某种单调性质,例如单调递增或单调递减。单调队列在处理…...
电脑windows系统压缩解压软件-Bandizip
一、软件功能 Bandizip是一款功能强大的压缩和解压缩软件,具有快速拖放、高速压缩、多核心支持以及广泛的文件格式支持等特点。 Bandizip软件的功能主要包括: 1. 支持多种文件格式 Bandizip可以处理多种压缩文件格式,包括ZIP, 7Z, RAR, A…...
图片公式识别@文档公式识别@表格识别@在线和离线OCR工具
文章目录 abstract普通文字识别本地软件识别公式扩展插件下载小结 在线识别网站/API👺Quicker整合(推荐)可视化编辑和识别公式其他多模态大模型识别图片中的公式排版 开源模型 abstract 本文介绍免费图片文本识别(OCR)工具,包括普通文字识别,公式识别,甚至是手写公…...
Java高阶私房菜:JVM分代收集算法介绍和各垃圾收集器原理分解
目录 什么是分代收集算法 GC的分类和专业术语 什么是垃圾收集器 垃圾收集器的分类及组合 编辑 应关注的核心指标 Serial和ParNew收集器原理 Serial收集器 ParNew收集器 Parallel和CMS收集器原理 Parallel 收集器 CMS收集器 新一代垃圾收集器G1和ZGC G1垃圾收集器…...
为什么IB损失要在100epochs后再用?
在给定的代码中,参数start_ib_epoch用于控制从第几轮开始使用IB(Instance-Balanced)损失函数进行训练。具体来说,如果start_ib_epoch的值大于等于100,那么在训练的前100轮中将使用普通的交叉熵损失函数(CE&…...
《Video Mamba Suite》论文笔记(4)Mamba在时空建模中的作用
原文翻译 4.4 Mamba for Spatial-Temporal Modeling Tasks and datasets.最后,我们评估了 Mamba 的时空建模能力。与之前的小节类似,我们在 Epic-Kitchens-100 数据集 [13] 上评估模型在zero-shot多实例检索中的性能。 Baseline and competitor.ViViT…...
【备战软考(嵌入式系统设计师)】10 - 软件工程基础
这一部分的内容是概念比较多,不要理解,去感受。 涉及的知识点是嵌入式系统开发和维护的部分,也就是和管理相关的,而不是具体如何进行嵌入式系统开发的细节。 系统开发生命周期 按照顺序有下面几个阶段,我们主要要记…...
随手笔记-GNN(朴素图神经网络)
自己看代码随手写的一点备忘录,自己看的,不喜勿喷 GNN (《------ 代码) 刚开始我还在怀疑为什么没有加weigth bias,已经为什么权重才两个,原来是对node_feats进行的network的传播,而且自己内部直接进行了。 下面是一…...
C 语言指针怎么理解?
在今天的学习中,我注意到有位学员似乎对 C 语言指针的理解有些困惑。为了帮助大家更好地理解,我来举个例子。 C 语言指针就好比 Windows 桌面上常见的快捷方式。快捷方式可以指向某个游戏,这就是普通指针;它也可以指向另一个快捷…...
HTTP协议:通信机制、特点及实践应用
目录 前言 1. 运行机制 2. 通信方式 3. 主要特点 4. 统一资源标识符(URL) 5. HTTP报文 6. HTTP请求 7. HTTP响应 8. 实体 9. 持续连接 结语 前言 HTTP(Hypertext Transfer Protocol)是互联网上应用最广泛的一种协议&a…...
Leetcode—289. 生命游戏【中等】
2024每日刷题(126) Leetcode—289. 生命游戏 算法思想 实现代码 class Solution { public:void gameOfLife(vector<vector<int>>& board) {int rows board.size();int cols board[0].size();int neighbors[3] {0, 1, -1};vector<…...
系统运维(虚拟化)
1.VLAN VLAN(Virtual Local Area Network)即虚拟局域网,是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。 每个VLAN是一个广播域,VLAN内的主机间可以直接通信,而VLAN间则不能直接互通。这样,广播报…...
Linux域名解析
1.hosts:windows c盘下面 Linux: /etc/hosts 作用:实现名字解析,主要为本地主机名、集群节点提供快速解析。平面式结构,集中式数据库。 缺点:不便于查询更新 2.DNS:域名系统 作用:实现名字解析(分层性,层次性) FQDN:完全合格域名/全称域…...
树形结构和列表的区别
树形结构和列表在数据组织、表示方式以及应用场景等方面存在明显的区别。 首先,树形结构是一种非线性的数据结构,表现为层次的嵌套结构。每个节点可以有多个子节点,这些子节点又可以有自己的子节点,形成一个层次分明的结构。这种一…...
Go中json的解析和反解析
在解析过程中,反向解析不包含其中的部分参数也不会报错,这是需要注意的一点 31 func main() { // E: main redeclared in this block 32 type A…...
SpringBoot+vue实现退出功能
目录 1. 创建点击事件 2. 编写退出逻辑 在Spring Boot 和 Vue.js 应用中实现退出功能,通常涉及到前端的用户界面操作和注销逻辑。 以下是实现退出功能的步骤: 1. 创建点击事件 在header.vue中创建一个点击事件 <span style"text-decoratio…...
Linux操作系统中管理磁盘的另外一种操作方式。即LVM——逻辑卷管理操作
在Linux操作系统中管理磁盘的一种方法名称——LVM,这种管理磁盘的优势。 1.使用LVM去管理磁盘可以在不影响原来数据的前提下去扩容磁盘空间或者是缩减磁盘空间。 在LVM中除了上层逻辑券可以扩容,下层的券组也可以扩容。 2.使用LVM管理的磁盘支持快照功…...
Lua 零基础入门
Lua 1.Lua是什么? 1.1 Lua的历史 Lua是由Roberto Ierusalimschy、Luiz Henrique de Figueiredo和Waldemar Celes于1993年创建的,当时他们是巴西里约热内卢天主教大学计算机图形技术组(Tecgraf)的成员。在开发Lua之前࿰…...
记录DemoApplication.java不变蓝问题
问题 解决方案 一、点击右下角加载 二、右键项目 勾选maven...
22_Scala集合Seq
文章目录 Seq序列1.构建集合2.List集合元素拼接&&集合拼接3.可变Seq&&List3.1 ListBuffer创建3.2 增删改查3.3 相互转化 Appendix1.Scala起别名2.Seq底层3.关于运算符操作: :4.空集合的表示 Seq序列 –Seq表示有序,数据可重复的集合 1.构建集合 …...
机器学习初学者 6 个核心算法!建议收藏,反复观看!
今天再来介绍机器学习算法的基本概念和适用场景! 首先,引用一句英国统计学家George E. P. Box的名言:All models are wrong, but some are useful. 没有哪一种算法能够适用所有情况,只有针对某一种问题更有用的算法。 也就是说&…...
网站改成响应式/聊城网站开发
ES7 提出的async 函数,终于让 JavaScript 对于异步操作有了终极解决方案。No more callback hell。async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。想较于 Generator,Async 函数的改进在于…...
网站开发需要什么工具/网店运营是做什么的
本笔记将介绍:如何安装 Certbot 工具;如何使用它获取证书;如何处理在操作过程中遇到的问题。 注意事项 本部分内容属于简述,详细内容请参考 certbot instructions 官方页面,依据提示操作即可。以下是操作大致流程&…...
武汉网站推广公司招聘/公司网站设计的内容有哪些
前言 上节讲到qt for android开发百度地图,已经可以打开地图了,里面的一些功能,如自定义搜索栏,添加控件等等,这些百度地图官方开发文档都提供了例子,可以自定义开发。但是问题也来了,我们开发百…...
做进行网站推广赚钱/深圳关键词排名优化系统
如:现有一张表名为trojans,其中others字段很多是重复的, 删除重复的记录语句如下: delete trojans from trojans , (select id from trojans GROUP by others having count(*)>1) as t2 where t2.idtrojans.id 这个语句一次只…...
专用车网站建设/百度信息流推广平台
(1)改code前dump出来的结果 (2)改code后dump出来的结果 一开始很纳闷为什么(1)Z轴surfaceview下面竟然还有另外一个activity的存在。原来是因为当前所在的MwPlayerActivity不是完全不透明的view 将 <ac…...
东莞网络营销全网推广/seo具体优化流程
计算平均值基础语法:{ $avg: }{ $avg: [ , ... ] }注:(1)$avg计算的时候会忽略非数值数据(包括缺失值)(2)在$group中使用时,若expression为数组,$avg会忽略这个expression进行计算(3)在其他管道符中使用时,对于单个expression&…...