Actionscript 3: Rotate multiple shapes around internal point - actionscript-3

I'm having trouble with AS3 which I have to use for a little research project of mine.
The AS3 project would create a number of randomly placed squares which all would rotate around its center point.
I managed to figure out how to rotate it internally using this handy walkthrough.
However I am having trouble applying this method to all squares which were created in a for loop with randomly selected stage points. Only the first created will rotate
Here is the code in question:-
for(var i=0; i<10; i++)
{
var square:Shape = new Shape();
this.addChild(square);
var posX = Math.floor(Math.random() * stage.stageWidth) + 50;
var posY = Math.floor(Math.random() * stage.stageHeight) + 50;
square.x=posX;
square.y=posY;
var curSquareAng:Number=0;
var squareRotCenter:Point=new Point(0,0);
drawShapes();
var squareMat:Matrix=square.transform.matrix.clone();
}
this.addEventListener(Event.ENTER_FRAME, onEnter);
function onEnter(e:Event):void {
curSquareAng = (curSquareAng+2)%360;
rotateSquare(curSquareAng);
}
function rotateSquare(deg:Number):void {
var mat:Matrix= squareMat.clone();
MatrixTransformer.rotateAroundInternalPoint(mat,squareRotCenter.x, squareRotCenter.y, deg);
square.transform.matrix = mat;
}
I realize I likely have to create an array for each of the squares initial center points and loop through them. However I am completely lost on how to do so. As you can likely tell I am unfamiliar with AS3 and would much appreciate any help you can give this beginner programmer. :P

You need to create your own class based on a shape, then you stuff it full of properties that represent rotation center point, current angle and whatever else you'd want your squares to contain, then give the class update method which will do what you wrote in onEnter function for itself only. Then you will have easier control over what your squares are able to do. This technique is called "encapsulation".
On a side note, if you're wanting your square to rotate around internal point of (0,0), you can set their rotation property to achieve the desired effect. For other points, the walkthrough or its equivalent should be used.
public class Square extends Shape {
public var rotationCenter:Point=new Point();
private var currentAngle:Number=0;
public var rotationSpeed:Number=2; // degrees per frame
private var baseMatrix:Matrix;
public function Square() {
// draw the shape on "this.graphics"
this.graphics.beginFill(0xffff00,1);
this.graphics.moveTo(-20,-20);
this.graphics.lineTo(20,-20);
this.graphics.lineTo(20,20);
this.graphics.lineTo(20,-20);
this.graphics.lineTo(-20,-20);
this.graphics.endFill();
// if not set in declaration, set internal vars
baseMatrix=this.transform.matrix; // likely identity matrix, but let's initialize anyway
}
public function storeMatrix():void {
// you are positioning a square after you create it, so probably you want its new location to be transformed
// that one's matrix will no longer be an identity, so go capture
baseMatrix=this.transform.matrix;
}
public function update():void {
// should be called once per frame
currentAngle=(currentAngle+rotationSpeed)%360;
var mat:Matrix= baseMatrix.clone();
MatrixTransformer.rotateAroundInternalPoint(mat,rotationCenter.x, rotationCenter.y, currentAngle);
this.transform.matrix = mat;
}
}
Now, you will have to maintain an array of squares to make them rotate separately:
var squares:Array=[];
for (var i:int=0;i<10;i++) {
var square:Square=new Square();
var posX = Math.floor(Math.random() * stage.stageWidth) + 50;
var posY = Math.floor(Math.random() * stage.stageHeight) + 50;
square.x=posX;
square.y=posY;
// after you position, give it a rotation point
square.rotationCenter.x=Math.random()*40-20;
square.rotationCenter.y=Math.random()*40-20; // -20 to 20, should do for this example
// now fix the position so your square will know that it should rotate
// its *current* transform matrix
square.storeMatrix();
// even if it's actually unchanged by changing X or Y
// also, should you desire to scale some square, you should do that prior to calling this
// now add the square to an array
squares.push(square);
}
addEventListener(Event.ENTER_FRAME,onEnter);
function onEnter(e:Event):void {
for (var i:int=0;i<squares.length;i++) squares[i].update();
// simple, isn't it? Each square will know what to do.
}

