WebGL之物体选择

原文地址: WebGL之物体选择

使用WebGL将图形绘制到画布后,如何与外部进行交互?这其中最关键的就是如何实现物体的选择。比如鼠标点击后判断是否选中了某个图形或图形的某个部分。

本节实现的效果: WebGL选中物体

如何实现选中物体

颜色区分法

《WebGL编程指南》中提出了一个原理很简单的解决方案,步骤如下:

  1. 鼠标按下时物体重绘为红色或其他能区分的颜色

  2. 读取鼠标点击处像素的颜色

    gl.readPixels(x,y,width,height,format,type,pixels)
    复制代码
  3. 使用物体原来的颜色进行重绘,以恢复物体本来颜色

  4. 判断第2步读取到的颜色是否与预设的颜色值相等,相等则表示点击中物体

可以说这是个非常容易实现的方案,不过要为每个物体分别设置不同的区分颜色却是个隐患,同时也不够友好。

光线投射法

这是使用最广泛也最精确的一种方案了,Three.js 中的光线投射器 (Raycaster) 就实现了这种方案,可以看里面的源代码。

它的基本原理: 从视点出发的光线首先投射到近截面,最后投射到远截面,结合鼠标点击的位置 (x, y) 和视图投影矩阵 (viewProjection)。可以得出由近截面坐标 (x1, y1, z1) 和远截面坐标 (x2, y2, z2) 组成的射线向量。然后我们就可以将物体坐标构成的面逐个与这个向量进行对比。这涉及到线性代数中的向量,点积,叉积,矩阵等概念,比较复杂。主要分两个步骤:

  1. 创建物体的包围盒,判断射线是否穿过该物体包围盒
  2. 判断射线是否穿过该物体的某个三角形面,如果经过即可判断选中了该物体

下面就分步实现光线投射算法的上面两个步骤

包围盒

包围盒算法原理如下:

首先用视图投影模型矩阵 (mvp) 对图形坐标进行变换,得到在屏幕中的绘制坐标[x,y,z]

遍历每个坐标得出一个由最大最小xy坐标 [xmax, xmin, ymax, ymin] 构成的二维包围盒

鼠标位置 (x, y) 与包围盒边界进行比较,如果坐标处于盒子边界之内,那么就可判断选中了该物体

核心代码如下:

canvas.addEventListener('mousemove', function(e) {//坐标转换为webgl表示区间const pos = util.windowToWebgl(tCanvas,e.clientX,e.clientY);const ps = [];Polygons.forEach((p,i)=>{//重置状态p.select = false;//mvp矩阵const matrix = m4.translate(viewProjection, p.pos);let xmax, ymax, xmin, ymin, zmax, zmin;//包围盒边界//遍历顶点获取包围盒的边界for(let j = 0; j < p.position.length; j = j+3){//对坐标进行矩阵转换const s = m4.transformPoint(matrix, p.position.slice(j,j+3));if(j == 0){xmax = s[0];xmin = s[0];ymax = s[1];ymin = s[1];zmax = s[2];zmin = s[2];continue;}if(s[0]>xmax) xmax = s[0];if(s[0]<xmin) xmin = s[0];if(s[1]>ymax) ymax = s[1];if(s[1]<ymin) ymin = s[1];if(s[2]>zmax) zmax = s[2];if(s[2]<zmin) zmin = s[2];}// 射线处于包围盒内if(pos.x >= xmin && pos.x <= xmax && pos.y >= ymin && pos.y <= ymax){p.coord = [(xmax+xmin)/2,(ymax+ymin)/2,(zmax+zmin)/2];ps.push(p);}});if(!ps.length) return;//获取最靠近视点的图形const sel = ps.length == 1? ps[0]: ps.sort((a,b)=> a.coord[2] - b.coord[2])[0];sel.select = true;
},false);
复制代码

射线与三角形相交

但是包围盒算法判断地不是很精准,在物体形状不是很规则或物体间靠拢的比较紧时表现得尤其明显。

我们知道WebGL图形是由三角形构成的,那么进一步判断射线是否相交该物体某个三角形面就会非常精确了。

数学原理如下:

三角形内的任意一点都可以用它相对于三角形的顶点的位置来定义:

T(u,v) = (1 - u - v)V0 + uV1 + vV2

其中 u >= 0, v >= 0, u + v <= 1 ,称为重心坐标

射线可以用参数方程表示为:

T(t) = P + td

