浏览代码

Merge pull request #14377 from mquander/triangle-point-voronoi

Make Triangle.closestPointToPoint much faster
Mr.doob 7 年之前
父节点
当前提交
036d9a894c
共有 3 个文件被更改,包括 165 次插入70 次删除
  1. 62 29
      src/math/Triangle.js
  2. 42 41
      test/benchmark/benchmarks.html
  3. 61 0
      test/benchmark/core/TriangleClosestPoint.js

+ 62 - 29
src/math/Triangle.js

@@ -1,6 +1,4 @@
 import { Vector3 } from './Vector3.js';
 import { Vector3 } from './Vector3.js';
-import { Line3 } from './Line3.js';
-import { Plane } from './Plane.js';
 
 
 /**
 /**
  * @author bhouston / http://clara.io
  * @author bhouston / http://clara.io
@@ -218,12 +216,14 @@ Object.assign( Triangle.prototype, {
 
 
 	closestPointToPoint: function () {
 	closestPointToPoint: function () {
 
 
-		var plane = new Plane();
-		var edgeList = [ new Line3(), new Line3(), new Line3() ];
-		var projectedPoint = new Vector3();
-		var closestPoint = new Vector3();
+		var vab = new Vector3();
+		var vac = new Vector3();
+		var vbc = new Vector3();
+		var vap = new Vector3();
+		var vbp = new Vector3();
+		var vcp = new Vector3();
 
 
-		return function closestPointToPoint( point, target ) {
+		return function closestPointToPoint( p, target ) {
 
 
 			if ( target === undefined ) {
 			if ( target === undefined ) {
 
 
@@ -232,48 +232,81 @@ Object.assign( Triangle.prototype, {
 
 
 			}
 			}
 
 
-			var minDistance = Infinity;
+			var a = this.a, b = this.b, c = this.c;
+			var v, w;
 
 
-			// project the point onto the plane of the triangle
+			// algorithm thanks to Real-Time Collision Detection by Christer Ericson,
+			// published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc.,
+			// under the accompanying license; see chapter 5.1.5 for detailed explanation.
+			// basically, we're distinguishing which of the voronoi regions of the triangle
+			// the point lies in with the minimum amount of redundant computation.
 
 
-			plane.setFromCoplanarPoints( this.a, this.b, this.c );
-			plane.projectPoint( point, projectedPoint );
+			vab.subVectors( b, a );
+			vac.subVectors( c, a );
+			vap.subVectors( p, a );
+			var d1 = vab.dot( vap );
+			var d2 = vac.dot( vap );
+			if ( d1 <= 0 && d2 <= 0 ) {
 
 
-			// check if the projection lies within the triangle
+				// vertex region of A; barycentric coords (1, 0, 0)
+				return target.copy( a );
 
 
-			if ( this.containsPoint( projectedPoint ) === true ) {
+			}
+
+			vbp.subVectors( p, b );
+			var d3 = vab.dot( vbp );
+			var d4 = vac.dot( vbp );
+			if ( d3 >= 0 && d4 <= d3 ) {
 
 
-				// if so, this is the closest point
+				// vertex region of B; barycentric coords (0, 1, 0)
+				return target.copy( b );
 
 
-				target.copy( projectedPoint );
+			}
 
 
-			} else {
+			var vc = d1 * d4 - d3 * d2;
+			if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) {
 
 
-				// if not, the point falls outside the triangle. the target is the closest point to the triangle's edges or vertices
+				v = d1 / ( d1 - d3 );
+				// edge region of AB; barycentric coords (1-v, v, 0)
+				return target.copy( a ).addScaledVector( vab, v );
 
 
-				edgeList[ 0 ].set( this.a, this.b );
-				edgeList[ 1 ].set( this.b, this.c );
-				edgeList[ 2 ].set( this.c, this.a );
+			}
 
 
-				for ( var i = 0; i < edgeList.length; i ++ ) {
+			vcp.subVectors( p, c );
+			var d5 = vab.dot( vcp );
+			var d6 = vac.dot( vcp );
+			if ( d6 >= 0 && d5 <= d6 ) {
 
 
-					edgeList[ i ].closestPointToPoint( projectedPoint, true, closestPoint );
+				// vertex region of C; barycentric coords (0, 0, 1)
+				return target.copy( c );
 
 
-					var distance = projectedPoint.distanceToSquared( closestPoint );
+			}
 
 
-					if ( distance < minDistance ) {
+			var vb = d5 * d2 - d1 * d6;
+			if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) {
 
 
-						minDistance = distance;
+				w = d2 / ( d2 - d6 );
+				// edge region of AC; barycentric coords (1-w, 0, w)
+				return target.copy( a ).addScaledVector( vac, w );
 
 
-						target.copy( closestPoint );
+			}
 
 
-					}
+			var va = d3 * d6 - d5 * d4;
+			if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) {
 
 
-				}
+				vbc.subVectors( c, b );
+				w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) );
+				// edge region of BC; barycentric coords (0, 1-w, w)
+				return target.copy( b ).addScaledVector( vbc, w ); // edge region of BC
 
 
 			}
 			}
 
 
-			return target;
+			// face region
+			var denom = 1 / ( va + vb + vc );
+			// u = va * denom
+			v = vb * denom;
+			w = vc * denom;
+			return target.copy( a ).addScaledVector( vab, v ).addScaledVector( vac, w );
 
 
 		};
 		};
 
 

+ 42 - 41
test/benchmark/benchmarks.html

@@ -1,50 +1,51 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html>
 <html>
-<head>
-  <meta charset="utf-8">
-  <title>ThreeJS Benchmark Tests - Using Files in /src</title>
-  <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:700" rel="stylesheet" type="text/css">
-  <link href="normalize.css" rel="stylesheet" type="text/css">
-  <link href="style.css" rel="stylesheet" type="text/css">
-  <script src="../../build/three.min.js"></script>
-  <script src="vendor/lodash.min.js"></script>
-  <script src="vendor/benchmark-2.1.0.min.js"></script>
-  <script src="benchmark.js"></script>
+  <head>
+    <meta charset="utf-8">
+    <title>ThreeJS Benchmark Tests - Using Files in /src</title>
+    <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:700" rel="stylesheet" type="text/css">
+    <link href="normalize.css" rel="stylesheet" type="text/css">
+    <link href="style.css" rel="stylesheet" type="text/css">
+    <script src="../../build/three.min.js"></script>
+    <script src="vendor/lodash.min.js"></script>
+    <script src="vendor/benchmark-2.1.0.min.js"></script>
+    <script src="benchmark.js"></script>
 
 
-  <script src="core/Vector3Components.js"></script>
-  <script src="core/Vector3Storage.js"></script>
-  <script src="core/Vector3Length.js"></script>
-  <script src="core/Float32Array.js"></script>
-  <script src="core/UpdateMatrixWorld.js"></script>
-</head>
-<body>
-  <header>
-    <h1>Three JS Benchmarks Suite</h1>
-  </header>
-  <section>
-  </section>
+    <script src="core/Vector3Components.js"></script>
+    <script src="core/Vector3Storage.js"></script>
+    <script src="core/Vector3Length.js"></script>
+    <script src="core/Float32Array.js"></script>
+    <script src="core/UpdateMatrixWorld.js"></script>
+    <script src="core/TriangleClosestPoint.js"></script>
+  </head>
+  <body>
+    <header>
+      <h1>Three JS Benchmarks Suite</h1>
+    </header>
+    <section>
+    </section>
 
 
-  <template id="suite">
-    <article>
-      <header>
-        <h2></h2>
-        <h3>Start</h3>
-      </header>
-      <div class="results">
-        <div class"head">
-          <p class="name">Name</p>
-          <p class="ops">Ops / Sec</p>
-          <p class="desv">±</p>
+    <template id="suite">
+      <article>
+        <header>
+          <h2></h2>
+          <h3>Start</h3>
+        </header>
+        <div class="results">
+          <div class"head">
+            <p class="name">Name</p>
+            <p class="ops">Ops / Sec</p>
+            <p class="desv">±</p>
+          </div>
         </div>
         </div>
-      </div>
-    </article>
-  </template>
+      </article>
+    </template>
 
 
-  <template id="suite-test">
-    <div>
-      <p class="name"></p>
-      <p class="ops"></p>
-      <p class="desv"></p>
+    <template id="suite-test">
+      <div>
+        <p class="name"></p>
+        <p class="ops"></p>
+        <p class="desv"></p>
     </div>
     </div>
   </template>
   </template>
 
 

+ 61 - 0
test/benchmark/core/TriangleClosestPoint.js

@@ -0,0 +1,61 @@
+(function() {
+
+  THREE = Bench.THREE;
+
+  // these vertices and triangles are those of a unit icosahedron centered at the origin
+  var phi = 1.618;
+  var verts = [
+    [phi, 1, 0], [-phi, 1, 0], [phi, -1, 0], [-phi, -1, 0],
+    [1, 0, phi], [1, 0, -phi], [-1, 0, phi], [-1, 0, -phi],
+    [0, phi, 1], [0, -phi, 1], [0, phi, -1], [0, -phi, -1],
+  ];
+  var createVertex = function(c) {
+    return new THREE.Vector3(c[0], c[1], c[2]);
+  };
+  var createTriangle = function(i0, i1, i2) {
+    return new THREE.Triangle(createVertex(verts[i0]), createVertex(verts[i1]), createVertex(verts[i2]));
+  };
+  var triangles = [
+    createTriangle(0, 8, 4),
+    createTriangle(0, 5, 10),
+    createTriangle(2, 4, 9),
+    createTriangle(2, 11, 5),
+    createTriangle(1, 6, 8),
+    createTriangle(1, 10, 7),
+    createTriangle(3, 9, 6),
+    createTriangle(3, 7, 11),
+    createTriangle(0, 10, 8),
+    createTriangle(1, 8, 10),
+    createTriangle(2, 9, 11),
+    createTriangle(3, 9, 11),
+    createTriangle(4, 2, 0),
+    createTriangle(5, 0, 2),
+    createTriangle(6, 1, 3),
+    createTriangle(7, 3, 1),
+    createTriangle(8, 6, 4),
+    createTriangle(9, 4, 6),
+    createTriangle(10, 5, 7),
+    createTriangle(11, 7, 5),
+  ];
+  // test a variety of points all in and around the icosahedron
+  var testPoints = [];
+  for (var x = -2; x <= 2; x += 0.5) {
+    for (var y = -2; y <= 2; y += 0.5) {
+      for (var z = -2; z <= 2; z += 0.5) {
+        testPoints.push(new THREE.Vector3(x, y, z));
+      }
+    }
+  }
+
+  var s = Bench.newSuite("Clamping point into triangles");
+
+  s.add('9^3 points, 20 triangles', function() {
+    var target = new THREE.Vector3();
+    for (var tidx = 0; tidx < triangles.length; tidx++) {
+      var triangle = triangles[tidx];
+      for (var pidx = 0; pidx < testPoints.length; pidx++) {
+        triangle.closestPointToPoint(testPoints[pidx], target);
+      }
+    }
+  });
+})();