Browse Source

3d onclick, see examples/hci_clickcube.html

mindlapse 15 years ago
parent
commit
a89865bda8

+ 46 - 0
examples/geometry/primitives/ClickCube.js

@@ -0,0 +1,46 @@
+/**
+ * @author mr.doob / http://mrdoob.com/
+ */
+
+var ClickCube = function (width, height, depth, onSelect) {
+
+	THREE.Geometry.call(this);
+
+	var scope = this,
+	width_half = width / 2,
+	height_half = height / 2,
+	depth_half = depth / 2;
+
+	v(  width_half,  height_half, -depth_half );
+	v(  width_half, -height_half, -depth_half );
+	v( -width_half, -height_half, -depth_half );
+	v( -width_half,  height_half, -depth_half );
+	v(  width_half,  height_half,  depth_half );
+	v(  width_half, -height_half,  depth_half );
+	v( -width_half, -height_half,  depth_half );
+	v( -width_half,  height_half,  depth_half );
+
+	f4( 0, 1, 2, 3 );
+	
+	f4( 4, 7, 6, 5 );
+	f4( 0, 4, 5, 1 );
+	f4( 2, 6, 7, 3 );
+	f4( 1, 5, 6, 2 );
+	f4( 4, 0, 3, 7 );
+
+	function v(x, y, z) {
+
+		scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) );
+	}
+
+	function f4(a, b, c, d) {
+		var f = new THREE.SelectableFace4( a, b, c, d, onSelect );
+		scope.faces.push(f);
+	}
+
+	this.computeNormals();
+
+};
+
+ClickCube.prototype = new THREE.Geometry();
+ClickCube.prototype.constructor = ClickCube;

+ 187 - 0
examples/hci_clickcube.html

