开启Three之旅(二)射线、拾取模型、解决鼠标点击、Hover以及CSS3Renderer点击穿透问题

文章目录

  • 射线Ray
  • Raycaster
  • 屏幕坐标 --> 设备坐标
  • 射线坐标计算(Canvas尺寸变化)
  • 射线拾取层级模型
  • 拾取Sprite控制场景
  • 场景一:用鼠标模拟hover事件
  • 场景二: 选中模型(click事件)
  • 场景三:处理射线穿透问题
  • 参考链接

背景

我们要是想要与场景中做交互,大概率都是需要射线去做,点击事件、模拟鼠标hover效果等。
因为鼠标属于二维,咱们的场景是属于3D, 计算这些东西还是比较麻烦的,不过幸好three有对应的拓展

射线Ray

const ray = new THREE.ray()  // 创建射线实例ray

Perporties

  • origin
  • direction

Methods

  • intersectTriangle

    计算射线和一个三角形在3D空间中是否交叉

    demo

    const p1 = new THREE.Vector3(100, 25, 0);
    const p2 = new THREE.Vector3(100, -25, 25);
    const p3 = new THREE.Vector3(100, -25, -25);
    const point = new THREE.Vector3();//用来记录射线和三角形的交叉点
    // `.intersectTriangle()`计算射线和三角形是否相交叉,相交返回交点,不相交返回null
    const result = ray.intersectTriangle(p1,p2,p3,false,point);
    console.log('交叉点坐标', point);
    console.log('查看是否相交', result);
    

    参数4 – 是否进行背面剔除

    如何判断正反面?

Raycaster

这个构造函数是我们本文的重点,这个对标的就是Mesh

const raycaster = new THREE.Raycaster();

Methods

  • intersectObject

    计算自身射线.ray相交的网格模型, 接收一组网格模型,返回值也是一个数组(对象在数组中按照先后顺序)。

    demo

    const raycaster = new THREE.Raycaster();
    raycaster.ray.origin = new THREE.Vector3(-100, 0, 0);
    raycaster.ray.direction = new THREE.Vector3(1, 0, 0);
    // 射线发射拾取模型对象
    const intersects = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
    console.log("射线器返回的对象", intersects);
    

屏幕坐标 --> 设备坐标

  1. 屏幕坐标我们是熟悉的,左上角为(0,0), 横向为x, 纵向为y
  2. 设备坐标是以中心为原点

image.png

转换(这个我们在后面会经常遇到)

// 坐标转化公式
addEventListener('click',function(event){
    const px = event.offsetX;
    const py = event.offsetY;
    //屏幕坐标px、py转标准设备坐标x、y
    //width、height表示canvas画布宽高度
    const x = (px / width) * 2 - 1;
    const y = -(py / height) * 2 + 1;
})
// 屏幕坐标转标准设备坐标
addEventListener('click',function(event){
    // left、top表示canvas画布布局,距离顶部和左侧的距离(px)
    const px = event.clientX-left;
    const py = event.clientY-top;
    //屏幕坐标px、py转标准设备坐标x、y
    //width、height表示canvas画布宽高度
    const x = (px / width) * 2 - 1;
    const y = -(py / height) * 2 + 1;
})

射线坐标计算(Canvas尺寸变化)

画布尺寸变化,我们的射线拾取也需要变化(要不然会拾取不到模拟对象)

射线拾取层级模型

我们上面已经知道aycaster.intersectObjects()方法可以拾取Mesh模型对象,但是真实开发中,一个层级模型可能里面有很多网格模型。

执行.intersectObjects(cunchu.children)对复杂的层级模型进行射线拾取计算,会得到他们的某个子类

其实这种有很多解决办法

  1. 给给需要射线拾取父对象的所有子对象Mesh自定义一个属性.ancestors,然后让该属性指向需要射线拾取父对象
    下面是官方文档中的例子
    最终是根据子模型的ancestors属性,判断是属于哪个层级,效率也是很高的
