WebGL射線拾取模型——八叉樹優化
經過前面2篇WebGL射線拾取模型的文章,相信大家對射線和模型面片相交的原理已經有所瞭解,那麼今天我們再深入探究關於射線拾取的一個問題,那就是遍歷場景中的所有與射線相交的模型的優化問題。首先我們來複習一下射線拾取模型的原理,請看下圖。
我們從上圖中可以看到,在frustum視稜臺區域中只有一個模型就是triangle2三角形2,那麼遍歷整個scene場景我們也只能取到一個geometry,取出該geometry後我們通過空間變換矩陣得到該三角形2在場景中的位置座標以及姿態資訊,再將空間中的這個已知位置和姿態的geometry去和射線P1P3求交點,如果有交點就代表滑鼠拾取到了該triangle2三角形2,這就是raycaster射線拾取的基本原理,前兩篇文章已經敘述過了,這裡複習一下。接下來我們引入今天的話題,射線碰撞raycaster遍歷場景中模型的優化。首先我們來看下圖。
我們看到在視稜臺範圍內存在2個模型geometry,他們分別是triangle2,triangle3兩個三角形,而滑鼠和相機構成的射線明顯不經過triangle3這個模型,但是我們還是要遍歷場景中所有的模型包括這個triangle3,遍歷到的每個模型都要和射線P1P3求交點,當求得的交點數大於零則表示射線碰撞到該模型,交點數等於零表示射線沒有碰到該模型,即滑鼠未拾取到該模型。這就引出了我們今天的話題,那就是優化這個遍歷所有模型的過程。作者:ccentry/蔥烤河鯽魚。 作為程式,我們追求的是高效能的演算法,如果每次做射線拾取都要從頭遍歷一遍所有的模型geometry,那麼系統將無疑會很卡,尤其當場景中模型的數量級達到一定的高度時,卡頓會尤其明顯。那麼我們就動腦筋想一個問題,我們能否不去遍歷scene場景下的所有模型,只遍歷必要的一部分模型,進而減少線段和三角面相交判斷的計算量。答案是可以,具體怎麼操作,請看下圖。
我們看上圖,我們將視稜臺劃分成8個區域,分別從區域1到區域8,所有場景中的模型geometry都分佈在這8個區域中,現在我們就通過這8個區域縮小射線碰撞的遍歷geometry模型的範圍。具體的操作很簡單,那就是先讓射線和這8個區域的稜臺幾何體進行射線相交計算,只有與射線產生交點的稜臺幾何體區域才是射線檢測的模型空間範圍,其餘和射線不產生交點的區域中的geometry模型就不必參與到raycaster檢測中來,這樣就極大的縮小了遍歷geometry的數量,從而優化了raycaster的功能。我們來看看上圖中依照8叉樹優化邏輯進行的raycaster步驟。首先,射線只交2個區域的稜臺他們分別是區域7和區域3,那麼區域1,2,4,5,6,8中的所有geometry就都不用參與raycaster射線碰撞檢測了,一下子我們就排除了Triangle3三角形3,因為他處於區域4中,不在檢測區域範圍內,是不是就減少了後面線段和麵相交的計算量,優化了raycaster整體的效能。這是非常好的一個做法,直接縮小了檢測範圍,而且還能繼續遞迴細分下去,比如區域3還能細分成8個小區域,將檢測範圍縮得更小,進一步排除檢測區域外的多餘模型,進一步減少計算量,這就是8叉樹在raycaster中的優化演算法,看上去很簡單,其實非常的高效。
好了,也許有同學會問,你怎麼知道模型geometry處在哪個區域中,對於這個問題,一句話告訴你,判斷geometry的中心,即geometry模型包圍盒的中心position座標是否在區域稜臺範圍中即可得到答案。簡單實用,對於跨區域,跨多個區域的模型也有效。那就有人擡槓了,萬一跨區域的模型中心不在檢測區域內,但該模型有部分在檢測區域裡怎麼辦,其實針對這種情況也很好辦,那就是用模型包圍盒的6個面去和檢測區域的稜臺幾何(其實在世界座標系下也是包圍盒)的6個面去進行碰撞檢測(不在本篇中論述),碰到即在檢測區域內。
以上論述是對raycaster進行8叉樹優化的理論描述,下面給出部分關鍵程式碼。
/* */ let Intersector = require('./Intersector'); let LineSegmentIntersection = require('./Intersection').LineSegmentIntersection; let Vec3 = require('./Vec3'); let Mat4 = require('./Mat4'); let Algorithm = require('./Algorithm'); let LineSegmentIntersector = function () { Intersector.call(this); //原始的起始點和臨界值,初始化設定的資料,保留作為參照,設定後不再變動 this._orginStart = Vec3.new();//線段起點 this._orginEnd = Vec3.new();//線段終點 this._orginThreshold = 0.0;//點和線求相交時的臨界值,完全相交是很難求到的 //臨時儲存,每次求交都可能會變動的資料 //對於有變換的幾何求交,不會變換幾何頂點而是變換起始點和臨界值 this._start = Vec3.new();//線段起點 this._end = Vec3.new();//線段終點 this._threshold = 0.0;//點和線求相交時的臨界值,完全相交是很難求到的 this._direction = Vec3.new(); this._length = 0; this._inverseLength = 0; this._matrix = Mat4.new(); }; LineSegmentIntersector.prototype = Object.create(Intersector.prototype); LineSegmentIntersector.prototype.constructor = LineSegmentIntersector; Object.assign(LineSegmentIntersector.prototype, { init: function (start, end, threshold) { Vec3.copy(this._orginStart, start); Vec3.copy(this._orginEnd, end); Vec3.copy(this._start, start); Vec3.copy(this._end, end); if (threshold !== undefined) { this._orginThreshold = threshold; this._threshold = threshold; } }, intersect: function (drawable) { //先使用包圍盒子 if (!drawable.getBoundingBox().intersectLineSegment(this._orginStart, this._orginEnd)) { return; } this._drawable = drawable; let geometry = drawable.getGeometry(); let vertexbuffer = geometry.getBufferArray('Vertex'); this._vertices = vertexbuffer.getArrayBuffer(); //沒有頂點資料不處理直接返回 if (!this._vertices) return; //沒有圖元不處理直接返回 let primitive = geometry.getPrimitive(); if (!primitive) return; //初始化求相交的各種資料 let matrix = drawable.getTransform(); if (this._transform !== matrix) {//如果不一樣,需要計算新的起始點以及各種臨時資料 this._transform = matrix; Mat4.invert(this._matrix, matrix); //根據矩陣計算新的臨界值 if (this._orginThreshold > 0.0) { let tmp = this._start; Mat4.getScale(tmp, this._matrix); let x = tmp[0]; let y = tmp[1]; let z = tmp[2]; this._threshold = this._orginThreshold * (x > y ? (x > z ? x : z) : y > z ? y : z); } //根據矩陣計算新的起始點 Vec3.transformMat4(this._start, this._orginStart, this._matrix); Vec3.transformMat4(this._end, this._orginEnd, this._matrix); //根據新的起始點計算各種臨時資料 Vec3.sub(this._direction, this._end, this._start); this._length = Vec3.length(this._direction);//長度 this._inverseLength = this._length <= Algorithm.EPSILON ? 0.0 : 1.0 / this._length; Vec3.scale(this._direction, this._direction, this._inverseLength);//求單位向量 }//如果變換與上次一樣,直接使用上次的資料求相交 //求相交 primitive.operate(this); }, intersectPoint: function (vertex) { // https://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistPointSegment.h //起點指向繪製點,向量M let m = Vec3.MemoryPool.alloc(); Vec3.sub(m, vertex, this._start); //起點指向終點,向量N let n = Vec3.MemoryPool.alloc(); Vec3.sub(n, this._end, this._start); //求M在N上的投影比例值 //|m|*|n|*cos / \n\*\n\ = |m|*cos/\n\ let r = Vec3.dot(m, n) * this._inverseLength * this._inverseLength; //計算繪製點到線段的距離 let sqrdist = 1.0; if (r < 0.0) {//夾角超過90度,繪製點在當前線段起點後面,求繪製點與起點的距離 sqrdist = Vec3.sqrLen(m); } else if (r > 1.0) {//繪製點在當前線段終點後面,求繪製點與終點的距離 sqrdist = Vec3.sqrDist(vertex, this._end); } else {//在0到1之間 //m - n * r 如果平行或者接近於平行,結果接近於0,相交 sqrdist = Vec3.sqrLen(Vec3.scaleAndAdd(m, m, n, -r)); } let intersection = undefined; if (sqrdist > this._threshold * this._threshold) {//超過了臨界值,沒有相交返回 } else { //相交 intersection = new LineSegmentIntersection(); //intersection._i1 = index; //intersection._r1 = 1.0; Vec3.scaleAndAdd(intersection._point, this._start, n, r); intersection._ratio = r; } Vec3.MemoryPool.free(m); Vec3.MemoryPool.free(n); return intersection; }, intersectLine: function (vertex0, vertex1) { // https://www.geometrictools.com/GTEngine/Samples/Geometrics/DistanceSegments3/DistanceSegments3.cpp //let epsilon = 0.00000001; //起點到終點的向量 let u = Vec3.MemoryPool.alloc(); Vec3.sub(u, vertex1, vertex0); let v = Vec3.MemoryPool.alloc(); Vec3.sub(v, this._end, this._start); let w = Vec3.MemoryPool.alloc(); Vec3.sub(w, vertex0, this._start); let a = Vec3.dot(u, u); let b = Vec3.dot(u, v); let c = Vec3.dot(v, v); let d = Vec3.dot(u, w); let e = Vec3.dot(v, w); let D = a * c - b * b; let sN; let tN; let sD = D; let tD = D; // compute the line parameters of the two closest points if (D < Algorithm.EPSILON) {//平行 // the lines are almost parallel sN = 0.0; // force using point P0 on segment S1 sD = 1.0; // to prevent possible division by 0.0 later tN = e; tD = c; } else { // get the closest points on the infinite lines sN = b * e - c * d; tN = a * e - b * d; if (sN < 0.0) { // sc < 0 => the s=0 edge is visible sN = 0.0; tN = e; tD = c; } else if (sN > sD) { // sc > 1 => the s=1 edge is visible sN = sD; tN = e + b; tD = c; } } if (tN < 0.0) { // tc < 0 => the t=0 edge is visible tN = 0.0; // recompute sc for this edge if (-d < 0.0) sN = 0.0; else if (-d > a) sN = sD; else { sN = -d; sD = a; } } else if (tN > tD) { // tc > 1 => the t=1 edge is visible tN = tD; // recompute sc for this edge if (-d + b < 0.0) sN = 0; else if (-d + b > a) sN = sD; else { sN = -d + b; sD = a; } } // finally do the division to get sc and tc let sc = Math.abs(sN) < Algorithm.EPSILON ? 0.0 : sN / sD; let tc = Math.abs(tN) < Algorithm.EPSILON ? 0.0 : tN / tD; // get the difference of the two closest points let closest0 = Vec3.MemoryPool.alloc(); let closest1 = Vec3.MemoryPool.alloc(); Vec3.scaleAndAdd(closest0, vertex0, u, sc); Vec3.scaleAndAdd(closest1, this._start, v, tc); let sqrDistance = Vec3.sqrDist(closest0, closest1); Vec3.MemoryPool.free(closest0); Vec3.MemoryPool.free(closest1); let intersection = undefined; if (sqrDistance > this._threshold * this._threshold) { } else { //相交 intersection = new LineSegmentIntersection(); // intersection._i1 = index0; // intersection._i2 = index1; // intersection._r1 = 1.0 - tc; // intersection._r2 = tc; Vec3.copy(intersection._point, closest1); intersection._ratio = tc; } Vec3.MemoryPool.free(u); Vec3.MemoryPool.free(v); Vec3.MemoryPool.free(w); return intersection; }, intersectTriangle: function (vertex0, vertex1, vertex2) { let e2 = Vec3.MemoryPool.alloc(); Vec3.sub(e2, vertex2, vertex0); let e1 = Vec3.MemoryPool.alloc(); Vec3.sub(e1, vertex1, vertex0); let pvec = Vec3.MemoryPool.alloc(); Vec3.cross(pvec, this._direction, e2); let intersection = undefined; //線段與三角麵點積 let det = Vec3.dot(pvec, e1); //判斷三角形所在的平面與線段是否平行,如果平行鐵定不相交,面片沒有厚度 if (Math.abs(det) < Algorithm.EPSILON) { //return undefined; }else{ let invDet = 1.0 / det; let tvec = Vec3.MemoryPool.alloc(); Vec3.sub(tvec, this._start, vertex0); let u = Vec3.dot(pvec, tvec) * invDet; //三角面超出了線段兩個點範圍外面,鐵定不相交 if (u < 0.0 || u > 1.0) { //return undefined; }else{ let qvec = Vec3.MemoryPool.alloc(); Vec3.cross(qvec, tvec, e1); let v = Vec3.dot(qvec, this._direction) * invDet; // if (v < 0.0 || u + v > 1.0) { //return undefined; }else{ let t = Vec3.dot(qvec, e2) * invDet; if (t < Algorithm.EPSILON || t > this._length) { //return undefined; }else{ //相交 intersection = new LineSegmentIntersection(); //求相交點 let r0 = 1.0 - u - v; let r1 = u; let r2 = v; let r = t * this._inverseLength; let interX = vertex0[0] * r0 + vertex1[0] * r1 + vertex2[0] * r2; let interY = vertex0[1] * r0 + vertex1[1] * r1 + vertex2[1] * r2; let interZ = vertex0[2] * r0 + vertex1[2] * r1 + vertex2[2] * r2; // intersection._i1 = index0; // intersection._i2 = index1; // intersection._i3 = index2; // intersection._r1 = r0; // intersection._r2 = r1; // intersection._r3 = r2; //這裡的點沒有經過變換,不是真實的世界座標點 Vec3.set(intersection._point, interX, interY, interZ); Vec3.transformMat4(intersection._point, intersection._point, this._transform); //求法向量,法向量未變換,如果有用途也要變換 let normal = intersection._normal; Vec3.cross(normal, e1, e2); Vec3.normalize(normal, normal); //比例,在相交線段上的比例,不需要變換 intersection._ratio = r; } } Vec3.MemoryPool.free(qvec); } Vec3.MemoryPool.free(tvec); } Vec3.MemoryPool.free(e1); Vec3.MemoryPool.free(e2); Vec3.MemoryPool.free(pvec); return intersection; // http://gamedev.stackexchange.com/questions/54505/negative-scale-in-matrix-4x4 // https://en.wikipedia.org/wiki/Determinant#Orientation_of_a_basis // you can't exactly extract scale of a matrix but the determinant will tell you // if the orientation is preserved //intersection._backface = mat4.determinant(intersection._matrix) * det < 0; }, intersectBoundingBox: function (box) { return box.intersectLineSegment(this._orginStart, this._orginEnd); }, }); module.exports = LineSegmentIntersector; // setDrawable: function (drawable) { // this._geometry = drawable.getGeometry(); // this._vertices = this._geometry.getBufferArray('Vertex'); // // let matrix = drawable.getTransform(); // if (this._transform === matrix) {//如果與上次的一樣,不再處理 // return; // } // // //如果不一樣,需要計算新的起始點已經各種臨時資料 // this._transform = matrix; // Mat4.invert(this._matrix, matrix); // // //根據矩陣計算新的臨界值 // if (this._orginThreshold > 0.0) { // let tmp = this._start; // Mat4.getScale(tmp, this._matrix); // let x = tmp[0]; // let y = tmp[1]; // let z = tmp[2]; // this._threshold = this._orginThreshold * (x > y ? (x > z ? x : z) : y > z ? y : z); // } // //根據矩陣計算新的起始點 // Vec3.transformMat4(this._start, this._orginStart, this._matrix); // Vec3.transformMat4(this._end, this._orginEnd, this._matrix); // // //根據新的起始點計算各種臨時資料 // Vec3.sub(this._direction, this._end, this._start); // this._length = Vec3.length(this._direction);//長度 // this._inverseLength = this._length <= Algorithm.EPSILON ? 1.0 / this._length : 0.0; // Vec3.scale(this._direction, this._direction, this._inverseLength);//求單位向量 // }, // setGeometry: function (geometry, matrix) { // Intersector.prototype.setGeometry.call(this, geometry, matrix); // // //如果不一樣,需要計算新的起始點已經各種臨時資料 // Mat4.invert(this._matrix, matrix); // // //根據矩陣計算新的臨界值 // if (this._orginThreshold > 0.0) { // let tmp = this._start; // Mat4.getScale(tmp, this._matrix); // let x = tmp[0]; // let y = tmp[1]; // let z = tmp[2]; // this._threshold = this._orginThreshold * (x > y ? (x > z ? x : z) : y > z ? y : z); // } // //根據矩陣計算新的起始點 // Vec3.transformMat4(this._start, this._orginStart, this._matrix); // Vec3.transformMat4(this._end, this._orginEnd, this._matrix); // // //根據新的起始點計算各種臨時資料 // Vec3.sub(this._direction, this._end, this._start); // this._length = Vec3.length(this._direction);//長度 // this._inverseLength = this._length <= Algorithm.EPSILON ? 1.0 / this._length : 0.0; // Vec3.scale(this._direction, this._direction, this._inverseLength);//求單位向量 // }, // setGeometry: function (geometry) { // //沒有頂點資料不處理直接返回 // let vertexbuffer = geometry.getBufferArray('Vertex'); // if(!vertexbuffer) return; // // //沒有圖元不處理直接返回 // let primitive = geometry.getPrimitive(); // if (primitive) // primitive.operate(this); // },
/* 相交遍歷器,暫時擯棄 這裡的遍歷有侷限性,遍歷的是RenderNode級別的物件,無法知道哪些被場景剔除,除了隱藏以外 所以如果不需要考慮被剔除的物件,沒必要使用此遍歷器 */ let NodeVisitor = require('./NodeVisitor'); let Mat4 = require('./Mat4'); let IntersectVisitor = function () { NodeVisitor.call(this); this._intersector = undefined; this._matrixStack = [];//模型變換矩陣棧 this._matrixStack.push(Mat4.new()); }; IntersectVisitor.prototype = Object.create(NodeVisitor.prototype); IntersectVisitor.prototype.constructor = IntersectVisitor; Object.assign(IntersectVisitor.prototype, { setIntersector: function (i) { this._intersector = i; }, getIntersector: function () { return this._intersector; }, //過載 apply: function (node) { let TransformSetting = require('../core/TransformSetting'); let Geometry = require('../core/Geometry'); if(node instanceof TransformSetting){ this.applyTransform(node); }else if(node instanceof Geometry){ this.applyGeometry(node); }else{ this.traverse(node); } }, applyTransform: function(t) { if (!this._intersector.valid(g)) return; let m = Mat4.new(); Mat4.copy(m, this.getMatrix()); //TransformSetting的子類都包含該函式 t.computeLocalToWorldMatrix(m); this.pushMatrix(m); this.traverse(t); this.popMatrix(); }, applyGeometry: function (g) { if (!this._intersector.valid(g)) return; this._intersector.setGeometry(g, this.getMatrix()); this._intersector.intersect(g); }, getMatrix: function () { return this._matrixStack.back(); }, pushMatrix: function (m) { this._matrixStack.push(m); }, popMatrix: function () { this._matrixStack.pop(); }, }); module.exports = IntersectVisitor;
/* */ //let Primitives = require('../core/Primitives'); let Vec3 = require('./Vec3'); let Intersector = function () { //圖元碼,確定哪些圖元需要求交,預設全都求 this._primitiveMask = Intersector.ALL_PRIMITIVES; // //this._intersectLimit = intersectionEnums.NO_LIMIT; //相交的結果 this._intersections = []; //臨時資料 this._drawable = undefined; //this._geometry = undefined; this._vertices = undefined; this._transform = undefined;//上一次的變換,如果一樣,不再處理 this._primitiveIndex = 0; }; // Intersector.NO_LIMIT = 0; // Intersector.LIMIT_ONE_PER_DRAWABLE = 1; // Intersector.LIMIT_ONE = 2; //PrimitiveMask Intersector.POINT_PRIMITIVES = 1 << 0; Intersector.LINE_PRIMITIVES = 1 << 1; Intersector.TRIANGLE_PRIMITIVES = 1 << 2; Intersector.ALL_PRIMITIVES = (1 << 0) | (1 << 1) | (1 << 2); let sortBackToFrontFunction = function (a, b) {//從大往小排序,從後向前 return b.getRatio() - a.getRatio(); }; let sortFrontToBackFunction = function (a, b) {//從小往大排序,從前向後 return a.getRatio() - b.getRatio(); }; Object.assign(Intersector.prototype, { reset: function() { //this._hit = false; this._drawable = undefined; this._vertices = undefined; this._primitiveIndex = 0; }, intersect: function (drawable) { // //沒有頂點資料不處理直接返回 // if(!this._vertices) return; // // //沒有圖元不處理直接返回 // let primitive = this._geometry.getPrimitive(); // if (primitive) // primitive.operate(this); }, getIntersections: function () {//相交的結果集 this._intersections.sort(sortFrontToBackFunction); return this._intersections; }, operatePoint: function (index) { //if (this._limitOneIntersection && this._hit) return; if ((this._primitiveMask & Intersector.POINT_PRIMITIVES) === 0) return; let vertex = Vec3.MemoryPool.alloc(); let vertices = this._vertices; Vec3.set(vertex, vertices[3 * index], vertices[3 * index + 1], vertices[3 * index + 2]); let intersection = this.intersectPoint(vertex); Vec3.MemoryPool.free(vertex); if(intersection){ intersection._primitiveIndex = this._primitiveIndex; intersection._drawable = this._drawable; this._intersections.push(intersection); //this._hit = true; } this._primitiveIndex++; }, operateLine: function (index0, index1) { //if (this._limitOneIntersection && this._hit) return; if ((this._primitiveMask & Intersector.LINE_PRIMITIVES) === 0) return; let vertex0 = Vec3.MemoryPool.alloc(); let vertex1 = Vec3.MemoryPool.alloc(); let vertices = this._vertices; Vec3.set(vertex0, vertices[3 * index0], vertices[3 * index0 + 1], vertices[3 * index0 + 2]); Vec3.set(vertex1, vertices[3 * index1], vertices[3 * index1 + 1], vertices[3 * index1 + 2]); let intersection = this.intersectLine(vertex0, vertex1); Vec3.MemoryPool.free(vertex0); Vec3.MemoryPool.free(vertex1); if(intersection){ intersection._primitiveIndex = this._primitiveIndex; intersection._drawable = this._drawable; this._intersections.push(intersection); //this._hit = true; } this._primitiveIndex++; }, operateTriangle: function (index0, index1, index2) { //if (this._limitOneIntersection && this._hit) return; if ((this._primitiveMask & Intersector.TRIANGLE_PRIMITIVES) === 0) return; let vertex0 = Vec3.MemoryPool.alloc(); let vertex1 = Vec3.MemoryPool.alloc(); let vertex2 = Vec3.MemoryPool.alloc(); let vertices = this._vertices; Vec3.set(vertex0, vertices[3 * index0], vertices[3 * index0 + 1], vertices[3 * index0 + 2]); Vec3.set(vertex1, vertices[3 * index1], vertices[3 * index1 + 1], vertices[3 * index1 + 2]); Vec3.set(vertex2, vertices[3 * index2], vertices[3 * index2 + 1], vertices[3 * index2 + 2]); let intersection = this.intersectTriangle(vertex0, vertex1, vertex2); Vec3.MemoryPool.free(vertex0); Vec3.MemoryPool.free(vertex1); Vec3.MemoryPool.free(vertex2); if(intersection){ intersection._primitiveIndex = this._primitiveIndex; intersection._drawable = this._drawable; this._intersections.push(intersection); //this._hit = true; } this._primitiveIndex++; }, intersectPoint: function (/*v0, p0*/) { }, intersectLine: function (/*v0, v1, p0, p1*/) { }, intersectTriangle: function (/*v0, v1, v2, p0, p1, p2*/) { }, }); module.exports = Intersector; // setDrawable: function (drawable) { // this._drawable = drawable; // // let vertexbuffer = drawable.getGeometry().getBufferArray('Vertex'); // this._vertices = vertexbuffer.getArrayBuffer(); // // let matrix = drawable.getTransform(); // if (this._transform === matrix) {//如果與上次的一樣,不再處理 // return; // } // // //如果不一樣,需要計算新的起始點已經各種臨時資料 // this._transform = matrix; // }, // setGeometry: function (geometry, matrix) { // this._geometry = geometry; // this._vertices = geometry.getBufferArray('Vertex'); // // if (this._transform === matrix) {//如果與上次的一樣,不再處理 // return; // } // // //如果不一樣,需要計算新的起始點已經各種臨時資料 // this._transform = matrix; // },
/* 相交的結果 */ let Vec3 = require('./Vec3'); let Intersection = function () { this._drawable = undefined; this._primitiveIndex = undefined; }; Object.assign(Intersection.prototype, { getDrawable: function () { return this._drawable; }, }); let LineSegmentIntersection = function () { Intersection.call(this); this._ratio = 0.0; this._point = Vec3.new(); this._normal = Vec3.create(1, 0, 0);//相交點的法向量 }; LineSegmentIntersection.prototype = Object.create(Intersection.prototype); LineSegmentIntersection.prototype.constructor = LineSegmentIntersection; Object.assign(LineSegmentIntersection.prototype, { getRatio: function () { return this._ratio; }, getPoint: function () { return this._point; }, }); module.exports.Intersection = Intersection; module.exports.LineSegmentIntersection = LineSegmentIntersection;
好了,到今天為止對射線拾取raycaster的討論就差不多都結束了,後續如有勘誤會陸續補充,謝謝同學們的耐心閱讀,再次感謝連俊,風哥的指導解惑,也謝謝群裡各位大佬的不吝指正。我會繼續努力學習,今天到這裡告一段落,下週再見。
本文系 ccentry/蔥烤河鯽魚 原創,如需引用請註明出處:https://www.cnblogs.com/ccentry/p/10011730.html