How do I calculate distance? - actionscript-3

I want to calculate distance, so like. "If (object 10 pixels or less close to this object)" or like (object 10 pixels or more far to this object)
Is there a good way to do this? Thanks.

You have to get the distance on each axis first, and then form a triangle between them using pythagorean theorem:
var p1:Point = new Point(0,0);
var p2:Point = new Point(3,4);
//distance on the x axis
var dx = p2.x - p1.x; //3
//distance on the y axis
var dy = p2.y - p1.y; //4
//c^2 = a^2 + b^2
var distance = Math.sqrt( (dy * dy) + (dx * dx) ); //5
Here is a function you can use to do this
function getDistance(p1:Point, p2:Point):Number
{
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
return Math.sqrt( (dy * dy) + (dx * dx) );
}

I believe the best way is to use the static method distance (Returns the distance between pt1 and pt2) of the class Point.
I also recommend you also to check some useful methods as distance, interpolate and so on.
var p1:Point = new Point(0,0);
var p2:Point = new Point(3,4);
trace(Point.distance(p1, p2)); // 5

Related

Centering on a canvas object within an HTML5 canvas

I have an Html5 canvas which i am drawing squares to.
The canvas itself is roughly the size of the window.
When i detect a click on a square i would like to translate the canvas so that the square is roughly in the center of the window. Any insights, hints, or straight-forward replies are welcome.
Here is what i tried so far:
If a square is at point (1000, 1000) I would simply translate the canvas (-1000, -1000). I know i need to add an offset so that it is centered in the window. However, the canvas always ends up off of the visible window (too far in the upper-left corner somewhere).
A more complex scenario:
Ultimately i would like to be able to center on a clicked object on a canvas that is transformed (rotated & skewed). I'm going for an isometric effect which seems to work really well. I'm wondering if this transformation affects the centering logic/math at all?
Transforming from screen to world and back
When working with non standard axis (or projections) such as isometrix it is always best to use a transformation matrix. It will cover every possible 2D projection with the same simple functions.
The coordinates of the iso world are called world coordinates. All you objects are stored as world coordinates. When you render them you project those coordinates to the screen coordinates using a transformation matrix.
The matrix, not a movie.
The matrix represents the direction and size in screen coordinates of the world
x and y axis and the screen location of the world origin (0,0)
For iso that is
x axis across 1 down 0.5
y axis across -1 down 0.5
z axis up 1 (-1 as up is the reverse of down) but this example does not use z
So the matrix as an array
const isoMat = [1,0.5,-1,0.5,0,0]; // ISO (pixel art) dimorphic projection
The first two are the x axis, the next two the y axis and the last two values are the screen coordinates of the origin.
Use the matrix to transform points
You apply a matrix to a point, this transforms the point from one coordinate system to another. You can also convert back via a inverse transform.
World to screen
You will need to convert from world coordinates to screen coordinates.
function worldToScreen(pos,retPos){
retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2] + isoMat[4];
retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3] + isoMat[5];
}
In the demo I ignore the origin as I set that at the center of the canvas at all times. Thus remove the origin from that function
function worldToScreen(pos,retPos){
retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2];
retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3];
}
Screen to world.
You will also need to convert from the screen coordinates to the world. For this you need to use the inverse transform. It's a bit like the inverse of multiply a * 2 = b is the inverse of b / 2 = a
There is a standard method for calculating the inverse matrix as follows
const invMatrix = []; // inverse matrix
// I call the next line cross, most call it the determinant which I
// think is stupid as it is effectively a cross product and is used
// like you would use a cross product. Anyways I digress
const cross = isoMat[0] * isoMat[3] - isoMat[1] * isoMat[2];
invMatrix[0] = isoMat[3] / cross;
invMatrix[1] = -isoMat[1] / cross;
invMatrix[2] = -isoMat[2] / cross;
invMatrix[3] = isoMat[0] / cross;
Then we have a function that converts from the screen x,y to the world position
function screenToWorld(pos,retPos){
const x = pos.x - isoMat[4];
const y = pos.y - isoMat[5];
retPos.x = x * invMatrix[0] + y * invMatrix[2];
retPos.y = x * invMatrix[1] + y * invMatrix[3];
}
So you get the mouse coords as screen pixels, use the above function to convert to world coords. Then you can use the world coords to find the object you are looking for.
To move a world object to the screen center you convert its coords to screen coords, add the position on the screen (the canvas center) and set the transform matrix origin to that location.
The demo
The demo creates a set of boxes in world coordinates. It sets the 2D context transform to the isoMat (isometric projection) via ctx.setTransform(
Every frame I convert the mouse screen coords to world coords then use that to check which box the mouse is over.
If the mouse button is down I then convert that box from world coords to screen and add the screen center. To smooth the step the new screen center is chased (smoothed)..
Well you should be able to work it out in the code, any problems ask in the comments.
const ctx = canvas.getContext("2d");
const moveSpeed = 0.4;
const boxMin = 20;
const boxMax = 50;
const boxCount = 100;
const boxArea = 2000;
// some canvas vals
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
const U = undefined;
// Helper function
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); };
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
const setOf = (count, cb) => {var a = [],i = 0; while (i < count) { a.push(cb(i ++)) } return a };
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
// mouse function and object
const mouse = {x : 0, y : 0, button : false, world : {x : 0, y : 0}}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// boxes in world coordinates.
const boxes = [];
function draw(){
if(this.dead){
ctx.fillStyle = "rgba(0,0,0,0.5)";
ctx.fillRect(this.x,this.y,this.w,this.h);
}
ctx.strokeStyle = this.col;
ctx.globalAlpha = 1;
ctx.strokeRect(this.x,this.y,this.w,this.h);
// the rest is just overkill
if(this.col === "red"){
this.mr = 10;
}else{
this.mr = 1;
}
this.mc += (this.mr-this.m) * 0.45;
this.mc *= 0.05;
this.m += this.mc;
for(var i = 0; i < this.m; i ++){
const m = this.m * (i + 1);
ctx.globalAlpha = 1-(m / 100);
ctx.strokeRect(this.x-m,this.y-m,this.w,this.h);
}
}
// make random boxes.
function createBoxes(){
boxes.length = 0;
boxes.push(...setOf(boxCount,()=>{
return {
x : randI(cw- boxArea/ 2, cw + boxArea/2),
y : randI(ch- boxArea/ 2, ch + boxArea/2),
w : randI(boxMin,boxMax),
h : randI(boxMin,boxMax),
m : 5,
mc : 0,
mr : 5,
col : "black",
dead : false,
draw : draw,
isOver : isOver,
}
}));
}
// use mouse world coordinates to find box under mouse
function isOver(x,y){
return x > this.x && x < this.x + this.w && y > this.y && y < this.y + this.h;
}
var overBox;
function findBox(x,y){
if(overBox){
overBox.col = "black";
}
overBox = undefined;
eachOf(boxes,box=>{
if(box.isOver(x,y)){
overBox = box;
box.col = "red";
return true;
}
})
}
function drawBoxes(){
boxes.forEach(box=>box.draw());
}
// next 3 values control the movement of the origin
// rather than move instantly the currentPos chases the new pos.
const currentPos = {x :0, y : 0};
const newPos = {x :0, y : 0};
const chasePos = {x :0, y : 0};
// this function does the chasing
function updatePos(){
chasePos.x += (newPos.x - currentPos.x) * moveSpeed;
chasePos.y += (newPos.y - currentPos.y) * moveSpeed;
chasePos.x *= moveSpeed;
chasePos.y *= moveSpeed;
currentPos.x += chasePos.x;
currentPos.y += chasePos.y;
}
// ISO matrix and inverse matrix plus 2world and 2 screen
const isoMat = [1,0.5,-1,0.5,0,0];
const invMatrix = [];
const cross = isoMat[0] * isoMat[3] - isoMat[1] * isoMat[2];
invMatrix[0] = isoMat[3] / cross;
invMatrix[1] = -isoMat[1] / cross;
invMatrix[2] = -isoMat[2] / cross;
invMatrix[3] = isoMat[0] / cross;
function screenToWorld(pos,retPos){
const x = pos.x - isoMat[4];
const y = pos.y - isoMat[5];
retPos.x = x * invMatrix[0] + y * invMatrix[2];
retPos.y = x * invMatrix[1] + y * invMatrix[3];
}
function worldToScreen(pos,retPos){
retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2];// + isoMat[4];
retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3];// + isoMat[5];
}
// main update function
function update(timer){
// standard frame setup
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
createBoxes();
}else{
ctx.clearRect(0,0,w,h);
}
ctx.fillStyle = "black";
ctx.font = "28px arial";
ctx.textAlign = "center";
ctx.fillText("Click on a box to center it.",cw,28);
// update position
updatePos();
isoMat[4] = currentPos.x;
isoMat[5] = currentPos.y;
// set the screen transform to the iso matrix
// all drawing can now be done in world coordinates.
ctx.setTransform(isoMat[0], isoMat[1], isoMat[2], isoMat[3], isoMat[4], isoMat[5]);
// convert the mouse to world coordinates
screenToWorld(mouse,mouse.world);
// find box under mouse
findBox(mouse.world.x, mouse.world.y);
// if mouse down and over a box
if(mouse.button && overBox){
mouse.button = false;
overBox.dead = true; // make it gray
// get the screen coordinates of the box
worldToScreen({
x:-(overBox.x + overBox.w/2),
y:-(overBox.y + overBox.h/2),
},newPos
);
// move it to the screen center
newPos.x += cw;
newPos.y += ch;
}
// forget what the following function does, think it does something like draw boxes, but I am guessing.. :P
drawBoxes();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>

