How to draw shapes in correct position in godot engine? - draw

I am trying to make a selection tool. The collision shape is drawn correctly, as you can see in form of the filled box. While the mouse is dragged, I want to draw a green rectangle. However, the box is drawn in the wrong place. What am I missing?
extends Area2D
onready var shape: Shape2D = $CollisionShape2D.shape
onready var collision: CollisionShape2D = $CollisionShape2D
export (Color, RGBA) var color = Color.green
export var stroke_width: float = 1.0
export (bool) var isFilled = true
var startOfDrag: Vector2
var endOfDrag: Vector2
var width: int = 1
var height: int = 1
var isMouseDragged: bool = false
var points: PoolVector2Array
func _process(delta: float) -> void:
if isMouseDragged:
update()
func _draw():
draw_polyline(points , color, stroke_width, isFilled)
func _input(event: InputEvent) -> void:
if Input.is_action_just_pressed("left_mouse_down") and not isMouseDragged:
startOfDrag = get_global_mouse_position()
position = startOfDrag
shape.set_extents(Vector2(1, 1))
isMouseDragged = true
if Input.is_action_just_released("left_mouse_down"):
isMouseDragged = false
handleSelection()
endOfDrag = get_global_mouse_position()
width = abs(startOfDrag.x - endOfDrag.x)
height = abs(startOfDrag.y - endOfDrag.y)
change_rectangle()
func handleSelection() -> void:
shape.set_extents(Vector2(width/2, height/2))
collision.position = Vector2(width/2, height/2)
func change_rectangle() -> void:
points = []
points.append(Vector2(startOfDrag.x, startOfDrag.y))
points.append(Vector2(startOfDrag.x + width, startOfDrag.y))
points.append(Vector2(startOfDrag.x + width, startOfDrag.y + height))
points.append(Vector2(startOfDrag.x, startOfDrag.y + height))
points.append(Vector2(startOfDrag.x, startOfDrag.y))

This is what I see:
startOfDrag = get_global_mouse_position()
position = startOfDrag
The mouse position is in global coordinates. While the position of your object is in local coordinates (you can think of it as coordinates relative to the parent).
Set the global_position instead:
startOfDrag = get_global_mouse_position()
global_position = startOfDrag
By the way, the method to_local converts global coordinates to local coordinates of the node on which you call it. Similarly, there is a to_global method that will convert local coordinates of the node on which you call it to global coordinates.
Keep track of which vectors are in global coordinates and which are in local coordinates.
If there is scaling applied your width and height might also not match. If you run into that, you should be able to fix it with to_local of the CollisionShape2D.

Related

Create Cesium.Ray from Cesium.Cartesian3 and Cesium.HeadingPitchRoll

