Browse Source

Merge remote-tracking branch 'stephomi/dev-ray' into dev

Mr.doob 12 years ago
parent
commit
035adee5e8
4 changed files with 168 additions and 98 deletions
  1. 5 13
      examples/canvas_interactive_lines.html
  2. 13 13
      src/core/Raycaster.js
  3. 100 49
      src/math/Ray.js
  4. 50 23
      test/unit/math/Ray.js

+ 5 - 13
examples/webgl_interactive_lines.html → examples/canvas_interactive_lines.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html lang="en">
 	<head>
-		<title>three.js webgl - interactive lines</title>
+		<title>three.js canvas - interactive lines</title>
 		<meta charset="utf-8">
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<style>
@@ -42,23 +42,15 @@
 				info.style.top = '10px';
 				info.style.width = '100%';
 				info.style.textAlign = 'center';
-				info.innerHTML = '<a href="http://threejs.org" target="_blank">three.js</a> webgl - interactive lines';
+				info.innerHTML = '<a href="http://threejs.org" target="_blank">three.js</a> canvas - interactive lines';
 				container.appendChild( info );
 
 				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
 
 				scene = new THREE.Scene();
 
-				var light = new THREE.DirectionalLight( 0xffffff, 2 );
-				light.position.set( 1, 1, 1 ).normalize();
-				scene.add( light );
-
-				var light = new THREE.DirectionalLight( 0xffffff );
-				light.position.set( -1, -1, -1 ).normalize();
-				scene.add( light );
-
 				var sphereGeometry = new THREE.SphereGeometry(3);
-				sphereInter = new THREE.Mesh( sphereGeometry, new THREE.MeshLambertMaterial( { color: 0xff0000 } ) );
+				sphereInter = new THREE.Mesh( sphereGeometry, new THREE.MeshBasicMaterial( { color: 0xff0000 } ) );
 				sphereInter.visible = false;
 				scene.add( sphereInter );
 
@@ -80,7 +72,7 @@
 
 				}
 
-				parentTransform = new THREE.Mesh();
+				parentTransform = new THREE.Object3D();
 				parentTransform.position.x = Math.random() * 40 - 20;
 				parentTransform.position.y = Math.random() * 40 - 20;
 				parentTransform.position.z = Math.random() * 40 - 20;
@@ -120,7 +112,7 @@
 				raycaster = new THREE.Raycaster();
 				raycaster.linePrecision = 3;
 
-				renderer = new THREE.WebGLRenderer();
+				renderer = new THREE.CanvasRenderer();
 				renderer.sortObjects = false;
 				renderer.setSize( window.innerWidth, window.innerHeight );
 

+ 13 - 13
src/core/Raycaster.js

@@ -164,11 +164,11 @@
 
 						var planeDistance = localRay.distanceToPlane( facePlane );
 
-						// bail if raycaster and plane are parallel
-						if ( Math.abs( planeDistance ) < precision ) continue;
+						// bail if the ray is too close to the plane
+						if ( planeDistance < precision ) continue;
 
-						// if negative distance, then plane is behind raycaster
-						if ( planeDistance < 0 ) continue;
+						// bail if the ray is behind the plane
+						if ( planeDistance === null ) continue;
 
 						// check if we hit the wrong side of a single sided face
 						side = material.side;
@@ -244,11 +244,11 @@
 
 					var planeDistance = localRay.distanceToPlane( facePlane );
 
-					// bail if raycaster and plane are parallel
-					if ( Math.abs( planeDistance ) < precision ) continue;
+					// bail if the ray is too close to the plane
+					if ( planeDistance < precision ) continue;
 
-					// if negative distance, then plane is behind raycaster
-					if ( planeDistance < 0 ) continue;
+					// bail if the ray is behind the plane
+					if ( planeDistance === null ) continue;
 
 					// check if we hit the wrong side of a single sided face
 					side = material.side;
@@ -348,18 +348,18 @@
 			var vertices = geometry.vertices;
 			var nbVertices = vertices.length;
 			var interSegment = new THREE.Vector3();
-			var interLine = new THREE.Vector3();
+			var interRay = new THREE.Vector3();
 			var step = object.type === THREE.LineStrip ? 1 : 2;
 
 			for ( var i = 0; i < nbVertices - 1; i = i + step ) {
 
-				localRay.distanceSqAndPointToSegment( vertices[ i ], vertices[ i + 1 ], interLine, interSegment );
+				localRay.distanceToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment );
 				interSegment.applyMatrix4( object.matrixWorld );
-				interLine.applyMatrix4( object.matrixWorld );
+				interRay.applyMatrix4( object.matrixWorld );
 
