Blog

Posts tagged with “as3”

The Ultimate Date Validation RegExp

Now, I'm not claiming to be a god at regular expressions, but I do own this shirt for a reason. That reason is that I like using RegExp to do a lot of heavy lifting for me. Case in point: validating a date format. It's one thing to pull out year, month and date, allowing for multiple delimiters; it's quite another to ensure that the actual values match expected results. Month values are only between 01 and 12. Days are not only between 01 and 31, but also exclude 29, 30, or 31 if the months aren't supposed to include them — including checking for leap years. I can do that. With Regular Expressions.

Here's how (for simplicty's sake, all years must start with 19xx or 20xx):

var yyyymmdd = /(?:((?:19|20)[0-9]{2})[\/\\\-. ]?(?:(0[1-9]|1[0-2])[\/\\\-. ]?([0-2][1-8]|[12]0|19)|(0[13-9]|1[0-2])[\/\\\-. ]?(29|30)|(0[13578]|1[02])[\/\\\-. ]?(31))|(19(?:[0][48]|[2468][048]|[13579][26])|20(?:[02468][048]|[13579][26]))[\/\\\-. ]?(02)[\/\\\-. ]?(29))/;
//if doing a replace: year is $1$8, month is $2$4$6$9, day is $3$5$7$10
var ddmmyyyy = /(?:(?:([0-2][1-8]|[12]0|19)[\/\\\-. ]?(0[1-9]|1[0-2])|(29|30)[\/\\\-. ]?(0[13-9]|1[0-2])|(31)[\/\\\-. ]?(0[13578]|1[02]))[\/\\\-. ]?((?:19|20)[0-9]{2})|(29)[\/\\\-. ]?(02)[\/\\\-. ]?(19(?:[0][48]|[2468][048]|[13579][26])|20(?:[02468][048]|[13579][26])))/;
//if doing a replace: year is $7$10, month is $2$4$6$9, day is $1$3$5$8
var mmddyyyy = /(?:(?:(0[1-9]|1[0-2])[\/\\\-. ]?([0-2][1-8]|[12]0|19)|(0[13-9]|1[0-2])[\/\\\-. ]?(29|30)|(0[13578]|1[02])[\/\\\-. ]?(31))[\/\\\-. ]?((?:19|20)[0-9]{2})|(02)[\/\\\-. ]?(29)[\/\\\-. ]?(19(?:[0][48]|[2468][048]|[13579][26])|20(?:[02468][048]|[13579][26])))/;
//if doing a replace: year is $7$10, month is $1$3$5$8, day is $2$4$6$9

I'll break down the first example.
The entire expression looks for most likely values first, then looks for less likely, yet still valid values.

  • First it looks for a year with
    • a month value is between 01-12 and the day value is between 01-28.
    • If not, it checks if the month value is 01, or 03-12 and the day is either 29 or 30.
    • Failing that, it checks if the month is one of 01,03,05,07,08,10, or 12 and the day is 31.
  • Failing that, it does one last sanity check to see if the year was a leap year (only checking the years starting with 19xx or 20xx, so values are actually easily calculatable) and the date is 02-29.

If all of that fails, it isn't a valid date.

Tags: , , , ,
2012.06.07 10:23 AM | Permalink 0 Comments

Object Pool Class for AS3

Object pools aren't really rocket science. At their simplest, they can be implemented with an array, as follows:

var pool:Array = new Array();

function getFromPool():* {
return (pool.length?pool.pop():new [YourPooledObjectClass]());
}

function returnToPool(o:*):void {
if (o is [YourPooledObjectClass]) {
pool.push(o);
}
}

… and that's it really. The only problem is that you need to ensure that your code then manually resets any default property values of an aquired object. It also means you need to "one off" each pool you use. To that end, I created the following Object pool class.

Its main features:

  • keeps an internal list of default properties to be set at aquisition time.
  • ensures that only declared properties can be set (this can be turned of if desired)
  • allows for per-instance property overrides as needed
  • allows a max count to be set and pool flushing for memory management
  • allows pool pre-population so that potentially expensive creation routines can be managed

