/* * PROJECT: FLARManager * http://transmote.com/flar * Copyright 2009, Eric Socolofsky * -------------------------------------------------------------------------------- * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this framework; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For further information please contact: * * http://transmote.com/flar */ package com.transmote.flar.marker { import __AS3__.vec.Vector; import com.transmote.flar.flarManagerInternal; import com.transmote.flar.source.IFLARSource; import com.transmote.flar.utils.geom.FLARGeomUtils; import com.transmote.flar.utils.smoother.IFLARMatrixSmoother; import flash.geom.Matrix; import flash.geom.Matrix3D; import flash.geom.Point; import flash.geom.Vector3D; /** * Container for information about a detected marker, including: *

*

*

* * @author Eric Socolofsky * @url http://transmote.com/flar/ * @see com.transmote.flar.marker.FLARMarkerEvent */ public class FLARMarker { public static const DEFAULT_UNSCALED_MARKER_WIDTH:Number = 80; private static const MAX_ADAPTIVE_SMOOTHING:Number = 15; private static const LOW_SPEED_EXPONENT:Number = 1.25; private static const HIGH_SPEED_EXPONENT:Number = 0.75; private static var sessionIdCounter:uint = 0; internal var _sessionId:int = -1; internal var _patternId:int; internal var _flarSource:IFLARSource; internal var _transformMatrix:Matrix3D; internal var _centerpoint2D:Point = null; internal var _centerpoint3D:Point = null; internal var _vector3D:Vector3D = null; protected var _corners:Vector.; private var _velocity:Vector3D; private var rotations:Vector3D; private var rotationSpeeds:Vector3D; private var removalAge:uint = 0; private var screenCenter:Point; private var matrixHistory:Vector.; /** * Constructor. * @param patternId ID of the pattern of the detected marker. * @param transformMatrix A Matrix3D object that describes the orientation of the detected marker relative to the viewer. * Apply this Matrix3D to a 3D model to make it appear tethered to the marker. * @param flarSource The IFLARSource from which the marker's image was extracted by the current tracker. */ public function FLARMarker (patternId:int, transformMatrix:Matrix3D, flarSource:IFLARSource) { this._patternId = patternId; this._transformMatrix = transformMatrix; this._flarSource = flarSource; this._velocity = new Vector3D(); if (this._flarSource.mirrored) { this.mirror(); } this.screenCenter = new Point(0.5*this._flarSource.sourceSize.width, 0.5*this._flarSource.sourceSize.height); } //-------------------------------// /** * ID unique to this FLARMarker in this session. * No two FLARMarker in a session share the same sessionId. */ public function get sessionId () :uint { return this._sessionId; } /** * ID of this FLARMarker's pattern. */ public function get patternId () :int { return this._patternId; } //---------------------------// //---------------// /** * A Matrix3D object that describes the orientation of the detected marker relative to the viewer. * Apply this Matrix3D to a 3D model to make it appear tethered to the marker. */ public function get transformMatrix () :Matrix3D { return this._transformMatrix; } /** * Return the transformation matrix of this FLARMarker as a Flash Matrix object, * for applying 2D transformations to Flash DisplayObject instances. * To apply to a DisplayObject, set displayObject.transform.matrix = flarMarker.matrix2D. */ public function get matrix2D () :Matrix { var matrix:Matrix = new Matrix(); var rotation:Number = Math.atan2(this.transformMatrix.rawData[4], this.transformMatrix.rawData[5]); if (this._flarSource.mirrored) { rotation = 2*Math.PI - rotation; } matrix.translate(-0.5*DEFAULT_UNSCALED_MARKER_WIDTH, -0.5*DEFAULT_UNSCALED_MARKER_WIDTH); matrix.rotate(rotation); matrix.scale(this.scale2D, this.scale2D); matrix.translate(this.x, this.y); return matrix; } /** * Return this.transformMatrix, adjusted for application directly to a DisplayObject * (by setting displayObject.transform.matrix3D = flarMarker.matrix3D). */ public function get matrix3D () :Matrix3D { var matrix3D:Matrix3D = this.transformMatrix.clone(); matrix3D.prependTranslation(-0.5*DEFAULT_UNSCALED_MARKER_WIDTH, -0.5*DEFAULT_UNSCALED_MARKER_WIDTH, 0); matrix3D.appendTranslation(this.x, this.y, 0); return matrix3D; } /** * The 2D X coordinate of the marker. */ public function get x () :Number { return this.centerpoint.x; // x position reported by this._transformMatrix is subject to z position // (distance from camera) and is relative to (0,0) instead of screen center. //return this._transformMatrix.rawData[12]; } /** * The 2D Y coordinate of the marker. */ public function get y () :Number { return this.centerpoint.y; // y position reported by this._transformMatrix is subject to z position // (distance from camera) and is relative to (0,0) instead of screen center. //return this._transformMatrix.rawData[13]; } /** * The Z coordinate of the marker. */ public function get z () :Number { return this._transformMatrix.rawData[14]; } /** * The centerpoint of the marker outline in the 2D space of the screen, * calculated as the average of the outline's four corner points. * To access the centerpoint reported by the tracker in three dimensions, * use FLARMarker.centerpoint3D. */ public function get centerpoint () :Point { if (!this._centerpoint2D) { this._centerpoint2D = this.calcCenterpoint2D(); } return this._centerpoint2D; } /** * Centerpoint of marker outline extracted from the tracker transformation matrix. * This centerpoint is determined based on the 3D location of the detected marker, * and is used by FLARManager in 3D calculations. * To avoid having to correct for Z location, use centerpoint2D. * * TODO: return a Vector3D with (x,y,z) coords. * update accordingly in FLARManager. */ public function get centerpoint3D () :Point { if (!this._centerpoint3D) { this._centerpoint3D = this.calcCenterpoint3D(this._transformMatrix); } return this._centerpoint3D; } /** * The centerpoint at the location toward which this FLARMarker is moving * (target location at end of smoothing animation). * * TODO: return a Vector3D with (x,y,z) coords. * update accordingly in FLARManager. */ public function get targetCenterpoint3D () :Point { if (!this.matrixHistory) { return this.centerpoint3D; } // find most recent stored transformation matrix var i:int = this.matrixHistory.length - 1; while (this.matrixHistory[i] == null) { i--; if (i == -1) { return this.centerpoint3D; } } return this.calcCenterpoint3D(this.matrixHistory[i]); } /** * A Vector3D instance that describes x, y, and z coordinates, * as well as rotationZ (stored as position.w). */ public function get position () :Vector3D { if (!this.rotations) { this.rotations = FLARGeomUtils.calcMatrix3DRotations(this._transformMatrix, true, this._flarSource.mirrored); } var position:Vector3D = this._transformMatrix.position.clone(); position.w = this.rotations.z; return position; } /** * The rotation of the marker along X axis. */ public function get rotationX () :Number { if (!this.rotations) { this.rotations = FLARGeomUtils.calcMatrix3DRotations(this._transformMatrix, true, this._flarSource.mirrored); } return this.rotations.x; } /** * The rotation of the marker along Y axis. */ public function get rotationY () :Number { if (!this.rotations) { this.rotations = FLARGeomUtils.calcMatrix3DRotations(this._transformMatrix, true, this._flarSource.mirrored); } return this.rotations.y; } /** * The rotation of the marker along Z axis. */ public function get rotationZ () :Number { if (!this.rotations) { this.rotations = FLARGeomUtils.calcMatrix3DRotations(this._transformMatrix, true, this._flarSource.mirrored); } return this.rotations.z; } /** * A Vector of four Points that describe the four points of the detected marker's outline. */ public function get corners () :Vector. { return this._corners; } /** * The scale of the marker for use in 2D applications. */ public function get scale2D () :Number { var diag1:Number = Point.distance(this.corners[0], this.corners[2]); var diag2:Number = Point.distance(this.corners[1], this.corners[3]); var size:Number = Math.sqrt(0.25 * (diag1*diag1 + diag2*diag2)); return (size / DEFAULT_UNSCALED_MARKER_WIDTH); } /** * The position toward which this FLARMarker is moving * (target position at end of smoothing animation). */ public function get targetPosition () :Vector3D { if (!this.matrixHistory) { return this._transformMatrix.position; } // find most recent stored transform matrix var i:int = this.matrixHistory.length - 1; while (this.matrixHistory[i] == null) { i--; if (i == -1) { return this._transformMatrix.position; } } return this.matrixHistory[i].position; } //---------------// //---------------------------------// /** * A Vector3D instance that describes change between the previous and current frames * in x, y, and z coordinates, as well as change in rotationZ (stored as velocity.w). */ public function get velocity () :Vector3D { return this._velocity; } /** * The length of the marker's (x,y) motion vector * between the previous and current frames. */ public function get motionSpeed2D () :Number { return Math.sqrt(this._velocity.x*this._velocity.x + this._velocity.y*this._velocity.y); } /** * The direction (in degrees) of the marker's (x,y) motion * between the previous and current frames. */ public function get motionDirection2D () :Number { return 180 * Math.atan2(this._velocity.y, this._velocity.x) / Math.PI; } /** * The amount of change (in degrees) in the marker's rotation along the x-axis * between the previous and current frames. */ public function get rotationSpeedX () :Number { return this.rotationSpeeds.x; } /** * The amount of change (in degrees) in the marker's rotation along the y-axis * between the previous and current frames. */ public function get rotationSpeedY () :Number { return this.rotationSpeeds.y; } /** * The amount of change (in degrees) in the marker's rotation along the z-axis * between the previous and current frames. */ public function get rotationSpeedZ () :Number { return this.rotationSpeeds.z; } //-----------------------------// //-------------------------------------------// /** * Copy the properties of a FLARMarker into this FLARMarker. * FLARMarkers are updated across frames by copying the properties of newly-detected markers. */ public function copy (otherMarker:FLARMarker) :void { this.calcRotationSpeeds(otherMarker); this.calcVelocity(otherMarker); this._patternId = otherMarker._patternId; this._transformMatrix = otherMarker._transformMatrix; this._flarSource = otherMarker._flarSource; this._corners = otherMarker._corners; this.resetAllCalculations(); } /** * Free this FLARMarker instance for garbage collection. */ public function dispose () :void { this._transformMatrix = null; this._flarSource = null; this.matrixHistory = null; this._vector3D = null; this.rotations = null; this.rotationSpeeds = null; this._corners = null; this._velocity = null; } public function toString () :String { return ("FLARMarker [patternId:"+ this.patternId +", sessionId:"+ this.sessionId +"]"); } //---------------------------------------// //------------------------------// /** * apply smoothing algorithm over a number of frames. * called by FLARManager as part of marker tracking/maintenance process. * * @param smoother IFLARMatrixSmoother to use * @param numFrames number of frames over which to smooth * @param adaptiveSmoothingCenter @see com.transmote.flar.FLARManager#adaptiveSmoothingCenter */ flarManagerInternal function applySmoothing (smoother:IFLARMatrixSmoother, numFrames:int, adaptiveSmoothingCenter:Number) :void { if (adaptiveSmoothingCenter > 0) { numFrames = this.adaptSmoothing(numFrames, adaptiveSmoothingCenter); } if (numFrames == 0) { this.matrixHistory = null; return; } if (!this.matrixHistory) { this.matrixHistory = new Vector.(numFrames, false); } else if (this.matrixHistory.length != numFrames) { // remove null values from array before changing size, // to insure no information is lost. var i:int = this.matrixHistory.length; var j:int; while (i-- > 0) { if (this.matrixHistory[i] != null) { continue; } j = i; while (j--) { if (this.matrixHistory[j] != null || j==-1) { break; } } this.matrixHistory.splice(j+1, i-j); i = j; } this.matrixHistory.length = numFrames; } for (i=0; i---------------------// //--------------------------------// protected function resetAllCalculations () :void { this._centerpoint2D = null; this._centerpoint3D = null; this._vector3D = null; this.rotations = null; } protected function mirror () :void { // TODO: this method currently has no effect. // however, should be able to mirror this.transformMatrix, // and never have to worry about mirroring in application code // or in XGeomUtils classes. // this._transformMatrix.rawData[3] = this._flarSource.sourceSize.width - this._transformMatrix.rawData[3]; } private function adaptSmoothing (numFrames:int, adaptiveSmoothingCenter:Number) :int { // evaluate marker speeds ((x,y,z) and rotationX/Y/Z) against adaptiveSmoothingCenter. // if speed is less, apply more smoothing; if speed is more, apply less smoothing. // choose lowest amount smoothing from four results, to ensure responsiveness during motion. var speeds:Vector. = Vector.([this.motionSpeed2D, this.velocity.z, this.rotationSpeeds.x, this.rotationSpeeds.y, this.rotationSpeeds.z]); speeds.fixed = true; var speed:Number; var smoothing:Number; var leastSmoothing:Number = MAX_ADAPTIVE_SMOOTHING; for (var i:int=0; i 180) { dRotX -= 360; } else if (dRotX < -180) { dRotX += 360; } var dRotY:Number = newMarker.rotationY - this.rotationY; if (dRotY > 180) { dRotY -= 360; } else if (dRotY < -180) { dRotY += 360; } var dRotZ:Number = newMarker.rotationZ - this.rotationZ; if (dRotZ > 180) { dRotZ -= 360; } else if (dRotZ < -180) { dRotZ += 360; } this.rotationSpeeds = new Vector3D( dRotX, dRotY, dRotZ, 0 ); } private function calcVelocity (newMarker:FLARMarker) :void { this._velocity = new Vector3D(newMarker.x-this.x, newMarker.y-this.y, newMarker.z-this.z, this.rotationSpeeds.z); } //----------------------------// } }