Is it possible to create a Cesium.Ray from a Cesium.Cartesian3 and a Cesium.HeadingPitchRoll?
If so, how would one go about this?
EDIT 1:
const hpr = Cesium.HeadingPitchRoll.fromDegrees(0, 0, 0)
const matrix3 = Cesium.Matrix3.fromHeadingPitchRoll(hpr)
// The UNIT_X vector is "forward" in Cesium. We'll multiply the rotation
// matrix by that unit vector, to get our direction vector.
const direction = Cesium.Matrix3.multiplyByVector(
matrix3,
Cesium.Cartesian3.UNIT_X,
new Cesium.Cartesian3()
)
const origin = Cesium.Cartesian3.fromDegrees(i.longitude, i.latitude, i.altitude)
// Now we can construct the Ray.
const ray = new Cesium.Ray(origin, direction)
const finalPoint = Cesium.Ray.getPoint(ray, i.altitude)
const e = map.viewer.entities.add({
name: i.id,
polyline: {
positions: [origin, finalPoint],
width: 4,
arcType: Cesium.ArcType.NONE,
material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.RED),
},
})
With HPR set to (0,0,0), this results in:
With a pitch of 0, I would be expecting the ray to be parallel with the horizon instead of what seems to be +45 degrees.
Yes, it is possible. These two kinds of objects hold different kinds of information, so be careful that you're not losing data in the conversion.
A Ray has an origin (Cartesian position) and a direction which is just a unit vector, not the same as a full orientation.
The HeadingPitchRoll is a full orientation. The Heading and Pitch work together to identify a particular "forward" direction, and Roll indicates a rotation about that direction vector. This Roll rotation does not affect the direction, and so will not have an effect on a Ray.
Here's the code:
// A Cesium Ray consists of an "origin" and a "direction".
// The origin is just a Cartesian3 from anywhere.
var origin = new Cesium.Cartesian3(1.0, 2.0, 3.0);
// For this question, "direction" comes from a HeadingPitchRoll.
// Let's establish an HPR here.
var heading = Cesium.Math.toRadians(0); // YOUR VALUE HERE
var pitch = Cesium.Math.toRadians(0); // YOUR VALUE HERE
var roll = Cesium.Math.toRadians(0); // This one does nothing.
var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
// Next we need a direction vector from the HPR. We'll use
// a rotation matrix (3x3) to do the conversion.
var matrix3 = Cesium.Matrix3.fromHeadingPitchRoll(hpr);
// The UNIT_X vector is "forward" in Cesium. We'll multiply the rotation
// matrix by that unit vector, to get our direction vector.
var direction = Cesium.Matrix3.multiplyByVector(matrix3, Cesium.Cartesian3.UNIT_X,
new Cesium.Cartesian3());
// Now we can construct the Ray.
var ray = new Cesium.Ray(origin, direction);
console.log('Ray: ' + JSON.stringify(ray));
If you run this with the default zero heading/pitch shown here, you'll get the unit X vector back again:
Ray: {"origin":{"x":1,"y":2,"z":3},"direction":{"x":1,"y":0,"z":0}}
But if you set the pitch to 45 degress:
var heading = Cesium.Math.toRadians(0);
var pitch = Cesium.Math.toRadians(45);
You'll see the X direction has pitched up towards +Z.
Ray: {"origin":{"x":1,"y":2,"z":3},"direction":{"x":0.7071067811865476,"y":0,"z":0.7071067811865475}}
A positive heading will rotate the X vector towards -Y (clockwise as seen from +Z above).
var heading = Cesium.Math.toRadians(45);
var pitch = Cesium.Math.toRadians(0);
Ray: {"origin":{"x":1,"y":2,"z":3},"direction":{"x":0.7071067811865476,"y":-0.7071067811865475,"z":0}}
EDIT: The question was revised a bit. The Ray's direction needs to be in Earth-Centered Fixed frame, not local-up frame, for the intended use.
Here's a longer demo that shows how to handle this. You can also view a live demo on Sandcastle.
var viewer = new Cesium.Viewer("cesiumContainer");
const i = {
id: 'test',
longitude: -79,
latitude: 40,
altitude: 500
};
// Create the origin and hpr.
const origin = Cesium.Cartesian3.fromDegrees(i.longitude, i.latitude, i.altitude)
let hpr = Cesium.HeadingPitchRoll.fromDegrees(0, 0, 0)
// Cesium uses East-North-Up axes, where East is zero. If you want North == Zero,
// you must subtract 90 degrees' worth of radians (pi/2).
hpr.heading -= Cesium.Math.PI_OVER_TWO
// We'll get the orientation, which is a quaternion. This is specific to the
// location on the planet. Also it comes in handy later for the 3D model vis.
const orientation = Cesium.Transforms.headingPitchRollQuaternion(origin, hpr);
// Get a Matrix3 version of that Quaternion.
const matrix3 = Cesium.Matrix3.fromQuaternion(orientation);
// The UNIT_X vector is "forward" in Cesium. We'll multiply the rotation
// matrix by that unit vector, to get our direction vector.
const direction = Cesium.Matrix3.multiplyByVector(
matrix3,
Cesium.Cartesian3.UNIT_X,
new Cesium.Cartesian3()
)
// Now we can construct the Ray.
const ray = new Cesium.Ray(origin, direction)
const finalPoint = Cesium.Ray.getPoint(ray, i.altitude)
const e = viewer.entities.add({
name: i.id,
polyline: {
positions: [origin, finalPoint],
width: 4,
arcType: Cesium.ArcType.NONE,
material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.RED),
},
})
// Optionally, show an aircraft model with this HPR.
const m = viewer.entities.add({
position: origin,
orientation: orientation,
model: {
uri: "../SampleData/models/CesiumAir/Cesium_Air.glb",
minimumPixelSize: 64,
}
})
viewer.zoomTo(e)