Canvas Spinning Cube

I have an HTML5 Canvas Spinning Cube on this codePen:
http://codepen.io/celli/pen/xwvnb
Can someone help to show how to remove the black background ? It looks like the ctx.fillStyle="#000000"; property in the JS is needed (try changing or removing it in the CodePen), but I'd like to have a transparent background, and can't seem to find a way to make that happen.
window.onload = startDemo;
function Point3D(x,y,z) {
this.x = x;
this.y = y;
this.z = z;
this.rotateX = function(angle) {
var rad, cosa, sina, y, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
y = this.y * cosa - this.z * sina
z = this.y * sina + this.z * cosa
return new Point3D(this.x, y, z)
}
this.rotateY = function(angle) {
var rad, cosa, sina, x, z
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
z = this.z * cosa - this.x * sina
x = this.z * sina + this.x * cosa
return new Point3D(x,this.y, z)
}
this.rotateZ = function(angle) {
var rad, cosa, sina, x, y
rad = angle * Math.PI / 180
cosa = Math.cos(rad)
sina = Math.sin(rad)
x = this.x * cosa - this.y * sina
y = this.x * sina + this.y * cosa
return new Point3D(x, y, this.z)
}
this.project = function(viewWidth, viewHeight, fov, viewDistance) {
var factor, x, y
factor = fov / (viewDistance + this.z)
x = this.x * factor + viewWidth / 2
y = this.y * factor + viewHeight / 2
return new Point3D(x, y, this.z)
}
}
var vertices = [
new Point3D(-1,1,-1),
new Point3D(1,1,-1),
new Point3D(1,-1,-1),
new Point3D(-1,-1,-1),
new Point3D(-1,1,1),
new Point3D(1,1,1),
new Point3D(1,-1,1),
new Point3D(-1,-1,1)
];
// Define the vertices that compose each of the 6 faces. These numbers are
// indices to the vertex list defined above.
var faces = [[0,1,2,3],[1,5,6,2],[5,4,7,6],[4,0,3,7],[0,4,5,1],[3,2,6,7]]
var angle = 0;
function startDemo() {
canvas = document.getElementById("cubeSpin");
if( canvas && canvas.getContext ) {
ctx = canvas.getContext("2d");
setInterval(loop,33);
}
}
function loop() {
var t = new Array();
ctx.fillStyle="#000000";
ctx.fillRect(0,0,320,200);
for( var i = 0; i < vertices.length; i++ ) {
var v = vertices[i];
var r = v.rotateX(angle).rotateY(angle).rotateZ(angle);
var p = r.project(320,200,128,3.5);
t.push(p)
}
ctx.strokeStyle = "rgb(255,255,255)"
for( var i = 0; i < faces.length; i++ ) {
var f = faces[i]
ctx.beginPath()
ctx.moveTo(t[f[0]].x,t[f[0]].y)
ctx.lineTo(t[f[1]].x,t[f[1]].y)
ctx.lineTo(t[f[2]].x,t[f[2]].y)
ctx.lineTo(t[f[3]].x,t[f[3]].y)
ctx.closePath()
ctx.stroke()
}
angle += 2
}
Change:
ctx.fillStyle="#FF0000";
ctx.fillRect(0,0,320,200);
To:
ctx.clearRect(0,0,320,200);

