How to find the associated polygon from pixel canvas HTML5 - html

I am working on a HTML5 canvas application. I am now able to find the co ordinates and the hex value of any point I am clicking over the canvas.
If suppose I am clicking an area which have a filled polygon (and I know the color of the polygon). Is there any way or algorithm to return the enclosing Co-ordinates which drew the polygon ??

Solution
Store the polygon points in objects or array and use the canvas method:
var boolean = context.isPointInPath(x, y);
By storing the points as objects/arrays you can simply iterate through the collection to re-build the path for each polygon and then call the method above (no need to redraw the polygon for checks).
If you have a hit then you know which object it is as it would be the current. You can store other meta data on these objects such as color.
Example
You can make a simple class to store coordinates as well as color (a basic objectyou can extend as needed):
function Polygon(pts, color) {
this.points = pts;
this.color = color;
return this;
}
Then define some points:
/// object collection
var myPolygons = [];
/// add polygons to it
myPolygons.push( new Polygon([[10,10], [30,30], [70,70]], 'black') );
myPolygons.push( new Polygon([[50,40], [70,80], [30,30]], 'red') );
Render the polygons
Then render the polygons:
for(var i = 0, p, points; p = myPolygons[i]; i++) {
points = p.points;
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for(var t = 1; t < points.length; t++) {
ctx.lineTo(points[t][0], points[t][1]);
}
ctx.closePath();
ctx.fillStyle = p.color;
ctx.fill();
}
Check if the point is in the path
Then check for hit based on position x, y when you do a click.
As you can see the function is almost identical to the render function (and of course you can refactor them into a single function) but this doesn't actually draw anything, only re-builds the path for check.
for(var i = 0, p, points; p = myPolygons[i]; i++) {
points = p.points;
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for(var t = 1; t < points.length; t++) {
ctx.lineTo(points[t][0], points[t][1]);
}
ctx.closePath();
/// instead of drawing anything, we check:
if (ctx.isPointInPath(x, y) === true) {
alert('Hit: ' + p.color);
return;
}
}
Conclusion
Typically this will turn out to be faster than iterating the bitmap array (which is also is a perfectly good solution). This is mainly because the checks are done internally in compiled code.
In the future we will have access to building Path objects ourselves and with that we can simply pass a single Path object already holding all the path info instead of re-building it, which means even higher speed.

Related

Flash AS3: Typewriter effect with copyPixels

