DAN ZEN EXPO - CODE EXHIBIT -
OSTRICH
package samples {
import flash.display.MovieClip;
import flash.events.*;
import flash.utils.Timer;
import com.danzen.interfaces.ostrich.*;
// class to make a custom cursor follow the motion in a Web cam
// just do not add the OstrichCursor object to the stage but follow it
// use an ENTER_FRAME event to animate your own cursor - like the fairy...
public class Fairy extends MovieClip {
private var myCamera:OstrichCamera;
private var myCursor:OstrichCursor;
public function Fairy() {
trace ("hi from Fairy");
// make an OstrichCamera object
myCamera = new OstrichCamera();
addChild(myCamera);
myCamera.alpha = .2;
setChildIndex(myFairy, numChildren-1);
// make an OstrichCursor object (there are more parameters available)
myCursor = new OstrichCursor(myCamera);
// uncomment this to see the cursor
// addChild(myCursor);
addEventListener(Event.ENTER_FRAME, animate);
}
private function animate(e:Event) {
// make the fairy flip its direction
if (myFairy.x < myCursor.x) {
myFairy.scaleX = -.7;
} else {
myFairy.scaleX = .7;
}
// make the fairy follow the OstrichCursor object
myFairy.x = myCursor.x;
myFairy.y = myCursor.y;
}
}
}
package com.danzen.interfaces.ostrich{
// OSTRICH INTRODUCTION
// Ostrich lets you follow video motion with a cursor
// for instance, you can wave at a Webcam and make a cursor move to where you wave
// this can be used as an interface to control elements of your application
// FEATURES AND CONSIDERATIONS
// you can specify a region in which to look for motion
// you can specify multiple cursors - each with their own region
// the OstrichButton class is provided to trigger over, out and hold events (no click)
// the fastest way to find out if a button is activated is to make it a cursor region
// then use the cursor start and stop to catch activity in that region
// you can make your own clips follow the OstrichCursor
// WORKINGS
// OstrichCursor is optimized for a single person standing in the middle using hands
// a rectangle is put around all motion and then a cursor is positioned as follows:
// the y position of the cursor is set to ten pixels beneath the top of the motion rectangle
// if rectangle is at the left of the camera it takes the left edge of rectangle for cursor x position
// if rectangle is at right it takes the right edge of rectangle for cursor x position
// if rectangle is in the center it takes the center of the rectangle for cursor x position
// if rectangle is anywhere else it uses the proportion to figure cursor x location
// you can adjust this by reworking the class
// http://ostrichflash.wordpress.com - by inventor Dan Zen - http://www.danzen.com
// if you are using Ostrich for commercial purposes, you are welcome to donate to Dan Zen
// donations can be made to agency@danzen.com at http://www.paypal.com
// also be aware that Gesture Tek and perhaps others hold patents in these areas
// INSTALLING CLASSES
// suggested installation:
// create a "classes" folder on your hard drive - for example c:\classes
// add the classes folder to your Flash class path:
// Flash menu choose Edit > Preferences > ActionScript - ActionScript 3 Settings
// then use the + sign for the source path box to add the path to your classes folder
// put the provided com/danzen/ directory with its folders and files in the classes folder
// the readme has more information if you need it
// USING OSTRICH
// please make sure that the following director is in a folder in your class path:
// com/danzen/interfaces/ostrich/
// see the samples for how to use the Ostrich classes
// OstrichCamera - sets up a Web cam for capturing motion
// OstrichCursor - sets up a cursor that follows the motion in OstrichCamera
// OstrichButton - sets up events for an invisible hotspot per OstrichCursor
// OstrichBlobs - puts blobs on any motion
// ******************
// This class should only be called after an OstrichCamera.READY event fires
import flash.display.Sprite;
import flash.filters.ColorMatrixFilter;
import flash.filters.BlurFilter;
import flash.media.Camera;
import flash.media.Video;
import flash.utils.Timer;
import flash.events.*;
public class OstrichCamera extends Sprite {
// CONSTRUCTOR
// OstrichCamera(theL:Number=0, theT:Number=0, theR:Number=640, theB:Number=480, theFlip:Boolean=true):void
// this class captures the camera and stores it in a cam Video object
// it flips the camera so you can match your motion like a mirror
// you can use this class without adding it to the stage with addChild()
// or you can add it and set the alpha or the visible as desired
// just use once and then all OstrichCursor objects can use it to capture motion
//
// PARAMETERS:
// theL:Number - the left side x of the camera
// theT:Number - the top side y of the camera
// theR:Number - the right side x of the camera
// theB:Number - the bottom side y of the camera
// theFlip:Boolean - do you want to flip the camera
// EVENTS
// OstrichCamera.READY - the camera is on and ready for motion capture
// OstrichCamera.NO_CAMERA - called if there is no camera to start
// METHODS (in addition to constructor)
// dispose():void
// stops camera - then can remake with different size if desired
// PROPERTIES
// camNum:Number - the cam num starting at 0
// camCheck:Boolean - a Boolean used as a safeguard by other Ostrich classes
// left, right, top and bottom:Number - read only - what was sent in as object was created
// but it extends a sprite so there is alpha, visible, etc.
// flip:Boolean - read only
// CONSTANTS
// READY:String - static constant (OstrichCamera.READY) for camera ready event
// NO_CAMERA:String - static constant (OstrichCamera.NO_CAMERA) for no camera at start
public static const READY:String="ready";
public static const NO_CAMERA:String="noCamera";
public var cam:Video;// the cam instance
public var signal:Camera;// the camera signal
// there are more public properties (getter/setter) down below
// static constants and related
private static var camTotal:Number=0;// keeps track of cursor numbers starting at 0
private var myCamNum:Number;// used with getter method at botton of class to return cursorNum
internal var myFlip:Boolean;
internal var cm:ColorMatrixFilter;// a color matrix
internal var bf:BlurFilter;// a blur filter
internal var camCheck:Boolean=false;// use the OstrichCursor.READY event instead!
private var timerCheckStart:Timer;// timers to check the availability of the camera
private var timerCheckStart2:Timer;
private var myTimer:Timer; // delay for camera list check
public function OstrichCamera(theL:Number=0, theT:Number=0, theR:Number=640, theB:Number=480, theFlip:Boolean=true) {
if (camTotal==0) {
trace("hi from OstrichCamera");
}
myCamNum=camTotal++; // which means camNum starts at 0
cam = new Video(theR-theL, theB-theT);
cam.x=theL;
cam.y=theT;
myFlip = theFlip;
myTimer = new Timer(200, 1);
myTimer.addEventListener(TimerEvent.TIMER, init);
myTimer.start();
}
private function init(e:TimerEvent) {
if (Camera.names.length == 0) {
dispatchEvent(new Event(OstrichCamera.NO_CAMERA));
return;
}
var macCamera:Number = -1;
for (var i:uint=0; i<Camera.names.length; i++) {
if (Camera.getCamera(String(i)).name == "USB Video Class Video") {
macCamera = i;
break;
}
}
if (macCamera >= 0) {
signal = Camera.getCamera(String(macCamera));
} else {
signal = Camera.getCamera();
}
signal.setMode(cam.width, cam.height, 30);
cam.attachCamera(signal);
addChild(cam);
if (myFlip) {
// flip the cam instance around to get a mirror effect
// need to also accomodate for this in cursor class
cam.scaleX=-1;
cam.x+=cam.width;
}
// need to find out when camera is active and set small delay to avoid motion trigger at start
// can't use status because it does not trigger when camera is automatically accepted
// set a check every 200 ms to see if camera is accepted
// once it is accepted, set a delay of 1000 ms until we start checking for motion with camCheck
timerCheckStart=new Timer(200);
timerCheckStart.addEventListener(TimerEvent.TIMER, startStopEvents);
timerCheckStart.start();
timerCheckStart2=new Timer(1000,1);
timerCheckStart2.addEventListener(TimerEvent.TIMER, startStopEvents2);
function startStopEvents(e:TimerEvent) {
if (! signal.muted) {
timerCheckStart.stop();
timerCheckStart2.start();
}
}
function startStopEvents2(e:TimerEvent) {
camCheck=true;
dispatchEvent(new Event(OstrichCamera.READY, true));
}
// set up some filters for better motion detection
// we will apply these in the cursor classes
// first we set up a color matrix filter to up the contrast of the image
// to do this we boost each color channel then reduce overall brightness
// we create a color matrix that will boost each color (multiplication)
// and then drop the brightness of the channel (addition)
var boost:Number=4;//3
var brightness:Number=-50;//-60;
var cmArray:Array = [
boost,0,0,0,brightness,
0,boost,0,0,brightness,
0,0,boost,0,brightness,
0,0,0,1,0
];
// create a new colorMatrixFilter so that we can apply our color matrix
cm=new ColorMatrixFilter(cmArray);
// set up a blur filter to help emphasize areas of change
bf=new BlurFilter(16,16,2);
}
// these getter setter methods prevent the camNum from being set
public function get camNum() {
return myCamNum;
}
public function set camNum(t) {
trace("camNum is read only");
}
// these getter setter methods prevent the dimensions from being set
public function get left() {
return cam.x;
}
public function set left(t) {
trace("left is read only - dispose() and recreate class if changes are needed");
}
public function get right() {
return cam.x + cam.width;
}
public function set right(t) {
trace("right is read only - dispose() and recreate class if changes are needed");
}
public function get top() {
return cam.y;
}
public function set top(t) {
trace("top is read only - dispose() and recreate class if changes are needed");
}
public function get bottom() {
return cam.y+cam.height;
}
public function set bottom(t) {
trace("bottom is read only - dispose() and recreate class if changes are needed");
}
public function get flip() {
return myFlip;
}
public function set flip(t) {
trace("flip is read only - dispose() and recreate class if changes are needed");
}
public function dispose() {
if (timerCheckStart) {
timerCheckStart.stop();
}
if (timerCheckStart2) {
timerCheckStart2.stop();
}
removeChild(cam);
}
}
}
package com.danzen.interfaces.ostrich {
// OSTRICH INTRODUCTION
// Ostrich lets you follow video motion with a cursor
// for instance, you can wave at a Webcam and make a cursor move to where you wave
// this can be used as an interface to control elements of your application
// FEATURES AND CONSIDERATIONS
// you can specify a region in which to look for motion
// you can specify multiple cursors - each with their own region
// the OstrichButton class is provided to trigger over, out and hold events (no click)
// the fastest way to find out if a button is activated is to make it a cursor region
// then use the cursor start and stop to catch activity in that region
// you can make your own clips follow the OstrichCursor
// WORKINGS
// OstrichCursor is optimized for a single person standing in the middle using hands
// a rectangle is put around all motion and then a cursor is positioned as follows:
// the y position of the cursor is set to ten pixels beneath the top of the motion rectangle
// if rectangle is at the left of the camera it takes the left edge of rectangle for cursor x position
// if rectangle is at right it takes the right edge of rectangle for cursor x position
// if rectangle is in the center it takes the center of the rectangle for cursor x position
// if rectangle is anywhere else it uses the proportion to figure cursor x location
// you can adjust this by reworking the class
// http://ostrichflash.wordpress.com - by inventor Dan Zen - http://www.danzen.com
// if you are using Ostrich for commercial purposes, you are welcome to donate to Dan Zen
// donations can be made to agency@danzen.com at http://www.paypal.com
// also be aware that Gesture Tek and perhaps others hold patents in these areas
// INSTALLING CLASSES
// suggested installation:
// create a "classes" folder on your hard drive - for example c:\classes
// add the classes folder to your Flash class path:
// Flash menu choose Edit > Preferences > ActionScript - ActionScript 3 Settings
// then use the + sign for the source path box to add the path to your classes folder
// put the provided com/danzen/ directory with its folders and files in the classes folder
// the readme has more information if you need it
// USING OSTRICH
// please make sure that the following director is in a folder in your class path:
// com/danzen/interfaces/ostrich/
// see the samples for how to use the Ostrich classes
// OstrichCamera - sets up a Web cam for capturing motion
// OstrichCursor - sets up a cursor that follows the motion in OstrichCamera
// OstrichButton - sets up events for an invisible hotspot per OstrichCursor
// OstrichBlobs - puts blobs on any motion
// ******************
// This class should only be called after an OstrichCamera.READY event fires
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.geom.*;
import flash.filters.ColorMatrixFilter;
import flash.filters.BlurFilter;
import fl.transitions.Tween;
import fl.transitions.easing.None;
import flash.media.Camera;
import flash.media.Video;
import flash.utils.Timer;
import flash.events.*
public class OstrichCursor extends Sprite {
// CONSTRUCTOR
// OstrichCursor(theCam:OstrichCamera, theL:Number=0, theT:Number=0, theR:Number=0, theB:Number=0, theResponse:Number=4):void
// OstrichCursor makes a cursor follow motion in a specified area of an OstrichCamera
// you can then hide the cursor and make another shape follow it (custom cursor)
// and / or you can use the cursor to rollover an OstrichButton and hold to activate
// or you can use the cursor just as any other moving sprite to make a game controller, etc.
//
// PARAMETERS:
// theCam:OstrichCamera - the cam used for motion detection
// theL:Number - the left side x of the region (with respect to the OstrichCam left)
// theT:Number - the top side y of the region (with respect to the OstrichCam top)
// theR:Number - the right side x of the region (with respect to the OstrichCam left)
// theB:Number - the bottom side y of the region (with respect to the OstrichCam top)
// theResponse:Number - from 1-10 default 4. 1 is fast but jumpy - 10 is slow and smooth
// EVENTS
// OstrichCursor.MOTION_START motion has started for a cursor
// OstrichCursor.MOTION_STOP motion has stopped for a cursor
// METHODS (in addition to constructor)
// dispose():void - stops and removes cursor
// PROPERTIES
// cursorNum:Number - read only - the cursor num starting at 0
// cam:OstrichCamera - the cam feed passed to the OstrichCursor object
// x:Number - the x position of the cursor - setting this will do you no good ;-)
// y:Number - the y position of the cursor - setting this will do you no good ;-)
// response:Number - between 1-10 - cursor is checked each followInterval
// but reported every response number of times
// movement between reports is averaged to smoothen the motion
// CONSTANTS
// MOTION_START:String - static constant (OstrichCursor.MOTION_START) for motion start event
// MOTION_STOP:String - static constant (OstrichCursor.MOTION_STOP) for motion stop event
// event constants
public static const MOTION_START:String = "MotionStart";
public static const MOTION_STOP:String = "MotionStop";
// static constants and related
private static var cursorTotal:Number = 0; // keeps track of cursor numbers starting at 0
private var myCursorNum:Number; // used with getter method at botton of class to return cursorNum
// a few initial condition varibles
private var cursorSize:Number = 14; // a square is drawn for the cursor
private var fromTop:Number = 10; // pixels cursor is drawn from top of motion rectangle
private var followInterval:Number = 100; // motion checking interval in ms
// various holder variables and checks
private var myCam:OstrichCamera; // the cam instance
private var myResponse:Number;
private var motionRectangle:Sprite; // a holder for the motion rectangle (hidden)
private var motionCursor:Sprite; // a holder for the cursor clip
private var older:BitmapData; // the old frame of motion
private var newer:BitmapData; // the new frame of motion
private var myMatrix:Matrix; // to handle flipping of the camera
private var rect:Rectangle; // from getColorBoundsRect around motion color between old and new frames
private var motionCheck:Boolean = false; // true when motion over an interval based on followInterval * response
private var timerFollow:Timer; // interval for testing motion based on followInterval
private var timerMoveCursor:Timer; // interval for moving the cursor based on response * followInterval
private var tweenObject:Object = new Object(); // store the tweens on an object so we can delete them
// these are variables used in the calculations
private var cursorSpeed:Number; // the interval the cursor moves based on followInterval * response in ms
private var mL:Number; // m for motionRectangle
private var mR:Number;
private var mT:Number;
private var mB:Number;
private var mW:Number;
private var mM:Number;
private var mX:Number;
private var regionL:Number;
private var regionR:Number;
private var regionT:Number;
private var regionB:Number;
private var regionW:Number;
private var regionH:Number;
private var regionR1:Number;
private var regionR2:Number;
private var regionT1:Number;
private var regionT2:Number;
private var moveX:Number;
private var moveY:Number;
private var motionL:Number = 0; // set some initial motion variables for the cursor
private var motionR:Number = 0;
private var motionT:Number = 0;
private var motionB:Number = 0;
private var motionLtotal:Number = 0;
private var motionRtotal:Number = 0;
private var motionTtotal:Number = 0;
private var motionBtotal:Number = 0;
private var motionTally:Number = 0;
public function OstrichCursor(theCam:OstrichCamera, theL:Number=0, theT:Number=0, theR:Number=0, theB:Number=0, theResponse:Number=4) {
myCam = theCam;
// set the region in which the cursor will work
regionL = theL;
regionT = theT;
regionR = (theR != 0) ? theR : theL + myCam.width;
regionB = (theB != 0) ? theB : theT + myCam.height;
myResponse = Math.max(theResponse, 1);
myResponse = Math.min(theResponse, 10);
if (theCam.camCheck) { // double check the camera is ready
init();
} else {
trace ("--------------------------------");
trace ("please call the OstrichCursor class");
trace ("after using an OstrichCamera.READY event");
trace ("--------------------------------");
}
}
private function init() {
if (cursorTotal == 0) {trace ("hi from OstrichCursor");}
myCursorNum = cursorTotal++; // which means cursorNum starts at 0
// create the sprite that will hold cursor
motionCursor = new Sprite();
addChild(motionCursor);
// create a sprite that will hold the overall motion rectangle that the cursor follows
motionRectangle = new Sprite();
//addChild(motionRectangle);
cursorSpeed = myResponse * followInterval;
// draw a cursor with black outside and grey inside square
drawCursor(motionCursor);
// get the width and height of the region to draw
regionW = regionR - regionL;
regionH = regionB - regionT;
// here we figure out translations required to capture our rectangle
if (myCam.myFlip) {
moveX = myCam.width - regionR;
moveY = regionT;
} else {
moveX = regionL;
moveY = regionT;
}
// set up the matrix to capture our region later on
myMatrix = new Matrix();
myMatrix.translate(-moveX, -moveY);
// prepare two bitmap objects to store the previous and current video frames
older = new BitmapData(regionW, regionH, false);
newer = new BitmapData(regionW, regionH, false);
// this interval runs the function follow every followInterval milliseconds
// follow puts a rectangle around motion
timerFollow = new Timer(followInterval);
timerFollow.addEventListener(TimerEvent.TIMER, follow);
timerFollow.start();
// this interval runs moveCursor which puts the cursor on the top
// of the motion rectangle and to the sides as applicable
timerMoveCursor = new Timer(cursorSpeed);
timerMoveCursor.addEventListener(TimerEvent.TIMER, moveCursor);
timerMoveCursor.start();
}
private function follow(c) {
// Generally, capture two frames across time and apply a difference filter
// color will only show where there is movement - turn this to one color
// draw a rectangle around the color to create a "motion rectangle"
// Technique learned from Grant Skinner Talk at FITC 2006 & at Interaccess
// We copy the picture from the old frame to a new bitmap
newer.copyPixels(older,older.rect,new Point(0,0));
// We then draw what is currently on the camera over top of the old frame
// As we are specifying using the difference filter, any pixels of the new
// frame that have the same color as the old frame will have a difference of zero
// zero means black and then every where that is not black will be some color
newer.draw(myCam.cam,myMatrix,null,"difference");
// Below we draw the unaffected camera feed to the old frame so that
// when the follow function is called again, we have a copy of the old frame
older.draw(myCam.cam,myMatrix,null);
// We apply the contrast color filter from the OstrichCamera to focus in on our motion
newer.applyFilter(newer,newer.rect,new Point(0,0),myCam.cm);
// We apply the blur filter from the OstrichCamera to smoothen our motion region
newer.applyFilter(newer,newer.rect,new Point(0,0),myCam.bf);
// We apply a threshold to turn all colors above almost black (first number)
// to green (second number) the last number is a mask number (confusing)
// this for some reason will not work unless we set the alpha channel up
// even though we are not caring about alpha in our bitmap declaration
// that is, the threshold would work but then the colorbounds would not
newer.threshold(newer,newer.rect,new Point(0,0),">",0x00110000,0xFF00FF00,0x00FFFFFF);
// Below we get a rectangle that encompasses the color (second number)
// the first number is a mask (confusing because it deals with bitwise operators)
// true means a rectangle around the color - false means a rectangle not around the color
rect = newer.getColorBoundsRect(0x00FFFFFF,0xFF00FF00,true);
// below we keep a running total of rectangle positions and a tally
// this will allow us to average the rectangle to position the cursor
if (rect.width > 0) {
if (myCam.myFlip) {
motionL = Math.round(myCam.x + myCam.cam.x - rect.right);
motionR = Math.round(myCam.x + myCam.cam.x - (rect.right - rect.width));
} else {
motionL = Math.round(myCam.x + myCam.cam.x + rect.left);
motionR = Math.round(myCam.x + myCam.cam.x + rect.right);
}
motionT = Math.round(myCam.y + myCam.cam.y + rect.top);
motionB = Math.round(myCam.y + myCam.cam.y + rect.bottom);
motionLtotal += motionL;
motionRtotal += motionR;
motionTtotal += motionT;
motionBtotal += motionB;
motionTally++;
}
}
private function moveCursor(c) {
// handle checking for any motion
if (motionTally > 0 && motionCheck == false && myCam.camCheck) {
motionCheck = true;
dispatchEvent(new Event(OstrichCursor.MOTION_START, true));
} else if (motionTally == 0 && motionCheck && myCam.camCheck) {
motionCheck = false;
dispatchEvent(new Event(OstrichCursor.MOTION_STOP, true));
}
// averaging cursor motion
mL = motionLtotal / motionTally + moveX * myCam.cam.scaleX;
mR = motionRtotal / motionTally + moveX * myCam.cam.scaleX;
mT = motionTtotal / motionTally + moveY * myCam.cam.scaleY;
mB = motionBtotal / motionTally + moveY * myCam.cam.scaleY;
// draw the motion rectangle
drawRect(motionRectangle);
// get a width and a middle used in the calculation that follows
mW = mR - mL;
if (myCam.myFlip) {
mM = mL + mW / 2 - (myCam.x + myCam.cam.x - myCam.width);
} else {
mM = mL + mW / 2 - (myCam.x + myCam.cam.x);
}
// place cursor to left more as motion rectangle moves left
// place cursor to right more as motion rectangle moves left
// place cursor at the top of the motion rectangle
mX = mL + mM / myCam.width * mW;
motionLtotal = motionRtotal = motionTtotal = motionBtotal = motionTally = 0;
if (mW > 0) {
// tween cursor to next position
delete(tweenObject.cursorTweenX);
tweenObject.cursorTweenX = new Tween(motionCursor, "x", None.easeOut, motionCursor.x, mX, cursorSpeed/1000, true);
delete(tweenObject.cursorTweenY);
tweenObject.cursorTweenY = new Tween(motionCursor, "y", None.easeOut, motionCursor.y, mT + fromTop, cursorSpeed/1000, true);
}
}
private function drawCursor(c) {
c.graphics.moveTo(-cursorSize/2,-cursorSize/2);
c.graphics.lineStyle(1,0x000000);
c.graphics.lineTo(cursorSize/2,-cursorSize/2);
c.graphics.lineTo(cursorSize/2, cursorSize/2);
c.graphics.lineTo(-cursorSize/2, cursorSize/2);
c.graphics.lineTo(-cursorSize/2,-cursorSize/2);
c.graphics.moveTo(-cursorSize/2+1,-cursorSize/2+1);
c.graphics.lineStyle(1,0xCCCCCC);
c.graphics.lineTo(cursorSize/2-1, -cursorSize/2+1);
c.graphics.lineTo(cursorSize/2-1, cursorSize/2-1);
c.graphics.lineTo(-cursorSize/2+1, cursorSize/2-1);
c.graphics.lineTo(-cursorSize/2+1,-cursorSize/2+1);
}
private function drawRect(r) {
// used to draw the overall motion rectangle that the cursor follows
r.graphics.clear();
r.graphics.lineStyle(2,0xcc0000);
r.graphics.moveTo(mL, mT);
r.graphics.lineTo(mR, mT);
r.graphics.lineTo(mR, mB);
r.graphics.lineTo(mL, mB);
r.graphics.lineTo(mL, mT);
}
// when another class follows the x y positions of the OstrichCursor it
// really follows the x y position of the motionCursor sprite within OstrichCursor
protected var theX:Number;
public override function get x():Number {
theX = motionCursor.x;
return this.theX;
}
public override function set x(t:Number):void {
motionCursor.x = t;
}
protected var theY:Number;
public override function get y():Number {
theY = motionCursor.y;
return this.theY;
}
public override function set y(t:Number):void {
motionCursor.y = t;
}
// these getter setter methods prevent the cursorNum from being set
public function get cursorNum() {return myCursorNum;}
public function set cursorNum(t) {trace ("cursorNum is read only");}
// these getter setter methods prevent the cam from being set
public function get cam() {return myCam;}
public function set cam(t) {trace ("cam is read only");}
public function get response():Number {
return myResponse;
}
public function set response(r:Number) {
myResponse = Math.max(Math.min(10,r),1);
cursorSpeed = myResponse * followInterval;
timerMoveCursor.delay = cursorSpeed;
}
public function dispose() {
if (timerFollow) {timerFollow.stop();}
if (timerMoveCursor) {timerMoveCursor.stop();}
delete(tweenObject.cursorTweenX);
delete(tweenObject.cursorTweenY);
removeChild(motionCursor);
}
}
}
package com.danzen.interfaces.ostrich {
// OSTRICH INTRODUCTION
// Ostrich lets you follow video motion with a cursor
// for instance, you can wave at a Webcam and make a cursor move to where you wave
// this can be used as an interface to control elements of your application
// FEATURES AND CONSIDERATIONS
// you can specify a region in which to look for motion
// you can specify multiple cursors - each with their own region
// the OstrichButton class is provided to trigger over, out and hold events (no click)
// the fastest way to find out if a button is activated is to make it a cursor region
// then use the cursor start and stop to catch activity in that region
// you can make your own clips follow the OstrichCursor
// **** people on macs may need to adjust their active camera setting in Flash
// WORKINGS
// OstrichCursor is optimized for a single person standing in the middle using hands
// a rectangle is put around all motion and then a cursor is positioned as follows:
// the y position of the cursor is set to ten pixels beneath the top of the motion rectangle
// if rectangle is at the left of the camera it takes the left edge of rectangle for cursor x position
// if rectangle is at right it takes the right edge of rectangle for cursor x position
// if rectangle is in the center it takes the center of the rectangle for cursor x position
// if rectangle is anywhere else it uses the proportion to figure cursor x location
// you can adjust this by reworking the class
// http://ostrichflash.wordpress.com - by inventor Dan Zen - http://www.danzen.com
// if you are using Ostrich for commercial purposes, you are welcome to donate to Dan Zen
// donations can be made to agency@danzen.com at http://www.paypal.com
// also be aware that Gesture Tek and perhaps others hold patents in these areas
// INSTALLING CLASSES
// suggested installation:
// create a "classes" folder on your hard drive - for example c:\classes
// add the classes folder to your Flash class path:
// Flash menu choose Edit > Preferences > ActionScript - ActionScript 3 Settings
// then use the + sign for the source path box to add the path to your classes folder
// put the provided com/danzen/ directory with its folders and files in the classes folder
// the readme has more information if you need it
// USING OSTRICH
// please make sure that the following director is in a folder in your class path:
// com/danzen/interfaces/ostrich/
// see the samples for how to use the Ostrich classes
// OstrichCamera - sets up a Web cam for capturing motion
// OstrichCursor - sets up a cursor that follows the motion in OstrichCamera
// OstrichButton - sets up events for an invisible hotspot per OstrichCursor
// OstrichBlobs - puts blobs on any motion
// ******************
// This class should only be called after an OstrichCamera.READY event fires
import flash.display.*
import flash.utils.Timer;
import flash.events.*
public class OstrichButton extends Sprite {
// CONSTRUCTOR
// OstrichCursor(theCursor:OstrichCursor, theButton:Object, theHoldDuration:Number=2):void
// OstrichButton takes in an OstrichCursor and some premade "button" DisplayObject
// It then checks to see if the cursor moves over or out of the button
// and also if the user holds on the button for the time given by the third constructor parameter
//
// PARAMETERS:
// theCursor:OstrichCursor - the OstrichCursor object we are using
// theButton:Object - a Sprite or MovieClip - the class just uses location and size
// theHoldDuration:Number - seconds on button before a hold is triggered (default 2)
// EVENTS
// OstrichButton.MOTION_OVER the cursor has moved over the button
// OstrichButton.MOTION_OUT the cursor has moved out from the button
// OstrichButton.MOTION_HOLD the cursor has stayed on the button for the specified myHoldTime
// METHODS (in addition to constructor)
// dispose():void - removes OstrichButton object - not your original button
// PROPERTIES
// buttonNum:Number - read only - the button num starting at 0
// button:Object - reference to the "button" passed in to the OstrichButton object
// cursor:OstrichCursor - the OstrichCursor passed in to the OstrichButton object
// CONSTANTS
// MOTION_START:String - static constant (OstrichCursor.MOTION_START) for motion start event
// MOTION_STOP:String - static constant (OstrichCursor.MOTION_STOP) for motion stop event
public static const MOTION_OVER:String = "MotionOver";
public static const MOTION_OUT:String = "MotionOut";
public static const MOTION_HOLD:String = "MotionHold";
// static constants and related
private static var buttonTotal:Number = 0; // keeps track of button numbers starting at 0
private var myButtonNum:Number; // used with getter method at botton of class to return buttonNum
// general holder and check variables
private var myCursor:OstrichCursor;
private var myButton:Object;
private var myHoldDuration:Number;
private var motionOnButton:Object;
private var motionOnButtonHold:Object;
private var clearCheck:Boolean;
private var clearCheckHold:Boolean = true;
private var timerButton:Timer;
private var timerHold:Timer;
public function OstrichButton(theCursor:OstrichCursor, theButton:Object, theHoldDuration:Number=2) {
if (buttonTotal == 0) {trace ("hi from OstrichButton");}
myButtonNum = buttonTotal++; // which means cursorNum starts at 0
myCursor = theCursor;
myButton = theButton;
myHoldDuration = theHoldDuration;
timerButton = new Timer(500);
timerButton.start();
motionOver();
motionOut();
motionHold();
}
// -------------- Over function ----------------------------
private function motionOver() {
timerButton.addEventListener(TimerEvent.TIMER, overFunction);
clearCheck = true;
}
private function overFunction(e:Event) {
if (myCursor.hitTestObject(DisplayObject(myButton))) {
if (clearCheck) {
dispatchEvent(new Event(OstrichButton.MOTION_OVER, true));
motionOnButton = myButton;
clearCheck = false;
}
} else {
clearCheck = true;
}
}
// -------------- Out function ----------------------------
private function motionOut() {
timerButton.addEventListener(TimerEvent.TIMER, outFunction);
clearCheck = true;
}
private function outFunction(e:Event) {
if (!myCursor.hitTestObject(DisplayObject(myButton))) {
if (motionOnButton == myButton) {
dispatchEvent(new Event(OstrichButton.MOTION_OUT, true));
motionOnButton = null;
}
}
}
// -------------- Hold function ----------------------------
private function motionHold() {
timerButton.addEventListener(TimerEvent.TIMER, holdFunction);
clearCheck = true;
}
private function holdFunction(e:Event) {
if (myCursor.hitTestObject(DisplayObject(myButton))) {
if (clearCheckHold) {
timerHold = new Timer(myHoldDuration*1000,1);
timerHold.addEventListener(TimerEvent.TIMER, holdTime);
timerHold.start();
motionOnButtonHold = myButton;
clearCheckHold = false;
}
} else {
clearCheckHold = true;
if (motionOnButtonHold == myButton) {
timerHold.stop();
motionOnButtonHold = null;
}
}
}
private function holdTime(e:Event) {
motionOnButtonHold = null;
dispatchEvent(new Event(OstrichButton.MOTION_HOLD, true));
}
// these getter setter methods prevent the buttonNum from being set
public function get buttonNum() {return myButtonNum;}
public function set buttonNum(t) {trace ("buttonNum is read only");}
// these getter setter methods prevent the cursor from being set
public function get cursor() {return myCursor;}
public function set cursor(t) {trace ("cursor is read only");}
// these getter setter methods prevent the button from being set
public function get button() {return myButton;}
public function set button(t) {trace ("button is read only");}
public function dispose() {
if (timerButton) {timerButton.stop();}
if (timerHold) {timerHold.stop();}
}
}
}
package com.danzen.interfaces.ostrich {
// OSTRICH INTRODUCTION
// Ostrich lets you follow video motion with a cursor
// for instance, you can wave at a Webcam and make a cursor move to where you wave
// this can be used as an interface to control elements of your application
// FEATURES AND CONSIDERATIONS
// you can specify a region in which to look for motion
// you can specify multiple cursors - each with their own region
// the OstrichButton class is provided to trigger over, out and hold events (no click)
// the fastest way to find out if a button is activated is to make it a cursor region
// then use the cursor start and stop to catch activity in that region
// you can make your own clips follow the OstrichCursor
// WORKINGS
// OstrichCursor is optimized for a single person standing in the middle using hands
// a rectangle is put around all motion and then a cursor is positioned as follows:
// the y position of the cursor is set to ten pixels beneath the top of the motion rectangle
// if rectangle is at the left of the camera it takes the left edge of rectangle for cursor x position
// if rectangle is at right it takes the right edge of rectangle for cursor x position
// if rectangle is in the center it takes the center of the rectangle for cursor x position
// if rectangle is anywhere else it uses the proportion to figure cursor x location
// you can adjust this by reworking the class
// http://ostrichflash.wordpress.com - by inventor Dan Zen - http://www.danzen.com
// if you are using Ostrich for commercial purposes, you are welcome to donate to Dan Zen
// donations can be made to agency@danzen.com at http://www.paypal.com
// also be aware that Gesture Tek and perhaps others hold patents in these areas
// INSTALLING CLASSES
// suggested installation:
// create a "classes" folder on your hard drive - for example c:\classes
// add the classes folder to your Flash class path:
// Flash menu choose Edit > Preferences > ActionScript - ActionScript 3 Settings
// then use the + sign for the source path box to add the path to your classes folder
// put the provided com/danzen/ directory with its folders and files in the classes folder
// the readme has more information if you need it
// USING OSTRICH
// please make sure that the following director is in a folder in your class path:
// com/danzen/interfaces/ostrich/
// see the samples for how to use the Ostrich classes
// OstrichCamera - sets up a Web cam for capturing motion
// OstrichCursor - sets up a cursor that follows the motion in OstrichCamera
// OstrichButton - sets up events for an invisible hotspot per OstrichCursor
// OstrichBlobs - puts blobs on any motion
// ******************
// This class should only be called after an OstrichCamera.READY event fires
import flash.display.Sprite;
import flash.events.*;
import flash.utils.Timer;
public class OstrichBlobs extends Sprite {
// CONSTRUCTOR
// OstrichBlobs(theCam:OstrichCamera, theResponse:Number=4):void
// OstrichBlobs puts blobs on any motion
// You can hide the blobs by not adding them to the stage
// and then you can use their location to trigger interactivity with hitTestPoint(), etc.
// or you can put your own Sprites or MovieClips where the blobs are
// a blob Sprite is made for each grid square the OstrichBlobs analyze
// if there is no motion in a square then the x of the blob is at -2000
//
// PARAMETERS:
// theCam:OstrichCamera - the cam used for motion detection
// theResponse:Number - from 1-10 default 4. 1 is fast but jumpy - 10 is slow and smooth
// METHODS (in addition to constructor)
// dispose():void - stops and removes cursor
// PROPERTIES
// cam:OstrichCamera - the cam feed passed to the OstrichCursor object
// response:Number - between 1-10 - cursor is checked each followInterval
// but reported every response number of times
// movement between reports is averaged to smoothen the motion
// blobs:Array - an array of blob Sprites - so you can get x, y and width, etc.
public var myCamera:OstrichCamera;
public var myCursors:Array = [];
private var myGridMotion:Array = [];
private var myGridLocation:Array = [];
private var myCursorClips:Array = [];
private var doneList:Array;
private var blobList:Array;
private var readyCheck:Boolean = false;
private var max:Number = 12;
private var threshhold:Number = 3;
private var startDelay:Number = 3; // seconds
private var myResponse:Number;
private var myTimer:Timer;
public function OstrichBlobs(theCam:OstrichCamera, theResponse:Number=4) {
trace ("hi from OstrichBlobs");
myTimer = new Timer(startDelay*1000,1);
myTimer.addEventListener(TimerEvent.TIMER, function (e:TimerEvent) {readyCheck = true;});
myTimer.start();
myCamera = theCam;
var temp:OstrichCursor;
var tempSprite:Sprite;
var gridW:Number = 640 / max;
var gridH:Number = 480 / max;
for (var i:uint = 0; i<max; i++) {
for (var j:uint = 0; j<max; j++) {
temp = new OstrichCursor(myCamera, i*gridW, j*gridH, (i+1)*gridW, (j+1)*gridH, theResponse);
myCursors.push(temp);
myGridMotion.push(0);
myGridLocation.push([i*gridW+gridW/2, j*gridH+gridH/2]);
temp.addEventListener(OstrichCursor.MOTION_START, onStart);
temp.addEventListener(OstrichCursor.MOTION_STOP, onStop);
tempSprite = new Sprite;
tempSprite.graphics.beginFill(0xFF99CC, .6);
tempSprite.graphics.drawCircle(0,0,100);
tempSprite.x = -2000;
addChild(tempSprite);
myCursorClips.push(tempSprite);
}
}
}
private function onStart(e:Event) {
myGridMotion[e.target.cursorNum] = 1;
analyseGrid();
}
private function onStop(e:Event) {
myGridMotion[e.target.cursorNum] = 0;
analyseGrid();
}
private function analyseGrid() {
if (!readyCheck) {return;}
//set the grid max to 6 and uncomment this to view this arrangement
/*myGridMotion = [1,1,0,0,0,0,
1,1,0,0,0,0,
0,0,0,0,0,0,
0,0,0,0,0,0,
0,0,0,0,1,1,
0,0,0,0,1,1];*/
doneList = [];
blobList = [];
var gridW:Number = 640 / max;
var gridH:Number = 480 / max;
var num:Number;
var m:uint;
var n:uint;
function goR(n:Number) {
var col:Number = n % max + 1;
if (col+1 > max) {return -1;} else {return n+1;}
}
function goL(n:Number) {
var col:Number = n % max + 1;
if (col-1 < 1) {return -1;} else {return n-1;}
}
function goB(n:Number) {
var row:Number = Math.floor(n / max) + 1;
if (row+1 > max) {return -1;} else {return n+max;}
}
function goT(n:Number) {
var row:Number = Math.floor(n / max) + 1;
if (row-1 < 1) {return -1;} else {return n-max;}
}
function checkAround(n:Number) {
var newNum:Number;
var functionList:Array = [goR, goL, goB, goT];
for (var r:uint=0; r<4; r++) {
newNum = functionList[r](n);
if (newNum != -1 && myGridMotion[newNum] == 1 && doneList.indexOf(newNum) == -1) {
doneList.push(newNum)
blobList[blobList.length-1].push(newNum);
checkAround(newNum);
}
}
}
for (var i:uint = 0; i<max; i++) {
for (var j:uint = 0; j<max; j++) {
num = i * max + j;
if (myGridMotion[num] == 1 && doneList.indexOf(num) == -1) {
blobList.push([num]);
doneList.push(num);
checkAround(num);
}
}
}
var e:Number;
var tX:Number;
var tY:Number;
var t:Number;
var factor:Number = (640+480)/2/max;
var blobCursors:Array = [];
for (var b:uint=0; b<blobList.length; b++) {
t = blobList[b].length;
if (t < threshhold) {continue;}
tX = tY = 0;
for (e=0; e<t; e++) {
tX += myGridLocation[blobList[b][e]][0];
tY += myGridLocation[blobList[b][e]][1];
}
// x average, y average, radius average
blobCursors.push([Math.round(tX / t), Math.round(tY / t), Math.sqrt(t)*factor/2]);
}
for (var q:uint=0; q<Math.pow(max,2); q++) {
myCursorClips[q].x = -2000;
}
var c:uint;
for (c=0; c<blobCursors.length; c++) {
myCursorClips[c].width = myCursorClips[c].height = blobCursors[c][2] * 2;
myCursorClips[c].x = blobCursors[c][0]+myCamera.x;
myCursorClips[c].y = blobCursors[c][1]+myCamera.y;
}
}
public function get response():Number {
return myResponse;
}
public function set response(r:Number) {
myResponse = Math.max(Math.min(10,r),1);
for (var i:uint = 0; i<max*max; i++) {
myCursors[i].response = myResponse;
}
}
public function get blobs():Array {
return myCursorClips;
}
public function dispose() {
for (var i:uint = 0; i<max*max; i++) {
myCursors[i].removeEventListener(OstrichCursor.MOTION_START, onStart);
myCursors[i].removeEventListener(OstrichCursor.MOTION_STOP, onStop);
myCursors[i].dispose();
removeChild(myCursorClips[i]);
delete myCursorClips[i];
}
for (i=0; i<max*max; i++) {
delete myCursors[i];
}
myTimer.stop();
myTimer = null;
}
}
}