Nevermind. Thank you Vesper for putting me on the right track I've managed to solve my problem thanks to your input (not necessarily through your way but your input helped me get to the destination per say).
I think I was making it a little too complicated going though the matrix route and instead used a shape array to loop through the squares and add rotation. The solution I came to is a little simple but gets the job done.
public var rotationSpeed:Number=2; // degrees per frame
public var square:Array = new Array( );
public function Square() {
for (var i:int=0;i<10;i++) {
square[i] = new Shape();
var posX = Math.floor(Math.random() * stage.stageWidth) + 50;
var posY = Math.floor(Math.random() * stage.stageHeight) + 50;
square[i].graphics.lineStyle();
var rgb = Math.random() * 0xFFFFFF;
square[i].graphics.beginFill(rgb);
// -50 determines where the spin will center from.
square[i].graphics.drawRect(-50,-50,100,100);
square[i].graphics.endFill();
square[i].x = posX;
square[i].y = posY;
addChild(square[i]);
}
addEventListener(Event.ENTER_FRAME,onEnter);
}
private function onEnter(e:Event):void {
for (var i:int=0; i < square.length; i++) {
getChildAt(i).rotation += rotationSpeed;
}
}

Related

Adding object within another

I have my main stage as 550x400. The header area is a stats bar. So I have an element underneath it which I named gameStage which is 550x350.
I am creating circles on a 1 second interval and then trying to randomly place them within my gameStage. It does not appear to be working. It seems like they're being added to a 550x350 element, but it starts at the top of my main stage -- not within my gameStage.
Also if I simply do addChild(circle) it creates an even 25 radius circle. As soon as I do gameStage.addChild(circle), the circle gets skewed slightly.
What am I doing wrong?
private function createCircle():void {
var stageSafeX:Number = Math.random()*gameStage.width;
var stageSafeY:Number = Math.random()*gameStage.height;
var circle:Sprite = new Sprite();
circle.graphics.clear();
circle.graphics.beginFill(Math.random()*0xFFFFFF, 1);
circle.graphics.drawCircle(0, 0, circleRadius);
circle.graphics.endFill();
circle.x = stageSafeX;
circle.y = stageSafeY;
circle.name = String(circleCount);
gameStage.addChild(circle);
}
Okay I'm using Flash Develop, so you'll have to forgive me as this program doesn't have FLA files, only classes and it uses a Main class to start the program (more reminiscent of Java if you've ever programmed in that). But the code I'll show you is more or less the same of how you want to do it.
First I would recommend you make a randomNumber function, I used it in making this code so if you want to use it here's the one I use (I put this in the Main class, you can put this wherever you want):
public static function randomNumber(minValue:Number, maxValue:Number):uint {
return Math.floor(Math.random() * (1 + maxValue - minValue)) + minValue;
}
This is inclusive, meaning if you put randomNumber(1, 10) it will give you a number between 1 to 10, including 1 and 10. It's more or less common sense, but I figured I might as well mention it just to clarify.
Now on to the addCircle function:
public static function addCircle(gameStage:Sprite, circleRadius:uint):void {
//Initializing the new circle instance
var newCircle:Sprite = new Sprite();
//Basically the same code you had (you don't need to set the alpha value to 1, it's default value is 1 regardless)
newCircle.graphics.beginFill(Math.random() * 0xFFFFFF);
newCircle.graphics.drawCircle(0, 0, circleRadius);
newCircle.graphics.endFill();
//Since the circle's origin is the center, you want its outer edges to be bound to the gameStage's edges
var safeStageX:Number = Main.randomNumber(newCircle.width / 2, gameStage.width - newCircle.width / 2);
var safeStageY:Number = Main.randomNumber(newCircle.height / 2, gameStage.height - newCircle.height / 2);
//Adding the circle to the gameStage's display field
gameStage.addChild(newCircle);
//Only set the circle's x and y AFTER you add it to the gameStage's display list, otherwise it might not set properly
newCircle.x = safeStageX;
newCircle.y = safeStageY;
}
Now following up I will give the code I made for the creation of the gameStage. You probably already have something for it, but I'll provide mine just in case you want to use it instead:
//Initializing the gameStage instance
var gameStage:Sprite = new Sprite();
//Adding the gameStage to the Stage's display field
this.stage.addChild(gameStage);
//Setting the gameStage's width and height (using "gameStage.width = 550" and "gameStage.height = 350" WILL NOT WORK)
//Use the color of your main game's background so you don't see this fill (unless you want to)
//Either do this or add a background picture, you need to do one or the other in order to set the gameStage's dimensions
gameStage.graphics.beginFill(0x000000);
gameStage.graphics.drawRect(0, 0, 550, 350);
gameStage.graphics.endFill();
//This puts the gameStage on the bottom of the screen (since it's 50 pixels shorter in the y direction)
gameStage.y = 50;
Lastly I will give you the actual for loop to create your circles (this function is present in the same class/FLA that your gameStage is on, because the addCircle function needs to take in that gameStage instance:
//Now let's populate your gameStage
for (var i:uint = 0; i < [number of circles you want]; i++) {
Main.addCircle(gameStage, [radius of the circle]);
}
And you're done! I'll also include the entire Main class, just so you can see how all the functions work together.
package {
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite {
public function Main() {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
var gameStage:Sprite = new Sprite();
this.stage.addChild(gameStage);
gameStage.graphics.beginFill(0x000000);
gameStage.graphics.drawRect(0, 0, 550, 350);
gameStage.graphics.endFill();
gameStage.y = 50;
for (var i:uint = 0; i < 150; i++) {
Main.addCircle(gameStage, Main.randomNumber(15, 25));
}
}
public static function addCircle(gameStage:Sprite, circleRadius:uint):void {
var newCircle:Sprite = new Sprite();
newCircle.graphics.beginFill(Math.random() * 0xFFFFFF);
newCircle.graphics.drawCircle(0, 0, circleRadius);
newCircle.graphics.endFill();
var safeStageX:Number = Main.randomNumber(newCircle.width / 2, gameStage.width - newCircle.width / 2);
var safeStageY:Number = Main.randomNumber(newCircle.height / 2, gameStage.height - newCircle.height / 2);
gameStage.addChild(newCircle);
newCircle.x = safeStageX;
newCircle.y = safeStageY;
}
public static function randomNumber(minValue:Number, maxValue:Number):uint {
return Math.floor(Math.random() * (1 + maxValue - minValue)) + minValue;
}
}
}

over drawn the circle when input angle in actionscript?

var theTextField:TextField = new TextField();
var theText:TextField = new TextField();
theTextField.type = TextFieldType.INPUT;
theTextField.border = true;
theTextField.x = 50;
theTextField.y = 10;
theTextField.height = 20;
theTextField.multiline = true;
theTextField.wordWrap = true;
theText.border = false;
theText.x = 10;
theText.y = 10;
theText.text = "Angle";
addChild(theText);
addChild(theTextField);
submit.addEventListener(MouseEvent.CLICK, click_handler);
function click_handler(event:MouseEvent):void
{
var txt:String = theTextField.text;
ang = Number(txt);
if (ang<0)
{
angle = - ang;
}
else
{
angle = 360 - ang;
}
var circleSlider:CircleSlider=new CircleSlider(120,angle); //draw Circle According to the angle i think here is problem becoz every time clicked it creates new circle and draw over the old circle.
circleSlider.x = stage.stageWidth / 2;
circleSlider.y = stage.stageHeight / 2;
circleSlider.addEventListener(CircleSliderEvent.CHANGE, circleSliderEventHandler);
addChild(circleSlider);
}
Can someone help me.
var circleSlider:CircleSlider=new CircleSlider(120,angle);//draw Circle According to the angle i think here is problem becoz every time clicked it creates new circle and draw over the old circle.
this code is the problem. CircleSlider is a separate class.I tried like this
circleSlider.CircleSlider(120,angle);
but it gives an error "" Call to a possibly undefined method CircleSlider through a reference with static type CircleSlider.""
when i run the program and input value as 90.
then i enter another value as 180 then it becomes
how can i overcome this error
Every time your click handler is executed you're creating a new instance of your circle class and adding it to the stage without removing the old instance. I think the best way to resolve it would be to move the logic you have in the constructor of your CircleSlider class into a separate public method, say draw and call that in the click handler.
Your code would look something like this:
// Set up the circle once
var circleSlider = new CircleSlider();
circleSlider.x = stage.stageWidth / 2;
circleSlider.y = stage.stageHeight / 2;
circleSlider.addEventListener(CircleSliderEvent.CHANGE, circleSliderEventHandler);
// and add it to the stage once
addChild(circleSlider);
function click_handler(event:MouseEvent):void
{
var txt:String = theTextField.text;
ang = Number(txt);
if (ang<0)
{
angle = - ang;
}
else
{
angle = 360 - ang;
}
// Now simply redraw in the same circle instance
circleSlider.draw(120,angle); //draw Circle According to the angle i think here is problem becoz every time clicked it creates new circle and draw over the old circle.
}
Assuming you're using the drawing API to draw the graphic, you could draw the circle (which seems to be constant) in the constructor (once) and the line illustrating the angle in the draw method (repeatedly). You'll need to clear the old line each time like this:
// Assumes you're drawing in the graphics property of the class
this.graphics.clear();

How to rotate a rectangle around centroid in flex 3

I am using matrix.rotate method to rotate the rectangle (box in my case).
My rotate event looks like below
public function transformObject(transformEvent:TransformEvent):void{
var numChildrn:int = _markedObjectLayer.numChildren;
var tempMatrix: Matrix = null;
var tempx:Number;
var tempy:Number;
var tempHeight:Number;
var tempWidth:Number;
for(var i:int = 0; i < numChildrn; i++){
var chld:MarkedObject = ObjectLayer.getChildAt(i)
if (chld.selected){
var height:int = (BoxObject) chld.height;
var width:int = (BoxObject) chld.width;
tempMatrix = chld.transform.matrix;
tempHeight=height;
tempWidth=width;
tempMatrix = MatrixTransformer.transform(tempMatrix,transformEvent.angle);
tempMatrix.tx=tempx;
tempMatrix.ty=tempy
chld.transform.matrix = tempMatrix;
}
}
invalidateDisplayList();
}
}
The Matrix.transform method calls matrix.rotate method
public static function transform(sourceMatrix:Matrix,
rotation:Number=0 ):Matrix
{
sourceMatrix = MatrixTransformer.rotate(sourceMatrix, rotation, "degrees");
return sourceMatrix;
}
/**
* Rotates a matrix and returns the result. The unit parameter lets the user specify "degrees",
* "gradients", or "radians".
*/
public static function rotate(sourceMatrix:Matrix, angle:Number, unit:String = "radians"):Matrix {
if (unit == "degrees")
{
angle = Math.PI * 2 *( angle / 360);
}
sourceMatrix. rotate(angle)
return sourceMatrix;
}
The issue is that x and y are left corener of the box and hence it is rotating around left corner. However, if I try to give temp.x and temp.y as centroid value it does not rotate around centroid?
Can any one suggest what am I doing wrong here?
Thanks
Akshay
If you really want or have to use matrices directly, you can do this more conveniently using a built-in flash class: fl.motion.MatrixTransformer:
MatrixTransformer.rotateAroundInternalPoint(matrix, centroidX, centroidY, angleInDegrees);
See the Adobe docs on MatrixTransformer for more information.
However, if you don't need to use the transformation matrix, the simpler solution would be to:
have your objects drawn in such a way that (0, 0) is their centroid
use the simple rotation property from DisplayObject which achieves the same goal in a much simpler manner
Figured it out. It appears that I was not translating them to appropriate coordinate positions before and after rotation
//Step 1 Fix the co-ordinates of rectangle . I added them to an event so that they remain static
if (TransformEvent.X == 0 && TransformEvent.Y == 0)
{
TransformEvent.X = chld.x;
TransformEvent.Y = chld.y;
}
//next get the centroid of rectangle
tempx = TransformEvent.X + width/2;
tempy= TransformEvent.Y +height/2;
// Step 3: translate before rotation
tempMatrix.translate(-1*tempx,-1*tempy);
//Rotate the rectangle
tempMatrix = MatrixTransformer.transform(tempMatrix,transformEvent.angle);
//translate to centroid after rotation
tempMatrix.translate(tempx,tempy);
//assign back the matrix to the rectangle
chld.transform.matrix = tempMatrix;
Thanks for all your help. Also the this site helped me with translation bit
http://www.foxarc.com/blog/article/66.htm

