JavaScript: Math.atan2 issue - javascript

Let's say I have an arbitrary polar coordinate:
let pc = {theta: 3.1544967, radius: 0.8339594};
Need to do some Cartesian math with that and transform it back to polar one.
However, I have noticed that if I just do this code:
const pc = {theta: 3.1544967, radius: 0.8339594};
let v = {x: pc.radius * Math.cos(pc.theta), y: pc.radius * Math.sin(pc.theta)};
console.log(pc.theta, Math.atan2(v.y, v.x), pc.radius, Math.sqrt(Math.pow(v.x, 2.0) + Math.pow(v.y, 2.0)));
The difference between original theta (3.1544967) and converted back (-3.1286886071795865) is a positive PI and it doesn't really fit Wikipedia conditions (https://en.wikipedia.org/wiki/Atan2#Definition_and_computation), while both v.x and v.y are negative, so atan2 have to be atan(y / x) - PI. And it's anyway -2.356194490192345.
What I should do to get 3.1544967 back?

The function Math.atan2 returns a number in the range
-pi <= result <= pi. The result you expect is not in that range.
Here is an example that calculates how many 2PIs need to be subtracted to get the input number within the negative pi to pi range.
Once atan2 calculates the angle, you can add that many 2PIs back on to get your expected result.
const pc = {theta: 3.1544967, radius: 0.8339594};
let v = {x: pc.radius * Math.cos(pc.theta), y: pc.radius * Math.sin(pc.theta)};
let m = Math.round(pc.theta / (Math.PI * 2));
console.log(pc.theta, Math.atan2(v.y, v.x) + Math.PI * 2 * m, pc.radius, Math.sqrt(Math.pow(v.x, 2.0) + Math.pow(v.y, 2.0)));

Related

What is the easiest way to calculate position of balls on collision?

I'm trying to make some simple pool game in java script. I have made it but I do not love way of checking if two balls will collide in next frame. I would like to have more easier way to calculate coordinates of balls when collision occurs. I found lot of answers base on collision kinematics, how to handle velocities and directions after collision, but no calculating a position when collision occurs.
As you can see in sample diagram, gold ball is moving slower than a blue ball, and with distance that each ball will have to move on next frame will not be considered as collision. But, as you can see, they should collide (dashed lines).
In that cause I have divided each movement into sectors and calculating if distance between the points is equal or smaller than ball diameter, which is slowing down process when many balls (like in snooker) have to be calculated in each frame, plus that way is not always 100% accurate and balls can go in inaccurate angles after hit (not a big difference, but important in snooker).
Is there any easier way to calculate those (XAC,YAC) and (XBC,YBC) values with knowing start positions and velocities of each ball without dividing ball paths into sectors and calculating many times to find a proper distance?
It is worth to precalculate collision event only once (this approach works well with reliable number of balls, because we have to treat all ~n^2 pairs of balls).
The first ball position is A0, velocity vector is VA.
The second ball position is B0, velocity vector is VB.
To simplify calculations, we can use Halileo principle - use moving coordinate system connected with the first ball. In that system position and velocity of the first ball are always zero. The second ball position against time is :
B'(t) = (B0 - A0) + (VB - VA) * t = B0' + V'*t
and we just need to find solution of quadratic equation for collision distance=2R:
(B0'.X + V'.X*t)^2 + (B0'.X + V'.Y*t)^2 = 4*R^2
Solving this equation for unknown time t, we might get cases: no solutions (no collision), single solution (only touch event), two solutions - in this case smaller t value corresponds to the physical moment of collision.
Example (sorry, in Python, ** is power operator):
def collision(ax, ay, bx, by, vax, vay, vbx, vby, r):
dx = bx - ax
dy = by - ay
vx = vbx - vax
vy = vby - vay
#(dx + vx*t)**2 + (dy + vy*t)**2 == 4*r*r solve this equation
#coefficients
a = vx**2 + vy**2
b = 2*(vx*dx + vy*dy)
c = dx**2+dy**2 - 4*r**2
dis = b*b - 4*a*c
if dis<0:
return None
else:
t = 0.5*(-b - dis**0.5)/a ##includes case of touch when dis=0
return [(ax + t * vax, ay + t * vay), (bx + t * vbx, by + t * vby)]
print(collision(0,0,100,0,50,50,-50,50,10)) #collision
print(collision(0,0,100,0,50,50,-50,80,10)) #miss
print(collision(0,0,100,0,100,0,99,0,10)) #long lasting chase along OX axis
[(40.0, 40.0), (60.0, 40.0)]
None
[(8000.0, 0.0), (8020.0, 0.0)]
Regarding to MBo's solution, here is a function in java script that will calculate coordinates of balls on collision and time in which collision will happen:
calcCollisionBallCoordinates(ball1_x, ball1_y, ball2_x, ball2_y, ball1_vx, ball1_vy, ball2_vx, ball2_vy, r) {
let dx = ball2_x - ball1_x,
dy = ball2_y - ball1_y,
vx = ball2_vx - ball1_vx,
vy = ball2_vy - ball1_vy,
a = Math.pow(vx, 2) + Math.pow(vy, 2),
b = 2 * (vx * dx + vy * dy),
c = Math.pow(dx, 2) + Math.pow(dy, 2) - 4 * Math.pow(r, 2),
dis = Math.pow(b, 2) - 4 * a * c;
if (dis < 0) {
//no collision
return false;
} else {
let t1 = 0.5 * (-b - Math.sqrt(dis)) / a,
t2 = 0.5 * (-b + Math.sqrt(dis)) / a,
t = Math.min(t1, t2);
if (t < 0) {
//time cannot be smaller than zero
return false;
}
return {
ball1: {x: ball1_x + t * ball1_vx, y: ball1_y + t * ball1_vy},
ball2: {x: ball2_x + t * ball2_vx, y: ball2_y + t * ball2_vy},
time: t
};
}
}

