Răsfoiți Sursa

Box point collision working properly

tentone 6 ani în urmă
părinte
comite
97138ca84b

+ 8 - 2
index.html

@@ -12,8 +12,10 @@
 
 	<script type="text/javascript" src="source/controls/ViewportControls.js"></script>
 
+	<script type="text/javascript" src="source/input/Key.js"></script>
+	<script type="text/javascript" src="source/input/Mouse.js"></script>
+
 	<script type="text/javascript" src="source/objects/Image.js"></script>
-	<script type="text/javascript" src="source/objects/Rect.js"></script>
 	<script type="text/javascript" src="source/objects/Text.js"></script>
 	<script type="text/javascript" src="source/objects/Box.js"></script>
 	<script type="text/javascript" src="source/objects/ConnectionLine.js"></script>
@@ -38,9 +40,13 @@
 		o.add(i);
 
 		var re = new Box();
-		re.position.set(50, 50);
+		re.position.set(0, 0);
 		o.add(re);
 
+		var ra = new Box();
+		ra.position.set(200, 0);
+		re.add(ra);
+
 		var rb = new Box();
 		rb.position.set(50, 300);
 		o.add(rb);

+ 28 - 8
source/Object2D.js

@@ -49,6 +49,20 @@ function Object2D()
 	 */
 	this.matrix = new Matrix();
 
+	/**
+	 * Global transformation matrix multiplied by the parent matrix.
+	 *
+	 * Used to transform the object before projecting into screen coordinates.
+	 */
+	this.globalMatrix = new Matrix();
+
+	/**
+	 * Inverse of the global matrix.
+	 *
+	 * Used to convert mouse input points into object coordinates.
+	 */
+	this.inverseGlobalMatrix = new Matrix();
+
 	/**
 	 * If true the matrix is updated before rendering the object.
 	 */
@@ -98,14 +112,12 @@ Object2D.prototype.remove = function(object)
 	}
 };
 
-Object2D.prototype.onPressDown = function(point){};
-Object2D.prototype.onPressUp = function(point){};
-Object2D.prototype.onOver = function(point){};
+Object2D.prototype.onOver = function(){};
 
 /**
  * Check if a point is inside of the object.
  */
-Object2D.prototype.inside = function(point)
+Object2D.prototype.isInside = function(point)
 {
 	return false;
 };
@@ -115,11 +127,19 @@ Object2D.prototype.inside = function(point)
  */
 Object2D.prototype.updateMatrix = function(context)
 {
-	//if(this.matrixNeedsUpdate)
-	//{
+	if(this.matrixNeedsUpdate)
+	{
 		this.matrix.compose(this.position.x, this.position.y, this.scale.x, this.scale.y, this.rotation);
-		this.matrixNeedsUpdate = false;
-	//}
+		this.globalMatrix.copy(this.matrix);
+
+		if(this.parent !== null)
+		{	
+			this.globalMatrix.premultiply(this.parent.globalMatrix);
+		}
+
+		this.inverseGlobalMatrix = this.globalMatrix.getInverse()
+		//this.matrixNeedsUpdate = false;
+	}
 };
 
 /**

+ 22 - 4
source/Renderer.js

@@ -12,6 +12,9 @@ function Renderer(canvas)
 	this.canvas = canvas;
 
 	this.context = canvas.getContext("2d");
+
+	this.mouse = new Mouse(canvas);
+	this.mouse.setCanvas(canvas);
 }
 
 /**
@@ -19,6 +22,9 @@ function Renderer(canvas)
  */
 Renderer.prototype.render = function(object, viewport)
 {
+	this.mouse.update();
+
+	var mouse = this.mouse;
 	var context = this.context;
 
 	// Clear canvas
@@ -28,14 +34,26 @@ Renderer.prototype.render = function(object, viewport)
 	// Update viewport transform matrix
 	viewport.updateMatrix();
 
-	// Render into the canvas
+	// Update object transformation matrices
 	object.traverse(function(child)
 	{
-		viewport.matrix.setContextTransform(context);
-
 		child.updateMatrix();
 		
-		child.matrix.tranformContext(context);
+		var point = mouse.position.clone();
+		point = viewport.inverseMatrix.transformPoint(point);
+		point = child.inverseGlobalMatrix.transformPoint(point);
+
+		if(child.isInside(point))
+		{
+			child.onOver();
+		}
+	});
+
+	// Render into the canvas
+	object.traverse(function(child)
+	{
+		viewport.matrix.setContextTransform(context);		
+		child.globalMatrix.tranformContext(context);
 		child.draw(context);
 	});
 };

