API Docs for:
Show:

File: ../src/components/cameraController.js

/**
* Camera controller
* Allows to move a camera with the user input. It uses the first camera attached to the same node
* @class CameraController
* @constructor
* @param {String} object to configure from
*/

function CameraController(o)
{
	this.enabled = true;

	this.no_button_action = CameraController.NONE;
	this.left_button_action = CameraController.ORBIT;
	this.right_button_action = CameraController.PAN;
	this.middle_button_action = CameraController.PAN;
	this.mouse_wheel_action = CameraController.CHANGE_DISTANCE;

	this.keyboard_walk = false;
	this.lock_mouse = false;

	this.rot_speed = 1;
	this.walk_speed = 10;
	this.wheel_speed = 1;
	this.smooth = false;

	this._moving = vec3.fromValues(0,0,0);

	this._collision_none = vec3.create();
	this._collision_left = vec3.create();
	this._collision_middle = vec3.create();
	this._collision_right = vec3.create();
	this._dragging = false; //true if the mousedown was caught so the drag belongs to this component
	this._camera = null;

	this.configure(o);
}

CameraController.NONE = 0; //no action

CameraController.ORBIT = 1; //orbits around the center
CameraController.ORBIT_HORIZONTAL = 2; //orbits around the center only around Y axis

CameraController.ROTATE = 5; //rotates relative to the camera
CameraController.ROTATE_HORIZONTAL = 6; //moves relative to the camera

CameraController.PAN = 10; //moves paralel to the near plane
CameraController.PAN_XZ = 11; //pans only in the XZ plane

CameraController.CHANGE_DISTANCE = 15; //scales the center from eye to center
CameraController.WALK = 16; //moves forward or backward
CameraController.ELEVATE = 17; //moves forward or backward
CameraController.FOV = 18; //changes zoom (FOV)


CameraController.icon = "mini-icon-cameracontroller.png";

CameraController.mode_values = { 
		"None": CameraController.NONE,
		"Orbit": CameraController.ORBIT,
		"Orbit Horizontal": CameraController.ORBIT_HORIZONTAL, 
		"Rotate": CameraController.ROTATE,
		"Rotate Horizontal": CameraController.ROTATE_HORIZONTAL, 
		"Pan": CameraController.PAN,
		"Pan XZ": CameraController.PAN_XZ,
		"Change Distance": CameraController.CHANGE_DISTANCE,
		"Walk": CameraController.WALK,
		"Elevate": CameraController.ELEVATE
	};

CameraController.wheel_values = { 
		"None": CameraController.NONE,
		"Change Distance": CameraController.CHANGE_DISTANCE,
		"FOV": CameraController.FOV,
		"Walk": CameraController.WALK,
		"Elevate": CameraController.ELEVATE
};

CameraController["@no_button_action"] = { type:"enum", values: CameraController.mode_values };
CameraController["@left_button_action"] = { type:"enum", values: CameraController.mode_values };
CameraController["@middle_button_action"] = { type:"enum", values: CameraController.mode_values };
CameraController["@right_button_action"] = { type:"enum", values: CameraController.mode_values };
CameraController["@mouse_wheel_action"] = { type:"enum", values: CameraController.wheel_values };

CameraController.prototype.onAddedToScene = function( scene )
{
	LEvent.bind( scene, "start",this.onStart,this);
	LEvent.bind( scene, "finish",this.onFinish,this);
	LEvent.bind( scene, "mousedown",this.onMouse,this);
	LEvent.bind( scene, "mousemove",this.onMouse,this);
	LEvent.bind( scene, "mousewheel",this.onMouse,this);
	LEvent.bind( scene, "touchstart",this.onTouch,this);
	LEvent.bind( scene, "touchmove",this.onTouch,this);
	LEvent.bind( scene, "touchend",this.onTouch,this);
	LEvent.bind( scene, "keydown",this.onKey,this);
	LEvent.bind( scene, "keyup",this.onKey,this);
	LEvent.bind( scene, "update",this.onUpdate,this);
}

CameraController.prototype.onRemovedFromScene = function( scene )
{
	LEvent.unbindAll( scene, this );
}

CameraController.prototype.onStart = function(e)
{
	if(this.lock_mouse)
	{
		LS.Input.lockMouse(true);
	}
}