const cunchu = model.getObjectByName('存储罐');
// 射线拾取模型对象(包含多个Mesh)
// 可以给待选对象的所有子孙后代Mesh,设置一个祖先属性ancestors,值指向祖先(待选对象)    
for (let i = 0; i < cunchu.children.length; i++) {
    const group = cunchu.children[i];
    //递归遍历chooseObj,并给chooseObj的所有子孙后代设置一个ancestors属性指向自己
    group.traverse(function (obj) {
        if (obj.isMesh) {
            obj.ancestors = group;
        }
    })
}
// 射线交叉计算拾取模型
const intersects = raycaster.intersectObjects(cunchu.children);
console.log('intersects', intersects);
if (intersects.length > 0) {
    // 通过.ancestors属性判断那个模型对象被选中了
    outlinePass.selectedObjects = [intersects[0].object.ancestors];
}

  1. 分组,对层级模型,进行分组,就是我我下面几个场景用的方式,比较暴力,遍历所有组的children进行与射线计算,比较耗时和耗性能

拾取Sprite控制场景

射线投射器Raycaster通过.intersectObjects()方法可以拾取精灵模型Sprite

在这里就不做多余赘述

场景一:用鼠标模拟hover事件

我们先清楚我们的目的

鼠标放模型上
射线判断
做一些处理

我们文章上面有这个判断,一般我们模型都是比较复杂的,我们还要清楚Group的概念

就是我们一般需要判断是哪一个组被鼠标放上去了(也方便点击事件)

我们可以把所有组都筛选出来,下面的思路是以Vue为例

this.groups = this.scene.children.filter(child => child instanceof THREE.Group);

这一步最好是在,确保模型加载完之后(这是我写的一个办法,当然也可以有其它办法)
因为后面会频繁计算(这也算一个小小的优化项)

其实,也可以你自己新new Group()一个实例也可以(需要手动添加)

async Model(url) {
  const loader = new GLTFLoader();
  const promises = url.map((element, index) => {
    return new Promise((resolve, reject) => {
      loader.load(element, (gltf) => {
        const model = gltf.scene;
        // 在这里做一些操作
        this.scene.add(model);
        resolve();
      });
    });
  });
  await Promise.all(promises);
  this.groups = this.scene.children.filter(child => child instanceof THREE.Group);
}

然后就是在画布之上(包裹画布的父元素就行xxx)加一个监听事件

this.debounceFn = (()=>debounce(xxx, 20))()
this.xxx.addEventListener('mousemove', this.debounceFn)

this.debounce是把该计算加了一个防抖处理,需要挂到data里面的属性值里面去(方便准确取消监听)

接下来就是计算函数debounceFn

this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.raycaster.setFromCamera(this.mouse, this.camera);
const intersects = this.raycaster.intersectObjects(this.groups ? this.groups : []);
if (intersects.length > 0) {
    // 处理 xxx
} else {
    // 处理 xxx
}

场景二: 选中模型(click事件)

坐标转换
计算射线
做出处理
//创建一个射线投射器`Raycaster`
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);

我们参考场景一:用鼠标模拟hover事件的导入分组Group

导入的时候(还是参考场景一的,我导入的时候就是一组模型,如果你的不是,可以手动分组,再添加到场景中),给group绑定上事件在属性userData对象上,就是挂载在上面

loader.load(element, (gltf) => {
    const model = gltf.scene;
    // 在这里做一些操作
    model.userData.onClick = Fn
    this.scene.add(model);
    resolve();
});

射线交叉计算,项目中一般是比较复杂的,我们就得去处理Group模型(就是一组模型,可以看看Group的概念)

this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.raycaster.setFromCamera(this.mouse, this.camera);
for (const group of this.groups) {
    const intersects = this.raycaster.intersectObject(group);
    if (intersects.length > 0) {
      group.userData.onClick()
      break;
    }
}

注意

射线用一个就好,最好还是复用,

场景三:处理射线穿透问题

  • 模型穿透,射线计算返回值intersects,是按照顺序返回的,直接判断第一个就行,再加一些操作

  • CSS3Renderer, html插入到场景中,很容易穿透(click事件为例)

    pointerEvents = 'none'以免模型标签HTML元素遮挡鼠标选择场景模型

    其实有一个简单的办法,先取消模型的点击事件,再加个0ms的异步任务,开启监听

    xxx.addEventListener('click', ()=>{
     this.xxxxxx.removeEventListener('click', this.onXXXXXXClick)
     setTimeout(()=>{
       this.xxxxxx.addEventListener('click', this.onXXXXXXClick)
     }, 0)
    })
    