+ 8 - 2
source/Viewport.js

@@ -32,6 +32,11 @@ function Viewport()
 	 */
 	this.matrix = new Matrix();
 
+	/**
+	 * Inverse of the local transformation matrix.
+	 */
+	this.inverseMatrix = new Matrix();
+
 	/**
 	 * If true the matrix is updated before rendering the object.
 	 */
@@ -46,10 +51,11 @@ function Viewport()
  */
 Viewport.prototype.updateMatrix = function(context, canvas)
 {
-	if(true) //this.matrixNeedsUpdate)
+	if(this.matrixNeedsUpdate)
 	{
 		this.matrix.compose(this.position.x, this.position.y, this.scale, this.scale, this.rotation);
-		this.matrixNeedsUpdate = false;
+		this.inverseMatrix = this.matrix.getInverse();
+		//this.matrixNeedsUpdate = false;
 	}
 };
 

+ 114 - 0
source/input/Key.js

@@ -0,0 +1,114 @@
+"use strict";
+
+/**
+ * Key is used by Keyboard, Mouse, etc, to represent a key state.
+ *
+ * @class Key
+ * @module Input
+*/
+function Key()
+{
+	/**
+	 * Indicates if this key is currently pressed.
+	 * @property pressed
+	 * @default false
+	 * @type {boolean}
+	 */
+	this.pressed = false;
+
+	/**
+	 * Indicates if this key was just pressed.
+	 * @property justPressed
+	 * @default false
+	 * @type {boolean}
+	 */
+	this.justPressed = false;
+	
+	/**
+	 * Indicates if this key was just released.
+	 * @property justReleased
+	 * @default false
+	 * @type {boolean}
+	 */
+	this.justReleased = false;
+}
+
+/**
+ * Down
+ * @attribute DOWN
+ * @type {Number}
+ */
+Key.DOWN = -1;
+
+/**
+ * Up
+ * @attribute UP
+ * @type {Number}
+ */
+Key.UP = 1;
+
+/**
+ * Reset
+ * @attribute RESET
+ * @type {Number}
+ */
+Key.RESET = 0;
+
+Key.prototype.constructor = Key;
+
+/**
+ * Update Key status based on new key state.
+ * 
+ * @method update
+ */
+Key.prototype.update = function(action)
+{
+	this.justPressed = false;
+	this.justReleased = false;
+
+	if(action === Key.DOWN)
+	{
+		if(this.pressed === false)
+		{
+			this.justPressed = true;
+		}
+		this.pressed = true;
+	}
+	else if(action === Key.UP)
+	{
+		if(this.pressed)
+		{
+			this.justReleased = true;
+		}
+		this.pressed = false;
+	}
+	else if(action === Key.RESET)
+	{
+		this.justReleased = false;
+		this.justPressed = false;
+	}
+};
+
+/**
+ * Set this key attributes manually.
+ * 
+ * @method set
+ */
+Key.prototype.set = function(justPressed, pressed, justReleased)
+{
+	this.justPressed = justPressed;
+	this.pressed = pressed;
+	this.justReleased = justReleased;
+};
+
+/**
+ * Reset key to default values.
+ * 
+ * @method reset
+*/
+Key.prototype.reset = function()
+{
+	this.justPressed = false;
+	this.pressed = false;
+	this.justReleased = false;
+};

+ 495 - 0
source/input/Mouse.js

