I have a bitmap in AS3 flash and I am sending it over to javascript using a base64 encoded jpg string.
All that works great. I need to send the img over scaled down, and rotated 180deg. The scaling works, but not the rotation. The rotation never takes. I know Im am obviously doing it wrong, but don't know the right way.
So basically I just need to rotate the image 180deg so when it is sent to javascript it is upside down.
Here is my transform code
var scaleFactor:int = 5;
var tempBitmap:BitmapData = new BitmapData(img.width/scaleFactor, img.height/scaleFactor);
var drawMatrix:Matrix = new Matrix(1/scaleFactor, 0, 0, 1 /scaleFactor );
drawMatrix.rotate(Math.PI); //180deg
tempBitmap.draw(img, drawMatrix);
I think you need to translate your object after rotation otherwise it gets clipped so you don't see it, I did an example with a bitmap in a MovieClip just on the stage using CS5.
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
var scaleFactor:int = 1;
var tempBitmap:BitmapData = new BitmapData(img.width, img.height);
var rotationMatrix:Matrix = new Matrix();
rotationMatrix.rotate(Math.PI);
rotationMatrix.translate(img.width, img.height);
tempBitmap.draw(box_mc, rotationMatrix);
var output:Bitmap = new Bitmap(tempBitmap);
addChild(output);
I didn't mess with the scaling, you can do that yourself I guess. The one I put on the stage is on the right, the bitmapData drawn one is top left of stage, and correctly inverted.
UPDATE
Also have a look at fl.motion.MatrixTransformer as per 32bitkid comment below!
/** flips individual blocks within a bitmap. Specifically used for doing sprite sheets. But I am sure you could find another use if you wanted.
* #param inBM
* #param spritePixelWidth
* #param spritePixelHeight
* #param inTransformType :An enum from TransFiveConstants. Example: TransFiveConstants.VERTICAL_FLIP
* #return a bitmap data object where all the sprites within the sprite sheet bitmap have been rotated or transformed **/
public static function makeTransformedCopyOfSpriteSheet(inSpriteSheet:BitmapData, spritePixelWidth:int, spritePixelHeight:int, inTransformType:int):BitmapData
{
//Do error check to make sure we evenly fit into the bitmap we are doing transforms on.
CONFIG::debug{
if ( (inSpriteSheet.width % spritePixelWidth ) != 0) { throw new Error("input error: width does not go in evenly! Fix it."); }
if ( (inSpriteSheet.height % spritePixelHeight) != 0) { throw new Error("input error: height does not go in evenly! Fix it."); }
}//CONFIG_debug
//Calculate width and height in sprites of the inSpriteSheet.
var widthInSprites :int = inSpriteSheet.width / spritePixelWidth ;
var heightInSprites:int = inSpriteSheet.height / spritePixelHeight;
/** Bitmap that takes rectangle chunks out of inSpriteSheet, one at a time. **/
var inBM:BitmapData = new BitmapData(spritePixelWidth, spritePixelHeight, true, 0x00);
//Inlined copy of code in makeTransformedCopy
////////////////////////////////////////////////////////////////////
var flipWidthHeight:Boolean = false;
if (inTransformType == TransFiveConstants.ROTATE_NEG_90 ||
inTransformType == TransFiveConstants.ROTATE_POS_90 )
{
flipWidthHeight = true;
}
var outWID:int = (flipWidthHeight ? inBM.height : inBM.width );
var outHGT:int = (flipWidthHeight ? inBM.width : inBM.height);
////////////////////////////////////////////////////////////////////
/** Bitmap that is a [rotated||transformed] version of inputBitmap: **/
var outBM:BitmapData = new BitmapData(outWID, outHGT, true, 0x00);
var outputSpriteSheetWidth :int = outBM.width * widthInSprites;
var outputSpriteSheetHeight:int = outBM.height * heightInSprites;
/** The output of this function **/
var outputSpriteSheet :BitmapData = new BitmapData(outputSpriteSheetWidth, outputSpriteSheetHeight, true, 0x00);
//scan through the sheet with a rectangle and make all the transformed copies you need.
//Every time you make a transformed chunk/copy, move it from the inSpriteSheet to the outputSpriteSheet
/** Places the [rotated||transformed] chunk in correct spot on outputSpriteSheet **/
var finalDestination:Point = new Point();
var cookieCutter:Rectangle = new Rectangle();
cookieCutter.width = spritePixelWidth ;
cookieCutter.height = spritePixelHeight;
for (var xx:int = 0; xx < widthInSprites ; ++xx){
for (var yy:int = 0; yy < heightInSprites; ++yy){
cookieCutter.x = xx * spritePixelWidth;
cookieCutter.y = yy * spritePixelHeight;
//Cut chunk out of main sprite sheet:
inBM.copyPixels(inSpriteSheet, cookieCutter, ZZ, null, null, true);
//Transform the chunk you cut out of the main sprite sheet:
makeTransformedCopy(inBM, inTransformType, outBM);
//Paste the transformed copy into the output sheet:
finalDestination.x = xx * outBM.width; //if outBM is rotated, this width will NOT BE SAME AS spritePixelWidth
finalDestination.y = yy * outBM.height;
outputSpriteSheet.copyPixels(outBM, outBM.rect, finalDestination, null, null, true);
}}//next [xx, yy]
return outputSpriteSheet;
}//makeTransformedCopyOfSpriteSheet
/** Flips/Mirrors and Rotates using a 1D index scan remap. "transformUsingScanRemap"
*
* Meaning, I put data into a 1D array, then change my assumptions on the "scan order" ( left-right, then top-bottom is default convention),
* I can effectively rotate or transform the pixel input.
*
* All we have to do is pack the bitmap into a 1D array. (scanning left-right, top-to-bottom)
* And then convert it BACK to the 2D array using a different formula assuming the data is packed via a different scan order.
*
* EXAMPLE:
* If we change our assumption to assume it is left-right, then BOTTOM-to-top, and make a formula for mapping 1D indexes
* to 2D values based on that scan order assumption, we achieve a vertical flip.
*
* #param inBM :Bitmap we are making a transformed copy of.
* #param inTransformType :The enum for the type of [transform||rotation] to use. For values check TransFiveConstants.as
* #param outputBitmap :Supply this to OVERWRITE an existing bitmap instead of create a NEW bitmap for output.
* #return a transformed or rotated bitmap **/
public static function makeTransformedCopy(inBM:BitmapData, inTransformType:int, outputBitmap:BitmapData = null):BitmapData
{
//If the bitmap is being ROTATED, we will have to flip the output width and height.
var flipWidthHeight:Boolean = false;
if (inTransformType == TransFiveConstants.ROTATE_NEG_90 ||
inTransformType == TransFiveConstants.ROTATE_POS_90 )
{
flipWidthHeight = true;
}
var outWID:int = (flipWidthHeight ? inBM.height : inBM.width );
var outHGT:int = (flipWidthHeight ? inBM.width : inBM.height);
//You can supply a reference to the OUTPUT of this function if you are doing some type of batch processing
//And want to avoid repetitively constructing new intermediary bitmaps:
if (outputBitmap == null)
{
var outputBitmap:BitmapData = new BitmapData(outWID, outHGT, true, 0x00);
}
else
{
if (outputBitmap.width != outWID) { ICU.error("Bad output bitmap supplied. Size is wrong."); }
if (outputBitmap.height != outHGT) { ICU.error("Bad output bitmap supplied. Size is wrong."); }
}
/** Max x index when remapping 1D values. **/
var maxXXX:int = outWID - 1;
/** Max y index when remapping 1D values. **/
var YYYmax:int = outHGT - 1;
/** Number of full columns, using 1D scan order for this orientation. **/
var fullColumns:int = 0;
/** Number of full rows, using 1D scan order for orientation specified. **/
var fullRows:int = 0;
/**What is left over after we have calculated the rows or collumns we have. **/
var remainder:int = 0;
var curPix:uint;
var index:int = ( -1);
var trans:IntPoint = new IntPoint();
inBM.lock();
outputBitmap.lock();
for (var yy:int = 0; yy < inBM.height; yy++) {
for (var xx:int = 0; xx < inBM.width ; xx++) {
++index;
//using 1D index position, remap that to correct 2D position.
//To do different transforms and rotations, simply change your assumptions on how we
//map from 1D to 2D.
//Standard 1D to 2D assumes scan lines go left-right, then top-bottom. Row by row.
//2D to 1D formula for standard:
//1D = (Y * width) + X.
//how many full widths you can get out of 1D == Y. Remainder == X.
//2D.Y = (1D/width);
//2D.X = 1D - (2D.Y * width);
if (inTransformType == TransFiveConstants.NO_TRANSFORM)
{ //[1][2] Assumed scan order (AKA 1D to 2D Mapping)
fullRows = (index / outWID); //[3][4] used to get full
remainder = index - (fullRows * outWID); //[5][6] rows and remainder.
trans.iy = fullRows;
trans.ix = remainder;
}else
if (inTransformType == TransFiveConstants.VERTICAL_FLIP)
{ //[5][6] Assumed scan order (AKA 1D to 2D Mapping)
fullRows = (index / outWID); //[3][4] used to get full
remainder = index - (fullRows * outWID); //[1][2] rows and remainder.
trans.iy = YYYmax - fullRows;
trans.ix = remainder;
}else
if (inTransformType == TransFiveConstants.ROTATE_NEG_90)
{ //[2][4][6] Assumed scan order (AKA 1D to 2D Mapping)
fullColumns = (index / outHGT); //[1][3][5] used to get full rows and remainder.
remainder = index - (fullColumns * outHGT);
trans.ix = fullColumns;
trans.iy = YYYmax - remainder;
}else
if (inTransformType == TransFiveConstants.ROTATE_POS_90)
{ //[5][3][1] Assumed scan order (AKA 1D to 2D Mapping)
fullColumns = (index / outHGT); //[6][4][2] used to get full rows and remainder.
remainder = index - (fullColumns * outHGT);
trans.ix = maxXXX - fullColumns;
trans.iy = remainder;
}else
if (inTransformType == TransFiveConstants.FLOOR_MIRROR)
{ //[2][1] Assumed scan order (AKA 1D to 2D Mapping)
fullRows = (index / outWID); //[4][3] used to get full
remainder = index - (fullRows * outWID); //[6][5] rows and remainder.
trans.iy = fullRows;
trans.ix = maxXXX - remainder;
}
else
{
throw new Error("Transform type not recognized");
}
//Copy and paste the pixel now that we know where to put it:
curPix = inBM.getPixel32(xx, yy);
outputBitmap.setPixel32(trans.ix, trans.iy, curPix);
}}//next [xx, yy]
inBM.unlock();
outputBitmap.unlock();
return outputBitmap;
}//transformUsingScanRemap
Related
I have a randomly sorted array of, say, 3 items. Instead of displaying all 3 items in one dynamic text box (see code below), I'd like to display each item across 3 different text boxes. How might I go about doing this?
var Questions:Array = new Array;
Questions[0] = "<b><p>Where Were You Born?</p><br/>";
Questions[1] = "<b><p>What is Your Name?</p><br/>";
Questions[2] = "<b><p>When is Your Birthday?</p><br/>";
function randomize (a:*, b:*): int {
return (Math.random() > .5) ? 1: -1;
}
questions_txtbox.htmlText = Questions.toString() && Questions.join("");
The following code accomplishes what you were asking for, although the shuffling function is crude, it gets the job done. I also dynamically generated the three Text Fields as opposed to creating them on the stage and giving them unique instance names, so you will need to adjust the x/y coordinates for these new textfields as you see fit. I tested this on Flash CC 2014 and it worked properly.
import flash.text.TextField;
var Questions:Array = new Array();
Questions[0] = "<b><p>Where Were You Born?</p><br/>";
Questions[1] = "<b><p>What is Your Name?</p><br/>";
Questions[2] = "<b><p>When is Your Birthday?</p><br/>";
var shuffleAttempts:int = 10 * Questions.length;
var questionTextFields:Array = new Array(3);
function randomize (a:*, b:*): int {
return (Math.random() > .5) ? 1: -1;
}
function shuffleQuestions(arr:Array):void {
var temp:String;
for(var i:int = 0; i < shuffleAttempts; i++ ) {
var randIndex1:int = Math.floor(Math.random() * Questions.length);
var randIndex2:int = Math.floor(Math.random() * Questions.length);
if( randIndex1 != randIndex2 ) {
temp = Questions[randIndex1];
Questions[randIndex1] = Questions[randIndex2];
Questions[randIndex2] = temp;
}
}
}
shuffleQuestions(Questions); // shuffle question list
for( var questionIndex:int = 0; questionIndex < 3; questionIndex++ ) {
if( questionIndex < Questions.length ) {
var questionField = new TextField(); // create new text field
questionField.htmlText = Questions[questionIndex]; // take a question from the questions list and set the text fields text property
questionField.y = questionIndex * 20; // move the text field so that it does not overlap another text field
questionField.autoSize = "left"; // autosize the text field to ensure all the text is readable
questionTextFields[questionIndex] = questionField; // store reference to question textfield instance in array for later use.
addChild(questionField); // add textfield to stage
}
}
I'm using starling framework for my game project and it hasn't got any draw dashed line method. Because of this they suggest me to draw dashed lines with using small rectangles which is called quads.
My math is not enough for it, could you give a sample method for rectangles with dashed lines occurring.
Thanks..
This class by Andy Woodruff draws dashed lines
/*
DashedLine class
by Andy Woodruff (http://cartogrammar.com/blog || awoodruff#gmail.com)
May 2008
Still in progress; I'll get a more perfected version eventually. For now take it as is.
This is a Sprite with the capability to do basic lineTo drawing with dashed lines.
Example:
var dashy:DashedLine = new DashedLine(2,0x333333,new Array(3,3,10,3,5,8,7,13));
dashy.moveTo(120,120);
dashy.beginFill(0xcccccc);
dashy.lineTo(220,120);
dashy.lineTo(220,220);
dashy.lineTo(120,220);
dashy.lineTo(120,120);
dashy.endFill();
*/
package com.cartogrammar.drawing {
import flash.display.Shape;
import flash.display.Sprite;
import flash.geom.Point;
import flash.display.CapsStyle;
public class DashedLine extends Sprite {
var lengthsArray:Array = new Array(); // array of dash and gap lengths (dash,gap,dash,gap....)
var lineColor:uint; // line color
var lineWeight:Number; // line weight
var lineAlpha:Number = 1; // line alpha
var curX:Number = 0; // stores current x as it changes with lineTo and moveTo calls
var curY:Number = 0; // same as above, but for y
var remainingDist:Number = 0; // stores distance between the end of the last full dash or gap and the end coordinates specified in lineTo
var curIndex = null; // current index in the length array, so we know which dash or gap to draw
var arraySum:Number = 0; // total length of the dashes and gaps... not currently being used for anything, but maybe useful?
var startIndex:int = 0; // array index (the particular dash or gap) to start with in a lineTo--based on the last dash or gap drawn in the previous lineTo (along with remainingDist, this is so our line can properly continue around corners!)
var fill:Shape = new Shape(); // shappe in the background to be used for fill (if any)
var stroke:Shape = new Shape(); // shape in the foreground to be used for the dashed line
public function DashedLine(weight:Number = 0, color:Number = 0, lengthsArray:Array = null){
if (lengthsArray != null){ // if lengths array was specified, use it
this.lengthsArray = lengthsArray;
} else { // if unspecified, use a default 5-5 line
this.lengthsArray = [5,5];
}
if (this.lengthsArray.length % 2 != 0){ // if array has more dashes than gaps (i.e. an odd number of values), add a 5 gap to the end
lengthsArray.push(5);
}
// sum the dash and gap lengths
for (var i:int in lengthsArray){
arraySum += lengthsArray[i];
}
// set line weight and color properties from constructor arguments
lineWeight = weight;
lineColor = color;
// set the lineStyle according to specified properties - beyond weight and color, we use the defaults EXCEPT no line caps, as they interfere with the desired gaps
stroke.graphics.lineStyle(lineWeight,lineColor,lineAlpha,false,"none",CapsStyle.NONE);
// add fill and stroke shapes
addChild(fill);
addChild(stroke);
}
// basic moveTo method
public function moveTo(x:Number,y:Number):void{
stroke.graphics.moveTo(x,y); // move to specified x and y
fill.graphics.moveTo(x,y);
// keep track of x and y
curX = x;
curY = y;
// reset remainingDist and startIndex - if we are moving away from last line segment, the next one will start at the beginning of the dash-gap sequence
remainingDist = 0;
startIndex = 0;
}
// lineTo method
public function lineTo(x:Number,y:Number):void{
var slope:Number = (y - curY)/(x - curX); // get slope of segment to be drawn
// record beginning x and y
var startX:Number = curX;
var startY:Number = curY;
// positive or negative direction for each x and y?
var xDir:int = (x < startX) ? -1 : 1;
var yDir:int = (y < startY) ? -1 : 1;
// keep drawing dashes and gaps as long as either the current x or y is not beyond the destination x or y
outerLoop : while (Math.abs(startX-curX) < Math.abs(startX-x) || Math.abs(startY-curY) < Math.abs(startY-y)){
// loop through the array to draw the appropriate dash or gap, beginning with startIndex (either 0 or determined by the end of the last lineTo)
for (var i:int = startIndex; i < lengthsArray.length; i++){
var dist:Number = (remainingDist == 0) ? lengthsArray[i] : remainingDist; // distance to draw is either the dash/gap length from the array or remainingDist left over from the last lineTo if there is any
// get increments of x and y based on distance, slope, and direction - see getCoords()
var xInc:Number = getCoords(dist,slope).x * xDir;
var yInc:Number = getCoords(dist,slope).y * yDir;
// if the length of the dash or gap will not go beyond the destination x or y of the lineTo, draw the dash or gap
if (Math.abs(startX-curX) + Math.abs(xInc) < Math.abs(startX-x) || Math.abs(startY-curY) + Math.abs(yInc) < Math.abs(startY-y)){
if (i % 2 == 0){ // if even index in the array, it is a dash, hence lineTo
stroke.graphics.lineTo(curX + xInc,curY + yInc);
} else { // if odd, it's a gap, so moveTo
stroke.graphics.moveTo(curX + xInc,curY + yInc);
}
// keep track of the new x and y
curX += xInc;
curY += yInc;
curIndex = i; // store the current dash or gap (array index)
// reset startIndex and remainingDist, as these will only be non-zero for the first loop (through the array) of the lineTo
startIndex = 0;
remainingDist = 0;
} else { // if the dash or gap can't fit, break out of the loop
remainingDist = getDistance(curX,curY,x,y); // get the distance between the end of the last dash or gap and the destination x/y
curIndex = i; // store the current index
break outerLoop; // break out of the while loop
}
}
}
startIndex = curIndex; // for next time, the start index is the last index used in the loop
if (remainingDist != 0){ // if there is a remaining distance, line or move from current x/y to the destination x/y
if (curIndex % 2 == 0){ // even = dash
stroke.graphics.lineTo(x,y);
} else { // odd = gap
stroke.graphics.moveTo(x,y);
}
remainingDist = lengthsArray[curIndex] - remainingDist; // remaining distance (which will be used at the beginning of the next lineTo) is now however much is left in the current dash or gap after that final lineTo/moveTo above
} else { // if there is no remaining distance (i.e. the final dash or gap fits perfectly), we're done with the current dash or gap, so increment the start index for next time
if (startIndex == lengthsArray.length - 1){ // go to the beginning of the array if we're at the end
startIndex = 0;
} else {
startIndex++;
}
}
// at last, the current x and y are the destination x and y
curX = x;
curY = y;
fill.graphics.lineTo(x,y); // simple lineTo (invisible line) on the fill shape so that the fill (if one was started via beginFill below) follows along with the dashed line
}
// returns a point with the vertical and horizontal components of a diagonal given the distance and slope
private function getCoords(distance:Number,slope:Number):Point {
var angle:Number = Math.atan(slope); // get the angle from the slope
var vertical:Number = Math.abs(Math.sin(angle)*distance); // vertical from sine of angle and length of hypotenuse - using absolute value here and applying negative as needed in lineTo, because this number doesn't always turn out to be negative or positive exactly when I want it to (haven't thought through the math enough yet to figure out why)
var horizontal:Number = Math.abs(Math.cos(angle)*distance); // horizontal from cosine
return new Point(horizontal,vertical); // return the point
}
// basic Euclidean distance
private function getDistance(startX:Number,startY:Number,endX:Number,endY:Number):Number{
var distance:Number = Math.sqrt(Math.pow((endX-startX),2) + Math.pow((endY-startY),2));
return distance;
}
// clear everything and reset the lineStyle
public function clear():void{
stroke.graphics.clear();
stroke.graphics.lineStyle(lineWeight,lineColor,lineAlpha,false,"none",CapsStyle.NONE);
fill.graphics.clear();
moveTo(0,0);
}
// set lineStyle with specified weight, color, and alpha
public function lineStyle(w:Number=0,c:Number=0,a:Number=1):void{
lineWeight = w;
lineColor = c;
lineAlpha = a;
stroke.graphics.lineStyle(lineWeight,lineColor,lineAlpha,false,"none",CapsStyle.NONE);
}
// basic beginFill
public function beginFill(c:uint,a:Number=1):void{
fill.graphics.beginFill(c,a);
}
// basic endFill
public function endFill():void{
fill.graphics.endFill();
}
}
}
Thanks For Your Replies, I Wrote My Own Class Which Draws Rectangles To Create A Dashed Lines.
Thanks..
I have a sprite in a movie symbol that I would like to hover back and forth within a 360 radius. I was hoping to make it smooth and random. Never really venturing from its original xy cordinates.
I've tried to create some stipulations with if statements and a starting momentum. Like this:
var num = 2;
stage.addEventListener(Event.ENTER_FRAME, hover);
function hover(evt:Event):void{
//start it moving
cloudWhite.y += num;
cloudWhite.x += num;
//declare these variables
var cX = cloudWhite.x;
var cY = cloudWhite.y;
// object travels 10 pixels
var cXP = cX + 10;
var cXN = cX - 10;
var cYP = cY + 10;
var cYN = cY - 10;
// if object goes 10 pixels reverse direction of momentum (maybe)
if (cX >= cXP) {
num = -2;
}
if (cX <= cXN){
num = 2;
}
if (cY >= cYP) {
num = 2;
}
if (cY <= cYN){
num = 2;
}
Clearly this is super wrong because when it runs the object just either goes to 0,0 or to some place that only the math gods know of.
I am clearly a noob at this kind of math so i apologize but I am very excited to learn the trig behind this.
Thank you for your help and thank you for reading.
You are setting all your variables inside the ENTER_FRAME loop, so none of your conditions ever evaluates to true. On every single frame you are doing this:
cloudWhite.x += 2;
cX = cloudWhite.x;
cXP = cX + 10; // Must == cloudWhite's previous x + 10 + 2;
cXN = cX - 10; // Must == cloudWite's previous x -10 + 2;
if(cX > cXP)... // Can never be true.
if(cX < cXN)... // Can never be true.
What you need to do is:
1) Store the original position of cloudWhite somewhere outside the loop, and store it before the loop begins.
2) Define your bounds relative to the original position of cloudWhite, again before your loop begins. Also define the amount you are going to change the position with each iteration.
3) Start your loop.
4) Increment the current position of cloudWhite on each iteration. Add a little random in here if you want the shape to move in a random manner.
5) Check if the new position of cW is outside your bounds and adjust the direction if it is.
The sample below is crude and jerky but I don't know exactly what effect you're looking for. If you want smoother, longer movements in each direction, consider using the Tween class or a Tween library such as the popular Greensock one, instead of incrementing / decrementing the position manually. There's a useful discussion of this here: http://www.actionscript.org/forums/archive/index.php3/t-163836.html
import flash.display.MovieClip;
import flash.events.Event;
// Set up your variables
var original_x:Number = 100; // Original x
var original_y:Number = 100; // Original y
var x_inc:Number = 5; // X Movement
var y_inc:Number = 5; // Y Movenent
var bounds:Number = 50; // Distance from origin allowed
// Doesn't take into account width of object so is distance to nearest point.
// Create an MC to show the bounds:
var display:MovieClip = addChild(new MovieClip()) as MovieClip;
display.graphics.lineStyle(1, 0x0000FF);
display.graphics.beginFill(0x0000FF, 0.5);
display.graphics.drawRect(0-bounds, 0-bounds, bounds * 2, bounds *2);
display.x = original_x;
display.y = original_y;
addChild(display);
// Create our moving mc:
var mc:MovieClip = addChild(new MovieClip()) as MovieClip;
mc.graphics.beginFill(0xFF0000, 1);
mc.graphics.drawCircle(-10, -10, 20);
// Position it:
mc.x = original_x;
mc.y = original_y;
addChild(mc);
// Loop:
function iterate($e:Event = null):void
{
// Move the mc by a random amount related to x/y inc
mc.x += (Math.random() * (2 * x_inc))/2;
mc.y += (Math.random() * (2 * y_inc))/2;
// If the distance from the origin is greater than bounds:
if((Math.abs(mc.x - original_x)) > bounds)
{
// Reverse the direction of travel:
x_inc == 5 ? x_inc = -5 : x_inc = 5;
}
// Ditto on the y axis:
if((Math.abs(mc.y - original_y)) > bounds)
{
y_inc == 5 ? y_inc = -5 : y_inc = 5;
}
}
// Start the loop:
addEventListener(Event.ENTER_FRAME, iterate);
This should get you started. I'm sure there are any number of other ways to do this with formal trig, but this has the benefit of being very simple, and just an extension of your existing method.
I know it was asked a thousand times before, but I still can't find a solution.
Searching SO, I indeed found the algorithm for it, but lacking the mathematical knowledge required to truly understand it, I am helplessly lost!
To start with the beginning, my goal is to compute an entire spectrogram and save it to an image in order to use it for a visualizer.
I tried using Sound.computeSpectrum, but this requires to play the sound and wait for it to end, I want to compute the spectrogram in a way shorter time than that will require to listen all the song. And I have 2 hours long mp3s.
What I am doing now is to read the bytes from a Sound object, the separate into two Vectors(.); Then using a timer, at each 100 ms I call a function (step1) where I have the implementation of the algorithm, as follows:
for each vector (each for a channel) I apply the hann function to the elements;
for each vector I nullify the imaginary part (I have a secondary vector for that)
for each vector I apply FFT
for each vector I find the magnitude for the first N / 2 elements
for each vector I convert squared magnitude to dB scale
end.
But I get only negative values, and only 30 percent of the results might be useful (in the way that the rest are identical)
I will post the code for only one channel to get rid off the "for each vector" part.
private var N:Number = 512;
private function step1() : void
{
var xReLeft:Vector.<Number> = new Vector.<Number>(N);
var xImLeft:Vector.<Number> = new Vector.<Number>(N);
var leftA:Vector.<Number> = new Vector.<Number>(N);
// getting sample range
leftA = this.channels.left.slice(step * N, step * (N) + (N));
if (leftA.length < N)
{
stepper.removeEventListener(TimerEvent.TIMER, getFreq100ms);
return;
}
else if (leftA.length == 0)
{
stepper.removeEventListener(TimerEvent.TIMER, getFreq100ms);
return;
}
var i:int;
// hann window function init
m_win = new Vector.<Number>(N);
for ( var i:int = 0; i < N; i++ )
m_win[i] = (4.0 / N) * 0.5 * (1 - Math.cos(2 * Math.PI * i / N));
// applying hann window function
for ( i = 0; i < N; i++ )
{
xReLeft[i] = m_win[i]*leftA[i];
//xReRight[i] = m_win[i]*rightA[i];
}
// nullify the imaginary part
for ( i = 0; i < N; i++ )
{
xImLeft[i] = 0.0;
//xImRight[i] = 0.0;
}
var magnitutel:Vector.<Number> = new Vector.<Number>(N);
fftl.run( xReLeft, xImLeft );
current = xReLeft;
currf = xImLeft;
for ( i = 0; i < N / 2; i++ )
{
var re:Number = xReLeft[i];
var im:Number = xImLeft[i];
magnitutel[i] = Math.sqrt(re * re + im * im);
}
const SCALE:Number = 20 / Math.LN10;
var l:uint = this.total.length;
for ( i = 0; i < N / 2; i++ )
{
magnitutel[i] = SCALE * Math.log( magnitutel[i] + Number.MIN_VALUE );
}
var bufferl:Vector.<Number> = new Vector.<Number>();
for (i = 0; i < N / 2 ; i++)
{
bufferl[i] = magnitutel[i];
}
var complete:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
complete[0] = bufferl;
this.total[step] = complete;
this.step++;
}
This function is executed in the event dispatched by the timer (stepper).
Obviously I do something wrong, as I said I have only negative values and further more values range between 1 and 7000 (at least).
I want to thank you in advance for any help.
With respect,
Paul
Negative dB values are OK. Just add a constant (representing your volume control) until the number of points you want to color become positive. The remaining values that stay negative are usually just displayed or colored as black in a spectrogram. No matter how negative (as they might just be the FFT's numerical noise, which can be a huge negative dB number or even NaN or -Inf for log(0)).
I have a MovieClip holding an irregular shape such as this one:
I need to generate a random point on this shape.
I can use brute force by generating points within the bounding box and then hitTesting to see if they reside on the irregular shape. However, I'm sure there's a more efficient way to tackle this problem.
What is the most efficient way to generate a random point on an irregular shape?
You mentioned hitTest, but I assume you meant hitTestPoint().
If so, a function go get the random points you mention, would look a bit like this:
function getRandomPointsInClip(target:MovieClip,numPoints:int):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>(numPoints,true);
var width:Number = target.width,height:Number = target.height;
for(var i:int = 0; i < numPoints ; i++){
var point:Point = new Point(target.x+Math.random() * width,target.y+Math.random() * height);
if(target.hitTestPoint(point.x,point.y,true)) points[i] = point;//is the random coord inside ?
else i = i-1;//nope, go back one step - > retry above until it is inside
}
return points;
}
The other I hinted at in my comment involves looping through non transparent pixels in a bitmap data of your object. This method would insure you don't have many duplicates, as opposed to the previous method, but it also means, you have less control over the number of points created and there's extra memory used for creating the bitmap. Still, for documentation purposes, here is the function:
function getGridPointsInClip(target:MovieClip,res:int,offset:Number = 3):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>();
var x:int,y:int,alpha:int,w:int = int(target.width),h:int = int(target.height);
var bmd:BitmapData = new BitmapData(w,h,true,0x00FFFFFF);bmd.draw(target);
var pixels:Vector.<uint> = bmd.getVector(bmd.rect),numPixels:int = w*h;
for(var i:int = 0; i < numPixels; i+=res) {
x = i%bmd.width;
y = int(i/bmd.width);
alpha = pixels[i] >>> 24;
if(alpha > 0) points.push(new Point(x+random(-offset,offset),y+random(-offset,offset)));
}
return points;
}
function random(from:Number,to:Number):Number {
if (from >= to) return from;
var diff:Number = to - from;
return (Math.random()*diff) + from;
}
And here'a very basic test:
var pts:Vector.<Point> = getRandomPointsInClip(mc,300);
//var pts:Vector.<Point> = getGridPointsInClip(mc,100,4);
for(var i:int = 0 ; i < pts.length; i++) drawCircle(pts[i].x,pts[i].y,3,0x009900);
function getRandomPointsInClip(target:MovieClip,numPoints:int):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>(numPoints,true);
var width:Number = target.width,height:Number = target.height;
for(var i:int = 0; i < numPoints ; i++){
var point:Point = new Point(target.x+Math.random() * width,target.y+Math.random() * height);
if(target.hitTestPoint(point.x,point.y,true)) points[i] = point;//is the random coord inside ?
else i = i-1;//nope, go back one step - > retry above until it is inside
}
return points;
}
function getGridPointsInClip(target:MovieClip,res:int,offset:Number = 3):Vector.<Point>{
var points:Vector.<Point> = new Vector.<Point>();
var x:int,y:int,alpha:int,w:int = int(target.width),h:int = int(target.height);
var bmd:BitmapData = new BitmapData(w,h,true,0x00FFFFFF);bmd.draw(target);
var pixels:Vector.<uint> = bmd.getVector(bmd.rect),numPixels:int = w*h;
for(var i:int = 0; i < numPixels; i+=res) {
x = i%bmd.width;
y = int(i/bmd.width);
alpha = pixels[i] >>> 24;
if(alpha > 0) points.push(new Point(x+random(-offset,offset),y+random(-offset,offset)));
}
return points;
}
function random(from:Number,to:Number):Number {
if (from >= to) return from;
var diff:Number = to - from;
return (Math.random()*diff) + from;
}
function drawCircle(x:Number,y:Number,radius:Number,color:uint):void{
graphics.lineStyle(1,color);
graphics.drawCircle(x-radius,y-radius,radius);
}
HTH
If you think of some non-blob like shapes, it's clear the check random pixel, try again method isn't really a good way. The bounding box area could be huge compared to the shape area.
What you could do to improve the effectiveness is getting a vector of the BitmapData of the shape. It should contain all pixels of the bounding box. Update - it would be nice now if we could pick a random point, and remove it from the vector if it isn't inside the shape. Unfortunately the vector only contains the pixels' colour, not the position which is implicit and only correct if we don't change the vector's length. Since we don't need to know the actual colour, we can omit all transparent pixels and store an inside pixel's position as it's value in the vector. This way we don't need to create a new object for each pixel of the shape (that would be quite expensive!).
var v:Vector.<uint> shapeBoxBitmap.getVector(shapeBoxBitmap.rect);
var pixelNum:int = v.length;
for(var i:uint = 0; i < pixelNum; i++) {
if( v[i] && 0xFF000000 == 0) { // transparent pixel, outside off shape
v.splice(i,1);
} else {
v[i] = i;
}
}
//get random point
var randomPixel:int = v[Math.floor(Math.random()*v.length)];
var point:Point = new Point(randomPixel%shapeBitmap.width,int(randomPixel/shapeBitmap.width));