Rotate image around center point of it's container

I have Image component inside some container with clipAndEnableScrolling property set to true. I need a static method which gets this Image, rotation angle and rotates Image around center point of container without loosing any previous transformations. The best method I've created adds error after few rotations.
I thing it must work like this
public static function rotateImageAroundCenterOfViewPort(image:Image, value:int):void
{
// Calculate rotation and shifts
var bounds:Rectangle = image.getBounds(image.parent);
var angle:Number = value - image.rotation;
var radians:Number = angle * (Math.PI / 180.0);
var shiftByX:Number = image.parent.width / 2 - bounds.x;
var shiftByY:Number = image.parent.height / 2 - bounds.y;
// Perform rotation
var matrix:Matrix = new Matrix();
matrix.translate(-shiftByX, -shiftByY);
matrix.rotate(radians);
matrix.translate(+shiftByX, +shiftByY);
matrix.concat(image.transform.matrix);
image.transform.matrix = matrix;
}
but it doesn't. Looks like I can't understand how transformation works(
If you are trying to rotate the object around it's center, I think you'll want some more like this:
var matrix:Matrix = image.transform.matrix;
var rect:Rectangle = image.getBounds( insertParentObject );
//translate matrix to center
matrix.translate(- ( rect.left + ( rect.width/2 ) ), - ( rect.top + ( rect.height/2 ) ) );
matrix.rotate(radians);
//translate back
matrix.translate(rect.left + ( rect.width/2 ), rect.top + ( rect.height/2 ) );
image.transform.matrix = matrix;
Also here is a link to the same SO question with varying answers including the one I provided:
Flex/ActionScript - rotate Sprite around its center
As discussed in the comments if you are looking to rotate an object around a point (that is the center of your container), here's a function that I think would work:
//pass rotateAmount as the angle you want to rotate in degrees
private function rotateAround( rotateAmount:Number, obj:DisplayObject, origin:Point, distance:Number = 100 ):void {
var radians:Number = rotateAmount * Math.PI / 180;
obj.x = origin.x + distance * Math.cos( radians );
obj.y = origin.y + distance * Math.sin( radians );
}
Then you just call it:
rotateAround( rotateAmount, image, new Point( container.width/2, container.height/2 ) );
The last parameter distance you can pass whatever you like, so for example if I wanted a distance of the image vector length:
var dx:Number = spr.x - stage.stageWidth/2;
var dy:Number = spr.y - stage.stageHeight/2;
var dist:Number = Math.sqrt(dx * dx + dy * dy);
rotateAround( rotateAmount, image, new Point( container.width/2, container.height/2 ), dist );
Here's the solution I've found:
public static function rotateImageAroundCenterOfViewPort(image:Image, value:int):void
{
// Calculate rotation and shifts
var center:Point = new Point(image.parent.width / 2, image.parent.height / 2);
center = image.parent.localToGlobal(center);
center = image.globalToLocal(center);
var angle:Number = value - image.rotation;
var radians:Number = angle * (Math.PI / 180.0);
var shiftByX:Number = center.x;
var shiftByY:Number = center.y;
// Perform rotation
var matrix:Matrix = new Matrix();
matrix.translate(-shiftByX, -shiftByY);
matrix.rotate(radians);
matrix.translate(+shiftByX, +shiftByY);
matrix.concat(image.transform.matrix);
image.transform.matrix = matrix;
image.rotation = Math.round(image.rotation);
}
Teste only with the angles like 90, 180 etc. (I don't need any else).

Drawing a rectangle with one side skewing based on degress/radians

I'm trying to take one side of a rectangle and skew the side based on degree/angle.
I whipped up some code for you
Any questions just ask.
import flash.geom.Matrix;
var temp_matrix = new Matrix();
var square:Sprite = new Sprite();
addChild(square);
square.graphics.lineStyle(3,0x000000);
square.graphics.drawRect(0,0,200,100);
square.graphics.endFill();
var angle:Number = -10; // the angle of degrees
temp_matrix.b = Math.PI * 2 * angle / 360;// y skew
//temp_matrix.c = Math.PI * 2 * angle / 360;// x skew
var sourceMatrix:Matrix = square.transform.matrix;// get existing matrix
sourceMatrix.concat(temp_matrix); // apply skew to existing matrix
square.transform.matrix = temp_matrix;// assign the new skew
square.x = 100
square.y = 100
[SECOND ROUND]
var trapezium:Sprite = new Sprite();
addChild(trapezium);
trapezium.x = 100;
trapezium.y = 100;
var dir:Boolean = true;
var side:Boolean = true;
var angle:Number = 0; // the angle of degrees
var w:Number = 300;
var h:Number = 80;
var timer:Timer = new Timer(16);
timer.addEventListener( TimerEvent.TIMER, tick );
timer.start();
function tick(e:TimerEvent):void{
var radians:Number = Math.PI/180*angle;
trapezium.graphics.clear();
trapezium.graphics.beginFill(0x000000)
if( side){
// long side is right side
trapezium.graphics.lineTo(w,0);
trapezium.graphics.lineTo(w,radians*w+h);
trapezium.graphics.lineTo(0,h);
trapezium.graphics.lineTo(0,0);
}else{
trapezium.graphics.lineTo(w,0);
trapezium.graphics.lineTo(w,h);
trapezium.graphics.lineTo(0,radians*w+h);
trapezium.graphics.lineTo(0,0);
}
trapezium.graphics.endFill();
if(angle>=10){
dir = false;
}
if(angle<=0){
dir = true;
}
if(dir){
angle = angle+.2;
}else{
angle = angle-.2;
}
if( Math.floor(angle*10) <= 0 ){
side = !side;
}
}
Take the tangent of the angle and multiply by the width of the rectangle to get the delta y for the bottom axis so you would have
[x1,y1] as the origin of the rectangle (which never changes)
[x1+length, y1+deltaY] as the right bottom corner
Don't know AS, but after editing this looks like filled polygon with vertices:
P0 =(X0, Y0)
P1 = (X1, Y0)
if Angle >= 0 then
P2 = (X1, Y1)
P3 = (X0, Y1 + (X1-X0) * Tan(Angle))
else
P2 = (X1, Y1 - (X1-X0) * Tan(Angle))
P3 = (X0, Y1)

How to calculate third point on line using atan2?

I'm trying to animate some bitmaps out in relation to a center point. They don't all start at that center point, but I want them to fly out as though a force from that center point slammed into them and pushed them outwards radially, such that they fly completely off the stage.
So: I know the center point, and the x and y position of each bitmap arranged around it. For each one I can draw a line from the center to that x,y point. I should then be able to get the angle formed by that line to the horizontal, and then set a destination point farther out on that line. The bitmap will be tweened out to that point. I believe that that is what Math.atan2 is for.
Here's what I've got as I iterate through the array of bitmaps (i is an object):
var angle:Number = Math.atan2(i.bitmap.y - centerY, i.bitmap.x - centerX) * 180 / Math.PI;
var dist:Number = 200; //arbitrary number, just to test
destX = centerX + dist * Math.cos(angle); //destination x
destY = centerY + dist * Math.sin(angle); //destination y
Instead of these things gliding out radially, they're jumping around.
I'm having trouble understanding atan2 and exactly what I'm doing wrong.
Thanks,
David
You can achieve the same effect without trigonometric functions using just vector operations:
var dist:Number = 200; //arbitrary number, just to test
var dx:Number = i.bitmap.x - centerX;
var dy:Number = i.bitmap.y - centerY;
var length:Number = Math.sqrt( dx*dx + dy*dy );
var normalizeddx:Number = dx / length;
var normalizeddy:Number = dy / length;
destX = centerX + dist * normalizeddx; //destination x
destY = centerY + dist * normalizeddy; //destination y
This should be much faster, than using trigonometric functions. I don't know the language specifics of actionscript, so probably this can be optimized more.
Try removing the *180/PI to keep the angle in radians.
var angle:Number = Math.atan2(i.bitmap.y-centerY, i.bitmap.x - centerX);
Then change destX and destY to
destX = i.bitmap.x + dist * Math.cos(angle);
destY = i.bitmap.y + dist * Math.sin(angle);
atan2 could work in this situation I suppose but I would just use atan:
var angle:Number = Math.atan((i.bitmap.y - centerY) / (i.bitmap.x - centerX));
ADDITION:
Code I just saw on another forum that appears to do what you want (there's only a slight difference from what you wrote in the first place)
var angle:Number = Math.atan2(mouseX,mouseY-180)-Math.PI/2;
var xNew:Number = 20*Math.cos(angle);
var yNew:Number = -20*Math.sin(angle);
You have to get rid of the *180/Math.PI part. The angle has to be in radians. So the first line would look like
var angle:Number = Math.atan2(i.bitmap.y - centerY, i.bitmap.x - centerX);
The rest should be fine.