The only caveat is that any poolable object cannot require arguments at creation time — unless someone can point me to the class constuctor equivilent of funtion.apply();

package ca.atomicnoggin.collection {

/**
*
* @author Patrick Denny heythere@atomicnoggin.ca
*
*/
public class ObjectPool {
private var objType:Class;
private var props:Object = {};
private var pool:Array = [];
private var max:uint;
private var dyno:Boolean;
/**
* Constructor
*
* @param type The object class to use in the pool
*
* @param defaultProperties A simple name:value object that
* lists any property presets for objects taken from the pool.
*
* @param allowDynamic Whether or not to allow default properties that
* are not explicitly defined within the Class.
*
* @param initialCount Number of objects to pre-populate the pool with.
*
* @param maxCount Maximum number of objects allowed to populate the pool.
*/
public function ObjectPool(type:Class = null,
defaultProperties:Object = null,
allowDynamic:Boolean=false,
initialCount:uint=0,
maxCount:uint=uint.MAX_VALUE)
{
objType = (type is Class)?type:Object;
dyno = allowDynamic;
max = maxCount;
if ((defaultProperties is Object)) {
loadDefaultProperties(defaultProperties);
}
this.length = initialCount;
}

private function getNextObj(removeFromPool:Boolean=false):* {
//either grab the first object added or create a new one.
var obj = pool.length?pool[0]:(new objType());
if (removeFromPool) pool.shift();
return obj;
}

/**
* Get an object from the pool
*
* @param properties A simple name:value object that
* lists any instance specific properties to be set for
* the object taken from the pool. If allowDynamic is
* false, Properties not explicitly defined within the Class
* will be ignored.
*
* @return an instance of the Class specified in the constructor
*
*/
public function pop(properties:Object = null):* {
var newObj = getNextObj(true);
//make a copy of the instance specific properties provided (if any)
var newP:Object = (properties is Object?properties?{});
//track instance properties already set
var used:Object = {};
//iterate through the default properties
for (var p:String in props) {
if (dyno || newObj.hasOwnProperty(p)) {
//if an instance property name matches the default property
if (newP.hasOwnProperty(p)) {
// use the provided property
newObj[p] = newP[p];
// and add it to the used list
used[p] = true;
}
//otherwise use the default property
else {
newObj[p] = props[p];
}
}
}
//iterate through the remaining instance properties and apply them.
for (p in newP) {
if ((dyno || newObj.hasOwnProperty(p)) && !used[p]) {
newObj[p] = newP[p];
}
}
return newObj;
}
/**
* Return an object to the pool
*
* @param object The object being returned. Any object provided
* that is not an instance of the Class will be ignored.
*
*/
public function push(object:*):void {
if ((object is objType)) {
pool.push(object);
//this forces the max value check
this.length = pool.length;
}
else {
//should throw error
}
}
/**
* The Class used to create pool objects
*
* setting this property will also clear the pool.
*/
public function get poolClass():Class {
return objType;
}

public function set poolClass(value:Class):void {
this.length = 0;
objType = value;
}

public function loadDefaultProperties(defaultProperties:Object, clearPrevious:Boolean=true) {
if (clearPrevious) {
props = {};
}
var obj = getNextObj();
for (var p:* in defaultProperties) {
if (dyno || obj.hasOwnProperty(p)) {
props[String(p)] = defaultProperties[p];
}
}
}
/**
* Get the value of a default property
*
* @param name The property to be looked up.
*
* @return The default value of the named property.
* If the property hasn't been set in the pool's default
* property list, then 'undefined' will be returned instead.
*/
public function getDefaultProperty(name:String):* {
var obj = getNextObj();
if (dyno || obj.hasOwnProperty(name)) {
return props[name];
}
else {
return undefined
}
}
/**
* Set the value of a default property
*
* @param name The name of the property to be set.
*
* @param value The value to be used. If allowDynamic is
* false, Properties not explicitly defined within the Class
* will be ignored.
*/
public function setDefaultProperty(name:String,value:*):void {
var obj = getNextObj();
if (dyno || obj.hasOwnProperty(name)) {
props[name] = value;
}
else {
//should throw error
}
}
/**
* Remove a property from the default properties list
*
* @param name The name of the default property to remove.
*
*/
public function removeDefaultProperty(name:String):void {
delete props[name];
}
/**
* Whether or not dynamic properties will be allowed to
* be added to the pool's objects. Setting this to true
* may cause Errors.
*
* default is false
*/
public function get allowDynamic():Boolean {
return dyno;
}

public function set allowDynamic(value:Boolean):void {
dyno = value;
if(!dyno) {
//reload properties to clear out dynamic ones
loadDefaultProperties(Object(props));
}
}
/**
* The maximum number of objects allowed to populate the pool
*
* default is uint.MAX_VALUE
*/
public function get maxCount():uint {
return max;
}

public function set maxCount(value:uint):void {
max = value;
if (pool.length > max) {
this.length = max;
}
}

/**
* The number of objects currently in the pool.
*
* If length is set to a value less than what is currently available,
* any objects beyond the new value will be deleted. If set to a value
* greater than what is available, new objects will be created
* to pre-populate the pool to match the amount provided.
*
*/
public function get length():uint {
return pool.length;
}
/**
*
*/
public function set length(value:uint):void {
var v:uint = (value > max)?max:value;
if (v < pool.length) {
delete pool.splice(v);
}
else if (v > pool.length) {
for(var a:uint = v - pool.length;a--;) {
pool.push(new objType());
}
}
}
}
}