其中P为起始点,d为方向向量

因此计算直线与三角的交点的等式为:

P + td = (1-u-v)V0 + uV1 + vV2

整理后最终得到一个齐次线性方程组,其中[t u v] 为1 x 3 的矩阵,(t,u,v) 是它的解

[-d V1-V0 V2-V0] [t u v] = [P-V0]

根据克莱姆法则求解,其中T = P - V0, E1 = V1 - V0, E2 = V2 - V0,( [(T x E1) • E2] [(d x E2) • T] [(T x E1) • d] ) 为 3 x 3 矩阵,等式最终可以写成如下:

(t,u,v) = 1/((d x E2) • E1) ( [(T x E1) • E2] [(d x E2) • T] [(T x E1) • d] )

具体实现代码如下:

// 射线处于包围盒内
if(pos.x >= xmin && pos.x <= xmax && pos.y >= ymin && pos.y <= ymax){p.coord = [(xmax+xmin)/2,(ymax+ymin)/2,(zmax+zmin)/2];const P = [pos.x,pos.y,0.5];//射线起始点const d = [0,0,1];//射线方向for(let j = 0; j < p.position.length; j = j + 9){//三角形顶点const V0 = m4.transformPoint(matrix, p.position.slice(j,j+3));const V1 = m4.transformPoint(matrix, p.position.slice(j+3,j+6));const V2 = m4.transformPoint(matrix, p.position.slice(j+6,j+9));const T = v3.subtract(P,V0);const E1 = v3.subtract(V1,V0);const E2 = v3.subtract(V2,V0);const M = v3.cross(d,E2);const det = v3.dot(M,E1);if(det == 0) continue;const K = v3.cross(T,E1);const t = v3.dot(K,E2)/det;const u = v3.dot(M,T)/det;const v = v3.dot(K,d)/det;//射线与三角形相加if(u >= 0 && v >= 0 && u+v<=1 ){ps.push(p);break;}}
}
复制代码

转载于:https://juejin.im/post/5cecfaa3e51d4510727c8010

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中国历史上影响最大的10首诗

中国是诗歌的国度&#xff0c;有许多诗都有很大的影响。这里所谓影响“最大”(而不是“最好”)的十首诗&#xff0c;除了要写得好之外&#xff0c;还必须通俗易懂、易记。 第一首&#xff1a;李白的《静夜思》 床前明月光&#xff0c;疑是地上霜。 举头望明月&#xff0c;低头…

XML建模

建模分两步&#xff1a;1、以面向对象的编程思想&#xff0c;描述xml资源文件。 2、将xml文件中内容封装进model实体对象。 导入文件&#xff1a;config.xml <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE config[<!ELEMENT config (acti…

Docker 方式安装部署 rocketMQ 、部署 图形化界面控制台、rocketMQ 控制台

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 直接上官网&#xff0c;找到工程&#xff0c;clone 到本地&#xff0c;地址&#xff1a;https://github.com/apache/rocketmq-extern…

迭代器(iterator)

Date: 2019-05-23 Author: Sun 为何要引入迭代器&#xff1f; ​ 通过列表生成式&#xff0c;我们可以直接创建一个列表&#xff0c;但是&#xff0c;受到内存限制&#xff0c;列表容量肯定是有限的&#xff0c;而且创建一个包含100万个元素的列表&#xff0c;不仅占用很大的存…

初识python之函数基础

课堂笔记&#xff1a; 1、什么是函数&#xff1f;函数相当于工具&#xff0c;需要事先准备好&#xff0c;在需要用时再使用。2、如何使用函数&#xff1f;函数必须先定义、后调用。3、函数的语法:# def 函数名(参数1,参数2...):# """# 注释# 函数的说明# 水…

java 的几种对象 (PO,VO,DAO,BO,POJO) 解释

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 一、PO:persistant object 持久对象,可以看成是与数据库中的表相映射的java对象。最简单的PO就是对应数据库中某个表中的一条记录&#x…

【随想】每日两题Day.22

题目&#xff1a;102. 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[…

帮助子女成功的十大路径

美国全国家长协会(National PTA)建议指出&#xff1a;作为家长您对你子女的成功起着非常重要的影响作用&#xff0c;并举出帮助子女成功的十种路径。 1、与子女沟通 如果我们尽早地与子女沟通&#xff0c;提供给他们信息与行为准则&#xff0c;获得子女的信任&#xff0c;在…

shell关闭指定进程