CameraController.prototype.onFinish = function(e)
{
	if(this.lock_mouse)
	{
		LS.Input.lockMouse(false);
	}
}

CameraController.prototype.onUpdate = function(e)
{
	if(!this._root || !this.enabled) 
		return;

	//get first camera attached to this node
	var cam = this._root.camera;

	//no camera or disabled, then nothing to do
	if(!cam || !cam.enabled)
		return;

	if(this._root.transform) //attached to node
	{
	}
	else 
	{
		if(this.keyboard_walk)
		{
			//move using the delta vector
			if(this._moving[0] != 0 || this._moving[1] != 0 || this._moving[2] != 0)
			{
				var delta = cam.getLocalVector( this._moving );
				vec3.scale(delta, delta, this.walk_speed );
				cam.move(delta);
				cam.updateMatrices();
			}
		}
	}

	if(this.smooth)
	{
		this._root.scene.requestFrame();
	}
}

CameraController.prototype.processMouseButtonDownEvent = function( mode, mouse_event, coll_point )
{
	var node = this._root;
	var cam = this._camera = node.camera;
	if(!cam || !cam.enabled)
		return;

	var is_global_camera = node._is_root;
	var changed = false;

	if(mode == CameraController.PAN)
		this.testPerpendicularPlane( mouse_event.canvasx, gl.canvas.height - mouse_event.canvasy, cam.getCenter(), coll_point );
	else if(mode == CameraController.PAN_XZ)
		this.testOriginPlane( mouse_event.canvasx, gl.canvas.height - mouse_event.canvasy, coll_point );

	return changed;
}

CameraController.prototype.processMouseButtonMoveEvent = function( mode, mouse_event, coll_point )
{
	var node = this._root;
	var cam = this._camera = node.camera;
	if(!cam || !cam.enabled)
		return;

	var is_global_camera = node._is_root;
	var changed = false;

	if(mode == CameraController.NONE)
		return false;

	if(mode == CameraController.ORBIT)
	{
		var yaw = mouse_event.deltax * this.rot_speed;
		var pitch = -mouse_event.deltay * this.rot_speed;

		//yaw rotation
		if( Math.abs(yaw) > 0.0001 )
		{
			if(is_global_camera)
			{
				cam.orbit( -yaw, [0,1,0] );
				cam.updateMatrices();
			}
			else
			{
				var eye = cam.getEye();
				node.transform.globalToLocal( eye, eye );
				node.transform.orbit( -yaw, [0,1,0], eye );
				cam.updateMatrices();
			}
			changed = true;
		}

		//pitch rotation
		var right = cam.getRight();
		var front = cam.getFront();
		var up = cam.getUp();
		var problem_angle = vec3.dot( up, front );
		if( !(problem_angle > 0.99 && pitch > 0 || problem_angle < -0.99 && pitch < 0)) //avoid strange behaviours
		{
			if(is_global_camera)
			{
				cam.orbit( -pitch, right, this.orbit_center );
			}
			else
			{
				var eye = cam.getEye();
				node.transform.globalToLocal( eye, eye );
				node.transform.orbit( -pitch, right, eye );
			}
			changed = true;
		}
	}
	else if(mode == CameraController.ORBIT_HORIZONTAL)
	{
		var yaw = mouse_event.deltax * this.rot_speed;

		if( Math.abs(yaw) > 0.0001 )
		{
			if(is_global_camera)
			{
				cam.orbit( -yaw, [0,1,0] );
				cam.updateMatrices();
			}
			else
			{
				var eye = cam.getEye();
				node.transform.globalToLocal( eye, eye );
				node.transform.orbit( -yaw, [0,1,0], eye );
				cam.updateMatrices();
			}
			changed = true;
		}
	}
	else if(mode == CameraController.ROTATE || mode == CameraController.ROTATE_HORIZONTAL )
	{
		var top = LS.TOP; //cam.getLocalVector(LS.TOP);
		cam.rotate( -mouse_event.deltax * this.rot_speed * 0.2, top );
		cam.updateMatrices();

		if( mode == CameraController.ROTATE )
		{
			var right = cam.getLocalVector(LS.RIGHT);
			if(is_global_camera)
			{
				cam.rotate(-mouse_event.deltay * this.rot_speed * 0.2,right);
				cam.updateMatrices();
			}
			else
			{
				node.transform.rotate( -mouse_event.deltay * this.rot_speed * 0.2, LS.RIGHT );
				cam.updateMatrices();
			}
		}
		changed = true;
	}
	else if(mode == CameraController.PAN)
	{
		var collision = vec3.create();
		var center = vec3.create();
		var delta = vec3.create();

		cam.getCenter( center );
		this.testPerpendicularPlane( mouse_event.canvasx, gl.canvas.height - mouse_event.canvasy, center, collision );
		vec3.sub( delta, coll_point, collision );

		if(is_global_camera)
		{
			cam.move( delta );
			cam.updateMatrices();
		}
		else
		{
			node.transform.translateGlobal( delta );
			cam.updateMatrices();
		}

		changed = true;	
	}
	else if(mode == CameraController.PAN_XZ)
	{
		var collision = vec3.create();
		var delta = vec3.create();
		this.testOriginPlane( mouse_event.canvasx, gl.canvas.height - mouse_event.canvasy, collision );
		vec3.sub( delta, coll_point, collision );
		if(is_global_camera)
			cam.move( delta );
		else
			node.transform.translateGlobal( delta );
		cam.updateMatrices();

		changed = true;
	}
	else if(mode == CameraController.CHANGE_DISTANCE)
	{
		var factor = mouse_event.deltay * this.wheel_speed;
		cam.orbitDistanceFactor(1 + factor * -0.05 );
		cam.updateMatrices();
		changed = true;
	}
	else if(mode == CameraController.WALK)
	{
		var delta = cam.getLocalVector( [0,0, mouse_event.deltay * this.walk_speed] );
		cam.move(delta);
		cam.updateMatrices();
		changed = true;
	}
	else if(mode == CameraController.ELEVATE)
	{
		cam.move([0,mouse_event.deltay * this.walk_speed,0]);
		cam.updateMatrices();
		changed = true;
	}

	return changed;
}