The simplest usage would be:

var myPool:ObjectPool = new ObjectPool(Sprite);

To create a Sprite pool.

And as a bonus, you can even attach a pool directly to an object to create a factory, as follows:

package {
import ca.atomicnoggin.collections.ObjectPool;

public class MyClass {

static private var _pool:ObjectPool;

static public function get pool():ObjectPool {
if (!(_pool is ObjectPool)) {
_pool = new ObjectPool(MyClass);
}
return _pool;
}

public function MyClass() {
//constuctor code
}
}
}
Tags: , ,
2010.12.01 01:06 PM | Permalink 0 Comments

Quick date validation with a Regular Expression

/(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:29|30))|(?:(?:0[13578]|1[02])-31))/

Here's the breakdown of what it does:

Given a date format of YYYY-MM-DD (standard MySQL date format and easiest format for sorting) it makes sure that

  1. the year is numeric and starts with 20 or 19, and
  2. the month is numeric and is either
    1. between 01 - 12 and followed by a numeric day value between 01-28;
    2. between 01 - 12 but not 02 and followed by a day value of 29 or 30; or
    3. one of 01,03,05,07,08,10,12 and followed by a day value of 31

I have left out Feb. 29th so that you are forced to do a secondary leap year check.

Tags: , , ,
2010.01.28 11:43 AM | Permalink 0 Comments

Recreating Grant Skinner's sphereTest Part 3 — actually getting it done the easy way.

After a massive amount of experimentation, trying to recreate Grant's Skinner's original sphereTest using the displayObject class's rotationY, rotationX and rotationZ properties, I realized that:

1) this was a royal pain in the ass, and
2) there had to be an easier way to do it.

That easier way was the Matrix3D, and Vector3D objects. Once an object had been positioned in 3D space by giving it a z position, Flash lets you manipulate it in 3D space via <object>.transform.matrix3D. Not only that you can get a Matrix3D object that relative to any other object on the stage, via var rel3D:Matric3D = <object>.transform.getRelativeMatrix3D(<RelativeObject>);. So I could position each disc in the sphere object and simply rotate them incrementally with disc.transform.matrix3D = rel3D.appendRotation(degrees,[Vector3D.X_AXIS|Vector3D.Y_AXIS]);. The number of degrees to rotate and which axis to rotate around can quickly be calculated by getting the relative position and distance of the mouse pointer from the center of the sphere. Not only that, the true z position, needed for stacking of the object is available through rel3D.position.z. Bang. Dead simple. I even simplified the stacking by creating 2 container objects for each original z position — one for the front of the sphere and one for the back — that were pre-stacked at creation time. With those there, I was able to move each disc into the appropriate container immediately after rotation, instead of sorting the array and re-iterating to stack. This also allowed me to drop in the larger inner-sphere object between the sphere half containers and just leave it there to do nothing but look pretty. What had taken literally days of trial and error previously, was pounded out in about an hour and a half.