AS3 : Scaling Sprite with Matrix on slider change?

I have a sprite that holds a bitmap data.
I want to give the user the ability to resize the image with a slider.
I am using the code beneath, as you can see the problem is that the scaling is additive so very quickly the image is gone totally.
I understand that i have to scale it in non additive way, just can not figure out how?
I tried to pass :
var m:Matrix = userImageCopy.transform.matrix;
when userImageCopy holds the original image. that helped for the scaling but then, each time the scaling started the userImage jumped to the position of the userImageCopy.
Any help?
function onSliderChange(evt:Event):void
{
trace( evt.target.value);
//this will create a point object at the center of the display object
var ptRotationPoint:Point = new Point(userImage.x + userImage.width / 2,userImage.y + userImage.height / 2);
//call the function and pass in the object to be rotated, the amount to scale X and Y (sx, sy), and the point object we created
scaleFromCenter(userImage, evt.target.value, evt.target.value, ptRotationPoint);
}
private function scaleFromCenter(ob:*, sx:Number, sy:Number, ptScalePoint:Point)
{
var m:Matrix = userImage.transform.matrix;
m.tx -= ptScalePoint.x;
m.ty -= ptScalePoint.y;
m.scale(sx, sy);
m.tx += ptScalePoint.x;
m.ty += ptScalePoint.y;
ob.transform.matrix = m;
}
Rather than combining your center values with the current tx and ty, just set them directly with the Matrix.translate method:
private function scaleFromCenter(ob:*, sx:Number, sy:Number, ptScalePoint:Point)
{
var m:Matrix = new Matrix();
m.translate(-ptScalePoint.x,-ptScalePoint.y);
m.scale(sx, sy);
m.translate(ptScalePoint.x,ptScalePoint.y);
ob.transform.matrix = m;
}