@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html lang="en">
+	<head>
+		<title>three.js - geometry - cube</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
+		<style type="text/css">
+			body {
+				font-family: Monospace;
+				background-color: #f0f0f0;
+				margin: 0px;
+				overflow: hidden;
+			}
+		</style>
+	</head>
+	<body>
+
+
+<script type="text/javascript" src="../src/Three.js"></script>
+<script type="text/javascript" src="../src/core/Color.js"></script>
+<script type="text/javascript" src="../src/core/Vector2.js"></script>
+<script type="text/javascript" src="../src/core/Vector3.js"></script>
+<script type="text/javascript" src="../src/core/Vector4.js"></script>
+<script type="text/javascript" src="../src/core/Rectangle.js"></script>
+<script type="text/javascript" src="../src/core/Matrix4.js"></script>
+<script type="text/javascript" src="../src/core/Vertex.js"></script>
+<script type="text/javascript" src="../src/core/Face3.js"></script>
+<script type="text/javascript" src="../src/core/Face4.js"></script>
+<script type="text/javascript" src="../src/core/UV.js"></script>
+<script type="text/javascript" src="../src/core/Geometry.js"></script>
+<script type="text/javascript" src="../src/cameras/Camera.js"></script>
+<script type="text/javascript" src="../src/objects/Object3D.js"></script>
+<script type="text/javascript" src="../src/objects/Line.js"></script>
+<script type="text/javascript" src="../src/objects/Mesh.js"></script>
+<script type="text/javascript" src="../src/objects/Particle.js"></script>
+<script type="text/javascript" src="../src/lights/Light.js"></script>
+<script type="text/javascript" src="../src/lights/AmbientLight.js"></script>
+<script type="text/javascript" src="../src/lights/DirectionalLight.js"></script>
+<script type="text/javascript" src="../src/materials/LineColorMaterial.js"></script>
+<script type="text/javascript" src="../src/materials/MeshBitmapUVMappingMaterial.js"></script>
+<script type="text/javascript" src="../src/materials/MeshColorFillMaterial.js"></script>
+<script type="text/javascript" src="../src/materials/MeshColorStrokeMaterial.js"></script>
+<script type="text/javascript" src="../src/materials/MeshFaceColorFillMaterial.js"></script>
+<script type="text/javascript" src="../src/materials/MeshFaceColorStrokeMaterial.js"></script>
+<script type="text/javascript" src="../src/materials/ParticleBitmapMaterial.js"></script>
+<script type="text/javascript" src="../src/materials/ParticleCircleMaterial.js"></script>
+<script type="text/javascript" src="../src/scenes/Scene.js"></script>
+<script type="text/javascript" src="../src/renderers/Renderer.js"></script>
+<script type="text/javascript" src="../src/renderers/CanvasRenderer.js"></script>
+<script type="text/javascript" src="../src/renderers/SVGRenderer.js"></script>
+<script type="text/javascript" src="../src/renderers/WebGLRenderer.js"></script>
+<script type="text/javascript" src="../src/renderers/renderables/RenderableFace3.js"></script>
+<script type="text/javascript" src="../src/renderers/renderables/RenderableFace4.js"></script>
+<script type="text/javascript" src="../src/renderers/renderables/RenderableParticle.js"></script>
+<script type="text/javascript" src="../src/renderers/renderables/RenderableLine.js"></script>
+<script type="text/javascript" src="../src/hci/ClickResolver.js"></script>
+<script type="text/javascript" src="../src/hci/SelectableFace3.js"></script>
+<script type="text/javascript" src="../src/hci/SelectableFace4.js"></script>
+<script type="text/javascript" src="geometry/primitives/ClickCube.js"></script>
+<script type="text/javascript" src="geometry/primitives/Plane.js"></script>
+
+		<script type="text/javascript" src="http://github.com/mrdoob/stats.js/raw/master/build/Stats.js"></script>
+
+		<script type="text/javascript">
+
+			var SCREEN_WIDTH = window.innerWidth;
+			var SCREEN_HEIGHT = window.innerHeight;
+
+			var container;
+			var stats;
+
+			var camera;
+			var scene;
+			var renderer;
+
+			var cube, plane;
+
+			var targetRotation = 0;
+			var targetRotationOnMouseDown = 0;
+
+			var mouseX = 0;
+			var mouseXOnMouseDown = 0;
+
+			var windowHalfX = window.innerWidth / 2;
+			var windowHalfY = window.innerHeight / 2;
+			var clickResolver;
+
+			init();
+			setInterval(loop, 1000/60);
+
+			function init() {
+
+				container = document.createElement('div');
+				document.body.appendChild(container);
+
+				var info = document.createElement('div');
+				info.id = 'msg';
+				info.style.position = 'absolute';
+				info.style.top = '10px';
+				info.style.width = '100%';
+				info.style.textAlign = 'center';
+				info.innerHTML = 'No click detected';
+				container.appendChild(info);
+
+				camera = new THREE.Camera( 70, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 10000 );
+				camera.position.y = 300;
+				camera.position.z = 500;
+				camera.target.position.y = 150;
+
+				scene = new THREE.Scene();
+				clickResolver = new THREE.ClickResolver( camera, scene );
+
+				// Click Cube
+				var opacity = 0.8;
+
+				geometry = new ClickCube( 200, 200, 200, 
+					function ( s, c, o, f, p ) {
+						f.color.setRGBA( Math.random() * 0.5, Math.random() * 0.5, Math.random() * 0.5, opacity );
+						document.getElementById( "msg" ).innerHTML = "Click detected at " + p; 
+					});
+
+				for (var i = 0; i < geometry.faces.length; i++) {
+					geometry.faces[i].color.setRGBA( Math.random() * 0.5, Math.random() * 0.5, Math.random() * 0.5, opacity );
+				}
+
+				cube = new THREE.Mesh( geometry, new THREE.MeshFaceColorFillMaterial() );
+				cube.position.y = 150;
+				scene.addObject(cube);
+
+				geometry2 = new ClickCube( 100, 100, 100, 
+						function ( s, c, o, f, p ) {
+							f.color.setRGBA( Math.random() * 0.5, Math.random() * 0.5, Math.random() * 0.5, opacity );
+							document.getElementById( "msg" ).innerHTML = "Click detected at " + p; 
+						});
+
+				for (var i = 0; i < geometry2.faces.length; i++) {
+					geometry2.faces[i].color.setRGBA( Math.random() * 0.5, Math.random() * 0.5, Math.random() * 0.5, opacity );
+				}
+				
+				cube2 = new THREE.Mesh( geometry2, new THREE.MeshFaceColorFillMaterial() );
+				cube2.position.y = 150;
+				cube2.position.z = 200;
+				scene.addObject(cube2);
+				
+				// Plane
+				renderer = new THREE.CanvasRenderer();
+				renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+
+				container.appendChild(renderer.domElement);
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild(stats.domElement);
+
+				document.addEventListener('mousedown', onDocumentMouseDown, false);
+			}
+
+			function onDocumentMouseDown( event ) {
+
+				event.preventDefault();
+
+				document.getElementById("msg").innerHTML = ""; 
+				
+				clickResolver.findIntersectInScene(
+						event.clientX/window.innerWidth,
+						event.clientY/window.innerHeight);
+					
+			}
+
+			var radius = 600;
+			var theta = 0;
+			
+			function loop() {
+
+				theta += 1;
+				camera.position.x = radius * Math.sin( theta * Math.PI / 360 );
+				camera.position.z = radius * Math.cos( theta * Math.PI / 360 );
+
+				renderer.render(scene, camera);
+				stats.update();
+			}
+
+		</script>
+
+	</body>
+</html>