Done. Easily done. Too easily in fact. In order to justify my earlier blind muddling, I felt I need to do something cooler than just redoing Grant's original work. So I used Google to find global land cover information by latitude and longitude, did some rough calculating to find relative diameters of each longitude (Using the Vector3D.distance static method, actually) and made this funky spinning globe:

Now, there's a lot more points to rotate in this one, so I slowed down the frame rate to make sure the math keeps up, but all in all I feel vindicated.

Mostly.

Tags: , ,
2009.12.14 01:15 PM | Permalink 0 Comments

Recreating Grant Skinner's sphereTest Part 2 — almost figuring it out the hard way.

Working off the tests I did previously to try and recreate Grant Skinner's original sphereTest, I drew a bunch of discs, moved them out along the Z-axis randomly between min and max values, and distributed them randomly along the X and Y axes using the rotationX and rotationY attributes. Then I incremented each disc's rotationY value with the onEnterFrame event.

Obviously, the stack order would be need to be worked on, but not a bad start. To get the stack order, I needed to find the relative position of each disc along the Z-axis. To do this, I used the Z position as a circle's radius, and derived approximately how many pixels of movement each degree of rotation along the X axis represented. Once I found that new value, I'd do the calculations again for the Y rotation. To cut down on per-frame math, I figured out the z-position relative to X rotation immediately, and stored it since it wouldn't change. I only did the position relative to Y when needed. Also, I only approximated the Z position by dividing the diameter values by 180 (or, multiplying by 0.005555555556, which is faster and results in the same value), instead of mucking about with the position along the arc. It wasn't exact, but it was a good enough value to sort on. once I had the relative position, Discs that were relatively "closer" to the viewer were then sorted to appear in front of ones that weren't. This involved iterating through the discs to change their Y rotation, sorting the disc array on their derived relative value, and re-iterating to do the stacking. Not great, performance-wise, and still a little jumpy at the poles, but it worked.

To make the stacking appear smoother I ensured that "closer" discs that had an initial maximum Z position were always stacked in front, of other discs, then worked back until the "farther" maximum Z discs were sorted to the back.  Next, I made the entire sphere "follow" the mouse. I did this by calculating the angle of the line from the mouse's position and the center of the sphere relative to the x-axis (non-trivial math, to be honest -- at least for me) and rotating the entire sphere around its Z-axis to make the disc's Y rotation move towards the mouse.

This looked fairly good to me. Then I went back to Grant's sphereTest and noticed that his sphere wasn't roting along the Z-axis. Each individual disc simply moved towards the mouse regardless of it's position relative to the Y-axis "pole". It was as if the sphere's Y-axis was moving independently of the discs. Actually, it looked as if they were moving around fixed X and Y axes simultaneously. But using each disc's rotationX and rotationY attributes meant the discs' X-axes were all over the map. My initial thought on how to emulate this behaviour was, oddly enough, quite complex. I thought that I would need rotate the sphere towards the mouse around its Z-axis; then correct that Z-rotation by moving the discs in the opposite direction around their own z-axis; then spin the entire sphere around it's own y-axis. Once that was done I'd need to calculate the relative Z of each disc based not only on their internal rotations, but on also on the sphere's rotations as well.

In short, a metric shit-load of math.

For each disc.

For every frame.

Not good.

Then I found the 3DMatrix object in Adobe's Flash CS4 documentation; Swore out loud; and threw out most of my work to this point. I'll let you know what I replaced it with in my next blog post.

Tags: , ,
2009.12.14 12:00 PM | Permalink 0 Comments
Next → Page 1 of 2