Finding Something lighter than Sprites!

I am making a Sim City like game. There are lots of tiles. When I first started. I was just using a tilesheet. I was copying the necessary pieaces from the tilesheet. on to a blank bitMapData. I then took the bitMapData and put it into a bitMap which I then put into a DisplayObject. It worked great!
tileSheet:BitMapData <----- data is already in
loop { loop through and tiled
bg:bitMapData= new bitMapData();
bg.copyPixel(tileSheet,rect,point);
}
canvas.BitMap(bg);
addChild(canvas);
Only problem was I needed to make my tiles interactive. I needed to highlight them and change colors and stuff. So I used the Sprite object. It works great but I can only have so many on the stage at once. or else it moves slow when I scroll. I need something Lighter then a sprite, but yet I can still turn into a object to make interactive. Anyone have any ideas ???
If you have a lot of tiles, that will impact performance because Flash needs to update the transformations of a lot of display objects (which internally means a lot of matrix calculations, and subsequent redraws of big areas of the screen.)
There is another way to achieve interactivity, if you find that you must use a single bitmap data for performance. Keep an "abstract" (i.e. not graphical) data model in memory, that stores your game state. Make sure that you are able to read from your store where a certain element is positioned in the game world. Then you can use a flat bitmap data to render the game world, because the individual positions are stored elsewhere.
When the user clicks the DisplayObject containing the bitmap data (a Sprite in which the bitmap is drawn using a bitmap fill, or that wraps a Bitmap), look in your model which of your game elements was hit by that click.
// myTileSprite is a Sprite with a bitmap fill
myTileSprite.addEventListener(MouseEvent.CLICK, handleWorldClick);
function handleWorldClick(ev : MouseEvent) : void
{
var i : int;
// Loop through all game element data models
for (i=0; i<myGameElements.length; i++) {
// Test the mouse position against the element model
if (myGameElements[i].hitTest(myTileSprite.mouseX, myTileSprite.mouseY)) {
trace('this was the element that was clicked: '+myGameElements[i].toString());
}
}
}
Here, whenever the player clicks the world graphics, the loop tries to find that element which was directly under the mouse position. You will need to implement a hitTest() method on all your game element data models, of course. Such a method simply checks the supplied world space position against the tile's area:
// GameElement.hitTest():
/**
* Tests a world position against the position and area of this game
* element tile. Returns a boolean indicating whether this tile was hit.
*/
public function hitTest(mouseX : Number, mouseY : Number) : void
{
var rect : Rectangle = new Rectangle(this.worldX, this.worldY, this.width, this.height);
if (mouseX > rect.left && mouseX < rect.right
&& mouseY > rect.top && mouseY < rect.top) {
return true;
}
else return false;
}
The GameElement class is not an display object, but has worldX and worldY properties indicating where it is located in the world. It's width and height properties define it's dimensions.
The trick from hereon is to make sure that the rendered bitmap and your model storage is synchronized, so that a tile's position on the bitmap really corresponds to it's worldX/worldY properties in the data model.
I am one step ahead of you. And that is a great idea. Its alot easier to keep a data representation of the world when the tiles are squared. I therefore can take my mouseX/tileWidth, and thats hw many columns I moved from left to right. same with the Y axis.
Not only that but coordinates start at top left corner.
But issue I have is that my tiles are Isometric. So instead of the X axis start off like...
012345678
0
1
2
3
4
5
6
7
8
My tiles are aligned like...
00
1 1
2 2
3 3
4 4
5 6
its a little sloppy. but the right side represents the y axis and the left represents the x axis. and the center origin is in the center of the screen. not on the top left. I am trying to figure out how to measure where my mouse is from the center and out on both sides. This sounds extremely difficult. I am not sure if its possible. The game is suppose to be like a sim city like game. The first sim city was squares not isometric. I dont think they went isometric until they started using 3d. I wonder if its possible to create a illusion of isometric on a square tile.
Ive been reading this great book on isometrics. They show to calculate tiles in 3d space. and even calculate your mouse in 3d space as well. here is the code. Its alot, but I hope someone else understands it more then I. The book was written by jobe makar on building multiplayer worlds. I wanted to share it because the code it is pretty simple as far as amount of code put into it. only 2 classes needed. I am not that good with trigonometry. so I cant really interpret how the math is getting the results. hopefully someone can explain that for me :D.
Y coordinates are not given because the width is = to height. The coordinates method is just a custom made Point class which holds x, y and z.
package com.gamebook.grid {
import com.gamebook.utils.geom.Coordinate;
import com.gamebook.utils.Isometric;
import flash.display.MovieClip;
import flash.events.MouseEvent;
/**
* ...
* #author Jobe Makar - jobe#electrotank.com
*/
public class Map extends MovieClip{
private var _grid:Array;
private var _iso:Isometric;
private var _tileWidthOnScreen:int;
private var _tileHeightOnScreen:int;
private var _tileWidth:Number;
private var _tileHeight:Number;
private var _cols:int;
private var _rows:int;
private var _lastTile:Tile;
public function Map() {
initialize();
}
private function initialize():void{
_iso = new Isometric();
//when mapped to the screen the tile makes a diamond of these dimensions
_tileWidthOnScreen = 64;
_tileHeightOnScreen = 32;
//figure out the width of the tile in 3D space
_tileWidth = _iso.mapToIsoWorld(64, 0).x;
//the tile is a square in 3D space so the height matches the width
_tileHeight = _tileWidth;
buildGrid();
addEventListener(MouseEvent.MOUSE_MOVE, mouseMoved);
}
private function mouseMoved(e:MouseEvent):void {
if (_lastTile != null) {
_lastTile.alpha = 1;
_lastTile = null;
}
var coord:Coordinate = _iso.mapToIsoWorld(mouseX, mouseY);
var col:int = Math.floor(coord.x / _tileWidth);
var row:int = Math.floor(Math.abs(coord.z / _tileHeight));
if (col < _cols && row < _rows) {
var tile:Tile = getTile(col, row);
tile.alpha = .5;
_lastTile = tile;
}
}
private function buildGrid():void{
_grid = [];
_cols = 10;
_rows = 10;
for (var i:int = 0; i < _cols;++i) {
_grid[i] = [];
for (var j:int = 0; j < _rows;++j) {
var t:Tile = new Tile();
var tx:Number = i * _tileWidth;
var tz:Number = -j * _tileHeight;
var coord:Coordinate = _iso.mapToScreen(tx, 0, tz);
t.x = coord.x;
t.y = coord.y;
_grid[i][j] = t;
addChild(t);
}
}
}
private function getTile(col:int, row:int):Tile {
return _grid[col][row];
}
}
}
Then we have the isometric class that calculates 3d space.
package com.gamebook.utils {
import com.gamebook.utils.geom.Coordinate;
/**
* #author Jobe Makar - jobe#electrotank.com
*/
public class Isometric {
//trigonometric values stored for later use
private var _sinTheta:Number;
private var _cosTheta:Number;
private var _sinAlpha:Number;
private var _cosAlpha:Number;
/**
* Isometric class contrustor.
* #param declination value. Defaults to the most common value, which is 30.
*/
public function Isometric() {
var theta:Number = 30;//even though the tiles are already isometric, you still have to put the degrees the tiles will be turned.
var alpha:Number = 45;//45 degrees on y axis, 30 dgrees on x axis
theta *= Math.PI/180; // then you translate to radians
alpha *= Math.PI/180;
_sinTheta = Math.sin(theta);
_cosTheta = Math.cos(theta);
_sinAlpha = Math.sin(alpha);
_cosAlpha = Math.cos(alpha);
}
/**
* Maps 3D coordinates to the 2D screen
* #param x coordinate
* #param y coordinate
* #param z coordinate
* #return Coordinate instance containig screen x and screen y
*/
public function mapToScreen(xpp:Number, ypp:Number, zpp:Number):Coordinate {
var yp:Number = ypp;
var xp:Number = xpp*_cosAlpha+zpp*_sinAlpha;
var zp:Number = zpp*_cosAlpha-xpp*_sinAlpha;
var x:Number = xp;
var y:Number = yp*_cosTheta-zp*_sinTheta;
return new Coordinate(x, y, 0);
}
/**
* Maps 2D screen coordinates into 3D coordinates. It is assumed that the target 3D y coordinate is 0.
* #param screen x coordinate
* #param screen y coordinate
* #return Coordinate instance containig 3D x, y, and z
*/
public function mapToIsoWorld(screenX:Number, screenY:Number):Coordinate {
var z:Number = (screenX/_cosAlpha-screenY/(_sinAlpha*_sinTheta))*(1/(_cosAlpha/_sinAlpha+_sinAlpha/_cosAlpha));
var x:Number = (1/_cosAlpha)*(screenX-z*_sinAlpha);
return new Coordinate(x, 0, z);
}
}
}