2D Shadow Effect - Points on circle - python

I am currently trying to implement flat shadow effects to a 2D game that i have written in Python. I have found a great deal of tutorials and methods of doing this online (http://ncase.me/sight-and-light/) however, these all use polygons as the obstructions, where all corner points are known while my game includes circles.
I was wondering if it were possible to calculate either the X and Y coordinates of each of the points of contact (P and Q) or the gradient of the line if the situation of A and O and the radius of the circle are known.
Thanks in advance and apologies if the question is off topic, but i couldn't find the answers anywhere else.

The trick is to notice what is happening at point P. At point P, the line AP is tangent to the circle so in other words the angle APO is 90 degrees. Likewise AQO is 90 degrees.
Now we know that we have a triangle, we know 2 of the lengths and one of the angles (We know AO, OP / OQ (Same thing), and APO / AQO).
We now use the law of sines.
AO/sin(APO) = OP/sin(PAO)
PAO = asin(OP*(sin(APO)/AO))
Remember to be conscious of the units (ie using 90 degrees as an input value and then forgetting that a your library function for sin may return in radians not degrees).
From here, you can find all of the angles by knowing that the sum of all angles in a triangle is 180 degrees. So now you have all three angles.
When you have angle AOP from the above calculation, you can use the law of sines again to calculate the length of AP.
AP = sin(AOP) * AO / sin(APO).
Note that sin(90 degrees) == 1 (And remember that APO and AQO are 90 degrees | pi/2 radians).
Now we have the length of AP. We can now find the coordinates (x, y) of P, assuming that A is at (0, 0). If A is not the origin just add A's coordinates as an offset.
To find the coordinates of P:
PxCoord = AxCoord + AP * cos(PAO)
PyCoord = AyCoord + AP * sin(PAO)
Reminder: Please check if your trig functions (sin / asin) use degrees or radians, and make sure to convert the 90 degrees to radians (it is pi/2 radians) if your function uses radians. Also note that if this is the case, your output will be in radians for the angle, and likewise instead of there being 180 degrees in a triangle you will have pi radians.

Let's vector V = OP (unknown), vector Q = AP, vector U = AO (known)
Note that Q = U + V
Vector V length is radius R, so
VX^2 + VY^2 = R^2 //1
Vectors V and A are perpendicular, so their scalar product is zero
VX * QX + VY * QY = 0
VX * (VX + UX) + VY * (VY + UY) = 0
VX * VX + VX * UX + VY * VY + VY * UY = 0
R^2 + VX * UX + VY * UY = 0 //2
Solve system of equations 1 and 2 and get solutions
LL = U.X^2 + U.Y^2
VY = (R^2 * UY +/- R * UX * Sqrt(LL - R^2)) / LL
VX = (R^2 - VY * UY) / UX
and finally
P.X = O.X + VX
P.Y = O.Y + VY

Related

Intersection coordinates (lat/lon) of two circles (given the coordinates of the center and the radius) on earth

I am not that experienced in python but improving it thanks to this community! I desperately need a function which takes the input and gives the ouput below:
Input:
1- Latitude/longitude coordinates of the center of circle 1 (e.g. (50.851295, 5.667969) )
2- The radius of circle 1 in meters (e.g. 200)
3- Latitude/longitude coordinates of the center of circle 2 (e.g. (50.844101, 5.725889) )
4- The radius of circle 2 in meters (e.g. 300)
Output: Possible output examples can be;
The intersection points are (50.848295, 5.707969) and (50.849295, 5.717969)
The circles are overlapping
The circles are tangential and the intersection point is (50.847295, 5.705969)
The circles do not intersect
I have examined the similar topics in this platform, other platforms, libraries, tried to combine different solutions but couldn't succeed. Any help is much appreciated!
EDIT:
The problem is solved many thanks to Ture Pålsson who commented below and directed me to whuber's brilliant work in this link https://gis.stackexchange.com/questions/48937/calculating-intersection-of-two-circles Based on that work, I wrote the code below and as far as I tested it works. I want to share it here in case someone might find it helpful. Any feedback is appreciated.
'''
FINDING THE INTERSECTION COORDINATES (LAT/LON) OF TWO CIRCLES (GIVEN THE COORDINATES OF THE CENTER AND THE RADII)
Many thanks to Ture Pålsson who directed me to the right source, the code below is based on whuber's brilliant logic and
explanation here https://gis.stackexchange.com/questions/48937/calculating-intersection-of-two-circles
The idea is that;
1. The points in question are the mutual intersections of three spheres: a sphere centered beneath location x1 (on the
earth's surface) of a given radius, a sphere centered beneath location x2 (on the earth's surface) of a given radius, and
the earth itself, which is a sphere centered at O = (0,0,0) of a given radius.
2. The intersection of each of the first two spheres with the earth's surface is a circle, which defines two planes.
The mutual intersections of all three spheres therefore lies on the intersection of those two planes: a line.
Consequently, the problem is reduced to intersecting a line with a sphere.
Note that "Decimal" is used to have higher precision which is important if the distance between two points are a few
meters.
'''
from decimal import Decimal
from math import cos, sin, sqrt
import math
import numpy as np
def intersection(p1, r1_meter, p2, r2_meter):
# p1 = Coordinates of Point 1: latitude, longitude. This serves as the center of circle 1. Ex: (36.110174, -90.953524)
# r1_meter = Radius of circle 1 in meters
# p2 = Coordinates of Point 2: latitude, longitude. This serves as the center of circle 1. Ex: (36.110174, -90.953524)
# r2_meter = Radius of circle 2 in meters
'''
1. Convert (lat, lon) to (x,y,z) geocentric coordinates.
As usual, because we may choose units of measurement in which the earth has a unit radius
'''
x_p1 = Decimal(cos(math.radians(p1[1]))*cos(math.radians(p1[0]))) # x = cos(lon)*cos(lat)
y_p1 = Decimal(sin(math.radians(p1[1]))*cos(math.radians(p1[0]))) # y = sin(lon)*cos(lat)
z_p1 = Decimal(sin(math.radians(p1[0]))) # z = sin(lat)
x1 = (x_p1, y_p1, z_p1)
x_p2 = Decimal(cos(math.radians(p2[1]))*cos(math.radians(p2[0]))) # x = cos(lon)*cos(lat)
y_p2 = Decimal(sin(math.radians(p2[1]))*cos(math.radians(p2[0]))) # y = sin(lon)*cos(lat)
z_p2 = Decimal(sin(math.radians(p2[0]))) # z = sin(lat)
x2 = (x_p2, y_p2, z_p2)
'''
2. Convert the radii r1 and r2 (which are measured along the sphere) to angles along the sphere.
By definition, one nautical mile (NM) is 1/60 degree of arc (which is pi/180 * 1/60 = 0.0002908888 radians).
'''
r1 = Decimal(math.radians((r1_meter/1852) / 60)) # r1_meter/1852 converts meter to Nautical mile.
r2 = Decimal(math.radians((r2_meter/1852) / 60))
'''
3. The geodesic circle of radius r1 around x1 is the intersection of the earth's surface with an Euclidean sphere
of radius sin(r1) centered at cos(r1)*x1.
4. The plane determined by the intersection of the sphere of radius sin(r1) around cos(r1)*x1 and the earth's surface
is perpendicular to x1 and passes through the point cos(r1)x1, whence its equation is x.x1 = cos(r1)
(the "." represents the usual dot product); likewise for the other plane. There will be a unique point x0 on the
intersection of those two planes that is a linear combination of x1 and x2. Writing x0 = ax1 + b*x2 the two planar
equations are;
cos(r1) = x.x1 = (a*x1 + b*x2).x1 = a + b*(x2.x1)
cos(r2) = x.x2 = (a*x1 + b*x2).x2 = a*(x1.x2) + b
Using the fact that x2.x1 = x1.x2, which I shall write as q, the solution (if it exists) is given by
a = (cos(r1) - cos(r2)*q) / (1 - q^2),
b = (cos(r2) - cos(r1)*q) / (1 - q^2).
'''
q = Decimal(np.dot(x1, x2))
if q**2 != 1 :
a = (Decimal(cos(r1)) - Decimal(cos(r2))*q) / (1 - q**2)
b = (Decimal(cos(r2)) - Decimal(cos(r1))*q) / (1 - q**2)
'''
5. Now all other points on the line of intersection of the two planes differ from x0 by some multiple of a vector
n which is mutually perpendicular to both planes. The cross product n = x1~Cross~x2 does the job provided n is
nonzero: once again, this means that x1 and x2 are neither coincident nor diametrically opposite. (We need to
take care to compute the cross product with high precision, because it involves subtractions with a lot of
cancellation when x1 and x2 are close to each other.)
'''
n = np.cross(x1, x2)
'''
6. Therefore, we seek up to two points of the form x0 + t*n which lie on the earth's surface: that is, their length
equals 1. Equivalently, their squared length is 1:
1 = squared length = (x0 + t*n).(x0 + t*n) = x0.x0 + 2t*x0.n + t^2*n.n = x0.x0 + t^2*n.n
'''
x0_1 = [a*f for f in x1]
x0_2 = [b*f for f in x2]
x0 = [sum(f) for f in zip(x0_1, x0_2)]
'''
The term with x0.n disappears because x0 (being a linear combination of x1 and x2) is perpendicular to n.
The two solutions easily are t = sqrt((1 - x0.x0)/n.n) and its negative. Once again high precision
is called for, because when x1 and x2 are close, x0.x0 is very close to 1, leading to some loss of
floating point precision.
'''
if (np.dot(x0, x0) <= 1) & (np.dot(n,n) != 0): # This is to secure that (1 - np.dot(x0, x0)) / np.dot(n,n) > 0
t = Decimal(sqrt((1 - np.dot(x0, x0)) / np.dot(n,n)))
t1 = t
t2 = -t
i1 = x0 + t1*n
i2 = x0 + t2*n
'''
7. Finally, we may convert these solutions back to (lat, lon) by converting geocentric (x,y,z) to geographic
coordinates. For the longitude, use the generalized arctangent returning values in the range -180 to 180
degrees (in computing applications, this function takes both x and y as arguments rather than just the
ratio y/x; it is sometimes called "ATan2").
'''
i1_lat = math.degrees( math.asin(i1[2]))
i1_lon = math.degrees( math.atan2(i1[1], i1[0] ) )
ip1 = (i1_lat, i1_lon)
i2_lat = math.degrees( math.asin(i2[2]))
i2_lon = math.degrees( math.atan2(i2[1], i2[0] ) )
ip2 = (i2_lat, i2_lon)
return [ip1, ip2]
elif (np.dot(n,n) == 0):
return("The centers of the circles can be neither the same point nor antipodal points.")
else:
return("The circles do not intersect")
else:
return("The centers of the circles can be neither the same point nor antipodal points.")
'''
Example: the output of below is [(36.989311051533505, -88.15142628069133), (38.2383796094578, -92.39048549120287)]
intersection_points = intersection((37.673442, -90.234036), 107.5*1852, (36.109997, -90.953669), 145*1852)
print(intersection_points)
'''
Depending on the precision you need, you may or may not consider the Earth as a sphere. In the second case, calculations become more complex.
The best option for precise measurements when the radius is small (as in your example) is to use a projection (UTM for example) and then apply the common flat euclidean calculations.
Let's first copy the flat circle intersection function from https://stackoverflow.com/a/55817881/2148416:
def circle_intersection(x0, y0, r0, x1, y1, r1):
d = math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
if d > r0 + r1: # non intersecting
return None
if d < abs(r0 - r1): # one circle within other
return None
if d == 0 and r0 == r1: # coincident circles
return None
a = (r0 ** 2 - r1 ** 2 + d ** 2) / (2 * d)
h = math.sqrt(r0 ** 2 - a ** 2)
x2 = x0 + a * (x1 - x0) / d
y2 = y0 + a * (y1 - y0) / d
x3 = x2 + h * (y1 - y0) / d
y3 = y2 - h * (x1 - x0) / d
x4 = x2 - h * (y1 - y0) / d
y4 = y2 + h * (x1 - x0) / d
return (x3, y3), (x4, y4)
The precise calculation for a small radius (up to a few kilometers) can be done in UTM coordinates with the help of the utm library. It handles all the complications regarding the fact the the Earth is more an ellipsoid than a sphere:
import utm
def geo_circle_intersection(latlon0, radius0, latlon1, radius1):
# Convert lat/lon to UTM
x0, y0, zone, letter = utm.from_latlon(latlon0[0], latlon0[1])
x1, y1, _, _ = utm.from_latlon(latlon1[0], latlon1 [1], force_zone_number=zone)
# Calculate intersections in UTM coordinates
a_utm, b_utm = circle_intersection(x0, y0, r0, x1, y1, r1)
# Convert intersections from UTM back to lat/lon
a = utm.to_latlon(a_utm[0], a_utm[1], zone, letter)
b = utm.to_latlon(b_utm[0], b_utm[1], zone, letter)
return a, b
Using your example (with slightly larger radii):
>>> p0 = 50.851295, 5.667969
>>> r0 = 2000
>>> p1 = 50.844101, 5.725889
>>> r1 = 3000
>>> a, b = geo_circle_intersection(p0, r0, p1, r1)
>>> print(a)
(50.836848562566004, 5.684869539768468)
>>> print(b)
(50.860635308778285, 5.692236858407678)

How to move mouse along arc given by 3 points and a length?

I'm currently working on a section of a program that moves the mouse in an arc.
I'm given three points that define the arc: a starting point p1, a intermediate point on the arc p2 , and the end point p3. I'm also given length of the arc. If length is greater than the actual length of the arc subtended by p1 and p3, then p3 will not be the end point of the arc, but the mouse will continue moving in a circle until it has traveled distance length.
I have worked out the center of the circle (x, y), its radius r, and angle sweeped.
To move the mouse, am hoping to divide angle into smaller intervals each with angle dAngle and moving the mouse between its current position and the new position after sweeping dAngle. What I have in mind is in the pseudocode below:
for i in range(intervals):
x = center.x + r * cos(i * dAngle)
y = center.y + r * sin(i * dAngle)
Move mouse to (x, y)
Sleep 1
However, I've encountered some problems while trying to parametrically find the new point on the arc.
My mouse does not start at p1, but at what I assume is at the point where the line from the mouse to the center and the horizontal line subtends 0 degees, as I haven't factored into the parameters the initial angle. How do I find the initial angle of the mouse?
How do I determine whether to rotate clockwise or anticlockwise, i.e. whether x = center.x + r * cos(i * dAngle) or x = center.x - r * cos(i * dAngle)
If there is a more efficient way of moving in an arc please suggest it.
You can calculate starting angle as
a1 = math.atan2(p1.y-center.y, p1.x-center.x)
then use it in
x = center.x + r * cos(a1 + i * dAngle)
y = center.y + r * sin(a1 + i * dAngle)
About direction - perhaps you can determine direction when arc center is calculated. If no, and arc sweep angle is less than Pi (180 degrees), just find sign of expression
sg = math.sign((p1.x-center.x) * (p3.y-center.y) - (p1.y-center.y) * (p3.x-center.x))
and use it with dAngle
x = center.x + r * cos(a1 + i * sg * dAngle)
similar for y
P.S. note that minus in x = center.x - r * cos(i * dAngle) is wrong way to change direction

calculating an intercept point between a straight line and an ellipse - python

Iv'e been trying lately to calculate a point an ellipse
The desired point is the green point , knowing the red dots
and the ellipse equation.
I've used numpy linspace to create an array on points
and iterate them using zip(x axis , y axis)
between the red points , and using the ellipse
equation figure which of the points is the closest to 1.
(which is the outcome of the ellipse equation ).
this concept works most of the time , but in some location
of the red outer dot , this method doesn't seem to give good outcome
long story short, any idea how to calculate the green dot in python?
p.s - ellipse might have angle, both of hes axis are known.
I end up using the ellipse equation from this answer:
and created an in_ellipse function
then Iv'e used the Intermediate value theorem , to get a good estimation
of the point
def in_ellipse(point, ellipse):
return true if point in ellipse
return false
dot_a = ellipse_center
dot_b = dot
for i in range(20):
center_point = ((dot_b.y - dot_a.y)/2, (dot_b.x - dot_a.x)/2)
if in_ellipse(center_point):
dot_a = center_point
else:
dot_b = center_point
return center_point
this system gives the point in 7 (2^20) digits resolution after decimal point
you can increase the range for better resolution.
Let ellipse center is (0,0) (otherwise just subtract center coordinates), semi-axes are a, b and rotation angle is theta. We can build affine tranformation to transform ellipse into circle and apply the same transform to point P.
1) Rotate by -theta
px1 = px * Cos(theta) + py * Sin(theta)
py1 = -px * Sin(theta) + py * Cos(theta)
2) Extend (or shrink) along OY axis by a/b times
px2 = px1
py2 = py1 * a / b
3) Find intersection point
plen = hypot(px2, py2) (length of p2 vector)
if (a > plen), then segment doesn't intersect ellipse - it fully lies inside
ix = a * px2 / plen
iy = a * py2 / plen
4) Make backward shrinking
ix2 = ix
iy2 = iy * b / a
5) Make backward rotation
ixfinal = ix2 * Cos(theta) - iy2 * Sin(theta)
iyfinal = ix2 * Sin(theta) + iy2 * Cos(theta)