@@ -0,0 +1,495 @@
+"use strict";
+
+/**
+ * Mouse instance for input in sync with the running 3D application.
+ *
+ * The mouse object provided by scripts is automatically updated by the runtime handler.
+ * 
+ * @class Mouse
+ * @module Input
+ * @param {DOM} domElement DOM element to craete the mouse events.
+ * @param {Boolean} dontInitialize If true the mouse events are not created.
+ */
+function Mouse(domElement, dontInitialize)
+{
+	//Raw data
+	this._keys = new Array(5);
+	this._position = new Vector2(0, 0);
+	this._positionUpdated = false;
+	this._delta = new Vector2(0, 0);
+	this._wheel = 0;
+	this._wheelUpdated = false;
+	this._doubleClicked = new Array(5);
+
+	/**
+	 * Array with mouse buttons status.
+	 *
+	 * @type {array}
+	 * @property keys
+	 */
+	this.keys = new Array(5);
+
+	/**
+	 * Mouse position inside of the window (coordinates in window space).
+	 *
+	 * @type {Vector2}
+	 * @property position
+	 */
+	this.position = new Vector2(0, 0);
+
+	/**
+	 * Mouse movement (coordinates in window space).
+	 *
+	 * @type {Vector2}
+	 * @property delta
+	 */
+	this.delta = new Vector2(0, 0);
+
+	/**
+	 * Mouse scroll wheel movement.
+	 *
+	 * @type {Number}
+	 * @property wheel
+	 */
+	this.wheel = 0;
+	
+	/**
+	 * Indicates a button of the mouse was double clicked.
+	 *
+	 * @type {Array}
+	 * @property doubleClicked
+	 */
+	this.doubleClicked = new Array(5);
+
+	/**
+	 * DOM element where to attach the mouse events.
+	 *
+	 * @property domElement
+	 * @type {DOM}
+	 */
+	this.domElement = (domElement !== undefined) ? domElement : window;
+
+	/**
+	 * Canvas attached to this mouse instance used to calculate position and delta in element space coordinates.
+	 *
+	 * @type {DOM}
+	 * @property canvas
+	 */
+	this.canvas = null;
+	
+	/**
+	 * Event manager responsible for updating the raw data variables.
+	 *
+	 * Diferent events are used depending on the host platform.
+	 *
+	 * When the update method is called the raw data is reset.
+	 *
+	 * @property events
+	 * @type {EventManager} 
+	 */
+	this.events = new EventManager();
+
+	//Initialize key instances
+	for(var i = 0; i < 5; i++)
+	{
+		this._doubleClicked[i] = false;
+		this.doubleClicked[i] = false;
+		this._keys[i] = new Key();
+		this.keys[i] = new Key();
+	}
+
+	//Self pointer
+	var self = this;
+
+	//Scroll wheel
+	if(window.onmousewheel !== undefined)
+	{
+		//Chrome, edge
+		this.events.add(this.domElement, "mousewheel", function(event)
+		{
+			self._wheel = event.deltaY;
+			self._wheelUpdated = true;
+		});
+	}
+	else if(window.addEventListener !== undefined)
+	{
+		//Firefox
+		this.events.add(this.domElement, "DOMMouseScroll", function(event)
+		{
+			self._wheel = event.detail * 30;
+			self._wheelUpdated = true;
+		});
+	}
+	else
+	{
+		this.events.add(this.domElement, "wheel", function(event)
+		{
+			self._wheel = event.deltaY;
+			self._wheelUpdated = true;
+		});
+	}
+
+	//Touchscreen input events
+	if(window.ontouchstart !== undefined || navigator.msMaxTouchPoints > 0)
+	{
+		//Auxiliar variables to calculate touch delta
+		var lastTouch = new Vector2(0, 0);
+
+		//Touch start event
+		this.events.add(this.domElement, "touchstart", function(event)
+		{
+			var touch = event.touches[0];
+
+			self.updatePosition(touch.clientX, touch.clientY, 0, 0);
+			self.updateKey(Mouse.LEFT, Key.DOWN);
+
+			lastTouch.set(touch.clientX, touch.clientY);
+		});
+
+		//Touch end event
+		this.events.add(this.domElement, "touchend", function(event)
+		{
+			self.updateKey(Mouse.LEFT, Key.UP);
+		});
+
+		//Touch cancel event
+		this.events.add(this.domElement, "touchcancel", function(event)
+		{
+			self.updateKey(Mouse.LEFT, Key.UP);
+		});
+
+		//Touch move event
+		this.events.add(document.body, "touchmove", function(event)
+		{
+			var touch = event.touches[0];
+			self.updatePosition(touch.clientX, touch.clientY, touch.clientX - lastTouch.x, touch.clientY - lastTouch.y);
+			lastTouch.set(touch.clientX, touch.clientY);
+		});
+	}
+
+	//Move
+	this.events.add(this.domElement, "mousemove", function(event)
+	{
+		self.updatePosition(event.clientX, event.clientY, event.movementX, event.movementY);
+	});
+
+	//Button pressed
+	this.events.add(this.domElement, "mousedown", function(event)
+	{
+		self.updateKey(event.which - 1, Key.DOWN);
+	});
+
+	//Button released
+	this.events.add(this.domElement, "mouseup", function(event)
+	{
+		self.updateKey(event.which - 1, Key.UP);
+	});
+
+	//Drag start
+	this.events.add(this.domElement, "dragstart", function(event)
+	{
+		self.updateKey(event.which - 1, Key.UP);
+	});
+
+	//Mouse double click
+	this.events.add(this.domElement, "dblclick", function(event)
+	{	
+		self._doubleClicked[event.which - 1] = true;
+	});
+
+	if(dontInitialize !== true)
+	{
+		this.create();
+	}
+}
+
+Mouse.prototype = Mouse;
+Mouse.prototype.constructor = Mouse;
+
+/**
+ * Left mouse button.
+ *
+ * @attribute LEFT
+ * @type {Number}
+ */
+Mouse.LEFT = 0;
+
+/**
+ * Middle mouse button.
+ *
+ * @attribute MIDDLE
+ * @type {Number}
+ */
+Mouse.MIDDLE = 1;
+
+/**
+ * Right mouse button.
+ *
+ * @attribute RIGHT
+ * @type {Number}
+ */
+Mouse.RIGHT = 2;
+
+/**
+ * Back mouse navigation button.
+ *
+ * @attribute BACK
+ * @type {Number}
+ */
+Mouse.BACK = 3;
+
+/**
+ * Forward mouse navigation button.
+ *
+ * @attribute FORWARD
+ * @type {Number}
+ */
+Mouse.FORWARD = 4;
+
+/**
+ * Element to be used for coordinates calculation relative to that canvas.
+ * 
+ * @method setCanvas
+ * @param {DOM} canvas Canvas to be attached to the Mouse instance
+ */
+Mouse.setCanvas = function(element)
+{
+	this.canvas = element;
+
+	element.mouseInside = false;
+
+	element.addEventListener("mouseenter", function()
+	{
+		this.mouseInside = true;
+	});
+
+	element.addEventListener("mouseleave", function()
+	{
+		this.mouseInside = false;
+	});
+};
+
+/**
+ * Check if mouse is inside attached canvas (updated async).
+ * 
+ * @method insideCanvas
+ * @return {boolean} True if mouse is currently inside the canvas
+ */
+Mouse.insideCanvas = function()
+{
+	return this.canvas !== null && this.canvas.mouseInside;
+};
+
+/**
+ * Set mouse lock state.
+ * 
+ * @method setLock
+ * @param {boolean} value If true pointer lock will be requested for the canvas attached to the Mouse instance
+ */
+Mouse.setLock = function(value)
+{
+	if(this.canvas !== null)
+	{
+		if(value)
+		{
+			if(this.canvas.requestPointerLock)
+			{
+				this.canvas.requestPointerLock();
+			}
+			else if(this.canvas.mozRequestPointerLock)
+			{
+				this.canvas.mozRequestPointerLock();
+			}
+			else if(this.canvas.webkitRequestPointerLock)
+			{
+				this.canvas.webkitRequestPointerLock();
+			}
+		}
+		else
+		{
+			if(document.exitPointerLock)
+			{
+				document.exitPointerLock();
+			}
+			else if(document.mozExitPointerLock)
+			{
+				document.mozExitPointerLock();
+			}
+			else if(document.webkitExitPointerLock)
+			{
+				document.webkitExitPointerLock();
+			}
+		}
+	}
+};
+
+/**
+ * Check if mouse button is currently pressed.
+ * 
+ * @method buttonPressed
+ * @param {Number} button Button to check status of
+ * @return {boolean} True if button is currently pressed
+ */
+Mouse.buttonPressed = function(button)
+{
+	return this.keys[button].pressed;
+};
+
+/**
+ * Check if Mouse button was double clicked.
+ * 
+ * @method buttonDoubleClicked
+ * @param {Number} button Button to check status of
+ * @return {boolean} True if some mouse button was just double clicked
+ */
+Mouse.buttonDoubleClicked = function(button)
+{
+	return this.doubleClicked[button];
+};
+
+/**
+ * Check if a mouse button was just pressed.
+ * 
+ * @method buttonJustPressed
+ * @param {Number} button Button to check status of
+ * @return {boolean} True if button was just pressed
+ */
+Mouse.buttonJustPressed = function(button)
+{
+	return this.keys[button].justPressed;
+};
+
+/**
+ * Check if a mouse button was just released.
+ * 
+ * @method buttonJustReleased
+ * @param {Number} button Button to check status of
+ * @return {boolean} True if button was just released
+ */
+Mouse.buttonJustReleased = function(button)
+{
+	return this.keys[button].justReleased;
+};
+
+/**
+ * Update mouse position.
+ *
+ * Automatically called by the runtime.
+ * 
+ * @method updatePosition
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} xDiff
+ * @param {Number} yDiff
+ */
+Mouse.updatePosition = function(x, y, xDiff, yDiff)
+{
+	if(this.canvas !== null)
+	{
+		var rect = this.canvas.getBoundingClientRect();
+		x -= rect.left;
+		y -= rect.top;
+	}
+
+	this._position.set(x, y);
+	this._delta.x += xDiff;
+	this._delta.y += yDiff;
+	this._positionUpdated = true;
+};
+
+/**
+ * Update a mouse button.
+ * 
+ * Automatically called by the runtime.
+ *
+ * @method updateKey
+ * @param {Number} button
+ * @param {Number} action
+ */
+Mouse.updateKey = function(button, action)
+{
+	if(button > -1)
+	{
+		this._keys[button].update(action);
+	}
+};
+
+/**
+ * Update mouse buttons state, position, wheel and delta synchronously.
+ * 
+ * @method update
+ */
+Mouse.update = function()
+{
+	//Update mouse keys state
+	for(var i = 0; i < 5; i++)
+	{
+		if(this._keys[i].justPressed && this.keys[i].justPressed)
+		{
+			this._keys[i].justPressed = false;
+		}
+		if(this._keys[i].justReleased && this.keys[i].justReleased)
+		{
+			this._keys[i].justReleased = false;
+		}
+
+		this.keys[i].set(this._keys[i].justPressed, this._keys[i].pressed, this._keys[i].justReleased);
+
+		//Update mouse double click
+		if(this._doubleClicked[i] === true)
+		{
+			this.doubleClicked[i] = true;
+			this._doubleClicked[i] = false;
+		}
+		else
+		{
+			this.doubleClicked[i] = false;
+		}
+	}
+
+	//Update mouse wheel
+	if(this._wheelUpdated)
+	{
+		this.wheel = this._wheel;
+		this._wheelUpdated = false;
+	}
+	else
+	{
+		this.wheel = 0;
+	}
+
+	//Update mouse Position if needed
+	if(this._positionUpdated)
+	{
+		this.delta.copy(this._delta);
+		this.position.copy(this._position);
+
+		this._delta.set(0,0);
+		this._positionUpdated = false;
+	}
+	else
+	{
+		this.delta.x = 0;
+		this.delta.y = 0;
+	}
+};
+
+/**
+ * Create mouse events.
+ * 
+ * @method create
+ */
+Mouse.create = function()
+{
+	this.events.create();
+};
+
+/**
+ * Dispose mouse events.
+ * 
+ * @method dispose
+ */
+Mouse.dispose = function()
+{
+	this.events.destroy();
+};