are Triangles Similar?

You have two triangles a1 b1 c1 and a2 b2 c3 on a plane. Your task is to determine whether they are, i.e. if their corresponding angles have the same measurements.
coordinates is an
array []
let coord = [0, 0, 0, 1, 1, 0, 0, 0, 0, -3, -3, 0];
where a1 is (coord[0],coord[1]), b1 (coord[2],coord[3]) ...
let s = [0, 0, 0, 1, 1, 0, 0, 0, 0, -3, -3, 0]
function areTrianglesSimilar(c) {
let result = null
let line1 = (Math.abs(c[2]) - Math.abs(c[0])) + (Math.abs(c[3]) - Math.abs(c[1]))
let line2 = (Math.abs(c[4]) - Math.abs(c[0])) + (Math.abs(c[5]) - Math.abs(c[1]))
let line3 = Math.abs(Math.sqrt( Math.pow(line1, 2)+ Math.pow(line2, 2)))
console.log(line1, line2, line3)
let angle1 = Math.atan2(line1, line2) * 180 / Math.PI
let angle2 = Math.atan2(line1, line3) * 180 / Math.PI
let angle3 = 180 - (angle1 + angle2)
console.log(angle1, angle2, angle3)
let arr1 = []
arr1.push(angle1, angle2, angle3)
let line4 = (Math.abs(c[8]) - Math.abs(c[6])) + (Math.abs(c[9]) - Math.abs(c[7]))
let line5 = (Math.abs(c[10]) - Math.abs(c[0])) + (Math.abs(c[11]) - Math.abs(c[1]))
let line6 = Math.abs(Math.sqrt( Math.pow(line4, 2)+ Math.pow(line5, 2)))
console.log(line4, line5, line6)
let angle4 = Math.atan2(line4, line5) * 180 / Math.PI
let angle5 = Math.atan2(line4, line6) * 180 / Math.PI
let angle6 = 180 - (angle4 + angle5)
console.log(angle6, angle5, angle4)
if (arr1.includes(angle4) && arr1.includes(angle5) && arr1.includes(angle6)){
return result = true
} else return result = false
}
console.log(areTrianglesSimilar(s))
this was my try but did not pass all tests, any better idea?
Thanks to Mbo
function areTrianglesSimilar(c) {
let dx1 = c[2] - c[0];
let dy1 = c[3] - c[1];
let dx2 = c[4] - c[0];
let dy2 = c[5] - c[1];
let dx3 = c[4] - c[2];
let dy3 = c[5] - c[3];
let l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
let l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
let l3 = Math.sqrt(dx3 * dx3 + dy3 * dy3);
console.log(l1,l2,l3);
let angle12 = Math.acos((dx1 * dx2 + dy1 * dy2) / (l1 * l2));
let angle13 = Math.acos((dx1 * dx3 + dy1 * dy3) / (l1 * l3));
let angle23 = Math.acos((dx3 * dx2 + dy3 * dy2) / (l3 * l2));
console.log(angle12, angle13, angle23);
let dx4 = c[8] - c[6];
let dy4 = c[9] - c[7];
let dx5 = c[10] - c[6];
let dy5 = c[11] - c[7];
let dx6 = c[10] - c[8];
let dy6 = c[11] - c[9];
let l4 = Math.sqrt(dx4 * dx4 + dy4 * dy4);
let l5 = Math.sqrt(dx5 * dx5 + dy5 * dy5);
let l6 = Math.sqrt(dx6 * dx6 + dy6 * dy6);
console.log(l4,l5,l6);
let angle45 = Math.acos((dx4 * dx5 + dy4 * dy5) / (l4 * l5));
let angle46 = Math.acos((dx4 * dx6 + dy4 * dy6) / (l4 * l6));
let angle56 = Math.acos((dx6 * dx5 + dy6 * dy5) / (l6 * l5));
console.log(angle45, angle46, angle56);
if (angle12 == angle45 && angle13 == angle46){
console.log('result'+':'+ true);
} else console.log("result" + ":" + false);
}
let coordinates = [3, 4, 4, 7, 6, 1, -2, -1, 0, 5, 4, -7];
console.log(areTrianglesSimilar(coordinates))
Your calculation is completely wrong. Dot product approach:
dx1 = c[2] - c[0]
dy1 = c[3] - c[1]
dx2 = c[4] - c[0]
dy2 = c[5] - c[1]
dx3 = c[4] - c[2]
dy3 = c[5] - c[3]
l1 = Math.sqrt(dx1*dx1+dy1*dy1)
l2 = Math.sqrt(dx2*dx2+dy2*dy2)
l3 = Math.sqrt(dx3*dx3+dy3*dy3)
angle12 = Math.acos((dx1*dx2+dy1*dy2)/(l1*l2)
and similar for angle13, and later you need to compare only two angles for equality
if angle12 == angle45 and angle13 == angle46 ...
or use some epsylon value to avoid floating calculation errors
if abs(angle12 -angle45) < 0.0000001 ...
Moreover, you can avoid angles and compare side length ratios
if l1/l4==l2/l5 and l1/l4==l3/l6...
There are quite a number of errors here. Aside from these, you should probably consider refactoring the code into separate functions which encapsulate commonly performed calculations. This will cut down on needless repetition and make copy-paste typos less possible. It will also make the code a little more self-documenting, which allows human beings to understand what you're doing better.
Assuming you want to determine the angles of the triangles and compare them (but you could also use side length ratios as #MBo pointed out), the general approach I would follow is this:
Write a function to convert the coordinates array into a pair of Triangle objects, where a Triangle is a three-tuple of Point objects, defined like this:
type Triangle = [Point, Point, Point];
interface Point { x: number, y: number };
function toTrianges(coords: number[]): [Triangle, Triangle] {
// implement this
}
Write a function that takes three Points, A, B, and C, and returns the (absolute value of the) measure of angle ∡ABC (with B as the vertex) in, say, degrees:
function measureAngleABC(a: Point, b: Point, c: Point): number {
// implement this
}
In order to do that, you might want to write functions that turn two Points A and B and produces the Vector from A to B, and that manipulate vectors:
type Vector = Point;
function vector(a: Point, b: Point): Vector { /* impl */ }
function vectorLength(v: Vector): number { /* impl */ }
function dotProduct(v1: Vector, v2: Vector): number { /* impl */ }
Note that the (unsigned) angle between two vectors can be determined by examining their lengths and their dot product.
Once you have these, you should be able to turn a Triangle into a (sorted) triplet of its (unsigned) angles:
type TriangleAngles = [number, number, number];
function angles(triangle: Triangle): TriangleAngles { /* impl * }
And finally, write a function that compares two TriangleAngles for near-equality. Not actual equality using ===, which is fraught with troubles. Since floating-point numbers do not have infinite precision, two different calculations that should yield the same quantity might actually produce two different floating-point results. The famous example is that 0.1 + 0.2 === 0.3 is false. When you compare two TriangleAngles, you need to decide how close is "close enough" to call two triangles similar:
function areNearlyEqual(ta1: TriangleAngles, ta2: TriangleAngles): boolean {
// impl here
}
I'm not going to write out how to implement these, since this looks like an exercise that benefits you most if you actually do it, not if someone does it for you.
In any case, here are the errors I see in your code:
The line (Math.abs(c[10]) - Math.abs(c[0])) + (Math.abs(c[11]) - Math.abs(c[1])) looks like a typo with indices, as you are seemingly comparing a point from one triangle with a point on a different triangle. This sort of typo would be much less likely if you refactor so as to move from an array of numbers to something like a pair of Triangles.
All code of the form Math.abs(c[k]) for some index k is highly suspect. This treats c[k] === 100 identically to c[k] === -100. If you take a triangle and flip the sign of the x or y coordinate of one of its vertices, you are almost certainly going to change the shape of the triangle by reflecting that vertex across the x or y axis:
If your code can't tell the difference between those two triangles, it's not going to be able to accurately determine if two triangles are similar or not.
The line let line1 = (Math.abs(c[2]) - Math.abs(c[0])) + (Math.abs(c[3]) - Math.abs(c[1])) and its brethren seem to looking at one of the sides of one of the triangles and adding the x component of its length to the y component of its length to get a single number. This doesn't represent much of anything that I can think of. The vector of x-component-of-length and y-component-of-length are important, but when you just add the components together you are throwing away information you need. You can verify this for yourself by coming up with a triangle where swapping c[2] and c[3] will change its shape, but the above code will not see a difference.
The line let line3 = Math.abs(Math.sqrt( Math.pow(line1, 2)+ Math.pow(line2, 2))) seems to assume that line1 and line2 represent the lengths of two sides of a right triangle and line3 is the length of the hypotenuse. But unless your two sides are really perpendicular to each other, this will not be true.
The line let angle2 = Math.atan2(line1, line3) * 180 / Math.PI is calculating an angle, but what angle? You can only use the arctangent to get an angle from the opposite and adjacent sides of a right triangle. But there might be no right triangles here, and since line3 was earlier assumed to be the hypotenuse of a right triangle where one of the sides was line1, there's no way line3 is now one of the perpendicular legs.
Um, I think I have to stop here. Suffice it to say that I would be very surprised if you could get this algorithm working by tweaking it. I'd strongly recommend starting over with reusable functions that perform well-defined calculations.
Good luck.

Getting two points on the edges of a rectangle

I have a rectangle and would like to:
Get a random point on one (any) of the sides.
Get a random point on one (except for the previously picked) side.
My initial approach is to create arrays for each possible side.
var arr:Array = [[{x:0,y:0}, // Top
{x:width,y:0}], //
[{x:width,y:0}, // Right
{x:width,y:height}], //
[{x:width,y:height}, // Bottom
{x:0,y:height}], //
[{x:0,y:height}, // Left
{x:0,y:0}]]; //
Then, I get the sides.
rand is an instance of Rand and has the methods:
.next() which provides a random number between 0 and 1
.between(x,y) which returns a random number between x and y.
var firstSide:Array = arr[rand.next() * arr.length];
var secondSide:Array;
do {
secondSide = arr[rand.next() * arr.length];
} while(secondSide.equals(firstSide));
Finally, I calculate my points.
var pointOnFirstSide:Object = {x:rand.between(firstSide[0].x, firstSide[1].x),
y:rand.between(firstSide[0].y, firstSide[1].y};
var pointOnSecondSide:Object = {x:rand.between(secondSide[0].x, secondSide[1].x),
y:rand.between(secondSide[0].y, secondSide[1].y};
I don't think this is the most efficient way to solve this.
How would you do it?
Assuming we have the following interfaces and types:
interface Rand {
next(): number;
between(x: number, y: number): number;
}
interface Point {
x: number;
y: number;
}
type PointPair = readonly [Point, Point];
and taking you at your word in the comment that the procedure is: first randomly pick two sides, and then pick random points on those sides... first let's see what's involved in picking two sides at random:
const s1 = Math.floor(rand.between(0, arr.length));
const s2 = (Math.floor(rand.between(1, arr.length)) + s1) % arr.length;
s1 and s2 represent the indices of arr that we are choosing. The first one chooses a whole number between 0 and one less than the length of the array. We do this by picking a real number (okay, floating point number, whatever) between 0 and the length of the array, and then taking the floor of that real number. Since the length is 4, what we are doing is picking a real number uniformly between 0 and 4. One quarter of those numbers are between 0 and 1, another quarter between 1 and 2, another quarter between 2 and 3, and the last quarter are between 3 and 4. That means you have a 25% chance of choosing each of 0, 1, 2 and 3. (The chance of choosing 4 is essentially 0, or perhaps exactly 0 if rand is implemented in the normal way which excludes the upper bound).
For s2 we now pick a number uniformly between 1 and the length of the array. In this case, we are picking 1, 2, or 3 with a 33% chance each. We add that number to s1 and then take the remainder when dividing by 4. Think of what we are doing as starting on the first side s1, and then moving either 1, 2, or 3 sides (say) clockwise to pick the next side. This completely eliminates the possibility of choosing the same side twice.
Now let's see what's involved in randomly picking a point on a line segment (which can be defined as a PointPair, corresponding to the two ends p1 and p2 of the line segment) given a Rand instance:
function randomPointOnSide([p1, p2]: PointPair, rand: Rand): Point {
const frac = rand.next(); // between 0 and 1
return { x: (p2.x - p1.x) * frac + p1.x, y: (p2.y - p1.y) * frac + p1.y };
}
Here what we do is pick a single random number frac, representing how far along the way from p1 to p2 we want to go. If frac is 0, we pick p1. If frac is 1, we pick p2. If frac is 0.5, we pick halfway between p1 and p2. The general formula for this is a linear interpolation between p1 and p2 given frac.
Hopefully between the two of those, you can implement the algorithm you're looking for. Good luck!
Link to code
jcalz already gave an excellent answer. Here is an alternate version for the variant I asked about in the comments: When you want your points uniformly chosen over two sides of the perimeter, so that if your w : h ratio was 4 : 1, the first point is four times as likely to lie on a horizontal side as a vertical one. (This means that the chance of hitting two opposite long sides is 24/45; two opposite short side, 1/45; and one of each, 20/45 -- by a simple but slightly tedious calculation.)
const rand = {
next: () => Math. random (),
between: (lo, hi) => lo + (hi - lo) * Math .random (),
}
const vertices = (w, h) => [ {x: 0, y: h}, {x: w, y: h}, {x: w, y: 0}, {x: 0, y: 0} ]
const edges = ([v1, v2, v3, v4]) => [ [v1, v2], [v2, v3], [v3, v4], [v4, v1] ]
const randomPoint = ([v1, v2], rand) => ({
x: v1 .x + rand .next () * (v2 .x - v1 .x),
y: v1 .y + rand .next () * (v2 .y - v1 .y),
})
const getIndex = (w, h, x) => x < w ? 0 : x < w + h ? 1 : x < w + h + w ? 2 : 3
const twoPoints = (w, h, rand) => {
const es = edges (vertices (w, h) )
const perimeter = 2 * w + 2 * h
const r1 = rand .between (0, perimeter)
const idx1 = getIndex (w, h, r1)
const r2 = (
rand. between (0, perimeter - (idx1 % 2 == 0 ? w : h)) +
Math .ceil ((idx1 + 1) / 2) * w + Math .floor ((idx1 + 1) / 2) * h
) % perimeter
const idx2 = getIndex (w, h, r2)
return {p1: randomPoint (es [idx1], rand), p2: randomPoint (es [idx2], rand)}
}
console .log (
// Ten random pairs on rectangle with width 5 and height 2
Array (10) .fill () .map (() => twoPoints (5, 2, rand))
)
The only complicated bit in there is the calculation of r2. We calculate a random number between 0 and the total length of the remaining three sides, by adding all four sides together and subtracting off the length of the current side, width if idx is even, height if it's odd. Then we add it to the total length of the sides up to and including the index (where the ceil and floor calls simply count the number of horizontal and vertical sides, these values multiplied by the width and height, respectively, and added together) and finally take a floating-point modulus of the result with the perimeter. This is the same technique as in jcalz's answer, but made more complex by dealing with side lengths rather than simple counts.
I didn't make rand an instance of any class or interface, and in fact didn't do any Typescript here, but you can add that yourself easily enough.

Why isn't my homing missile algorithm working?

I've taken code that's heavily inspired by this answer but my projectile is not homing in the way I expect. The initial projectile direction is often perpendicular to the target. At which point, it does seem to home in on his direction, but if it "passes" him, it seems to get stuck in place like it's frozen at a point but then seems to follow the movements the target makes without moving at its intended speed. I've commented a line of code that I'm concerned about. He's using V3 and V4 in his algorithm which I suspect is a typo on his part but I'm not sure. If anyone can help me with what I'm doing wrong here, I'd be very grateful.
normalizedDirectionToTarget = root.vector.normalize(target.pos.x - attack.x, target.pos.y - attack.y) #V4
V3 = root.vector.normalize(attack.velocity.x, attack.velocity.y)
normalizedVelocity = root.vector.normalize(attack.velocity.x, attack.velocity.y)
angleInRadians = Math.acos(normalizedDirectionToTarget.x * V3.x + normalizedDirectionToTarget.y * V3.y)
maximumTurnRate = 50 #in degrees
maximumTurnRateRadians = maximumTurnRate * (Math.PI / 180)
signOfAngle = if angleInRadians >= 0 then 1 else (-1)
angleInRadians = signOfAngle * _.min([Math.abs(angleInRadians), maximumTurnRateRadians])
speed = 3
attack.velocity = root.vector.normalize(normalizedDirectionToTarget.x + Math.sin(angleInRadians), normalizedDirectionToTarget.y + Math.cos(angleInRadians)) #I'm very concerned this is the source of my bug
attack.velocity.x = attack.velocity.x * speed
attack.velocity.y = attack.velocity.y * speed
attack.x = attack.x + attack.velocity.x
attack.y = attack.y + attack.velocity.y
Edit: Code that Works
normalizedDirectionToTarget = root.vector.normalize(target.pos.x - attack.x, target.pos.y - attack.y) #V4
normalizedVelocity = root.vector.normalize(attack.velocity.x, attack.velocity.y)
angleInRadians = Math.acos(normalizedDirectionToTarget.x * normalizedVelocity.x + normalizedDirectionToTarget.y * normalizedVelocity.y)
maximumTurnRate = .3 #in degrees
maximumTurnRateRadians = maximumTurnRate * (Math.PI / 180)
crossProduct = normalizedDirectionToTarget.x * normalizedVelocity.y - normalizedDirectionToTarget.y * normalizedVelocity.x
signOfAngle = if crossProduct >= 0 then -1 else 1
angleInRadians = signOfAngle * _.min([angleInRadians, maximumTurnRateRadians])
speed = 1.5
xPrime = attack.velocity.x * Math.cos(angleInRadians) - attack.velocity.y * Math.sin(angleInRadians)
yPrime = attack.velocity.x * Math.sin(angleInRadians) + attack.velocity.y * Math.cos(angleInRadians)
attack.velocity = root.vector.normalize(xPrime, yPrime)
attack.velocity.x *= speed
attack.velocity.y *= speed
attack.x = attack.x + attack.velocity.x
attack.y = attack.y + attack.velocity.y
According to me, if you have a vector (x,y) and you want to rotate it by angle 'theta' about the origin, the new vector (x1,y1) becomes:
x1 = x*cos(theta) - y*sin(theta)
y1 = y*cos(theta) + x*sin(theta)
(the above can be derived using polar coordinates)
EDIT: I'm not sure if I understand correctly, but if you know the speed and the absolute value of the final angle (say phi), then why can't you simply do:
Vx = speed*cos( phi )
Vy = speed*sin( phi )
EDIT 2: also, while taking cos-inverse, there can be multiple possiblities for the angleinradians. You may have to check the quadrant in which both vectors lie. Your maximum turning rate is 50 degrees in either direction. Hence, the cosine for that angle shall always be positive. (cosine is negative only for 90 to 270 degrees.
EDIT 3: I think to get information about +ve turn direction or -ve turn direction, cross product is a better idea.
EDIT 4: Vx / Vy should work if you carry out the following:
initialAngleInRadians = Math.atan(normalizedVelocity.y / normalizedVelocity.x)
finalAngleInRadians = initialAngleInRadians + angleInRadians
Vx = speed*cos(finalAngleInRadians)
Vy = speed*sin(finalAngleInRadians)

Find column, row on 2D isometric grid from x,y screen space coords (Convert equation to function)

I'm trying to find the row, column in a 2d isometric grid of a screen space point (x, y)
Now I pretty much know what I need to do which is find the length of the vectors in red in the pictures above and then compare it to the length of the vector that represent the bounds of the grid (which is represented by the black vectors)
Now I asked for help over at mathematics stack exchange to get the equation for figuring out what the parallel vectors are of a point x,y compared to the black boundary vectors. Link here Length of Perpendicular/Parallel Vectors
but im having trouble converting this to a function
Ideally i need enough of a function to get the length of both red vectors from three sets of points, the x,y of the end of the 2 black vectors and the point at the end of the red vectors.
Any language is fine but ideally javascript
What you need is a base transformation:
Suppose the coordinates of the first black vector are (x1, x2) and the coordinates of the second vector are (y1, y2).
Therefore, finding the red vectors that get at a point (z1, z2) is equivalent to solving the following linear system:
x1*r1 + y1*r2 = z1
x2*r1 + y2*r2 = z2
or in matrix form:
A x = b
/x1 y1\ |r1| = |z1|
\x2 y2/ |r2| |z2|
x = inverse(A)*b
For example, lets have the black vector be (2, 1) and (2, -1). The corresponding matrix A will be
2 2
1 -1
and its inverse will be
1/4 1/2
1/4 -1/2
So a point (x, y) in the original coordinates will be able to be represened in the alternate base, bia the following formula:
(x, y) = (1/4 * x + 1/2 * y)*(2,1) + (1/4 * x -1/2 * y)*(2, -1)
What exactly is the point of doing it like this? Any isometric grid you display usually contains cells of equal size, so you can skip all the vector math and simply do something like:
var xStep = 50,
yStep = 30, // roughly matches your image
pointX = 2*xStep,
pointY = 0;
Basically the points on any isometric grid fall onto the intersections of a non-isometric grid. Isometric grid controller:
screenPositionToIsoXY : function(o, w, h){
var sX = ((((o.x - this.canvas.xPosition) - this.screenOffsetX) / this.unitWidth ) * 2) >> 0,
sY = ((((o.y - this.canvas.yPosition) - this.screenOffsetY) / this.unitHeight) * 2) >> 0,
isoX = ((sX + sY - this.cols) / 2) >> 0,
isoY = (((-1 + this.cols) - (sX - sY)) / 2) >> 0;
// isoX = ((sX + sY) / isoGrid.width) - 1
// isoY = ((-2 + isoGrid.width) - sX - sY) / 2
return $.extend(o, {
isoX : Math.constrain(isoX, 0, this.cols - (w||0)),
isoY : Math.constrain(isoY, 0, this.rows - (h||0))
});
},
// ...
isoToUnitGrid : function(isoX, isoY){
var offset = this.grid.offset(),
isoX = $.uD(isoX) ? this.isoX : isoX,
isoY = $.uD(isoY) ? this.isoY : isoY;
return {
x : (offset.x + (this.grid.unitWidth / 2) * (this.grid.rows - this.isoWidth + isoX - isoY)) >> 0,
y : (offset.y + (this.grid.unitHeight / 2) * (isoX + isoY)) >> 0
};
},
Okay so with the help of other answers (sorry guys neither quite provided the answer i was after)
I present my function for finding the grid position on an iso 2d grid using a world x,y coordinate where the world x,y is an offset screen space coord.
WorldPosToGridPos: function(iPosX, iPosY){
var d = (this.mcBoundaryVectors.upper.x * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.upper.y * this.mcBoundaryVectors.lower.x);
var a = ((iPosX * this.mcBoundaryVectors.lower.y) - (this.mcBoundaryVectors.lower.x * iPosY)) / d;
var b = ((this.mcBoundaryVectors.upper.x * iPosY) - (iPosX * this.mcBoundaryVectors.upper.y)) / d;
var cParaUpperVec = new Vector2(a * this.mcBoundaryVectors.upper.x, a * this.mcBoundaryVectors.upper.y);
var cParaLowerVec = new Vector2(b * this.mcBoundaryVectors.lower.x, b * this.mcBoundaryVectors.lower.y);
var iGridWidth = 40;
var iGridHeight = 40;
var iGridX = Math.floor((cParaLowerVec.length() / this.mcBoundaryVectors.lower.length()) * iGridWidth);
var iGridY = Math.floor((cParaUpperVec.length() / this.mcBoundaryVectors.upper.length()) * iGridHeight);
return {gridX: iGridX, gridY: iGridY};
},
The first line is best done once in an init function or similar to save doing the same calculation over and over, I just included it for completeness.
The mcBoundaryVectors are two vectors defining the outer limits of the x and y axis of the isometric grid (The black vectors shown in the picture above).
Hope this helps anyone else in the future

Categories