+ 2 - 0
src/cameras/Camera.js

@@ -4,6 +4,8 @@
 
 THREE.Camera = function ( fov, aspect, near, far ) {
 
+	this.fov = fov;
+	this.aspect = aspect;
 	this.position = new THREE.Vector3( 0, 0, 0 );
 	this.target = { position: new THREE.Vector3( 0, 0, 0 ) };
 

+ 2 - 0
src/core/Matrix4.js

@@ -70,6 +70,8 @@ THREE.Matrix4.prototype = {
 			v.z = v.z / vw;
 
 		}
+		
+		return v;
 
 	},
 

+ 12 - 0
src/core/Vector2.js

@@ -16,6 +16,8 @@ THREE.Vector2.prototype = {
 
 		this.x = x;
 		this.y = y;
+		
+		return this;
 
 	},
 
@@ -23,6 +25,8 @@ THREE.Vector2.prototype = {
 
 		this.x = v.x;
 		this.y = v.y;
+		
+		return this;
 
 	},
 
@@ -39,6 +43,8 @@ THREE.Vector2.prototype = {
 
 		this.x = v1.x + v2.x;
 		this.y = v1.y + v2.y;
+		
+		return this;
 
 	},
 
@@ -55,6 +61,8 @@ THREE.Vector2.prototype = {
 
 		this.x = v1.x - v2.x;
 		this.y = v1.y - v2.y;
+		
+		return this;
 
 	},
 
