WebGL模型拾取——射线法二

  这篇文章是对射线法raycaster的补充,上一篇文章主要讲的是raycaster射线法拾取模型的原理,而这篇文章着重讲使用射线法要注意的地方。首先我们来看下图。

  我来解释一下上图中的originTriangle,这就是Triangle2三角形第一次绘制在空间中的位置,而Triangle2当前的位置是经过一系列空间变换而来的(这些位置姿态变换大多是由用户鼠标交互产生),变换矩阵就是transformMatrix。这下就引出了本文第一个重点,那就是做raycaster的时候要保证线段碰撞模型的时候一定是模型当前所处的空间位置,即已经做过transformMatrix空间(姿态,位置)变换,否则线段如果和模型之前的初始化位置求交显然没有交点,就拾取失败了。这就是做raycaster要注意的第一个重点,即射线一定要和空间变换后的模型求交。

  接下来我们再看一张图,请看下图。

  我们看到,射线和模型有2个交点,P2_0和P2_1,分别交四面体的前面于P2_0和交四面体的后面于P2_1。这就是我们要着重关注的本文第二个重点,即raycaster射线拾取模型过程中射线与单个模型有多个交点的问题。处理这个问题其实有很多办法,这里我们采用最简单的方式,就是距离相机(人眼)位置近者胜出的策略。计算交点的算法上一篇文章已经提到,这里不再赘述,但要说明的是,我们计算的每一个面在数据结构中都有自身模型父节点geometry,如果像上图一个四面体的geometry和射线产生了多个面相交,那我们就认为鼠标选中的是该模型geometry离相机(camera)(人眼)最近的交面上的交点。

  对于上面2点的叙述,配合部分代码展示,是geometry空间变换的,代码如下。