//triggered on mouse move, or button clicked
CameraController.prototype.onMouse = function(e, mouse_event)
{
	if(!this._root || !this.enabled) 
		return;
	
	var node = this._root;
	var cam = node.camera;
	if(!cam || !cam.enabled)
		return;

	var is_global_camera = node._is_root;

	if(!mouse_event)
		mouse_event = e;

	if(mouse_event.eventType == "mousewheel")
	{
		var wheel = mouse_event.wheel > 0 ? 1 : -1;

		switch( this.mouse_wheel_action )
		{
			case CameraController.CHANGE_DISTANCE: 
				cam.orbitDistanceFactor(1 + wheel * -0.05 * this.wheel_speed );
				cam.updateMatrices();
				break;
			case CameraController.FOV: 
				cam.fov = cam.fov - wheel;
				cam.updateMatrices();
				break;
		}

		node.scene.requestFrame();
		return;
	}

	var changed = false;

	if(mouse_event.eventType == "mousedown")
	{
		if( LS.Input.Mouse.isButtonPressed( GL.LEFT_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonDownEvent( this.left_button_action, mouse_event, this._collision_left );
		if( LS.Input.Mouse.isButtonPressed( GL.MIDDLE_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonDownEvent( this.middle_button_action, mouse_event, this._collision_middle );
		if( LS.Input.Mouse.isButtonPressed( GL.RIGHT_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonDownEvent( this.right_button_action, mouse_event, this._collision_right );
		this._dragging = true;
	}
	if(!mouse_event.dragging)
		this._dragging = false;

	//mouse move
	if( mouse_event.eventType == "mousemove" && this.lock_mouse && document.pointerLockElement )
		changed |= this.processMouseButtonMoveEvent( this.no_button_action, mouse_event, this._collision_none  );

	//regular mouse dragging
	if( mouse_event.eventType == "mousemove" && mouse_event.dragging && this._dragging )
	{
		if( LS.Input.Mouse.isButtonPressed( GL.LEFT_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonMoveEvent( this.left_button_action, mouse_event, this._collision_left  );
		if( LS.Input.Mouse.isButtonPressed( GL.MIDDLE_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonMoveEvent( this.middle_button_action, mouse_event, this._collision_middle  );
		if( LS.Input.Mouse.isButtonPressed( GL.RIGHT_MOUSE_BUTTON ) )
			changed |= this.processMouseButtonMoveEvent( this.right_button_action, mouse_event, this._collision_right  );
	}

	if(changed)
		this._root.scene.requestFrame();
}

//manage pinching and dragging two fingers in a touch pad
CameraController.prototype.onTouch = function( e, touch_event)
{
	if(!this._root || !this.enabled) 
		return;
	
	var node = this._root;
	var cam = node.camera;
	if(!cam || !cam.enabled)
		return;

	var is_global_camera = node._is_root;

	if(!touch_event)
		touch_event = e;

	//console.log( e );
	//touch!
	if( touch_event.type == "touchstart" )
	{
		if( touch_event.touches.length == 2)
		{
			var distx = touch_event.touches[0].clientX - touch_event.touches[1].clientX;
			var disty = touch_event.touches[0].clientY - touch_event.touches[1].clientY;
			this._touch_distance = Math.sqrt(distx*distx + disty*disty);
			this._touch_center = [ (touch_event.touches[0].clientX + touch_event.touches[1].clientX) * 0.5,
									(touch_event.touches[0].clientY + touch_event.touches[1].clientY) * 0.5 ];
			touch_event.preventDefault();
			return false; //block
		}
	}
	if( touch_event.type == "touchmove" )
	{
		if(touch_event.touches.length == 2)
		{
			var distx = touch_event.touches[0].clientX - touch_event.touches[1].clientX;
			var disty = touch_event.touches[0].clientY - touch_event.touches[1].clientY;
			var distance = Math.sqrt(distx*distx + disty*disty);
			if(distance < 0.1)
				distance = 0.1;
			var delta_dist = this._touch_distance / distance;
			this._touch_distance = distance;
			//console.log( delta_dist );
			cam.orbitDistanceFactor( delta_dist );
			cam.updateMatrices();

			var delta_x = (touch_event.touches[0].clientX + touch_event.touches[1].clientX) * 0.5 - this._touch_center[0];
			var delta_y = (touch_event.touches[0].clientY + touch_event.touches[1].clientY) * 0.5 - this._touch_center[1];
			var panning_factor = cam.focalLength / gl.canvas.width;
			cam.panning( -delta_x, delta_y, panning_factor );
			this._touch_center[0] = (touch_event.touches[0].clientX + touch_event.touches[1].clientX) * 0.5;
			this._touch_center[1] = (touch_event.touches[0].clientY + touch_event.touches[1].clientY) * 0.5;

			cam.updateMatrices();
			this._root.scene.requestFrame();
			touch_event.preventDefault();
			return false; //block
		}
	}
}

CameraController.prototype.testOriginPlane = function(x,y, result)
{
	var cam = this._root.camera;
	var ray = cam.getRayInPixel( x, gl.canvas.height - y );
	var result = result || vec3.create();

	//test against plane at 0,0,0
	if( geo.testRayPlane( ray.origin, ray.direction, LS.ZEROS, LS.TOP, result ) )
		return true;
	return false;
}

CameraController.prototype.testPerpendicularPlane = function(x,y, center, result)
{
	var cam = this._root.camera;
	var ray = cam.getRayInPixel( x, gl.canvas.height - y );

	var front = cam.getFront();
	var center = center || cam.getCenter();
	var result = result || vec3.create();

	//test against plane
	if( geo.testRayPlane( ray.origin, ray.direction, center, front, result ) )
		return true;
	return false;
}

CameraController.prototype.onKey = function(e, key_event)
{
	if(!this._root || !this.enabled) 
		return;

	//trace(key_event);
	if(key_event.keyCode == 87)
	{
		if(key_event.type == "keydown")
			this._moving[2] = -1;
		else
			this._moving[2] = 0;
	}
	else if(key_event.keyCode == 83)
	{
		if(key_event.type == "keydown")
			this._moving[2] = 1;
		else
			this._moving[2] = 0;
	}
	else if(key_event.keyCode == 65)
	{
		if(key_event.type == "keydown")
			this._moving[0] = -1;
		else
			this._moving[0] = 0;
	}
	else if(key_event.keyCode == 68)
	{
		if(key_event.type == "keydown")
			this._moving[0] = 1;
		else
			this._moving[0] = 0;
	}
	
	if(key_event.keyCode == 16) //shift in windows chrome
	{
		if(key_event.type == "keydown")
			vec3.scale( this._moving, this._moving, 10 );
	}

	//LEvent.trigger(Scene,"change");
}

LS.registerComponent( CameraController );