参考链接

Three.js中文网

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/649162.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

德勤:中国、印度等对ChatGPT等生成式AI应用,处领先地位

全球四大会计事务所之一的德勤&#xff08;Deloitte&#xff09;在官网发布了一份&#xff0c;名为《Generative AI in Asia Pacific: Young employees lead as employers play catch-up》的深度调查报告。 主要查看中国、澳大利亚、印度、日本、新加坡、韩国、中国台湾等亚太…

2016届蓝桥杯大赛软件类国赛Java大学B组 愤怒小鸟 数学模拟

注意开浮点数 ​​​​ import java.util.Scanner;public class Main {static Scanner scnew Scanner(System.in);public static void main(String[] args) {double t0;int cnt0;double distance1000;while(distance>1){//相撞时间tdistance/60.0;distance-t*20;cnt;}Syste…

【深度学习】xformers与pytorch的版本对应关系

https://github.com/facebookresearch/xformers/tree/v0.0.23 找tag&#xff1a; tag下面写了对应关系&#xff1a; 安装指令就是&#xff1a; pip install xformers0.0.23 --no-deps -i https://download.pytorch.org/whl/cu118

minaActivatorA12+物主锁完美解信号,可登iCloud,有消息通知,支持iOS17.5.1+

原创 IOS福利部落 IOS福利部落 2024-05-26 19:35 福建 Mina Activator A12是一款绕过物主锁界面的解锁工具&#xff0c;可以激活所有iPhone恢复信号&#xff0c;并且支持插卡接打电话、收发短信、4G流量上网&#xff0c;支持iCloud登录&#xff0c;有消息通知&#xff0c;支持i…

如何保证员工在精益变革中始终保持积极的态度?

在当今日新月异的商业环境中&#xff0c;企业为了保持竞争力&#xff0c;需要不断寻求创新和变革。精益变革作为一种提升效率和质量的有效手段&#xff0c;已逐渐成为企业转型升级的关键。然而&#xff0c;变革往往伴随着挑战和不确定性&#xff0c;如何保证员工在精益变革中始…

高职高校实训教学实验室管理系统一体化

盛元广通高职高校实训教学实验室管理系统一体化是确保实验教学有序进行的关键环节。通过更加科学 、有效、合理的管理&#xff0c;明确排课原则、收集课程信息、评估实验室资源、制定排课计划、冲突检测与调整、发布排课信息、调课管理以及数据统计与分析等措施。实现了实验室资…

第14章-蓝牙遥控小车 手把手做蓝牙APP遥控小车 蓝牙串口通讯讲解

本文讲解手机蓝牙如何遥控小车&#xff0c;如何编写串口通信指令 第14章-手机遥控功能 我们要实现蓝牙遥控功能&#xff0c;蓝牙遥控功能要使用:1.单片机的串口、2.蓝牙通信模块 所以我们先调试好:单片机的串口->蓝牙模块->接到一起联调 14.1-电脑控制小车 完成功能…

目标检测——无人机图像数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

【Linux安全】Firewalld防火墙基础

目录 一、Firewalld概述 二、Firewalld和iptables的关系 三、Firewalld网络区域 1、firewalld防火墙预定义了9个区域: 2、firewalld 数据包处理原则 3、firewalld数据处理流程 4、firewalld检查数据包的源地址的规则 四、Firewalld防火墙的配置方法 1、firewalld 命令…

气膜建筑:室内滑雪场的理想选择—轻空间

近年来&#xff0c;室内滑雪场成为越来越多投资者关注的热门项目。随着滑雪运动的普及&#xff0c;滑雪场的生意也愈加红火。对于投资者来说&#xff0c;选择一种省钱又实惠的搭建方案至关重要&#xff0c;而气膜建筑无疑是一个理想的选择。以下将详细介绍气膜建筑在室内滑雪场…

若依微服务实现分布式事务

一、基本介绍 1、什么是分布式事务 指一次大的操作由不同的小操作组成的&#xff0c;这些小的操作分布在不同的服务器上&#xff0c;分布式事务需要保证这些小操作要么全部成功&#xff0c;要么全部失败。从本质上来说&#xff0c;分布式事务就是为了保证不同数据库的数据一致…

RabbitMQ 交换机类型

常用交换机 发布订阅&#xff08;Publish/Subscribe&#xff09;交换机 一个生产者给多个队列发送消息&#xff0c;X 代表交换机。 交换机的作用&#xff1a;类似网络路由器&#xff0c;主要提供转发功能&#xff0c;解决怎么把消息转发到不同的队列中&#xff0c;让消费者从不…

国家数据局发布《数字中国发展报告 (2023年)》

2023年数字中国建设总体呈现发展基础更加夯实、赋能效应更加凸显、数字安全和治理体系更加完善、数字领域国际合作更加深入等四方面特点。 为贯彻落实党中央、国务院关于建设数字中国的重要部署&#xff0c;国家数据局会同有关单位系统总结2023年数字中国建设重要进展和工作成效…

【UE5.1 角色练习】08-传送技能

前言 在上一篇&#xff08;【UE5.1 角色练习】07-AOE技能&#xff09;基础上继续实现人物通过鼠标点击然后传送技能的功能。 效果 步骤 1. 首先需要显示鼠标光标&#xff0c;我们可以在玩家控制器中勾选“显示鼠标光标” 2. 在项目设置中添加一个操作映射&#xff0c;设置按…

2-Django项目进阶--继续学生管理系统

目录 项目框架: urls.py views.py modules.py class_data.html add_and_modify.html add_stu.html 笔记: 继承语法 模板继承总结&#xff1a; 班级添加 add_and_modify.html 修改添加公用一个页面即可 views.py 班级修改 views.py url.py 班级删除 views.py…

嵌入式UI开发-lvgl+wsl2+vscode系列:2、label(标签)+button(按钮)+slider(滑块)控件熟悉及其示例demo运行

文章目录 一、前言二、常见控件示例demo模拟环境运行及接口熟悉&#xff08;重要&#xff09;如何修改示例main函数测试各种示例1、label示例1.1、label示例1&#xff08;标签基础示例&#xff09;1.2、label示例2&#xff08;标签带阴影效果&#xff09;1.3、label示例3&#…

Textual for Mac:轻量级IRC客户端

在寻找一款高效、轻量级的IRC客户端时&#xff0c;Textual for Mac无疑是你的不二之选。它集成了众多现代技术&#xff0c;如本机IPv6、最新的IRCv3规范&#xff0c;以及客户端证书身份验证&#xff0c;让你的聊天体验更加顺畅和安全。 Textual for Mac v7.2.2免激活版下载 Tex…

代码随想录算法训练营第五十九天|503.下一个更大元素II、42. 接雨水

503. 下一个更大元素 II 思路&#xff1a; 这道题和739. 每日温度 (opens new window)也几乎如出一辙。 不过&#xff0c;本题要循环数组了。 本篇我侧重与说一说&#xff0c;如何处理循环数组。 相信不少同学看到这道题&#xff0c;就想那我直接把两个数组拼接在一起&…

MySQL导入SQL脚本---超详细介绍

1.新建xxx数据库&#xff0c;字符集选对。 2.在mysql安装目录下cmd进入小黑窗 3.执行mysql -uroot -p123456 --default-character-setutf8命令 4.use xxx; 5.source xxx.sql 执行完上面的命令等待结束就可以了 需要注意的是--default-character-setutf8&#xff0c;要不然可…

如何把学浪的视频保存到手机

你是不是还在为无法将学浪的视频保存到手机而烦恼&#xff1f;别担心&#xff0c;接下来我将为大家分享一个非常实用的方法&#xff0c;让你轻松实现这一目标&#xff01; 下载学浪的工具我已经打包好了&#xff0c;有需要的自己下载一下 学浪下载工具打包链接&#xff1a;百…