Autodesk Forge: programmatically zoom out after focus on object by fitToView()

After select and focusing on an object with fitToView(objectIds, model),
I want to zoom out the camera programatically instead of via mouse events.
I tried the following code and it was able to zoom out, but the 3D space was distorted.
If you know how to correctly zoom out, please let me know.
// select and isolate target object
let dbid = [1137]
viewer.impl.visibilityManager.isolate(dbid, viewer.model)
viewer.select(dbid)
//focus on target object
viewer.getAggregateSelection((data) => {
let rootId = data.selector.getInstanceTree().nodeAccess.rootId
viewer.fitToView(rootId, viewer.model)
})
//zoom out camera
let cam = viewer.getCamera().perspectiveCamera
cam.zoom *= 0.05
viewer.impl.syncCamera();
It's not the zoom property that you need to change but the camera position:
var nav = viewer.navigation
var pos = nav.getPosition()
var target = nav.getTarget()
var viewdir = new THREE.Vector3()
viewdir.subVectors (pos, target).normalize()
// zooms out by 100 along the view direction
viewdir.multiplyScalar (100)
pos.add(viewdir)
nav.setPosition(pos)
It's not the zoom property that you need to change but set the camera position:
let camera = this.viewer3D.getCamera();
viewer.fitToView(dbIds, null, true); //provide your device id to locate
var direction = new THREE.Vector3();
camera.getWorldDirection( direction );
//set distance move the camera forward to your needs (gives value in minus for zoomout)
camera.position.add( direction.multiplyScalar(-15));
let fov = viewer.navigation.getVerticalFov(); //or horizontalFov
viewer.navigation.setRequestTransition(true, camera.position,viewer.navigation.getTarget(), fov);
//if not work then try fov + 20,30,40 etc..

Maximum call stack size exceeded error / Properly writing constructors