+ 15 - 9
source/math/Matrix.js

@@ -18,6 +18,14 @@ function Matrix(values)
 	}
 }
 
+/**
+ * Copy the content of another matrix and store in this one.
+ */
+Matrix.prototype.copy = function(mat)
+{
+	this.m = mat.m.slice(0);
+};
+
 /**
  * Reset this matrix to indentity.
  */
@@ -97,6 +105,7 @@ Matrix.prototype.rotate = function(rad)
 	var m12 = this.m[1] * c + this.m[3] * s;
 	var m21 = this.m[0] * -s + this.m[2] * c;
 	var m22 = this.m[1] * -s + this.m[3] * c;
+
 	this.m[0] = m11;
 	this.m[1] = m12;
 	this.m[2] = m21;
@@ -131,14 +140,14 @@ Matrix.prototype.determinant = function()
 };
 
 /**
- * Get the ivnerse matrix.
+ * Get the inverse matrix.
  */
 Matrix.prototype.getInverse = function()
 {
 	var d = this.determinant();
 
-	return [this.m[3] * d, -this.m[1] * d, -this.m[2] * d, this.m[0] * d, d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]), d * (this.m[1] * this.m[4] - this.m[0] * this.m[5])];
-}
+	return new Matrix([this.m[3] * d, -this.m[1] * d, -this.m[2] * d, this.m[0] * d, d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]), d * (this.m[1] * this.m[4] - this.m[0] * this.m[5])]);
+};
 
 /**
  * Set a canvas context to use this transformation.
@@ -159,13 +168,10 @@ Matrix.prototype.tranformContext = function(context)
 /**
  * Transform a point using this matrix.
  */