quaternion rotation vs. rotation matrix have a slight difference

I have a 3D rotation over time represented as a momentary rotation around each of the axis (roll, pitch, yaw).
I'm trying to accumulate this rotation over time (about 50k measurements in total). I've tried doing it in 2 different ways. Using rotation matrices, and using quaternions calculation. The rotation matrices implementation seem to give correct results, but I know it is less recommended for accumulating many rotations.
The 2 results seem quite similar, but it accumulates a slight difference between the 2 results over time (about 1 degree every 250 measurements). I'm not sure where this difference comes from. Whether it is caused by floating point precision in calculating many matrices multiplications, or by using wrong parameters for the quaternion initialization.
This is the code I use:
# Last known rotation. As quaternion and as rotation matrix
last_rotation_matrix = ....
last_quaternion_rotation = .....
# time_diff_seconds is approximately 4/1000
# the momentary rotation speed is around 0-1 radian per second. so [roll,pitch,yaw] are around 0-0.004)
roll = rotation_x_speed * time_diff_seconds
pitch = rotation_y_speed * time_diff_seconds
yaw = rotation_z_speed * time_diff_seconds
total_rotation = np.sqrt(roll**2 + pitch**2 + yaw**2)
# This function creates a rotation matrix based on the given parameters
diff_rotation_matrix = rotation_matrix(roll, pitch, yaw)
# THIS IS THE LINE THAT I SUSPECT:
diff_quaternion_rotation = Quaternion(axis=[rotation_x_speed, rotation_y_speed, rotation_z_speed], radians=total_rotation)
new_rotation_matrix = diff_quaternion_rotation.dot(last_rotation_matrix)
new_quaternion_rotation = diff_quaternion_rotation * last_rotation_matrix
The line that I suspect is the line initializing the diff_quaternion_rotation variable.
total_rotation = np.sqrt(roll**2 + pitch**2 + yaw**2)
This is wrong - Euler angles cannot be added in this way. Neither is your axis calculation correct.
Instead there is an explicit algorithm for converting Euler angles to quaternions:
(If your custom library doesn't have this function):
cy, sy = cos(yaw * 0.5), sin(yaw * 0.5)
cr, sr = cos(roll * 0.5), sin(roll * 0.5)
cp, sp = cos(pitch * 0.5), sin(pitch * 0.5)
diff_quaternion_rotation = Quaternion(w = cy * cr * cp + sy * sr * sp,
x = cy * sr * cp - sy * cr * sp,
y = cy * cr * sp + sy * sr * cp,
z = sy * cr * cp - cy * sr * sp)

Calculating distance between two points using latitude longitude and altitude (elevation)

I'm trying to calculate distance between two points, using latitude longitude and altitude (elevation).
I was using euklides formula in order to get my distance:
D=√((Long1-Long2)²+(Lat1-Lat2)²+(Alt1-Alt2)²)
My points are geographical coordinates and ofcourse altitude is my height above the sea.
I only have lat and lng, I'm using GOOGLE API Elevation to get my altitude.
I'm developing an application which calculates my traveled distance (on my skis). Every application which I have used, gets distance traveled with included altitude. Like #Endomondo or #Garmin I cannot get my distance in 2D space because true distances are going to vary from the ones I've returned.
Which formula would be the best to calculate my distance ? Ofcourse with included altitude.
I'm writing my app in Python, with PostGis.
You can calculate distance between flat coordinates in, say, meters by using geopy package or Vincenty's formula, pasting coordinates directly. Suppose the result is d meters. Then the total distance travelled is sqrt(d**2 + h**2) where h is the change in elevation in meters.
EDIT 2019: Since this answer, I composed a Q&A style example to answer similar questions (including this one as an example): How to calculate 3D distance (including altitude) between two points in GeoDjango.
In sort:
We need to calculate the 2D great-circle distance between 2 points using either the Haversine formula or the Vicenty formula and then we can combine it with the difference (delta) in altitude between the 2 points to calculate the Euclidean distance between them as follows:
dist = sqrt(great_circle((lat_1, lon_1), (lat_2, lon_2)).m**2, (alt_1 - alt_2)**2)
The solution assumes that the altitude is in meters and thus converts the great_circle's result into meters as well.
You can get the correct calculation by translating your coordinates from Polar (long, lat, alt) to Cartesian (x, y, z):
Let:
polar_point_1 = (long_1, lat_1, alt_1)
and polar_point_2 = (long_2, lat_2, alt_2)
Translate each point to it's Cartesian equivalent by utilizing this formula:
x = alt * cos(lat) * sin(long)
y = alt * sin(lat)
z = alt * cos(lat) * cos(long)
and you will have p_1 = (x_1, y_1, z_1) and p_2 = (x_2, y_2, z_2) points respectively.
Finally use the Euclidean formula:
dist = sqrt((x_2-x_1)**2 + (y_2-y_1)**2 + (z_2-z_1)**2)
I used the solution provided by John Moutafis but I didn't get a right answer.The formula needs some corrections. You will get the conversion of coordinates from Polar to Cartesian (x, y, z) at http://electron9.phys.utk.edu/vectors/3dcoordinates.htm.
Use the above formula to convert spherical coordinates(Polar) to Cartesian and calculate Euclidean distance.
I used the following c# in a console app.
Considering following dummy lat long
double lat_1 = 18.457793 * (Math.PI / 180);
double lon_1 = 73.3951930277778 *(Math.PI/180);
double alt_1 = 270.146;
double lat_2 = 18.4581253333333 * (Math.PI / 180);
double lon_2 = 73.3963755277778 * (Math.PI / 180);
double alt_2 = 317.473;
const Double r = 6376.5 *1000; // Radius of Earth in metres
double x_1 = r * Math.Sin(lon_1) * Math.Cos(lat_1);
double y_1 = r * Math.Sin(lon_1) * Math.Sin(lat_1);
double z_1 = r * Math.Cos(lon_1);
double x_2 = r * Math.Sin(lon_2) * Math.Cos(lat_2);
double y_2 = r * Math.Sin(lon_2) * Math.Sin(lat_2);
double z_2 = r * Math.Cos(lon_2);
double dist = Math.Sqrt((x_2 - x_1) * (x_2 - x_1) + (y_2 - y_1) *
(y_2 - y_1) + (z_2 - z_1) * (z_2 - z_1));

Categories

Resources