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
WebGL模型拾取——射線法二