-				if ( interLine.distanceToSquared( interSegment ) <= precisionSq ) {
+				if ( interRay.distanceToSquared( interSegment ) <= precisionSq ) {
 
-					var distance = raycaster.ray.origin.distanceTo( interLine );
+					var distance = raycaster.ray.origin.distanceTo( interRay );
 
 					if ( raycaster.near <= distance && distance <= raycaster.far ) {
 

+ 100 - 49
src/math/Ray.js

@@ -59,6 +59,12 @@ THREE.Ray.prototype = {
 		result.subVectors( point, this.origin );
 		var directionDistance = result.dot( this.direction );
 
+		if ( directionDistance < 0 ) {
+
+			return this.origin.clone();
+
+		}
+
 		return result.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
 
 	},
@@ -70,6 +76,15 @@ THREE.Ray.prototype = {
 		return function ( point ) {
 
 			var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction );
+
+			// point behind the ray
+
+			if ( directionDistance < 0 ) {
+
+				return this.origin.distanceTo( point );
+
+			}
+
 			v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
 
 			return v1.distanceTo( point );
@@ -78,81 +93,112 @@ THREE.Ray.prototype = {
 
 	}(),
 
-	distanceSqAndPointToSegment: function ( v0, v1, optionalPointOnLine, optionalPointOnSegment ) {
+	distanceToSegment: function( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
 
-		// from http://www.geometrictools.com/LibMathematics/Distance/Wm5DistLine3Segment3.cpp
-		// It returns the min distance between the ray (actually... the line) and the segment
+		// from http://www.geometrictools.com/LibMathematics/Distance/Wm5DistRay3Segment3.cpp
+		// It returns the min distance between the ray and the segment
 		// defined by v0 and v1
 		// It can also set two optional targets :
-		// - The closest point on the ray (...line)
+		// - The closest point on the ray
 		// - The closest point on the segment
 
 		var segCenter = v0.clone().add( v1 ).multiplyScalar( 0.5 );
 		var segDir = v1.clone().sub( v0 ).normalize();
-		var segExtent = v0.distanceTo( v1 ) *0.5;
+		var segExtent = v0.distanceTo( v1 ) * 0.5;
 		var diff = this.origin.clone().sub( segCenter );
-		var a01 = -this.direction.dot( segDir );
+		var a01 = - this.direction.dot( segDir );
 		var b0 = diff.dot( this.direction );
+		var b1 = - diff.dot( segDir );
 		var c = diff.lengthSq();
 		var det = Math.abs( 1 - a01 * a01 );
-		var b1, s0, s1, sqrDist, extDet;
-
-		if ( det >= 0 ) {
+		var s0, s1, sqrDist, extDet;
+		if (det >= 0) {
 
-			// The line and segment are not parallel.
+			// The ray and segment are not parallel.
 
-			b1 = -diff.dot( segDir );
+			s0 = a01 * b1 - b0;
 			s1 = a01 * b0 - b1;
 			extDet = segExtent * det;
 
-			if ( s1 >= -extDet ) {
+			if (s0 >= 0) {
+
+				if (s1 >= -extDet) {
+
+					if (s1 <= extDet) {
+
+						// region 0
+						// Minimum at interior points of ray and segment.
 
-				if ( s1 <= extDet ) {
+						var invDet = 1 / det;
+						s0 *= invDet;
+						s1 *= invDet;
+						sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
 
-					// Two interior points are closest, one on the line and one
-					// on the segment.
+					} else {
 
-					var invDet = 1 / det;
-					s0 = ( a01 * b1 - b0 ) * invDet;
-					s1 *= invDet;
-					sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
+						// region 1
+
+						s1 = segExtent;
+						s0 = Math.max( 0, - ( a01 * s1 + b0) );
+						sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+					}
+
+				}
 
-				} else {
+				else {
 
-					// The endpoint e1 of the segment and an interior point of
-					// the line are closest.
+					// region 5
 
-					s1 = segExtent;
-					s0 = - ( a01 * s1 + b0 );
+					s1 = - segExtent;
+					s0 = Math.max( 0, - ( a01 * s1 + b0) );
 					sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
 
 				}
 
 			} else {
 
-				// The end point e0 of the segment and an interior point of the
-				// line are closest.
+				if ( s1 <= - extDet) {
 
-				s1 = - segExtent;
-				s0 = - ( a01 * s1 + b0 );
-				sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+					// region 4
+
+					s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
+					s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+					sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+				} else if (s1 <= extDet) {
+
+					// region 3
+
+					s0 = 0;
+					s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
+					sqrDist = s1 * ( s1 + 2 * b1 ) + c;
+
+				}	else {
+
+					// region 2
+
+					s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
+					s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+					sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+				}
 
 			}
 
-		} else {
+		}	else {
 
-			// The line and segment are parallel.  Choose the closest pair so that
-			// one point is at segment center.
+			// Ray and segment are parallel.
 
-			s1 = 0;
-			s0 = - b0;
-			sqrDist = b0 * s0 + c;
+			s1 = ( a01 > 0 ) ? - segExtent : segExtent;
+			s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
+			sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
 
 		}
 
-		if ( optionalPointOnLine ) {
+		if ( optionalPointOnRay ) {
 
-			optionalPointOnLine.copy( this.direction.clone().multiplyScalar( s0 ).add( this.origin ) );
+			optionalPointOnRay.copy( this.direction.clone().multiplyScalar( s0 ).add( this.origin ) );
 
 		}
 
@@ -174,24 +220,26 @@ THREE.Ray.prototype = {
 
 	isIntersectionPlane: function ( plane ) {
 
-		// check if the line and plane are non-perpendicular, if they
-		// eventually they will intersect.
+		// check if the ray lies on the plane first
 
-		var denominator = plane.normal.dot( this.direction );
+		var distToPoint = plane.distanceToPoint( this.origin );
 
-		if ( denominator != 0 ) {
+		if ( distToPoint === 0 ) {
 
 			return true;
 
 		}
 
-		// line is coplanar, return origin
-		if( plane.distanceToPoint( this.origin ) == 0 ) {
+		var denominator = plane.normal.dot( this.direction );
 
-			return true;
+		if ( denominator * distToPoint < 0 ) {
+
+			return true
 
 		}
 
+		// ray origin is behind the plane (and is pointing behind it)
+
 		return false;
 
 	},
@@ -208,14 +256,17 @@ THREE.Ray.prototype = {
 
 			}
 
-			// Unsure if this is the correct method to handle this case.
-			return undefined;
+			// Null is preferable to undefined since undefined means.... it is undefined
+
+			return null;
 
 		}
 
 		var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
 
-		return t;
+		// Return if the ray never intersects the plane
+
+		return t >= 0 ? t :  null;
 
 	},
 
@@ -223,9 +274,9 @@ THREE.Ray.prototype = {
 
 		var t = this.distanceToPlane( plane );
 
-		if ( t === undefined ) {
+		if ( t === null ) {
 
-			return undefined;
+			return null;
 		}
 
 		return this.at( t, optionalTarget );

+ 50 - 23
test/unit/math/Ray.js

@@ -64,25 +64,33 @@ test( "recast/clone", function() {
 test( "closestPointToPoint", function() {
 	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
 
-	// nearby the ray
+	// behind the ray
 	var b = a.closestPointToPoint( zero3 );
-	ok( b.equals( new THREE.Vector3( 1, 1, 0 ) ), "Passed!" );
+	ok( b.equals( one3 ), "Passed!" );
+
+	// front of the ray
+	var c = a.closestPointToPoint( new THREE.Vector3( 0, 0, 50 ) );
+	ok( c.equals( new THREE.Vector3( 1, 1, 50 ) ), "Passed!" );
 
 	// exactly on the ray
-	var c = a.closestPointToPoint( one3 );
-	ok( c.equals( one3 ), "Passed!" );
+	var d = a.closestPointToPoint( one3 );
+	ok( d.equals( one3 ), "Passed!" );
 });
 
 test( "distanceToPoint", function() {
 	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
 
-	// nearby the ray
+	// behind the ray
 	var b = a.distanceToPoint( zero3 );
-	ok( b == Math.sqrt( 2 ), "Passed!" );
+	ok( b === Math.sqrt( 3 ), "Passed!" );
+
+	// front of the ray
+	var c = a.distanceToPoint( new THREE.Vector3( 0, 0, 50 ) );
+	ok( c === Math.sqrt( 2 ), "Passed!" );
 
 	// exactly on the ray
-	var c = a.distanceToPoint( one3 );
-	ok( c == 0, "Passed!" );
+	var d = a.distanceToPoint( one3 );
+	ok( d === 0, "Passed!" );
 });
 
 test( "isIntersectionSphere", function() {
@@ -94,7 +102,7 @@ test( "isIntersectionSphere", function() {
 	var f = new THREE.Sphere( two3, 1 );
 
 	ok( ! a.isIntersectionSphere( b ), "Passed!" );
-	ok( a.isIntersectionSphere( c ), "Passed!" );
+	ok( ! a.isIntersectionSphere( c ), "Passed!" );
 	ok( a.isIntersectionSphere( d ), "Passed!" );
 	ok( ! a.isIntersectionSphere( e ), "Passed!" );
 	ok( ! a.isIntersectionSphere( f ), "Passed!" );
@@ -103,7 +111,7 @@ test( "isIntersectionSphere", function() {
 test( "isIntersectionPlane", function() {
 	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
 
-	// parallel plane behind
+	// parallel plane in front of the ray
 	var b = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), one3.clone().sub( new THREE.Vector3( 0, 0, -1 ) ) );
 	ok( a.isIntersectionPlane( b ), "Passed!" );
 
@@ -111,9 +119,9 @@ test( "isIntersectionPlane", function() {
 	var c = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), one3.clone().sub( new THREE.Vector3( 0, 0, 0 ) ) );
 	ok( a.isIntersectionPlane( c ), "Passed!" );
 
-	// parallel plane infront
+	// parallel plane behind the ray
 	var d = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), one3.clone().sub( new THREE.Vector3( 0, 0, 1 ) ) );
-	ok( a.isIntersectionPlane( d ), "Passed!" );
+	ok( ! a.isIntersectionPlane( d ), "Passed!" );
 
 	// perpendical ray that overlaps exactly
 	var e = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 1, 0, 0 ), one3 );
@@ -129,15 +137,15 @@ test( "intersectPlane", function() {
 
 	// parallel plane behind
 	var b = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), new THREE.Vector3( 1, 1, -1 ) );
-	ok( a.intersectPlane( b ).equals( new THREE.Vector3( 1, 1, -1 ) ), "Passed!" );
+	ok( a.intersectPlane( b ) === null, "Passed!" );
 
 	// parallel plane coincident with origin
 	var c = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), new THREE.Vector3( 1, 1, 0 ) );
-	ok( a.intersectPlane( c ).equals( new THREE.Vector3( 1, 1, 0 ) ), "Passed!" );
+	ok( a.intersectPlane( c ) === null, "Passed!" );
 
 	// parallel plane infront
 	var d = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), new THREE.Vector3( 1, 1, 1 ) );
-	ok( a.intersectPlane( d ).equals( new THREE.Vector3( 1, 1, 1 ) ), "Passed!" );
+	ok( a.intersectPlane( d ).equals( a.origin ), "Passed!" );
 
 	// perpendical ray that overlaps exactly
 	var e = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 1, 0, 0 ), one3 );
@@ -145,7 +153,7 @@ test( "intersectPlane", function() {
 
 	// perpendical ray that doesn't overlap
 	var f = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 1, 0, 0 ), zero3 );
-	ok( a.intersectPlane( f ) === undefined, "Passed!" );
+	ok( a.intersectPlane( f ) === null, "Passed!" );
 });
 
 
@@ -174,19 +182,38 @@ test( "applyMatrix4", function() {
 });
 
 
-test( "distanceSqAndPointToSegment4", function() {
+test( "distanceToSegment", function() {
 	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
-	var v0 = new THREE.Vector3( 3, 5, 50 );
-	var v1 = new THREE.Vector3( 50, 50, 50 ); // just a far away point
 	var ptOnLine = new THREE.Vector3();
 	var ptOnSegment = new THREE.Vector3();
-	var distSqr = a.distanceSqAndPointToSegment( v0, v1, ptOnLine, ptOnSegment );
-	var m = new THREE.Matrix4();
+
+	//segment in front of the ray
+	var v0 = new THREE.Vector3( 3, 5, 50 );
+	var v1 = new THREE.Vector3( 50, 50, 50 ); // just a far away point
+	var distSqr = a.distanceToSegment( v0, v1, ptOnLine, ptOnSegment );
 
 	ok( ptOnSegment.distanceTo( v0 ) < 0.0001, "Passed!" );
 	ok( ptOnLine.distanceTo( new THREE.Vector3(1, 1, 50) ) < 0.0001, "Passed!" );
 	// ((3-1) * (3-1) + (5-1) * (5-1) = 4 + 16 = 20
-	ok( distSqr === 20, "Passed!" );
+	ok( Math.abs( distSqr - 20 ) < 0.0001, "Passed!" );
+
+	//segment behind the ray
+	v0 = new THREE.Vector3( -50, -50, -50 ); // just a far away point
+	v1 = new THREE.Vector3( -3, -5, -4 );
+	distSqr = a.distanceToSegment( v0, v1, ptOnLine, ptOnSegment );
+
+	ok( ptOnSegment.distanceTo( v1 ) < 0.0001, "Passed!" );
+	ok( ptOnLine.distanceTo( one3 ) < 0.0001, "Passed!" );
+	// ((-3-1) * (-3-1) + (-5-1) * (-5-1) + (-4-1) + (-4-1) = 16 + 36 + 25 = 77
+	ok( Math.abs( distSqr - 77 ) < 0.0001, "Passed!" );
+
+	//exact intersection between the ray and the segment
+	v0 = new THREE.Vector3( -50, -50, -50 );
+	v1 = new THREE.Vector3( 50, 50, 50 );
+	distSqr = a.distanceToSegment( v0, v1, ptOnLine, ptOnSegment );
+
+	ok( ptOnSegment.distanceTo( one3 ) < 0.0001, "Passed!" );
+	ok( ptOnLine.distanceTo( one3 ) < 0.0001, "Passed!" );
+	ok( distSqr < 0.0001, "Passed!" );
 });
 
-