I'm making a Flash AS3 based game, and I'm building a custom font system. It works like this: a BitmapData class takes a PNG file from the library (FontSource), loops between every character in a given string and then gets its x, y, width and height inside the image from a function (getOffset), and then it uses copyPixels to draw the custom-font text.
Here's the code:
public function draw(str) {
var png = new FontSource(480, 32);
var offsets;
var char;
var rect;
var x;
var y;
this.lock();
for (var i = 0; i < str.length; i++) {
char = str.substr(i, 1);
offsets = getOffsets(char);
rect = new Rectangle(offsets[0], offsets[1], offsets[2], offsets[3]);
this.copyPixels(png, rect, new Point(x, y));
x += offsets[2];
}
this.unlock();
}
Now, the question here is: I'm building a typewriter effect class based on the ENTER_FRAME event; each new frame, a new character is added to a string. So, I wanted to ask which one of these approaches would do better related in a matter of performance:
1) Call draw() to redraw the whole BitmapData each frame.
2) Making an alpha mask and expand its width each frame.
3) Making separate Objects and adding them to stage each frame.
Thank you for your time.
As an alternative, you don't need to redraw the entire string. You can just draw more characters at its end. I'd implement this as follows: You give your bitmapped textfield a string it should draw per frame, once. It clears, then at each enter frame event it just adds 1 to the length of the string drawn, and draws only one single character. Just save more data in your class for this. For example:
public class BTF extends Sprite {
private var str:String;
private var source:FontSource; // set once!
private var bd:BitmapData; // the one that'll get drawn
private var lastIndex:int;
private var lastX:int; // coords to draw next symbol to
// assuming Y is always 0 - adjust per font
public function BTF(source:FontSource,width:int,height:int) {
this.source=source;
bd=new BitmapData(width,height,0x00808080,true); // check if I didn't mix up parameters
addChild(new Bitmap(bd)); // make it be drawn on your BTF
}
... // rest of class skipped
public function onEnterFrame(e:Event):void {
if (lastIndex>=str.length) return; // drawn all text already
var c:char=str.charAt(lastIndex++); // get next char to draw
var r:Rectangle=source.getOffsets(c); // you need to specify source for that - it's font that gives you bounds
bd.copyPixels(source.fontBitmapData,r,new Point(lastX,0)); // draw that char
lastX+=r.width; // move "cursor"
}

How do I change the hitbox of a movie clip in actionscript 3 to not use the bounding box?

We have an object (mc_robert) that collides with another object (mc_left). When the collision occurs, we have an action take place. What we're trying to figure out is how to change the collision box of the object (mc_robert) so that the objects overlap when the collision detects. We don't want the default collision box of the object (mc_robert). Any help that can be offered would be greatly appreciated.
This is what we have for our code currently:
var numX:Number = 0;
var numY:Number = -2;
addEventListener(Event.ENTER_FRAME,loop);
function loop(e:Event)
{
mc_robert.y += numY;
mc_robert.x += numX;
if (mc_robert.hitTestObject(mc_left))
{
numX = -2;
numY = 0;
mc_robert.rotation = -90;
}
}
For pixel perfect collision convert the shapes into BitmapData and use the BitmapData's Hit-test method.
If you don't want to do the above approach you can use hitTestPoint and create multiple points in a for loop to do the collision.
eg: Haven't tested the following code but the concept is there. This will create a bounding box, or you can use some trig to collide from a circle or custom create the points.
for(var y:int = 0;y<2;y++){
for(var x:int=0;x<2;x++){
var w:int = mc2.width*x;
var h:int = mc2.height*y;
if(mc1.hitTestPoint(mc2.x+w, mc2.y+h, true)){
trace("collides");
}
}
}
How ever the solution I would use for the best accuracy would be a BitmapData collision.

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

How to Save a Shape (or Sprite) to a Folder After Creation?

I am trying to create a county map of Illinois using x,y coordinates from a shp file. I have the x,y coordinates and the county names saved in a CSV file that I have no problem reading in via ActionScript 3 (and the map looks great), but in order to save time in my future application I would like to save each county shape as a permanent shape in my application and give them labels, i.e. Sprite1 (label='Champaign'). Is this possible? If so, any help would be greatly appreciated.
In case this is not possible I am trying an alternate solution: I create a new sprite (var spr:Sprite = new Sprite();) for each county, draw it using spr.graphics.drawPath, and give it a name (spr.name='Champaign') and then 'push' it to a vector of sprites (var xy_sprites:Vector. = new Vector.();). This would be great, but it doesn't work when I try to loop through each sprite in the vector and add an EventListener to that Sprite to pop up the name when you MouseOver any of the counties. Is the Sprite data type not the correct way to go about this or am a missing something about Sprites?
Some of my code to draw the shapes and save in a vector:
function drawXYMap(str:String):Vector.<Sprite> {
var arr:Array = str.split("\n");
var xy_Sprites:Vector.<Sprite> = new Vector.<Sprite>();
for (var i:int=0; i<arr.length-1; ++i) {
var spr:Sprite = new Sprite();
spr.graphics.lineStyle(1.0, 0x000000);
spr.graphics.beginFill(0x666699);
arr[i] = arr[i].split(',');
var xy_commands:Vector.<int> = new Vector.<int>();
var xy_coord:Vector.<Number> = new Vector.<Number>();
for (var j:int=1; j<arr[i].length; ++j) {
xy_coord.push(arr[i][j]*6);
if (j==1) {
xy_commands.push(1); // 1 is a move-to command
var cntry:String = arr[i][j-1] as String; //country name
}
else if (j % 2 == 1) {
xy_commands.push(2); // 2 is a line-to command
}
}
spr.graphics.drawPath(xy_commands, xy_coord);
spr.name = cntry;
xy_Sprites.push(spr);
addChild(spr);
}
return xy_Sprites;
}
But I can't seem to add an Event Listener to each sprite in the vector of sprites I created:
var str:String = csvLoader.data as String;
var xy_spr:Vector.<Sprite> = drawXYMap(str);
for each (var spr:Sprite in xy_spr) {
spr.addEventListener(MouseEvent.MOUSE_OVER,onOver);
}
function onOver(e:MouseEvent):void {
spr.alpha = .25;
}
Any help would be great.
Thanks!
You could put all of your x, y co-ords into a Vector data structure, and then use the graphics.drawPath() method, to iterate over the co-ords any time you instantiate the county.
If you created a class that extended Shape, you could add a Vector that references the CSV file, and every time you create the shape, it automatically draws the path.
Check out this useful document from Adobe:
http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WSA8BD9022-BAB1-46d3-9B26-0D9649743C8E.html
PS don't forget to use the proper drawing commands for the drawPath(). You should be able to interact with it as normal if you do.