I'm trying to calculate triangles base on the Area and the angles. If Angle-B is 90° then the formula works, but in my case, the angle can be from 0.1° to 179.8°. The formula assumes that the angle is 90, so I was thinking that there might be something that is hidden that could work for very angle. Here is the formula:
The formula in code would be:
Height = sqrt((2 * Area) / (tan(Angle-A)));
I'm looking for the second half of the formula. Would the next part of the formula be something like this:
cos(sin(AngleB))
Okay, new try: If my calculations are correct, side B equals sqrt(2*area*sin(angle-B)/(sin(angle-A)*sin(angle-C))
Since Area = 1/2 * A * B * sin(c) = 1/2 * C * B * sin(a) = 1/2 * A * C * sin(b) we get:
A = 2 * area / (B * sin(c)) and using this we get:
C = sin(c) * B / sin(b) and when we place that back into the equation of area, we get:
B = sqrt(2*area*sin(angle-B)/(sin(angle-A)*sin(angle-C))
When you know one side and all the angles, calculating the other sides should be easy using normal trigonometry.
tziki's answer is correct, but I'd like to elaborate on how it's derived.
We start with angles and area as knowns. I'm going to use the labels in the OP's diagram for this explanation.
First we have the basic truth that the area of a triangle is half the product of its base and height: Area = base * height / 2. We want to be able to determine the relationship between base and height so that we can reduce this equation to one unknown and solve for base.
Another important thing to know is that the height of the triangle is proportional to Side-A: height = Side-A * sin(Angle-B). So knowing Side-A will give us the height.
Now we need to establish a relationship between Side-A and Side-C (the base). The most appropriate rule here is the sine law: Side-A/sin(A) = Side-C/sin(C). We re-arrange this equation to find Side-A in terms of Side-C: Side-A = Side-C * sin(A)/sin(C).
We can now insert this result into the height equation to get the formula for height in terms of Side-C only: height = Side-C * sin(A) * sin(B) / sin(C)
Using Side-C as the base in the area equation, we can now find the area in terms of Side-C only: Area = Side-C^2 * sin(A) * sin(B) / 2sin(C)
Then re-arrange this equation to find Side-C in terms of Area:
Side-C = SQRT(2 * Area * sin(C) / (sin(B) * (sin(A)))
And that gives you one side. This can be repeated to find the other sides, or you can use a different approach to find the other sides knowing this one.
You already have your answer, but I had to solve this kind of exercise for a job interview some time ago. It's not hard, and it didn't took me much time to arrive to the following solution.
Read it through and it should be self explanatory.
Create a Python module that solves triangles by aplying the sin and cosin theorems.
The module receives as parameters some of a triangle's values and, if possible, returns the values of all it's angles and side lengths.
The parameters are received as a dict and it should be able to be called stand-alone from the command line.
from __future__ import division
import sys, logging
from math import radians, degrees, acos, cos, sin, sqrt, asin
class InconsistentDataError(TypeError):
pass
class InsufficientDataError(TypeError):
pass
class NonUpdatable(dict):
"""Dictionary whose items can be set only once."""
def __setitem__(self, i, y):
if self.get(i, None):
raise InconsistentDataError()
super(NonUpdatable, self).__setitem__(i, y)
def get_known_sides(**kwarg):
"""Filter from the input elements the Side elements."""
return dict([i for i in kwarg.iteritems() if i[0].isupper()])
def get_known_angles(**kwarg):
"""Filter from the input elements the Angle elements."""
return dict([i for i in kwarg.iteritems() if i[0].islower()])
def get_opposite_angle(C, B, A):
"""
Get the angle corresponding to C.
Keyword arguments:
A -- right side of the angle (real number > 0)
B -- left side of the angle (real number > 0)
C -- side opposite to the angle (real number > 0)
Returns:
angle opposite to C
"""
return degrees(acos((A**2 + B**2 - C**2) / (2 * A * B)))
def get_side(A, B, c):
"""
Calculate the Side corresponding to the Angle c.
Keyword arguments:
A -- left side of C (real number > 0)
B -- right side of C (real number > 0)
c -- angle opposite to side C (real number)
Returns:
side C, opposite to c
"""
return sqrt(A**2 + B**2 - 2*A*B*cos(radians(c)))
def get_overlapping_angle(known_angles, known_sides):
"""
Calculate the Angle of a known side, knowing the angle to another known side.
Keyword arguments:
known_angles -- (dict of angles)
known_sides -- (dict of sides)
Returns:
angle of the known side, to which there is no known angle
"""
a = (set([i.lower() for i in known_sides.iterkeys()]) -
set([i.lower() for i in known_angles.iterkeys()])).pop()
b = (set([i.lower() for i in known_sides.iterkeys()]) &
set([i.lower() for i in known_angles.iterkeys()])).pop()
y = (known_sides[a.upper()]/known_sides[b.upper()]) * sin(radians(known_angles[b.lower()]))
if y > 1: y = 1 #Rounding error fix --- y = 1.000000000001; asin(y) -> Exception
return {a.lower(): degrees(asin(y))}
def get_angles(A, B, C):
"""
Calculate all the angles, given the length of all the sides.
Keyword arguments:
A -- side A (real number > 0)
B -- side B (real number > 0)
C -- side C (real number > 0)
Returns:
dict of angles
"""
sides = {"A":A,"B":B,"C":C}
_sides = sides.keys()
angles = {}
for side in sides.keys():
angles[side.lower()] = get_opposite_angle(
sides[_sides[0]],
sides[_sides[1]],
sides[_sides[2]])
_sides.append(_sides.pop(0))
return angles
def get_triangle_values(**kwargs):
"""Calculate the missing values of a triangle based on the known values."""
known_params = kwargs
angles = NonUpdatable({
"a":0,
"b":0,
"c":0,
})
sides = NonUpdatable({
"A":0,
"B":0,
"C":0,
})
if len(known_params) < 3:
raise InsufficientDataError("Three parameters are needed to calculate triangle's values.")
if str(known_params.keys()).islower():
raise TypeError("At least one length needed.")
known_sides = NonUpdatable(get_known_sides(**known_params))
sides.update(known_sides)
known_angles = NonUpdatable(get_known_angles(**known_params))
angles.update(known_angles)
if len(known_angles) == 3 and sum(known_angles.itervalues()) != 180:
raise InconsistentDataError("One of the sides is too long.")
if len(known_sides) == 3:
x=[side for side in known_sides.itervalues() if (sum(known_sides.itervalues()) - side) < side]
if len(x):
raise InconsistentDataError("One of the sides is too long.")
for angle, value in get_angles(**known_sides).iteritems():
# Done this way to force exception when overwriting a
# user input angle, otherwise it would be a simple assignment.
# >>> angles = get_angles(**known_sides)
# This means inconsistent input data.
angles[angle] = value
else: # There are angles given and not enough sides.
if len(known_angles) > 1:
#2 angles given. Get last angle and calculate missing sides
for angle, val in angles.iteritems():
if val == 0:
angles[angle] = 180. - sum(angles.itervalues())
known_sides = known_sides.items()
for side, length in sides.iteritems():
if length == 0:
sides[side] = known_sides[0][1] / \
sin(radians(angles[known_sides[0][0].lower()])) * \
sin(radians(angles[side.lower()]))
else:
unknown_side = (set(sides.keys()) - set(known_sides.keys())).pop()
chars = [ord(i.lower()) for i in known_params.iterkeys()]
chars.sort()
if chars[0] < chars[1] < chars[2]:
sides[unknown_side] = get_side(known_sides.values()[0], known_sides.values()[1], known_angles[unknown_side.lower()])
angles = get_angles(**sides)
else:
known_angles.update(get_overlapping_angle(known_angles, known_sides))
angles.update(known_angles)
for angle, val in angles.iteritems():
if val == 0:
angles[angle] = 180. - sum(angles.itervalues())
sides[unknown_side] = get_side(known_sides.values()[0], known_sides.values()[1], angles[unknown_side.lower()])
angles.update(sides)
return angles
if __name__ == "__main__":
try:
values = get_triangle_values( **eval(sys.argv[1], {}, {}) )
except IndexError, e:
values = get_triangle_values(A=10,B=10,C=10)
except InsufficientDataError, e:
print "Not enough data!"
exit(1)
except InconsistentDataError, e:
print "Data is inconsistent!"
exit(1)
print values
A, B and C are the side lengths and a, b and c are the angles, so c is the angle opposite to the side C.
Tests
How about calculating the lengths of two of the sides of a triangle where the third size is assigned a length of 1 (using the law of sines on the angles, with that side assigned length 1), then scale the triangle until its area matches the area you have? Then you can calculate the height fairly easily. http://en.wikipedia.org/wiki/Triangle_area#Using_trigonometry
If I made no mistake the length of a side x between two angles a and b should be the following.
________________
/ 2A 2A
x = / ------ + ------
\/ tan(a) tan(b)
So to calculate side-C you but in angle-A and angle-B.
(Now double checked it - seems to be correct.)
The formula you give for Side-A seems to be correct IF the triangle is isosceles, i.e., Angle-B = Angle-C (you get this using the law of sines and the sine formula for the area). If it is not isosceles, you seem to need to know the other angles; the general formula is:
Side-A = sqrt(2*Area*sin(Angle-A)/(sin(Angle-B)*sin(Angle-C)))
Of course, I did this in my head, so check the math ;)
Edit: Ok, fixed the formula after checkin on paper.
Related
I am trying to determine if a point lies between two bearings from a central point.
The diagram below attempts to explain things
I have a central point labelled A
I have two points (labelled B & C) which provide the boundaries of the search area (based on bearing only - there is no distance element required).
I'm trying to determine if point D is within the sector formed by A-B and A-C
I've calculated the bearings from A to each B & C
In my real scenario the angle created between the bearings can be anything from 0 to 360.
There are some similar questions & answers
however in my case I'm not interested in restricting my search to the radius of a circle. And there seems to be some implementation issues around angle size and the location of the points in terms of clockwise vs counter-clockwise
It seems so simple in theory but my maths is clearly not up to scratch :(
Any advice or pseudo-code would be greatly appreciated.
Here would be my approach:
calculate first bearing angle X
calculate second bearing angle Y
calculate angle Z towards point D
if X < Z < Y, return true; otherwise, return false
In your example it looks like you'd calculate Z ~ 90deg and find 45 < 90 < 135 (is your picture wrong? is says 315).
You can use something like the "atan2" function in whatever language you're using. This is an extension of the basic arctangent function which takes not just the slope but both the rise and run and instead of returning an angle from only a 180-degree range, it returns the true angle from a 360-degree range. So
Z = atan2(Dy, Dx)
Should give you the angle (possibly in radians; be careful) that you can compare to your bearings to tell whether you're inside the search. Note that the order of X and Y matter since the order is what defines which of the two sections is in the search area (X to Y gives ~90 deg in your picture, but Y to X gives ~270 deg).
You can calculate and compare the cross products of the vectors (AB X BD), and (AC X CD).
if (AB X BD) > 0, you have a counter clock wise turn
if (AC X CD) < 0, you have a clock wise turn
If both above tests are true, then the point D is in the sector BAC
This allows you to completely avoid using expensive trig functions.
class Point:
"""small class for point arithmetic convenience
"""
def __init__(self, x: float = 0, y: float = 0) -> None:
self.x = x
self.y = y
def __sub__(self, other: 'Point') -> 'Vector':
return Vector(self.x - other.x, self.y - other.y)
class Vector:
"""small class for vector arithmetic convenience
"""
def __init__(self, x: float = 0, y: float = 0) -> None:
self.x = x
self.y = y
def cross(self, other: 'Vector') -> float:
return (self.x * other.y) - (self.y * other.x)
def in_sector(A: Point, B: Point, C: Point, D: Point) -> bool:
# construct vectors:
ab = B - A
bd = D - B
ac = C - A
cd = D - C
print(f'ab x bc = {ab.cross(bd)}, ac x cd = {ac.cross(cd)}')
return ab.cross(bd) > 0 and ac.cross(cd) < 0
if __name__ == '__main__':
A = Point(0, 0)
B = Point(1, 1)
C = Point(-1, 1)
D = Point(0, 1)
print(f'D in sector ABC: {in_sector(A, B, C, D)}', end='\n\n')
print(f'D in sector ACB: {in_sector(A, C, B, D)}') # inverting the sector definition so D is now outside
Output:
ab x bc = 1, ac x cd = -1
D in sector ABC: True
ab x bc = -1, ac x cd = 1
D in sector ACB: False
I am trying to create a binary tree from a lot of segments in 3d space sharing the same origin.
When merging two segments I want to have a specific angle between the lines to the child nodes.
The following image illustrates my problem. C shows the position of the parent node and A and B the child positions. N is the average vector of the vectors from C to A and C to B.
With a given angle, how can I determine point P?
Thanks for any help
P = C + t * ((A + B)/2 - C) t is unknown parameter
PA = A - P PA vector
PB = B - P PB vector
Tan(Fi) = (PA x PB) / (PA * PB) (cross product in the nominator, scalar product in the denominator)
Tan(Fi) * (PA.x*PB.x + PA.y*PB.y) = (PA.x*PB.y - PA.y*PB.x)
this is quadratic equation for t, after solving we will get two (for non-degenerate cases) possible positions of P point (the second one lies at other side of AB line)
Addition:
Let's ax = A.x - A point X-coordinate and so on,
abcx = (ax+bx)/2-cx, abcy = (ay+by)/2-cy
pax = ax-cx - t*abcx, pay = ay-cy - t*abcy
pbx = bx-cx - t*abcx, pby = by-cy - t*abcy
ff = Tan(Fi) , then
ff*(pax*pbx+pay*pby)-pax*pby+pay*pbx=0
ff*((ax-cx - t*abcx)*(bx-cx - t*abcx)+(ay-cy - t*abcy)*(by-cy - t*abcy)) -
- (ax-cx - t*abcx)*(by-cy - t*abcy) + (ay-cy - t*abcy)*(bx-cx - t*abcx) =
t^2 *(ff*(abcx^2+abcy^2)) +
t * (-2*ff*(abcx^2+abcy^2) + abcx*(by-ay) + abcy*(ax-bx) ) +
(ff*((ax-cx)*(bx-cx)+(ay-cy)*(by-cy)) - (ax-cx)*(by-cy)+(bx-cx)*(ay-cy)) =0
This is quadratic equation AA*t^2 + BB*t + CC = 0 with coefficients
AA = ff*(abcx^2+abcy^2)
BB = -2*ff*(abcx^2+abcy^2) + abcx*(by-ay) + abcy*(ax-bx)
CC = ff*((ax-cx)*(bx-cx)+(ay-cy)*(by-cy)) - (ax-cx)*(by-cy)+(bx-cx)*(ay-cy)
P.S. My answer is for 2d-case!
For 3d: It is probably simpler to use scalar product only (with vector lengths)
Cos(Fi) = (PA * PB) / (|PA| * |PB|)
Another solution could be using binary search on the vector N, whether P is close to C then the angle will be smaller and whether P is far from C then the angle will be bigger, being it suitable for a binary search.
I'm writing my own basic physic engine and now I come to a problem I can't solve. Probably because I don't how to google this problem.
So here is my problem. I hope this image can explain it:
Collision response
I have two objects. The gray one is fixed and don't move and the green one which falls from the top.
The green object has three vectors: a force, the acceleration and the velocity. It collides with the fixed gray object.
The real question is how can I get the rotation of the green object when it falls down?
It sounds like you may not have an understanding of the fundamental physics underlying rigid body dynamics. I say that only because you don't mention any of the terminology commonly used when talking about this kind of problem. You'll need to introduce the idea of orientation and angular velocity (the rotational analogs of position and linear velocity) to each dynamic body in the system, and compute all kinds of intermediate quantities like moment of inertia, angular acceleration, and torque.
Perhaps the best introductory reference for this is Chris Hecker's series of articles for Game Developer Magazine. Assuming you already have non-rotational dynamics (covered in part 1) and collision detection (not covered by this series) solved, you should begin with part 2 and proceed to part 3. They'll give you a solid foundation in the physics and mathematics necessary for implementing rotational collision response.
You do as described below once, when the objects collide.
Let us call the green rectangle "a", and the other one "b".
1.
First you need the rectangles "rotational mass", mass of inertia.
a.i = 4/3 * width * height * (width^2 + height^2) * a.density
2.
Then you need the vector pointing from the rectangle's center of mass (average position of all corners) to the contact position (where the rectangles collide), let us call it "r".
3.
Then you need to find the collision normal. This normal is the direction of an impulse being applied to a from b. The normal is a vector with length 1 unit. In your example the normal would probably point upwards. Let us call the normal vector "n".
4.
Now you will need the velocity of the contact point on a. If a is not rotating, the formula would be:
vp = a.vel
If a is rotating the formula would be:
vp = a.vel + cross(a.r_vel, r)
a.r_vel is a's rotational velocity given in radians and positive direction is counter clockwise.
cross() means cross product, the function is:
cross (v,i) = [-i * v.y , i * v.x]
The expanded formula would be:
vp = a.v + [-r * a.r_vel.y , r * a.r_vel.x]
5.
Now you need to calculate whether the objects are moving towards each other. Project the vp onto n.
vp_p = dot(vp, n)
dot (v1, v2) = v1.x * v2.x + v1.y * v2.y
vp_p is a scalar (a value, not a vector).
If vp_p is negative the obejcts are moving towards each other, if it is > 0 they are moving apart.
6.
Now you need to calculate the impulse to stop a from moving into b, the impulse is:
j = -vp_p / (
1/a.mass + cross(r,n)^2 / a.i
)
The cross product between two vectors are:
cross(v1,v2) = v1.x * v2.y - v1.y * v2.x
It returns a scalar.
Multiply the impulse with the normal to get the impulse vector:
jn = j * n
7.
Now you need to apply the impulse to a:
a.new_vel = a.old_vel + jn / a.mass;
a.new_r_vel = a.old_r_vel + cross(r,jn) / a.i;
If you want the collision to be fully elastic, you must multiply the impulse by 2. Let us call this multiplier "e". e needs to be between 1 and 2. 1 means no energy is conserved, 2 means all energy is conserved.
Example code:
var vp = a.vel + cross(a.r_vel, r);
var vp_p = dot(vp,n); // negative val = moving towards each other
if (vp_p >= 0) { // do they move apart?
return false;
}
// normal impulse
var j = - e * vp_p / (
1/a.mass + cross(r,n)^2 / a.i
);
var jn = j * n;
//
a.vel = a.vel + jn / a.mass;
a.r_vel = a.r_vel + cross(r,jn) / a.i;
If b is not static the algorithm will be slightly different:
a.r = vector pointing from a's center of mass to the contact position
var vp = a.vel + cross(a.r_vel, a.r) - b.vel - cross(b.r_vel, b.r);
var vp_p = dot(vp,n); // negative val = moving towards each other
if (vp_p >= 0) { // do they move apart?
return false;
}
// normal impulse
var j = - e * vp_p / (
1/a.mass + cross(a.r,n)^2 / a.i +
1/b.mass + cross(b.r,n)^2 / b.i
);
var jn = j * n;
//
a.vel = a.vel + jp / a.mass;
a.r_vel = a.r_vel + cross(a.r,jn) / a.i;
b.vel = b.vel - jp / b.mass;
b.r_vel = b.r_vel - cross(b.r,jn) / b.i;
How the formulas work / sources:
http://www.myphysicslab.com/collision.html#resting_contact
How can I find the line of intersection between two planes?
I know the mathematics idea, and I did the cross product between the the planes normal vectors
but how to get the line from the resulted vector programmatically
The equation of the plane is ax + by + cz + d = 0, where (a,b,c) is the plane's normal, and d is the distance to the origin. This means that every point (x,y,z) that satisfies that equation is a member of the plane.
Given two planes:
P1: a1x + b1y + c1z + d1 = 0
P2: a2x + b2y + c2z + d2 = 0
The intersection between the two is the set of points that verifies both equations. To find points along this line, you can simply pick a value for x, any value, and then solve the equations for y and z.
y = (-c1z -a1x -d1) / b1
z = ((b2/b1)*(a1x+d1) -a2x -d2)/(c2 - c1*b2/b1)
If you make x=0, this gets simpler:
y = (-c1z -d1) / b1
z = ((b2/b1)*d1 -d2)/(c2 - c1*b2/b1)
Finding the line between two planes can be calculated using a simplified version of the 3-plane intersection algorithm.
The 2'nd, "more robust method" from bobobobo's answer references the 3-plane intersection.
While this works well for 2 planes (where the 3rd plane can be calculated using the cross product of the first two), the problem can be further reduced for the 2-plane version.
No need to use a 3x3 matrix determinant,instead we can use the squared length of the cross product between the first and second plane (which is the direction of the 3'rd plane).
No need to include the 3rd planes distance,(calculating the final location).
No need to negate the distances.Save some cpu-cycles by swapping the cross product order instead.
Including this code-example, since it may not be immediately obvious.
// Intersection of 2-planes: a variation based on the 3-plane version.
// see: Graphics Gems 1 pg 305
//
// Note that the 'normal' components of the planes need not be unit length
bool isect_plane_plane_to_normal_ray(
const Plane& p1, const Plane& p2,
// output args
Vector3f& r_point, Vector3f& r_normal)
{
// logically the 3rd plane, but we only use the normal component.
const Vector3f p3_normal = p1.normal.cross(p2.normal);
const float det = p3_normal.length_squared();
// If the determinant is 0, that means parallel planes, no intersection.
// note: you may want to check against an epsilon value here.
if (det != 0.0) {
// calculate the final (point, normal)
r_point = ((p3_normal.cross(p2.normal) * p1.d) +
(p1.normal.cross(p3_normal) * p2.d)) / det;
r_normal = p3_normal;
return true;
}
else {
return false;
}
}
Adding this answer for completeness, since at time of writing, none of the answers here contain a working code-example which directly addresses the question.
Though other answers here already covered the principles.
Finding a point on the line
To get the intersection of 2 planes, you need a point on the line and the direction of that line.
Finding the direction of that line is really easy, just cross the 2 normals of the 2 planes that are intersecting.
lineDir = n1 × n2
But that line passes through the origin, and the line that runs along your plane intersections might not. So, Martinho's answer provides a great start to finding a point on the line of intersection (basically any point that is on both planes).
In case you wanted to see the derivation for how to solve this, here's the math behind it:
First let x=0. Now we have 2 unknowns in 2 equations instead of 3 unknowns in 2 equations (we arbitrarily chose one of the unknowns).
Then the plane equations are (A terms were eliminated since we chose x=0):
B1y + C1z + D1 = 0
B2y + C2z + D2 = 0
We want y and z such that those equations are both solved correctly (=0) for the B1, C1 given.
So, just multiply the top eq by (-B2/B1) to get
-B2y + (-B2/B1)*C1z + (-B2/B1)*D1 = 0
B2y + C2z + D2 = 0
Add the eqs to get
z = ( (-B2/B1)*D1 - D2 ) / (C2 * B2/B1)*C1)
Throw the z you find into the 1st equation now to find y as
y = (-D1 - C1z) / B1
Note the best variable to make 0 is the one with the lowest coefficients, because it carries no information anyway. So if C1 and C2 were both 0, choosing z=0 (instead of x=0) would be a better choice.
The above solution can still screw up if B1=0 (which isn't that unlikely). You could add in some if statements that check if B1=0, and if it is, be sure to solve for one of the other variables instead.
Solution using intersection of 3 planes
From user's answer, a closed form solution for the intersection of 3 planes was actually in Graphics Gems 1. The formula is:
P_intersection = (( point_on1 • n1 )( n2 × n3 ) + ( point_on2 • n2 )( n3 × n1 ) + ( point_on3 • n3 )( n1 × n2 )) / det(n1,n2,n3)
Actually point_on1 • n1 = -d1 (assuming you write your planes Ax + By + Cz + D=0, and not =-D). So, you could rewrite it as:
P_intersection = (( -d1 )( n2 × n3 ) + ( -d2 )( n3 × n1 ) + ( -d3 )( n1 × n2 )) / det(n1,n2,n3)
A function that intersects 3 planes:
// Intersection of 3 planes, Graphics Gems 1 pg 305
static Vector3f getIntersection( const Plane& plane1, const Plane& plane2, const Plane& plane3 )
{
float det = Matrix3f::det( plane1.normal, plane2.normal, plane3.normal ) ;
// If the determinant is 0, that means parallel planes, no intn.
if( det == 0.f ) return 0 ; //could return inf or whatever
return ( plane2.normal.cross( plane3.normal )*-plane1.d +
plane3.normal.cross( plane1.normal )*-plane2.d +
plane1.normal.cross( plane2.normal )*-plane3.d ) / det ;
}
Proof it works (yellow dot is intersection of rgb planes here)
Getting the line
Once you have a point of intersection common to the 2 planes, the line just goes
P + t*d
Where P is the point of intersection, t can go from (-inf, inf), and d is the direction vector that is the cross product of the normals of the two original planes.
The line of intersection between the red and blue planes looks like this
Efficiency and stability
The "robust" (2nd way) takes 48 elementary ops by my count, vs the 36 elementary ops that the 1st way (isolation of x,y) uses. There is a trade off between stability and # computations between these 2 ways.
It'd be pretty catastrophic to get (0,inf,inf) back from a call to the 1st way in the case that B1 was 0 and you didn't check. So adding in if statements and making sure not to divide by 0 to the 1st way may give you the stability at the cost of code bloat, and the added branching (which might be quite expensive). The 3 plane intersection method is almost branchless and won't give you infinities.
This method avoids division by zero as long as the two planes are not parallel.
If these are the planes:
A1*x + B1*y + C1*z + D1 = 0
A2*x + B2*y + C2*z + D2 = 0
1) Find a vector parallel to the line of intersection. This is also the normal of a 3rd plane which is perpendicular to the other two planes:
(A3,B3,C3) = (A1,B1,C1) cross (A2,B2,C2)
2) Form a system of 3 equations. These describe 3 planes which intersect at a point:
A1*x1 + B1*y1 + C1*z1 + D1 = 0
A2*x1 + B2*y1 + C2*z1 + D2 = 0
A3*x1 + B3*y1 + C3*z1 = 0
3) Solve them to find x1,y1,z1. This is a point on the line of intersection.
4) The parametric equations of the line of intersection are:
x = x1 + A3 * t
y = y1 + B3 * t
z = z1 + C3 * t
The determinant-based approach is neat, but it's hard to follow why it works.
Here's another way that's more intuitive.
The idea is to first go from the origin to the closest point on the first plane (p1), and then from there go to the closest point on the line of intersection of the two planes. (Along a vector that I'm calling v below.)
Given
=====
First plane: n1 • r = k1
Second plane: n2 • r = k2
Working
=======
dir = n1 × n2
p1 = (k1 / (n1 • n1)) * n1
v = n1 × dir
pt = LineIntersectPlane(line = (p1, v), plane = (n2, k2))
LineIntersectPlane
==================
#We have n2 • (p1 + lambda * v) = k2
lambda = (k2 - n2 • p1) / (n2 • v)
Return p1 + lambda * v
Output
======
Line where two planes intersect: (pt, dir)
This should give the same point as the determinant-based approach. There's almost certainly a link between the two. At least the denominator, n2 • v, is the same, if we apply the "scalar triple product" rule. So these methods are probably similar as far as condition numbers go.
Don't forget to check for (almost) parallel planes. For example: if (dir • dir < 1e-8) should work well if unit normals are used.
You can find the formula for the intersection line of two planes in this link.
P1: a1x + b1y + c1z = d1
P2: a2x + b2y + c2z = d2
n1=(a1,b1,c1); n2=(a2,b2,c2); n12=Norm[Cross[n1,n2]]^2
If n12 != 0
a1 = (d1*Norm[n2]^2 - d2*n1.n2)/n12;
a2 = (d2*Norm[n1]^2 - d1*n1.n2)/n12;
P = a1 n1 + a2 n2;
(*formula for the intersection line*)
Li[t_] := P + t*Cross[n1, n2];
The cross product of the line is the direction of the intersection line. Now you need a point in the intersection.
You can do this by taking a point on the cross product, then subtracting Normal of plane A * distance to plane A and Normal of plane B * distance to plane b. Cleaner:
p = Point on cross product
intersection point = ([p] - ([Normal of plane A] * [distance from p to plane A]) - ([Normal of plane B] * [distance from p to plane B]))
Edit:
You have two planes with two normals:
N1 and N2
The cross product is the direction of the Intersection Line:
C = N1 x N2
The class above has a function to calculate the distance between a point and a plane. Use it to get the distance of some point p on C to both planes:
p = C //p = 1 times C to get a point on C
d1 = plane1.getDistance(p)
d2 = plane2.getDistance(p)
Intersection line:
resultPoint1 = (p - (d1 * N1) - (d2 * N2))
resultPoint2 = resultPoint1 + C
Okay, this all takes place in a nice and simple 2D world... :)
Suppose I have a static object A at position Apos, and a linearly moving object B at Bpos with bVelocity, and an ammo round with velocity Avelocity...
How would I find out the angle that A has to shoot, to hit B, taking into account B's linear velocity and the speed of A's ammo ?
Right now the aim's at the current position of the object, which means that by the time my projectile gets there the unit has moved on to safer positions :)
I wrote an aiming subroutine for xtank a while back. I'll try to lay out how I did it.
Disclaimer: I may have made one or more silly mistakes anywhere in here; I'm just trying to reconstruct the reasoning with my rusty math skills. However, I'll cut to the chase first, since this is a programming Q&A instead of a math class :-)
How to do it
It boils down to solving a quadratic equation of the form:
a * sqr(x) + b * x + c == 0
Note that by sqr I mean square, as opposed to square root. Use the following values:
a := sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)
b := 2 * (target.velocityX * (target.startX - cannon.X)
+ target.velocityY * (target.startY - cannon.Y))
c := sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y)
Now we can look at the discriminant to determine if we have a possible solution.
disc := sqr(b) - 4 * a * c
If the discriminant is less than 0, forget about hitting your target -- your projectile can never get there in time. Otherwise, look at two candidate solutions:
t1 := (-b + sqrt(disc)) / (2 * a)
t2 := (-b - sqrt(disc)) / (2 * a)
Note that if disc == 0 then t1 and t2 are equal.
If there are no other considerations such as intervening obstacles, simply choose the smaller positive value. (Negative t values would require firing backward in time to use!)
Substitute the chosen t value back into the target's position equations to get the coordinates of the leading point you should be aiming at:
aim.X := t * target.velocityX + target.startX
aim.Y := t * target.velocityY + target.startY
Derivation
At time T, the projectile must be a (Euclidean) distance from the cannon equal to the elapsed time multiplied by the projectile speed. This gives an equation for a circle, parametric in elapsed time.
sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
== sqr(t * projectile_speed)
Similarly, at time T, the target has moved along its vector by time multiplied by its velocity:
target.X == t * target.velocityX + target.startX
target.Y == t * target.velocityY + target.startY
The projectile can hit the target when its distance from the cannon matches the projectile's distance.
sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
== sqr(target.X - cannon.X) + sqr(target.Y - cannon.Y)
Wonderful! Substituting the expressions for target.X and target.Y gives
sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
== sqr((t * target.velocityX + target.startX) - cannon.X)
+ sqr((t * target.velocityY + target.startY) - cannon.Y)
Substituting the other side of the equation gives this:
sqr(t * projectile_speed)
== sqr((t * target.velocityX + target.startX) - cannon.X)
+ sqr((t * target.velocityY + target.startY) - cannon.Y)
... subtracting sqr(t * projectile_speed) from both sides and flipping it around:
sqr((t * target.velocityX) + (target.startX - cannon.X))
+ sqr((t * target.velocityY) + (target.startY - cannon.Y))
- sqr(t * projectile_speed)
== 0
... now resolve the results of squaring the subexpressions ...
sqr(target.velocityX) * sqr(t)
+ 2 * t * target.velocityX * (target.startX - cannon.X)
+ sqr(target.startX - cannon.X)
+ sqr(target.velocityY) * sqr(t)
+ 2 * t * target.velocityY * (target.startY - cannon.Y)
+ sqr(target.startY - cannon.Y)
- sqr(projectile_speed) * sqr(t)
== 0
... and group similar terms ...
sqr(target.velocityX) * sqr(t)
+ sqr(target.velocityY) * sqr(t)
- sqr(projectile_speed) * sqr(t)
+ 2 * t * target.velocityX * (target.startX - cannon.X)
+ 2 * t * target.velocityY * (target.startY - cannon.Y)
+ sqr(target.startX - cannon.X)
+ sqr(target.startY - cannon.Y)
== 0
... then combine them ...
(sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)) * sqr(t)
+ 2 * (target.velocityX * (target.startX - cannon.X)
+ target.velocityY * (target.startY - cannon.Y)) * t
+ sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y)
== 0
... giving a standard quadratic equation in t. Finding the positive real zeros of this equation gives the (zero, one, or two) possible hit locations, which can be done with the quadratic formula:
a * sqr(x) + b * x + c == 0
x == (-b ± sqrt(sqr(b) - 4 * a * c)) / (2 * a)
+1 on Jeffrey Hantin's excellent answer here. I googled around and found solutions that were either too complex or not specifically about the case I was interested in (simple constant velocity projectile in 2D space.) His was exactly what I needed to produce the self-contained JavaScript solution below.
The one point I would add is that there are a couple special cases you have to watch for in addition to the discriminant being negative:
"a == 0": occurs if target and projectile are traveling the same speed. (solution is linear, not quadratic)
"a == 0 and b == 0": if both target and projectile are stationary. (no solution unless c == 0, i.e. src & dst are same point.)
Code:
/**
* Return the firing solution for a projectile starting at 'src' with
* velocity 'v', to hit a target, 'dst'.
*
* #param ({x, y}) src position of shooter
* #param ({x, y, vx, vy}) dst position & velocity of target
* #param (Number) v speed of projectile
*
* #return ({x, y}) Coordinate at which to fire (and where intercept occurs). Or `null` if target cannot be hit.
*/
function intercept(src, dst, v) {
const tx = dst.x - src.x;
const ty = dst.y - src.y;
const tvx = dst.vx;
const tvy = dst.vy;
// Get quadratic equation components
const a = tvx * tvx + tvy * tvy - v * v;
const b = 2 * (tvx * tx + tvy * ty);
const c = tx * tx + ty * ty;
// Solve quadratic
const ts = quad(a, b, c); // See quad(), below
// Find smallest positive solution
let sol = null;
if (ts) {
const t0 = ts[0];
const t1 = ts[1];
let t = Math.min(t0, t1);
if (t < 0) t = Math.max(t0, t1);
if (t > 0) {
sol = {
x: dst.x + dst.vx * t,
y: dst.y + dst.vy * t
};
}
}
return sol;
}
/**
* Return solutions for quadratic
*/
function quad(a, b, c) {
let sol = null;
if (Math.abs(a) < 1e-6) {
if (Math.abs(b) < 1e-6) {
sol = Math.abs(c) < 1e-6 ? [0, 0] : null;
} else {
sol = [-c / b, -c / b];
}
} else {
let disc = b * b - 4 * a * c;
if (disc >= 0) {
disc = Math.sqrt(disc);
a = 2 * a;
sol = [(-b - disc) / a, (-b + disc) / a];
}
}
return sol;
}
// For example ...
const sol = intercept(
{x:2, y:4}, // Starting coord
{x:5, y:7, vx: 2, vy:1}, // Target coord and velocity
5 // Projectile velocity
)
console.log('Fire at', sol)
First rotate the axes so that AB is vertical (by doing a rotation)
Now, split the velocity vector of B into the x and y components (say Bx and By). You can use this to calculate the x and y components of the vector you need to shoot at.
B --> Bx
|
|
V
By
Vy
^
|
|
A ---> Vx
You need Vx = Bx and Sqrt(Vx*Vx + Vy*Vy) = Velocity of Ammo.
This should give you the vector you need in the new system. Transform back to old system and you are done (by doing a rotation in the other direction).
Jeffrey Hantin has a nice solution for this problem, though his derivation is overly complicated. Here's a cleaner way of deriving it with some of the resultant code at the bottom.
I'll be using x.y to represent vector dot product, and if a vector quantity is squared, it means I am dotting it with itself.
origpos = initial position of shooter
origvel = initial velocity of shooter
targpos = initial position of target
targvel = initial velocity of target
projvel = velocity of the projectile relative to the origin (cause ur shooting from there)
speed = the magnitude of projvel
t = time
We know that the position of the projectile and target with respect to t time can be described with some equations.
curprojpos(t) = origpos + t*origvel + t*projvel
curtargpos(t) = targpos + t*targvel
We want these to be equal to each other at some point (the point of intersection), so let's set them equal to each other and solve for the free variable, projvel.
origpos + t*origvel + t*projvel = targpos + t*targvel
turns into ->
projvel = (targpos - origpos)/t + targvel - origvel
Let's forget about the notion of origin and target position/velocity. Instead, let's work in relative terms since motion of one thing is relative to another. In this case, what we now have is relpos = targetpos - originpos and relvel = targetvel - originvel
projvel = relpos/t + relvel
We don't know what projvel is, but we do know that we want projvel.projvel to be equal to speed^2, so we'll square both sides and we get
projvel^2 = (relpos/t + relvel)^2
expands into ->
speed^2 = relvel.relvel + 2*relpos.relvel/t + relpos.relpos/t^2
We can now see that the only free variable is time, t, and then we'll use t to solve for projvel. We'll solve for t with the quadratic formula. First separate it out into a, b and c, then solve for the roots.
Before solving, though, remember that we want the best solution where t is smallest, but we need to make sure that t is not negative (you can't hit something in the past)
a = relvel.relvel - speed^2
b = 2*relpos.relvel
c = relpos.relpos
h = -b/(2*a)
k2 = h*h - c/a
if k2 < 0, then there are no roots and there is no solution
if k2 = 0, then there is one root at h
if 0 < h then t = h
else, no solution
if k2 > 0, then there are two roots at h - k and h + k, we also know r0 is less than r1.
k = sqrt(k2)
r0 = h - k
r1 = h + k
we have the roots, we must now solve for the smallest positive one
if 0<r0 then t = r0
elseif 0<r1 then t = r1
else, no solution
Now, if we have a t value, we can plug t back into the original equation and solve for the projvel
projvel = relpos/t + relvel
Now, to the shoot the projectile, the resultant global position and velocity for the projectile is
globalpos = origpos
globalvel = origvel + projvel
And you're done!
My implementation of my solution in Lua, where vec*vec represents vector dot product:
local function lineartrajectory(origpos,origvel,speed,targpos,targvel)
local relpos=targpos-origpos
local relvel=targvel-origvel
local a=relvel*relvel-speed*speed
local b=2*relpos*relvel
local c=relpos*relpos
if a*a<1e-32 then--code translation for a==0
if b*b<1e-32 then
return false,"no solution"
else
local h=-c/b
if 0<h then
return origpos,relpos/h+targvel,h
else
return false,"no solution"
end
end
else
local h=-b/(2*a)
local k2=h*h-c/a
if k2<-1e-16 then
return false,"no solution"
elseif k2<1e-16 then--code translation for k2==0
if 0<h then
return origpos,relpos/h+targvel,h
else
return false,"no solution"
end
else
local k=k2^0.5
if k<h then
return origpos,relpos/(h-k)+targvel,h-k
elseif -k<h then
return origpos,relpos/(h+k)+targvel,h+k
else
return false,"no solution"
end
end
end
end
Following is polar coordinate based aiming code in C++.
To use with rectangular coordinates you would need to first convert the targets relative coordinate to angle/distance, and the targets x/y velocity to angle/speed.
The "speed" input is the speed of the projectile. The units of the speed and targetSpeed are irrelevent, as only the ratio of the speeds are used in the calculation. The output is the angle the projectile should be fired at and the distance to the collision point.
The algorithm is from source code available at http://www.turtlewar.org/ .
// C++
static const double pi = 3.14159265358979323846;
inline double Sin(double a) { return sin(a*(pi/180)); }
inline double Asin(double y) { return asin(y)*(180/pi); }
bool/*ok*/ Rendezvous(double speed,double targetAngle,double targetRange,
double targetDirection,double targetSpeed,double* courseAngle,
double* courseRange)
{
// Use trig to calculate coordinate of future collision with target.
// c
//
// B A
//
// a C b
//
// Known:
// C = distance to target
// b = direction of target travel, relative to it's coordinate
// A/B = ratio of speed and target speed
//
// Use rule of sines to find unknowns.
// sin(a)/A = sin(b)/B = sin(c)/C
//
// a = asin((A/B)*sin(b))
// c = 180-a-b
// B = C*(sin(b)/sin(c))
bool ok = 0;
double b = 180-(targetDirection-targetAngle);
double A_div_B = targetSpeed/speed;
double C = targetRange;
double sin_b = Sin(b);
double sin_a = A_div_B*sin_b;
// If sin of a is greater than one it means a triangle cannot be
// constructed with the given angles that have sides with the given
// ratio.
if(fabs(sin_a) <= 1)
{
double a = Asin(sin_a);
double c = 180-a-b;
double sin_c = Sin(c);
double B;
if(fabs(sin_c) > .0001)
{
B = C*(sin_b/sin_c);
}
else
{
// Sin of small angles approach zero causing overflow in
// calculation. For nearly flat triangles just treat as
// flat.
B = C/(A_div_B+1);
}
// double A = C*(sin_a/sin_c);
ok = 1;
*courseAngle = targetAngle+a;
*courseRange = B;
}
return ok;
}
Here's an example where I devised and implemented a solution to the problem of predictive targeting using a recursive algorithm: http://www.newarteest.com/flash/targeting.html
I'll have to try out some of the other solutions presented because it seems more efficient to calculate it in one step, but the solution I came up with was to estimate the target position and feed that result back into the algorithm to make a new more accurate estimate, repeating several times.
For the first estimate I "fire" at the target's current position and then use trigonometry to determine where the target will be when the shot reaches the position fired at. Then in the next iteration I "fire" at that new position and determine where the target will be this time. After about 4 repeats I get within a pixel of accuracy.
I just hacked this version for aiming in 2d space, I didn't test it very thoroughly yet but it seems to work. The idea behind it is this:
Create a vector perpendicular to the vector pointing from the muzzle to the target.
For a collision to occur, the velocities of the target and the projectile along this vector (axis) should be the same!
Using fairly simple cosine stuff I arrived at this code:
private Vector3 CalculateProjectileDirection(Vector3 a_MuzzlePosition, float a_ProjectileSpeed, Vector3 a_TargetPosition, Vector3 a_TargetVelocity)
{
// make sure it's all in the horizontal plane:
a_TargetPosition.y = 0.0f;
a_MuzzlePosition.y = 0.0f;
a_TargetVelocity.y = 0.0f;
// create a normalized vector that is perpendicular to the vector pointing from the muzzle to the target's current position (a localized x-axis):
Vector3 perpendicularVector = Vector3.Cross(a_TargetPosition - a_MuzzlePosition, -Vector3.up).normalized;
// project the target's velocity vector onto that localized x-axis:
Vector3 projectedTargetVelocity = Vector3.Project(a_TargetVelocity, perpendicularVector);
// calculate the angle that the projectile velocity should make with the localized x-axis using the consine:
float angle = Mathf.Acos(projectedTargetVelocity.magnitude / a_ProjectileSpeed) / Mathf.PI * 180;
if (Vector3.Angle(perpendicularVector, a_TargetVelocity) > 90.0f)
{
angle = 180.0f - angle;
}
// rotate the x-axis so that is points in the desired velocity direction of the projectile:
Vector3 returnValue = Quaternion.AngleAxis(angle, -Vector3.up) * perpendicularVector;
// give the projectile the correct speed:
returnValue *= a_ProjectileSpeed;
return returnValue;
}
I made a public domain Unity C# function here:
http://ringofblades.com/Blades/Code/PredictiveAim.cs
It is for 3D, but you can easily modify this for 2D by replacing the Vector3s with Vector2s and using your down axis of choice for gravity if there is gravity.
In case the theory interests you, I walk through the derivation of the math here:
http://www.gamasutra.com/blogs/KainShin/20090515/83954/Predictive_Aim_Mathematics_for_AI_Targeting.php
I've seen many ways to solve this problem mathematically, but this was a component relevant to a project my class was required to do in high school, and not everyone in this programming class had a background with calculus, or even vectors for that matter, so I created a way to solve this problem with more of a programming approach. The point of intersection will be accurate, although it may hit 1 frame later than in the mathematical computations.
Consider:
S = shooterPos, E = enemyPos, T = targetPos, Sr = shooter range, D = enemyDir
V = distance from E to T, P = projectile speed, Es = enemy speed
In the standard implementation of this problem [S,E,P,Es,D] are all givens and you are solving either to find T or the angle at which to shoot so that you hit T at the proper timing.
The main aspect of this method of solving the problem is to consider the range of the shooter as a circle encompassing all possible points that can be shot at any given time. The radius of this circle is equal to:
Sr = P*time
Where time is calculated as an iteration of a loop.
Thus to find the distance an enemy travels given the time iteration we create the vector:
V = D*Es*time
Now, to actually solve the problem we want to find a point at which the distance from the target (T) to our shooter (S) is less than the range of our shooter (Sr). Here is somewhat of a pseudocode implementation of this equation.
iteration = 0;
while(TargetPoint.hasNotPassedShooter)
{
TargetPoint = EnemyPos + (EnemyMovementVector)
if(distanceFrom(TargetPoint,ShooterPos) < (ShooterRange))
return TargetPoint;
iteration++
}
Basically , intersection concept is not really needed here, As far as you are using projectile motion, you just need to hit at a particular angle and instantiate at the time of shooting so that you get the exact distance of your target from the Source and then once you have the distance, you can calculate the appropriate velocity with which it should shot in order to hit the Target.
The following link makes teh concept clear and is considered helpful, might help:
Projectile motion to always hit a moving target
I grabbed one of the solutions from here, but none of them take into account movement of the shooter. If your shooter is moving, you might want to take that into account (as the shooter's velocity should be added to your bullet's velocity when you fire). Really all you need to do is subtract your shooter's velocity from the target's velocity. So if you're using broofa's code above (which I would recommend), change the lines
tvx = dst.vx;
tvy = dst.vy;
to
tvx = dst.vx - shooter.vx;
tvy = dst.vy - shooter.vy;
and you should be all set.