1  /**
     2   * com.sekati.geom.Sort
     3   * @version 1.0.7
     4   * @author jason m horwitz | sekati.com
     5   * Copyright (C) 2007  jason m horwitz, Sekat LLC. All Rights Reserved.
     6   * Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
     7   */
     8  
     9  import com.sekati.core.CoreObject;
    10  import com.sekati.geom.Point;
    11  import com.sekati.math.MathBase;
    12  
    13  /**
    14   * Sort an array of Objects positions into various shapes and patterns.
    15   */
    16  class com.sekati.geom.Sort extends CoreObject {
    17  
    18  	private var _items:Array;
    19  	private var _sort:Array;
    20  
    21  	/**
    22  	 * Sort Constructor
    23  	 * @param items (Array)
    24  	 * @throws Error on invalid items argument array
    25  	 * {@code Usage:
    26  	 * 	var sort:Sort = new Sort([mc0, mc1, mc2, mc3]);
    27  	 * }
    28  	 */
    29  	public function Sort(items:Array) {
    30  		// verify that each item in the array has an _x,_y or x,y property to be sorted.
    31  		for (var i:Number = 0; i < items.length ; i++) {
    32  			if((!items[i]._x && !items[i]._y) && (!items[i].x && !items[i].y)) {
    33  				throw new Error( "@@@ " + this.toString( ) + " Error: constructor expects 'items' argument array to contain objects with '_x',_y' or 'x','y' properties." );
    34  				return;
    35  			}
    36  		}
    37  		_items = items;
    38  		_sort = new Array( );
    39  	}
    40  
    41  	/**
    42  	 * Sort items in grid.
    43  	 * @param start (Point)
    44  	 * @param numPerRow (Number)
    45  	 * @param offset (Number)
    46  	 * @return Array
    47  	 * {@code Usage:
    48  	 * 	var positions:Array = new Sort(itemArr).grid(new Point(100,100), 10, 1);
    49  	 * }
    50  	 */	
    51  	public function grid(start:Point, numPerRow:Number, offset:Number):Array {
    52  		var _xpos:Number = 0;
    53  		var _ypos:Number = 0;
    54  		var _offset:Number = (!offset) ? 0 : offset;
    55  		
    56  		// if there is no _width,_height lets assume the items are Points and count them as one pixel.
    57  		var _w:Number = (!_items[0]._width) ? 1 : _items[0]._width;
    58  		var _h:Number = (!_items[0]._height) ? 1 : _items[0]._height;
    59  		var _numPerRow:Number = (!numPerRow) ? Math.ceil( (Stage.width - start.x) / (_w + _offset) ) : numPerRow;
    60  		
    61  		for (var i:Number = 0; i < _items.length ; i++) {
    62  			if ((i % _numPerRow) == 0) {
    63  				_xpos = _offset;
    64  				_ypos += _h + _offset;
    65  			} else {
    66  				_xpos += _w + _offset;
    67  				_ypos += 0;
    68  			}
    69  			_sort[i] = new Point( Math.round( _xpos ) + start.x, Math.round( _ypos ) + start.y );
    70  		}
    71  		return _sort;		
    72  	}
    73  
    74  	/**
    75  	 * Sort items in circle.
    76  	 * @param center (Point)
    77  	 * @param radius (Number)
    78  	 * @return Array
    79  	 * {@code Usage:
    80  	 * 	var positions:Array = new Sort(itemArr).circle(new Point(250,250), 255);
    81  	 * }
    82  	 */
    83  	public function circle(center:Point, radius:Number):Array {
    84  		for (var i:Number = 0; i < _items.length ; i++) {
    85  			var angle:Number = i * (360 / _items.length);
    86  			var x:Number = Math.round( (center.x + (radius * Math.cos( (angle - 180) * Math.PI / 180 ))) );
    87  			var y:Number = Math.round( (center.y + (radius * Math.sin( (angle - 180) * Math.PI / 180 ))) );
    88  			_sort[i] = new Point( x, y );
    89  		}
    90  		return _sort;
    91  	}
    92  
    93  	/**
    94  	 * Sort items in sine wave.
    95  	 * @param waves (Number)
    96  	 * @param width (Number) sine wave width
    97  	 * @param width (Number) sine wave y position 
    98  	 * @param widthCap (Number) use max 90% of the width
    99  	 * @param heightCap (Number) use max 60% of the height
   100  	 * @return Array
   101  	 * {@code Usage:
   102  	 * 	var positions:Array = new Sort(itemArr).sine(1.5, Stage.width, Stage.height/2, 0.95, 0.5);
   103  	 * }
   104  	 */
   105  	public function sine(waves:Number, width:Number, yPos:Number, widthCap:Number, heightCap:Number):Array {
   106  		var _degStep:Number = (waves * 2 * Math.PI) / (_items.length - 1);
   107  		var _xStep:Number = (width * widthCap) / _items.length;
   108  		for (var i:Number = 0; i < _items.length ; i++) {
   109  			var x:Number = Math.round( (width - width * widthCap) / 2 + _xStep * i );
   110  			var y:Number = Math.round( Math.sin( _degStep * i ) * yPos * heightCap + yPos );
   111  			_sort[i] = new Point( x, y );
   112  		}
   113  		return _sort;
   114  	}
   115  
   116  	/**
   117  	 * Sort items in triangle.
   118  	 * @param center (Point)
   119  	 * @param sideLength (Number)
   120  	 * @return Array
   121  	 * {@code Usage:
   122  	 * 	var positions:Array = new Sort(itemArr).triangle(new Point(250,250), 500);
   123  	 * }
   124  	 */
   125  	public function triangle(center:Point, sideLength:Number):Array {
   126  		var cY:Number = center.y - (sideLength / 2);
   127  		//var perimeter:Number = sideLength*3;
   128  		var objBySide:Number = Math.floor( _items.length / 3 );
   129  		var leftObj:Number = _items.length - objBySide * 3;
   130  		var posArray:Array = new Array( );
   131  		var radAngle:Number = Math.PI / 3;
   132  	    
   133  		var spacing:Number;
   134  		for (var i:Number = 0; i < _items.length ; i++) {
   135  			posArray[i] = new Point( );
   136  			if (i < objBySide) {
   137  				spacing = sideLength / objBySide;
   138  				posArray[i].x = (spacing * i) * Math.cos( radAngle );
   139  				posArray[i].y = (spacing * i) * Math.sin( radAngle );
   140  			}
   141  			if (i >= objBySide && i < objBySide * 2 + Math.ceil( leftObj / 2 )) {
   142  				spacing = (leftObj != 0) ? sideLength / (objBySide + 1) : sideLength / objBySide;
   143  				posArray[i].x = (sideLength * Math.cos( radAngle )) - (spacing * (i - objBySide));
   144  				posArray[i].y = sideLength * Math.sin( radAngle );
   145  			}
   146  			if (i >= objBySide * 2 + Math.ceil( leftObj / 2 )) {
   147  				spacing = (leftObj == 2) ? sideLength / (objBySide + 1) : sideLength / objBySide;
   148  				posArray[i].x = -sideLength * Math.cos( radAngle ) + (spacing * (i - (objBySide * 2 + Math.ceil( leftObj / 2 )))) * Math.cos( radAngle );
   149  				posArray[i].y = sideLength * Math.sin( radAngle ) - (spacing * (i - (objBySide * 2 + Math.ceil( leftObj / 2 )))) * Math.sin( radAngle );
   150  			}
   151  			var x:Number = Math.round( center.x + posArray[i].x );
   152  			var y:Number = Math.round( cY + posArray[i].y );
   153  			_sort[i] = new Point( x, y );
   154  		}
   155  		return _sort;
   156  	}
   157  
   158  	/**
   159  	 * Sort items in flower.
   160  	 * @param center (Point)
   161  	 * @param radius (Number)
   162  	 * @return Array
   163  	 * {@code Usage:
   164  	 * 	var positions:Array = new Sort(itemArr).flower(new Point(250,250), 250);
   165  	 * }
   166  	 */	
   167  	public function flower(center:Point, radius:Number):Array {
   168  		var posArray:Array = new Array( );
   169  		var step:Number = (2 * Math.PI) / _items.length;
   170  		var b:Number = 1;
   171  		var k:Number = 2;
   172  		for (var i:Number = 0; i < _items.length ; i++) {
   173  			var r:Number = b * Math.cos( k * i * step );
   174  			posArray[i] = new Point( );
   175  			posArray[i].x = (r * Math.cos( i * step )) * radius;
   176  			posArray[i].y = (r * Math.sin( i * step )) * radius;
   177  			var x:Number = Math.round( center.x + posArray[i].x );
   178  			var y:Number = Math.round( center.y + posArray[i].y );
   179  			_sort[i] = new Point( x, y );
   180  		}
   181  		return _sort;
   182  	}
   183  
   184  	/**
   185  	 * Sort items in hedron (star,square,triangle,hexagon,octagon,etc)
   186  	 * @param center (Point)
   187  	 * @param heightCap (Number) use max 90% of the height [default: 0.8]
   188  	 * @param corners (Number)
   189  	 * @param rotate  (Number)
   190  	 * @param isIntraRadial (Boolean) if true star shapes will be drawn else closed hedrons [default: false]
   191  	 * @return Array
   192  	 * {@code Usage:
   193  	 * 	var positions:Array = new Sort(itemArr).hedron(new Point(250,250), 0.6, 8, 90); // create a octagon and rotate 90 degrees.
   194  	 * }
   195  	 */
   196  	public function hedron(center:Point, heightCap:Number, corners:Number, rotate:Number, isIntraRadial:Boolean):Array {
   197  		heightCap = (!heightCap) ? 0.8 : heightCap;
   198  		var outerRadius:Number = center.y * heightCap;
   199  		var innerRadius:Number = (!isIntraRadial) ? outerRadius * Math.sqrt( (1 + Math.cos( (2 * Math.PI) / corners )) / 2 ) : outerRadius / 2;
   200  		var cursors:Number = _items.length;
   201  		var lines:Number = corners * 2;
   202  		var cursorsPerLine:Number = Math.floor( cursors / lines );
   203  		var cursorsLeftOver:Number = cursors - cursorsPerLine * lines; 
   204  		// cursors for adoption...
   205  		var alphaStep:Number = (2 * Math.PI) / corners;
   206  		rotate = (Math.PI / 180) * rotate;
   207  		var alpha:Number = rotate;
   208  		var actualCursor:Number = 0;
   209  		var cursorsOnActualLine:Number;
   210  		var xStep:Number;
   211  		var yStep:Number;
   212  		var xAct:Number;
   213  		var yAct:Number;	    
   214  		for (var i:Number = 0; i < corners ; i++) {
   215  			var xOuter1:Number = Math.sin( alpha ) * outerRadius;
   216  			var yOuter1:Number = Math.cos( alpha ) * outerRadius;
   217  			var xInner:Number = Math.sin( alpha - alphaStep / 2 ) * innerRadius;
   218  			var yInner:Number = Math.cos( alpha - alphaStep / 2 ) * innerRadius;
   219  			var xOuter2:Number = Math.sin( alpha - alphaStep ) * outerRadius;
   220  			var yOuter2:Number = Math.cos( alpha - alphaStep ) * outerRadius;
   221  			// plot first line
   222  			cursorsOnActualLine = cursorsPerLine;
   223  			if (cursorsLeftOver > 0) {
   224  				cursorsOnActualLine++; 
   225  				// add one of the left over cursors on the line
   226  				cursorsLeftOver--; // one was adopted!
   227  			}
   228  			xStep = (xInner - xOuter1) / cursorsOnActualLine;
   229  			yStep = (yInner - yOuter1) / cursorsOnActualLine;
   230  			xAct = center.x + xOuter1;
   231  			yAct = center.y + yOuter1;
   232  			for (var j:Number = 0; j < cursorsOnActualLine ; j++) {
   233  				_sort[actualCursor] = new Point( int( xAct ), int( yAct ) );
   234  				xAct += xStep;
   235  				yAct += yStep;
   236  				actualCursor++;
   237  			}
   238  			// plot second line
   239  			cursorsOnActualLine = cursorsPerLine;
   240  			if (cursorsLeftOver > 0) {
   241  				cursorsOnActualLine++; 
   242  				// add one of the left over cursors on the line
   243  				cursorsLeftOver--; // another one was adopted!
   244  			}
   245  			xStep = (xOuter2 - xInner) / cursorsOnActualLine;
   246  			yStep = (yOuter2 - yInner) / cursorsOnActualLine;
   247  			xAct = center.x + xInner;
   248  			yAct = center.y + yInner;
   249  			for (var k:Number = 0; k < cursorsOnActualLine ; k++) {
   250  				_sort[actualCursor] = new Point( int( xAct ), int( yAct ) );
   251  				xAct += xStep;
   252  				yAct += yStep;
   253  				actualCursor++;
   254  			}
   255  			alpha -= alphaStep;
   256  		}
   257  		return _sort;
   258  	}
   259  
   260  	/**
   261  	 * Sort items in Star shapes, special {@link com.sekati.geom.Sort.hedon} wrapper.
   262  	 * @param center (Point)
   263  	 * @param heightCap (Number) use max 90% of the height [default: 0.8]
   264  	 * @param corners (Number) of start points
   265  	 * @param rotate (Number)
   266  	 * @return Array
   267  	 * {@code Usage:
   268  	 * 	var positions:Array = new Sort(itemArr).star(new Point(250,250), 0.6, 5, 180);
   269  	 * }
   270  	 */
   271  	public function star(center:Point, heightCap:Number, corners:Number, rotate:Number):Array {
   272  		return hedron( center, heightCap, corners, rotate, true );
   273  	}
   274  
   275  	/**
   276  	 * Sort items in square, {@link com.sekati.geom.Sort.hedon} wrapper.
   277  	 * @param center (Point)
   278  	 * @param heightCap (Number) use max 90% of the height [default: 0.8]
   279  	 * @return Array
   280  	 * {@code Usage:
   281  	 * 	var positions:Array = new Sort(itemArr).square(new Point(250,250), 0.8);
   282  	 * }
   283  	 */
   284  	public function square(center:Point, heightCap:Number):Array { 
   285  		return hedron( center, heightCap, 4, 225 ); 
   286  	}
   287  
   288  	/**
   289  	 * Sort items in hexagon, {@link com.sekati.geom.Sort.hedon} wrapper.
   290  	 * @param center (Point)
   291  	 * @param heightCap (Number) use max 90% of the height [default: 0.8]
   292  	 * @return Array
   293  	 * {@code Usage:
   294  	 * 	var positions:Array = new Sort(itemArr).hexagon(new Point(250,250));
   295  	 * }
   296  	 */
   297  	public function hexagon(center:Point, heightCap:Number):Array { 
   298  		return hedron( center, heightCap, 6, 90 );
   299  	}
   300  
   301  	/**
   302  	 * Unsort items in random arrangement.
   303  	 * @param topLeft (Point)
   304  	 * @param bottomLeft (Point)
   305  	 * @return Array
   306  	 * {@code Usage:
   307  	 * 	var positions:Array = new Sort(itemArr).unsort(new Point(0,0), new Point(Stage.width, Stage.height));
   308  	 * }
   309  	 */
   310  	public function unsort(topLeft:Point, bottomRight:Point):Array {
   311  		for (var i:Number = 0; i < _items.length ; i++) {
   312  			_sort[i] = new Point( MathBase.rnd( topLeft.x, bottomRight.x ), MathBase.rnd( topLeft.y, bottomRight.y ) );
   313  		}
   314  		return _sort;
   315  	}	
   316  }