-Matrix.prototype.transformPoint = function(px, py)
+Matrix.prototype.transformPoint = function(p)
 {
-	var x = px;
-	var y = py;
-
-	px = x * this.m[0] + y * this.m[2] + this.m[4];
-	py = x * this.m[1] + y * this.m[3] + this.m[5];
+	var px = p.x * this.m[0] + p.y * this.m[2] + this.m[4];
+	var py = p.x * this.m[1] + p.y * this.m[3] + this.m[5];
 
 	return new Vector2(px, py);
 };

+ 22 - 6
source/objects/Box.js

@@ -4,19 +4,35 @@ function Box(src)
 {
 	Object2D.call(this);
 
-	this.width = 100;
+	/**
+	 * Box object containing the size of the object.
+	 */
+	this.box = new Box2(new Vector2(-50, -35), new Vector2(50, 35));
 
-	this.height = 70;
+	/**
+	 * Color of the box border line.
+	 */
+	this.borderColor = "#000000";
 }
 
 Box.prototype = Object.create(Object2D.prototype);
 
+Box.prototype.onOver = function(point)
+{
+	this.borderColor = "#FF0000";
+};
+
+Box.prototype.isInside = function(point)
+{
+	return this.box.containsPoint(point);
+};
+
 Box.prototype.draw = function(context)
 {
-	var halfWidth = this.width / 2.0;
-	var halfHeight = this.height / 2.0;
+	var width = this.box.max.x - this.box.min.x;
+	var height = this.box.max.y - this.box.min.y;
 
 	context.lineWidth = 2;
-	context.strokeStyle = "#000000";
-	context.strokeRect(-halfWidth, -halfHeight, this.width, this.height);
+	context.strokeStyle = this.borderColor;
+	context.strokeRect(this.box.min.x, this.box.min.y, width, height);
 };

+ 6 - 0
source/objects/ConnectionLine.js

@@ -4,8 +4,14 @@ function ConnectionLine(src)
 {
 	Object2D.call(this);
 
+	/**
+	 * Initial point of the line.
+	 */
 	this.from = new Vector2();
 
+	/**
+	 * Final point of the line.
+	 */
 	this.to = new Vector2();
 }
 

+ 0 - 13
source/objects/Rect.js

@@ -1,13 +0,0 @@
-"use strict";
-
-function Rect(src)
-{
-	Object2D.call(this);
-}
-
-Rect.prototype = Object.create(Object2D.prototype);
-
-Rect.prototype.draw = function(context)
-{
-	context.fillRect(-20, -20, 40, 40);
-};