Firstly, I got a 'maximum call stack size exceeded' error on some JavaScript code I'll be posting below, and I'm not sure where it's coming from. I don't think I'm calling anything recursively, and I couldn't find any other cause of the error from looking around. I'm using Chrome to execute the program, and the only details I can get out of the console are 'Vector Vector Vector Vector Vector Vector Vector...' etc. I think this means that the Vector function is recursively calling itself, but, again, I can't figure out where that's happening. So, firstly, here's the Vector function:
function Vector(top,left) { //a vector constructor, with all necessary vector operations
this.top = top; //the 'top' component, which points down
this.left = left; //the 'left' component, which points right
this.in = 0; //the 'in' component, which points out of the screen
this.magnitude = Math.sqrt(this.top*this.top+this.left*this.left); //the magnitude of the vector
this.reverse = new Vector(-this.top,-this.left); //the vector that points opposite of this vector
this.unit = this.Scale(1/this.magnitude); //the unit vector pointing in this vector's direction
this.Scale = Scale; //multiplying vectors by a constant
this.Add = Add; //easier vector addition
this.Subtract = Subtract; //and subtraction
this.Dot = Dot; //a dot product operation
this.Cross = Cross; //the cross product of two planar vectors; it returns the 'in' component of the resulting vector
this.Project = Project; //the projetion of the parameter vector onto this vector
function Scale(scalar) {return new Vector(scalar*this.top,scalar*this.left);}
function Add(vector) {return new Vector(this.top+vector.top,this.left+vector.left);}
function Subtract(vector) {return new Vector(this.top-vector.top,this.left-vector.left);} //the abovedescribed methods
function Dot(vector) {return this.top*vector.top+this.left*vector.left;}
function Cross(vector) {return this.top*vector.left-this.left*vector.top;}
function Project(vector) {return this.Scale(this.Dot(vector)/Math.pow(this.magnitude,2))}
}
var zeroVector = new Vector(0,0);
And here's the full code. Feel free to completely ignore it, though maybe something will be blatantly obvious to you which was impeccably hidden from me.
<!DOCTYPE html>
<html>
<head>
<title>temp</title>
</head>
<body>
<canvas
id="canvas"
height="768px"
width="1366px"
style="position:fixed;top:0px;left:0px;"
/>
</body>
<script>
/*------------------------------------------------------------Composition Center--------------------------------------------------*/ {
window.onload = run; //it'll start running as soon as it's started up
var interval; //the interval variable for the 'run' function
function run() {
interval = setInterval(function() {
for (i=0; i!=0; i++) {
var Point = point;
var vPrime = zeroVector;
var netForce = zeroVector;
for (j=0; j!=0; j++) {
var Edge = edge;
updateCollision(Point,Edge);
}
updatePosition(Point);
updateVelocity(Point);
draw();
}
},16);
}
/*---------------------------------------------------------------------------------------------------------------------------------*/ }
/*-------------------------------------------------------------Physics Engine------------------------------------------------------*/ {
//--Constructors--//
function PointObject(mass,top,left) { //a point object
this.mass = mass; //mass
this.position = new Vector(top,left); //position
this.velocity = zeroVector; //velocity
this.near = nullEdge; //a memory tool which stores the edge the point is close to
}
function Rectangle(top,left,height,width) { //a rectangle that'll be solid, but not affected by gravity or anything
this.position = new Vector(top,left);
this.height = height; //self-explanatory
this.width = width;
}
function Edge(start,end) {
this.start = start; //the start point (vector)
this.end = end; //end point (vector)
this.vector = end.Subtract(start); //the vector pointing from the start to the end point of the edge
this.RUN = new Vector(this.vector.left,-this.vector.top).unit; //the RIGHT pointing UNIT NORMAL vector to the edge vector
this.rightPermeable = false; //whether or not the edge can be passed through from the right to the left
this.leftPermeable = true; //or from the left to the right
}
//--Objects--//
var base = new Rectangle(3/4*768,33,768/4,1300); //the main base solid
var point = new PointObject(1,base.position.top-201,1366/2); //the point object at the center of this whole ordeal
var edge = new Edge(new Vector(base.position.top,base.position.left+base.width),base.position); //a test edge. they'll be auto-assembled later
var nullEdge = new Edge(zeroVector,zeroVector);
var deltaT = .01; //standard time interval
//--Loop o' Physics--//
//Update Collision
function updateCollision(Point,edge) { //check if any points are crossing edges and deal with that
var startToPoint = Point.position.Subtract(edge.start); //the vector from the start point of the edge to the tested point
if (edge.rightPermeable && edge.vector.Cross(startToPoint) < 0) {return "Negatory.";} //if it could only approach from a permeable side, stop testing
if (edge.leftPermeable && edge.vector.Cross(startToPoint) > 0) {return "Negatory.";}
var projection = edge.vector.Project(startToPoint); //the projection of that vector onto the edge vector
if (projection.Dot(edge.vector) < 0) {return "Negatory.";} //if it's not pointing in the same dircetion as the edge vector, it's not gonna hit the edge
var distance = startToPoint.Subtract(projection); //the normal vector to the edge that points to the Point
var velocity = distance.Project(Point.velocity); //the component of the point's velocity orthogonal to the edge
if (distance.Dot(velocity) >= 0) {return "Negatory.";} //if the orthogonal velocity component is pointing 'to the point,' it won't collide
var specificT = distance.magnitude/velocity.magnitude; //the time it'll take for the point to hit the edge, if it continues with it's current velocity
if (deltaT <= specificT) {return "Negatory.";} //if the point can't move far enough to cover the distance, it won't collide
//at this point, the collision will happen
Point.near = edge; //have the point remember that it collided with this edge, so it won't have to be totally re-tested for normal forces
Point.position = Point.position.Add(Point.velocity.Scale(specificT)).Add(distance.Scale(.0001)); //move the point just a wee bit away from the edge
vPrime = Point.velocity.Add(velocity.reverse); //impulse away the velocity orthogonal to the edge
Point.velocity = Point.velocity.Scale(0); //zero the current velocity
}
//Update Position
function updatePosition(Point) {Point.position = Point.position.Add(Point.velocity.Scale(deltaT));} //d=vt, essentially
//Update Velocity
function updateVelocity(Point) { //via forces
var gravity = new Vector(20,0); //gravity... hobviously
netForce = netForce.Add(gravity); //add up all the current forces to get the right normal force
if (Point.near != nullEdge) { //calculating normal force, so it needs to be near an edge
var startToPoint = Point.position.Subtract(Point.near.start);
var projection = Point.near.vector.Project(startToPoint); //same stuff as earlier
var distance = startToPoint.Subtract(projection);
if (distance.magnitude <= .01) { //if it's very near to the edge
normalForce = distance.Project(netForce.reverse); //exert enough force to cancel anything going into the edge
if (normalForce.Dot(distance) > 0) {netForce = netForce.Add(normalForce);} //if it is indeed going into the edge
} else if (distance.magnitude > 1) {Point.near = nullEdge;} //it's not near the edge any more
}
Point.velocity = Point.velocity.Add(netForce.Scale(deltaT/Point.mass)); //v=at, essentially
}
/*---------------------------------------------------------------------------------------------------------------------------------*/ }
/*----------------------------------------------------Graphics----------------------------------------------------------------------*/ {
var c = document.getElementById("canvas").getContext("2d"); //the means for drawing all this
PointObject.prototype.Draw = function() { //the draw method for point objects
c.fillStyle = "#000000"; //a black square with the point at its center pixel
c.fillRect(this.position.left-2,this.position.top-2,5,5);
}
Rectangle.prototype.Draw = function() { //draw method for rectangles
c.fillStyle = "#c0c0c0"; //a grey rectangle; the position is the top left corner
c.fillRect(this.position.left,this.position.top,this.width,this.height);
}
function draw() { //draws everything
c.clearRect(0,0,1366,768); //clear the screen
base.Draw(); //paint stuff
point.Draw();
}
/*----------------------------------------------------------------------------------------------------------------------------------*/ }
/*------------------------------------------------------------Math Tools-------------------------------------------------------------*/ {
//--Vector Tools--//
function Vector(top,left) { //a vector constructor, with all necessary vector operations
this.top = top; //the 'top' component, which points down
this.left = left; //the 'left' component, which points right
this.in = 0; //the 'in' component, which points out of the screen
this.magnitude = Math.sqrt(this.top*this.top+this.left*this.left); //the magnitude of the vector
this.reverse = new Vector(-this.top,-this.left); //the vector that points opposite of this vector
this.unit = this.Scale(1/this.magnitude); //the unit vector pointing in this vector's direction
this.Scale = Scale; //multiplying vectors by a constant
this.Add = Add; //easier vector addition
this.Subtract = Subtract; //and subtraction
this.Dot = Dot; //a dot product operation
this.Cross = Cross; //the cross product of two planar vectors; it returns the 'in' component of the resulting vector
this.Project = Project; //the projetion of the parameter vector onto this vector
function Scale(scalar) {return new Vector(scalar*this.top,scalar*this.left);}
function Add(vector) {return new Vector(this.top+vector.top,this.left+vector.left);}
function Subtract(vector) {return new Vector(this.top-vector.top,this.left-vector.left);} //the abovedescribed methods
function Dot(vector) {return this.top*vector.top+this.left*vector.left;}
function Cross(vector) {return this.top*vector.left-this.left*vector.top;}
function Project(vector) {return this.Scale(this.Dot(vector)/Math.pow(this.magnitude,2))}
}
var zeroVector = new Vector(0,0);
/*-----------------------------------------------------------------------------------------------------------------------------------*/ }
</script>
</html>
Anyway, second thing is about how I've written some bits of the Vector constructor, which may possibly somehow be causing my error. At various points I use 'new Vector()' in the properties and methods of the function, and I don't know if that's illegal or not. I think I've done it before without error, but I'm not sure.
Also, for the 'magnitude,' 'reverse,' and 'unit' properties, should I rephrase them as methods? I think the way they are they're being locked into the magnitude/reverse/unit vector of the vector as it is created, so if the top or left properties are changed, they'll return incorrect values. The only reason I haven't changed them yet is that they strike me more as properties than methods, just conceptually. It hasn't caused any problems yet or anything; just wondering.
If you want this on JSFiddle or something else like that, I'd be happy to oblige you.

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

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);
}
}
}