@@ -71,6 +79,8 @@ THREE.Vector2.prototype = {
 
 		this.multiplyScalar( 1 / this.length() );
 
+		return this;
+		
 	},
 
 	length: function () {
@@ -89,6 +99,8 @@ THREE.Vector2.prototype = {
 
 		this.x = - this.x;
 		this.y = - this.y;
+		
+		return this;
 
 	},
 

+ 18 - 0
src/core/Vector3.js

@@ -19,6 +19,8 @@ THREE.Vector3.prototype = {
 		this.x = x;
 		this.y = y;
 		this.z = z;
+		
+		return this;
 
 	},
 
@@ -28,6 +30,8 @@ THREE.Vector3.prototype = {
 		this.y = v.y;
 		this.z = v.z;
 
+		return this;
+		
 	},
 
 	add: function( v1, v2 ) {
@@ -35,6 +39,8 @@ THREE.Vector3.prototype = {
 		this.x = v1.x + v2.x;
 		this.y = v1.y + v2.y;
 		this.z = v1.z + v2.z;
+		
+		return this;
 
 	},
 
@@ -64,6 +70,8 @@ THREE.Vector3.prototype = {
 		this.y = v1.y - v2.y;
 		this.z = v1.z - v2.z;
 
+		return this;
+		
 	},
 
 	subSelf: function ( v ) {
@@ -82,6 +90,8 @@ THREE.Vector3.prototype = {
 		this.y = v1.z * v2.x - v1.x * v2.z;
 		this.z = v1.x * v2.y - v1.y * v2.x;
 
+		return this;
+		
 	},
 
 	crossSelf: function ( v ) {
@@ -179,6 +189,14 @@ THREE.Vector3.prototype = {
 
 		}
 
+		return this;
+		
+	},
+	
+	setLength: function( len ) {
+		
+		return this.normalize().multiplyScalar( len );
+		
 	},
 
 	isZero: function () {

+ 8 - 0
src/core/Vector4.js

@@ -20,6 +20,8 @@ THREE.Vector4.prototype = {
 		this.y = y;
 		this.z = z;
 		this.w = w;
+		
+		return this;
 
 	},
 
@@ -30,6 +32,8 @@ THREE.Vector4.prototype = {
 		this.z = v.z;
 		this.w = v.w;
 
+		return this;
+
 	},
 
 	add: function ( v1, v2 ) {
@@ -39,6 +43,8 @@ THREE.Vector4.prototype = {
 		this.z = v1.z + v2.z;
 		this.w = v1.w + v2.w;
 
+		return this;
+
 	},
 
 	addSelf: function ( v ) {
@@ -59,6 +65,8 @@ THREE.Vector4.prototype = {
 		this.z = v1.z - v2.z;
 		this.w = v1.w - v2.w;
 
+		return this;
+
 	},
 
 	subSelf: function ( v ) {

+ 202 - 0
src/hci/ClickResolver.js

@@ -0,0 +1,202 @@
+
+THREE.ClickResolver = function( camera, scene ) {
+
+	this.camera = camera;
+	this.scene  = scene;
+	this._debug = false;
+	
+};
+
+THREE.ClickResolver.prototype = {
+	
+	findIntersectInScene : function ( xPercent, yPercent ) {
+	
+		var objects = this.scene.objects;
+		var intersects = [];
+		
+		var mouseRayStart = this.translateScreenCoordsToZIndex( xPercent, yPercent, 300 );
+		var mouseRayEnd =   this.translateScreenCoordsToZIndex( xPercent, yPercent, 800 );
+		
+		var mouseRayDir = new THREE.Vector3().sub( mouseRayEnd, mouseRayStart );
+		
+		var closestIntersect = null;
+		
+		for ( var i = 0; i < objects.length; i++ ) {
+			
+			var o = objects[i];
+			var intersect = this.getIntersectingFaces( this.scene, camera, o, mouseRayStart, mouseRayDir );
+			
+			if ( intersect.face != null &&
+					(closestIntersect == null ||
+					 closestIntersect.distance > intersect.distance)
+					 ) {
+				
+				closestIntersect = intersect;
+			}
+		}
+		
+		if ( closestIntersect != null && closestIntersect.face.onSelect ) {
+			
+			closestIntersect.face.onSelect( scene, camera, o, closestIntersect.face, closestIntersect.point );
+		}
+	},
+		
+	
+	translateScreenCoordsToZIndex : function ( xPercent, yPercent, targetZIndex ) {
+
+		var maxVisibleXatZIndex, maxVisibleYatZIndex;
+		var rayToZIndex   = new THREE.Vector3();
+		var left          = new THREE.Vector3();
+		var up            = new THREE.Vector3();
+		var coordAtZIndex = new THREE.Vector3();
+
+		rayToZIndex.sub( this.camera.target.position, this.camera.position ).setLength( targetZIndex );
+		
+		maxVisibleYatZIndex = rayToZIndex.length() * Math.tan( this.camera.fov * Math.PI / 360 );
+		maxVisibleXatZIndex = maxVisibleYatZIndex  * this.camera.aspect;
+		
+		left.cross( this.camera.up, rayToZIndex );
+		up  .cross( rayToZIndex, left );
+		
+		return coordAtZIndex.add( this.camera.position, rayToZIndex ).
+			addSelf( left.setLength( maxVisibleXatZIndex * ( 1 - 2 * xPercent ))).
+			addSelf( up  .setLength( maxVisibleYatZIndex * ( 1 - 2 * yPercent )));
+	},
+	
+	
+	logPoint: function( scene, v, hex ) {
+		
+		if ( this._debug ) {
+			
+			var vg = new THREE.Geometry();
+			
+			vg.vertices[ 0 ] = new THREE.Vertex( v );
+			vg.vertices[ 1 ] = new THREE.Vertex( v );
+			
+			scene.addObject( new THREE.Line( vg, new THREE.LineColorMaterial( hex, 1, 10 )));
+		}
+	},
+	
+	
+	logLine: function( scene, s, e, hex ) {
+
+		if ( this._debug ) {
+
+			this.logPoint( scene, s.clone(), 0x000000 );
+			
+			var lg = new THREE.Geometry();
+			
+			lg.vertices[0] = new THREE.Vertex( s.clone() );
+			lg.vertices[1] = new THREE.Vertex( e.clone() );
+			
+			scene.addObject(new THREE.Line( lg, new THREE.LineColorMaterial( hex, 1, 4 ) ));
+		}
+
+	},
+	
+	
+	getIntersectingFaces: function( scene, camera, object3d, linePoint, lineDir ) {
+		
+		var intersect = {
+			face     : null,
+			point    : null,
+			distance : Number.MAX_VALUE
+		};
+		
+		var geo    = object3d.geometry;
+		var matrix = object3d.matrix;
+		
+		for ( f = 0; f < geo.faces.length; f++ ) {
+			
+			var face = geo.faces[ f ];
+			
+			if ( !face.selectable ) continue;
+			
+			var a = matrix.transform( geo.vertices[ face.a ].position.clone() );
+			var b = matrix.transform( geo.vertices[ face.b ].position.clone() );
+			var c = matrix.transform( geo.vertices[ face.c ].position.clone() );
+			var d = null;
+			
+			if ( face.d ) {
+				
+				d = matrix.transform( geo.vertices[ face.d ].position.clone() );
+			}
+			
+			var lineStart = linePoint.clone();
+			var lineDirection = lineDir.clone();
+			var dot = face.normal.dot( lineDirection );
+			
+			if ( this._debug ) {
+
+				this.logLine( scene, a, new THREE.Vector3().add( a, new THREE.Vector3().addSelf( face.normal ).multiplyScalar( 100 )), 0x0000FF );
+				this.logLine( scene, lineStart, lineStart.clone().addSelf(lineDirection), 0x55FF88 );
+				this.logPoint( scene, a, 0xFF0000 ); // r
+				this.logPoint( scene, b, 0x00FF00 ); // g
+				this.logPoint( scene, c, 0x0000FF ); // b
+				this.logPoint( scene, d, 0xFFFF00 ); // y
+			}
+				
+			if ( Math.abs(dot) > .0001 ) {
+				
+				var s = face.normal.dot( new THREE.Vector3().sub( a, lineStart ) ) / dot;
+				var planeIntersect = lineStart.addSelf( lineDirection.multiplyScalar( s ) );
+				
+				if ( this._debug ) this.logPoint( scene, planeIntersect, 0xFFCCAA );
+				
+				if ( d == null ) {
+					
+					var ab = isInsideBoundary( planeIntersect, a, b, c );
+					var bc = isInsideBoundary( planeIntersect, b, c, a );
+					var ca = isInsideBoundary( planeIntersect, c, a, b );
+					
+					if ( ab && bc && ca ) {
+						
+						if ( this._debug ) this.logPoint( scene, planeIntersect, 0xFF0000 );
+						logIntersect( planeIntersect, face );
+						
+					}
+				} else {
+					
+					var ab = isInsideBoundary( planeIntersect, a, b, c );
+					var bc = isInsideBoundary( planeIntersect, b, c, d );
+					var cd = isInsideBoundary( planeIntersect, c, d, a );
+					var da = isInsideBoundary( planeIntersect, d, a, b );
+					
+					if ( ab && bc && cd && da ) {
+						
+						if ( this._debug ) this.logPoint( scene, planeIntersect, 0xFF0000 );
+						logIntersect( planeIntersect, face );
+						
+					}
+				}
+			}
+		}
+		
+		function logIntersect( planeIntersect, face ) {
+			
+			var distance = camera.position.distanceTo( planeIntersect );
+			
+			if ( distance < intersect.distance ) {
+				
+				intersect.distance = distance;
+				intersect.face = face;
+				intersect.point = planeIntersect;
+			}
+		}
+		
+		function isInsideBoundary( pointOnPlaneToCheck, pointInside, boundaryPointA, boundaryPointB ) {
+		
+			var toB = boundaryPointB.clone().subSelf( boundaryPointA );
+			var toI = pointInside.clone().subSelf( boundaryPointA );
+			var pointMid = toB.setLength( toI.dot( toB ) ).addSelf( boundaryPointA );
+			var pointMirror = pointMid.subSelf( pointInside ).multiplyScalar( 2 ).addSelf( pointInside );
+			
+			return pointOnPlaneToCheck.distanceToSquared( pointInside ) <
+				   pointOnPlaneToCheck.distanceToSquared( pointMirror );
+		};
+		
+		return intersect;
+	}
+
+
+};

+ 10 - 0
src/hci/SelectableFace3.js

@@ -0,0 +1,10 @@
+THREE.SelectableFace3 =  function ( a, b, c, normal, color, onSelect) {
+	
+	THREE.Face3.call( this, a, b, c, normal, color );
+	
+	this.selectable = true;
+	this.onSelect = onSelect;
+};
+
+THREE.SelectableFace3.prototype = new THREE.Face3();
+THREE.SelectableFace3.prototype.constructor = THREE.SelectableFace3;

+ 11 - 0
src/hci/SelectableFace4.js

@@ -0,0 +1,11 @@
+var SelectableFace4 = function ( a, b, c, d, onSelect, normal, color) {
+	
+	THREE.Face4.call( this, a, b, c, d, normal, color );
+	
+	this.selectable = true;
+	this.onSelect = onSelect;
+};
+
+SelectableFace4.prototype = new THREE.Face4();
+SelectableFace4.prototype.constructor = SelectableFace4;  
+THREE.SelectableFace4 = SelectableFace4;