例如要关闭jupyter-notebook这个进程&#xff1a; ps -ef | grep jupyter-notebook | grep -v grep | cut -c 9-15 | xargs kill -9 说明&#xff1a;管道符“|”用来隔开两个命令&#xff0c;管道符左边命令的输出会作为管道符右边命令的输入。 “ps -ef” 查看所有进程  …

垃圾回收算法与垃圾回收器

Java与C等语言最大的技术区别&#xff1a;自动化的垃圾回收机制&#xff08;GC&#xff09; 为什么要了解GC和内存分配策略 1、面试需要 2、GC对应用的性能是有影响的&#xff1b; 3、写代码有好处 栈&#xff1a;栈中的生命周期是跟随线程&#xff0c;所以一般不需要关注 堆&a…

提高孩子睡眠质量 学业事半功倍

睡眠如同大脑的食物。在睡眠期间&#xff0c;许多重要的身体机能静静地发生著作用。省略睡眠是有害的&#xff0c;如果一个严重缺觉的人开着车&#xff0c;他会脸色苍白、喜怒无常、反应迟钝&#xff0c;可能是致命的危险。缺少睡眠让青少年很难与人相处&#xff0c;学业表现不…

实体类(VO,DO,DTO)的划分

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 从领域建模中的实体划分、项目中的实际应用情况两个角度&#xff0c;对这几个概念进行简析。 得出的主要结论是&#xff1a;在项目应用…

IIS新建站点服务器,localhost能登录但是IP访问登录不了。

IIS服务器新建站点之后&#xff0c;浏览页面&#xff0c;服务器本地是可以登录&#xff0c;但是localhost换成IP就无法访问。其他站点IP却可以访问。 1.如果浏览直接失败&#xff0c;说明端口号需要更换。 2.如果出现IP不能访问&#xff0c;localhost能访问&#xff0c;需要在高…

eclipse问题_Alt+/不给提示,只补充代码问题的解决方案

今天用eclipse敲代码的时候遇到的问题 我还以为是冲突什么的 还重新装了软件 最后才发现原来是快捷键设置的问题 解决方案&#xff1a; 1&#xff1a;打开菜单window→Preferences&#xff0c;然后在窗口的左侧树选择General->Keys 2&#xff1a;在下图中的5框的地方输入“w…

领域驱动设计:浅析 VO、DTO、DO、PO 概念、区别、用处

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 本篇文章主要讨论一下我们经常会用到的一些对象&#xff1a;VO、DTO、DO和PO。 由于不同的项目和开发人员有不同的命名习惯&#xff0c…

动脑的生活教育

心理学家华生曾经说过&#xff1a;“如果给我一打孩子&#xff0c;我可以把他们变成律师、医师、科学家&#xff0c;或是强盗、土匪。”华生认为&#xff0c;教育孩子就如同马戏团的驯兽师训练野兽一样&#xff0c;是“刺激”与“反应”的联结&#xff0c;不需要任何的“思考”…

前端知识点回顾之重点篇——CORS

CORS&#xff08;cross origin resource sharing&#xff09;跨域资源共享 来源&#xff1a;http://www.ruanyifeng.com/blog/2016/04/cors.html 它允许浏览器向跨源服务器&#xff0c;发出XMLHttpRequest请求&#xff0c;从而克服了AJAX只能同源使用的限制。 简介 CORS需要浏览…

案例:隐秘而低调的内存泄露(OOM)

内存泄露测试的整个过程如下&#xff1a;在手机里启动被测APP并打开DDMS。在DDMS中选中【com.example.android.hcgallery】之后单击按钮【show heap updates】&#xff0c;然后切换到标签页【VM Heap】&#xff0c;再单击按钮【Cause GC】。不断操作APP&#xff0c;并观察Heap。…

员工价值——如何体现自己价值,如何被自己的领导认可

到公司工作快三年了&#xff0c;比我后来的同事陆续得到了升职的机会&#xff0c;我却原地不动&#xff0c;心里颇不是滋味。终于有一天&#xff0c;冒着被解聘的危险&#xff0c;我找到老板理论。 “老板&#xff0c;我有过迟到、早退或乱章违纪的现象吗&#xff1f;”我问。 …

java: PO,VO,TO,BO,DAO,POJO 解释

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 O/R Mapping 是 Object Relational Mapping&#xff08;对象关系映射&#xff09;的缩写。通俗点讲&#xff0c;就是将对象与关系数据库绑…