Object.assign(CubeSection.prototype, {
    //重载,每一帧同步数据
    sync: function () {
        if(this._mode === "face") {//根据剖切模式管理鼠标拖拽逻辑
            if (this._selectFace) {
                let camera = this._viewer.getMainCamera();
                let last = this._mousePoints.getLast();
                let lastX = camera.getNormalizedX(last[0]);
                let lastY = camera.getNormalizedY(last[1]);
                let current = this._mousePoints.getCurrent();
                let currentX = camera.getNormalizedX(current[0]);
                let currentY = camera.getNormalizedY(current[1]);

                this._mousePoints.sync();
                let deltaX = currentX - lastX;
                let deltaY = currentY - lastY;
                if (Math.abs(deltaX) < Algorithm.EPSILON && Math.abs(deltaY) < Algorithm.EPSILON)
                    return;

                //如果面被选中,并且有移动量,需要进行剖切面移动处理
                let start = Vec3.MemoryPool.alloc();
                let end = Vec3.MemoryPool.alloc();
                camera.computeScreenToWorldNearFar(lastX, lastY, start, end, true);
                //获取起点与平面的交点
                let plane = this._cubeClip.getClipPlane(this._selectFace.getName());
                let planePt1 = Vec3.MemoryPool.alloc();
                if (Plane.intersectLine(planePt1, start, end, plane)) {
                    //将模型交点再转换到屏幕坐标上,主要为了获取z值给终点
                    let temppt = Vec3.MemoryPool.alloc();
                    camera.computeWorldToScreen(planePt1, temppt);

                    Vec3.set(temppt, currentX, currentY, temppt[2]);

                    let planePt2 = Vec3.MemoryPool.alloc();
                    camera.computeScreenToWorld(temppt, planePt2);
                    Vec3.sub(temppt, planePt2, planePt1);
                    let dist = Vec3.dot(plane, temppt);
                    this.move(dist);
                    Vec3.MemoryPool.free(planePt2);
                    Vec3.MemoryPool.free(temppt);
                }
                Vec3.MemoryPool.free(planePt1);
                Vec3.MemoryPool.free(start);
                Vec3.MemoryPool.free(end);
            }
        } else if(this._mode === "translate"){
            if(this._selectAxis) {
                let camera = this._viewer.getMainCamera();
                let last = this._mousePoints.getLast();//前一帧鼠标的XY坐标
                let lastX = camera.getNormalizedX(last[0]);
                let lastY = camera.getNormalizedY(last[1]);
                let current = this._mousePoints.getCurrent();//目前帧鼠标的XY坐标
                let currentX = camera.getNormalizedX(current[0]);
                let currentY = camera.getNormalizedY(current[1]);

                this._mousePoints.sync();//继续下一帧同步鼠标XY坐标
                let deltaX = currentX - lastX;//X偏移量
                let deltaY = currentY - lastY;//Y偏移量
                if (Math.abs(deltaX) < Algorithm.EPSILON && Math.abs(deltaY) < Algorithm.EPSILON) {
                    //如果XY偏移量都为零,就直接返回,什么操作都不做
                    return;
                }

                //坐标系轴被选中,并且有偏移量,就要移动整个包围盒子
                let start = Vec3.MemoryPool.alloc();
                let end = Vec3.MemoryPool.alloc();
                //把屏幕上的XY坐标换算到视棱台near,far截面上的XY坐标
                camera.computeScreenToWorldNearFar(lastX, lastY, start, end, true);
                //当前pick的坐标轴
                let axis = this._selectAxis;
                //near-far线段截axis坐标轴的交点
                let intersectPoint1 = Vec3.MemoryPool.alloc();
                //射线碰撞
                let intersections = this._drawActor.linesegmentIntersect(start, end);//对场景中的所有物体进行线段碰撞检测
                //遍历intersections列表,按照离相机从远到近排列
                for (let i = 0; i < intersections.length; i++) {
                    let geometry = intersections[i].getDrawable().getGeometry();
                    if (geometry && new String(geometry._name).substring(0, 4) === "axis") {
                        intersectPoint1 = intersections[i]._point;//获取到near-far线段和坐标轴的交点
                        break;
                    }
                }
                //将near-far和坐标轴的交点再转换到屏幕坐标上,主要为了获取z值给终点
                let screenPoint = Vec3.MemoryPool.alloc();
                camera.computeWorldToScreen(intersectPoint1, screenPoint);
                //screePoint(currentX, currentY, screenPoint.z)
                Vec3.set(screenPoint, currentX, currentY, screenPoint[2]);
                //鼠标移动的第二个场景坐标系里的点坐标
                let intersectPoint2 = Vec3.MemoryPool.alloc();
                //把屏幕归一化坐标转化为场景世界坐标
                camera.computeScreenToWorld(screenPoint, intersectPoint2);
                Vec3.sub(screenPoint, intersectPoint2, intersectPoint1);
                let dist = 0;
                if(this._selectAxis._name === "axisX"){
                    dist = screenPoint[0];
                }else if(this._selectAxis._name === "axisY"){
                    dist = screenPoint[1];
                }else if(this._selectAxis._name === "axisZ"){
                    dist = screenPoint[2];
                }
                this.move(dist);
                //析构向量
                Vec3.MemoryPool.free(intersectPoint1);
                Vec3.MemoryPool.free(intersectPoint2);
                Vec3.MemoryPool.free(screenPoint);
                Vec3.MemoryPool.free(start);
                Vec3.MemoryPool.free(end);
            }
        } else if(this._mode === "rotate"){

        } else if(this._mode === "scale"){

        }
    },
updateTransform: function () {
        let mat = this._cubeRoot.getMatrix();

        //重新计算坐标系模型的_matrix
        this._coordinateSection.update(this._clipBox, this._scale, this._translate, this._rotate, this._scaleMatrix, this._translateMatrix, this._rotateMatrix, mat);

        Mat4.fromScaling(this._scaleMatrix, this._scale);
        Mat4.fromTranslation(this._translateMatrix, this._translate);
        Mat4.fromQuat(this._rotateMatrix, this._rotate);

        Mat4.mul(mat, this._translateMatrix, this._rotateMatrix);
        Mat4.mul(mat, mat, this._scaleMatrix);

        //剖切面数据的变换
        this._cubeClip.resetClipPlane();
        this._cubeClip.transformClipPlane(mat);

        //包围盒子更新
        this._clipBox.setMaxValue(0.5, 0.5, 0.5);
        this._clipBox.setMinValue(-0.5, -0.5, -0.5);
        this._clipBox.transformMat4(mat);
    },

接下来是选取离相机近的交点,代码如下

//拾取物体,根据当前剖切模式选择intersections列表中的碰撞对象
    pick: function (x, y) {
        let camera = this._viewer.getMainCamera();
        let start = Vec3.MemoryPool.alloc();
        let end = Vec3.MemoryPool.alloc();
        camera.computeScreenToWorldNearFar(x, y, start, end);
        let intersections = this._drawActor.linesegmentIntersect(start, end);
        let l = intersections.length;
        if (l !== 0) {
            switch(this._mode){
                case "face" : {//面剖切
                    let intersection = intersections[0];//LineSegmentIntersection
                    let geometry = intersection.getDrawable().getGeometry();
                    if (geometry) {
                        this._selectFace = geometry;
                        this._selectFace.setStateSet(this._selectState);
                        return true;
                    }
                }
                case "translate" : {//平移剖切
                    //遍历intersections列表,按照离相机从远到近排列
                    for(var i=0; i<l; i++){
                        let geometry = intersections[i].getDrawable().getGeometry();
                        if(geometry && new String(geometry._name).substring(0, 4) === "axis"){
                            this._selectAxis = geometry;
                            this._selectAxis.setStateSet(this._selectStateAxis);
                            break;
                        }
                    }
                    return true;
                }
                case "rotate" : {//旋转剖切
                    //遍历intersections列表,按照离相机从远到近排列
                    for(var i=0; i<l; i++){
                        let geometry = intersections[i].getDrawable().getGeometry();
                        if(geometry && new String(geometry._name).substring(0, 4) === "face"){
                            this._selectAxisFace = geometry;
                            this._selectAxisFace.setStateSet(this._selectStateAxisFace);
                            break;
                        }
                    }
                    return true;
                }
                case "scale" : {//缩放剖切
                    //遍历intersections列表,按照离相机从远到近排列
                    for(var i=0; i<l; i++){
                        let geometry = intersections[i].getDrawable().getGeometry();
                        if(geometry && new String(geometry._name).substring(0, 4) === "axis"){
                            this._selectAxis = geometry;
                            this._selectAxis.setStateSet(this._selectStateAxis);
                            break;
                        }
                    }
                    return true;
                }
            }
        }
        return false;
    },

其中intersections[]交点列表是按照离相机由远到近距离排序的,intersection[i]交点离相机距离比intersection[i+1]交点离相机距离要近。这就是我们采取的离相机近交点胜出原则。

  好了,以上就是raycaster射线拾取模型要注意的地方,如有错误,希望读者斧正,欢迎诸位同学留言。如需转载本文,请注明出处:https://www.cnblogs.com/ccentry/p/9977490.html

原文地址:https://www.cnblogs.com/ccentry/p/9977490.html

时间: 2024-10-31 17:59:36

WebGL模型拾取——射线法二的相关文章

WebGL模型拾取——射线法的补充

这篇文章是对射线法raycaster的补充,上一篇文章主要讲的是raycaster射线法拾取模型的原理,而这篇文章着重讲使用射线法要注意的地方.首先我们来看下图. 我来解释一下上图中的originTriangle,这就是Triangle2三角形第一次绘制在空间中的位置,而Triangle2当前的位置是经过一系列空间变换而来的(这些位置姿态变换大多是由用户鼠标交互产生),变换矩阵就是transformMatrix.这下就引出了本文第一个重点,那就是做raycaster的时候要保证线段碰撞模型的时候

hlg1306再遇攻击--射线法判断点是否在多边形内部

再遇攻击 Time Limit: 1000 MS Memory Limit: 65536 K Total Submit: 313(40 users) Total Accepted: 91(32 users) Rating:  Special Judge: No Description Dota中英雄技能攻击会有一个范围,现在释放一个技能给出他的攻击范围和目标英雄的位置,问是否能攻击到.攻击范围保证是一个多边型. Input 有多组测试数据 第一行输入1个整数n, 期中n代表攻击范围是给出的n个点

LightOj1190 - Sleepwalking(判断点与多边形的位置关系--射线法模板)

题目链接:http://lightoj.com/volume_showproblem.php?problem=1190 题意:给你一个多边形含有n个点:然后又m个查询,每次判断点(x, y)是否在多边形的内部; 射线法判断即可适用于任何(凸或凹)多边形;时间复杂度为O(n); 判断一个点是在多边形内部,边上还是在外部,时间复杂度为O(n):射线法可以正确用于凹多边形: 射线法是使用最广泛的算法,这是由于相比较其他算法而言,它不但可以正确使用在凹多边形上,而且不需要考虑精度误差问题.该算法思想是从

matlab练习程序(射线法判断点与多边形关系)

依然是计算几何. 射线法判断点与多边形关系原理如下: 从待判断点引出一条射线,射线与多边形相交,如果交点为偶数,则点不在多边形内,如果交点为奇数,则点在多边形内. 原理虽是这样,有些细节还是要注意一下,比如射线过多边形顶点或射线与多边形其中一边重合等情况还需特别判断. 这里就不特别判断了,因为我只是熟悉原理,并不是实际运用. 好吧,我实际是太懒了,不想判断了. 结果如下: 结果图和线性分类器的组合有几分相似. matlab代码如下: clear all;close all;clc; polyn=

概率图模型学习笔记(二)贝叶斯网络-语义学与因子分解

概率分布(Distributions) 如图1所示,这是最简单的联合分布案例,姑且称之为学生模型. 图1 其中包含3个变量,分别是:I(学生智力,有0和1两个状态).D(试卷难度,有0和1两个状态).G(成绩等级,有1.2.3三个状态). 表中就是概率的联合分布了,表中随便去掉所有包含某个值的行,就能对分布表进行缩减. 例如可以去掉所有G不为1的行,这样就只剩下了1.4.7.10行,这样他们的概率之和就不为1了,所以可以重新标准化(Renormalization).如图2所示. 图2 反之也可以

CSS3中的弹性流体盒模型技术详解(二)

在上一篇文章<CSS3中的弹性流体盒模型技术详解(一)>里,我给大家列出了,从css1到css3各版本中盒子模型的基本元素.本篇我会把余下的属性进行详细讲解. box-pack 作用:用来规定子元素在盒子内的水平空间分配方式 box-pack 语法:box-pack: start | end | center | justify; start 对于正常方向的框,首个子元素的左边缘吸附在盒子的左边框显示 对于相反方向的框,最后子元素的右边缘吸附在盒子的右边框显示 end 对于正常方向的框,最后子

用射线法实现判断点是否在多边形内部

最近工作中遇到了这个问题,检索之后发现这种实现方式挺有意思的,无论是凸多边形还是凹多边形都可以判断. 射线法是用被测点向任意方向(通常为了好算,使其射向右侧)做一条射线,判断射线与多边形的交点.如果交点的数量为奇数,则被测点在多边形内:如果交点的数量为偶数,则被测点在多边形以外. 期间,有些特殊情况需要判断,比如: 1. 射线刚好经过凸多边形两条相邻边的交点上的情况会导致重复判断: 2.射线和多边形的边重合的情况. 先上js代码. function isDotInPolygon(point, p

【模板】计几 射线法判断点是否在简单多边形内

1 // 2 //线段交点个数 3 int SegCross(Segment a,Segment b){ 4 double x1 = a.s.cross(a.e,b.s); 5 double x2 = a.s.cross(a.e,b.e); 6 double x3 = b.s.cross(b.e,a.s); 7 double x4 = b.s.cross(b.e,a.e); 8 if( b.e.OnLine(a.s,a.e) && b.s.OnSeg(a.s,a.e) || 9 b.s.O

射线法

射线法 这是一个大佬看了都说简单的算法....(甚至觉得没有掌握的必要) QAQ 这个算法是用来判断一个点是否在一个多边形以内.很简单 将这个点沿着x轴的正方向作射线.如果穿过的边数为基数,那么这个点在多边形内:反之不在. 由于有可能出现经过两条边的相邻的点,而被重复计算的情况.我们只需要采用上加下不加策略,即如果射线经过了这条边的上顶点,则cnt++,如果经过下顶点,则不变.经过这条边上除了上顶点和下顶点的地方,cnt++.正确性显然. 另外因为我们需要将每条边都遍历完.而我们只向 x的正方向