this is my first post on here.
So I am trying to make a model solar system using visual python. I am defining each planet as a sphere, with radius, distance from sun, mass, and momentum variables. Then each planet (or body) is put into a list structure. As you can see currently I have a list with [earth, moon, mars] the sun is excluded for reason i will explain shortly.
So my problem comes when I'm trying to calculate the force caused by each body on each other body. What I am doing here is, for every ith value in the bodies list, the force between that and the nth body is calculated, the force on the ith body in the list bodies is the sum of the force between the ith body and each nth body from 0 to the end of the list. (i.e. the sum of all the forces due to every other body in the list)
This works correctly for the moon and mars (2nd and 3rd items in the list) but not for the earth. The output for the code below is,
<3.57799e+022, 0, 0>
<4.3606e+020, 0, 0>
<1.64681e+021, 0, 0>
<-1.#IND, -1.#IND, -1.#IND> - this is the total force on earth.
<0, 2.07621e+027, 0>
<0, 9.83372e+027, 0>
from visual import *
AU = 149.6e9
MU = 384.4e6 # moon - earth orbital - radius
MarU = 227.92e9
G =6.673e-11
sun_mass =2e30
sun_radius =6.96e8
earth_mass =6e24
earth_radius =6.37e6
moon_mass =7.35e22
moon_radius =1.74e6
mars_mass = 6.41e23
mars_radius = 3390000
sun = sphere ( pos =(0 , 0 ,0) , velocity = vector (0 ,0 ,0) ,mass = sun_mass , radius =0.1* AU , color = color . yellow )
earth = sphere ( pos =( AU , 0 ,0) ,mass = earth_mass , radius =63170000, color = color . cyan ,make_trail=True )# create a list of gravitating objects
moon = sphere ( pos =( AU+MU , 0 ,0) ,mass = moon_mass , radius =17380000 , color = color . white, make_trail=True )
mars = sphere ( pos =( MarU , 0 ,0) ,mass = mars_mass , radius = mars_radius , color = color . red, make_trail=True )
#initialise values:
we = 1.9578877e-7
wm = sqrt(G*earth.mass/3.38e8**3)
wma = 9.617e-5
dt = 3*60
earth.mom = vector(0,1.5e11*earth.mass*we,0)
mars.mom = vector(0, 9.833720638948e+27,0)
moon.mom = moon.mass*(earth.mom/earth.mass+vector(0,-3.48e8*wm,0))
bodies = [earth, moon, mars]
*N = 0
initialdiff = 0
for i in bodies:
initialdiff = i.pos - sun.pos
TotalForce = (G * i. mass * sun. mass * norm ( initialdiff )/ initialdiff . mag2)
print TotalForce
while N < len(bodies):
if N!=i:
diff = i.pos - bodies[N].pos
Force = (G * i. mass * bodies[N]. mass * norm ( diff )/ diff . mag2)
TotalForce = TotalForce + Force
i.mom = i.mom+TotalForce*dt
N = N+1
else:
N = N+1
print earth.mom
print moon.mom
print mars.mom*
Thanks for any help you can give.
'''Abel Tilahun HW 3 '''
# 02/03 / 2015
# make the necessary imports , visual import gives visual output, cos, sin and pi allows calculation of the positions
# in real time. The numpy arange import returns evenly spaced values within a given interval (angles)
from visual import *
from math import cos,sin,pi
from numpy import arange
# i used 'a' to magnify the sizes of the planet for better visualization
a=600
# the following line defines a sphere for the sun centered at the origin(0,0,0) all the other planets are positioned at a radial distance from this center.
Sun=sphere(pos=(0,0,0),radius=6955500*(a/100),color=color.yellow)
#the next 5 commands code the planets with their center being positioned at a distance of the radii of their orbit. Their radii are multiplied by a factor of 'a' to magnify them
Mercury=sphere(pos=vector(579e5,0,0),radius=2440*(a),color=color.red)
Venus=sphere(pos=vector(1082e5,0,0),radius=6052*a,color=color.orange)
Earth=sphere(pos=vector(1496e5,0,0),radius=6371*a,color=color.green)
Mars=sphere(pos=vector(2279e5,0,0),radius=3386*a,color=color.white)
Jupiter=sphere(pos=vector(7785e5,0,0),radius=69173*(a),color=color.cyan)
Saturn=sphere(pos=[14334e5,0,0],radius=57316*(a),color=color.magenta)
# the for loop calculates position of the planets by changing
# the arange function increases the angle from 0 to pi with a small increment of 0.025 each time
for theta in arange(0,100*pi,0.025):
rate(30)
x = 579e5*cos(theta)
y = 579e5*sin(theta)
Mercury.pos = [x,y,0]
x = 1082e5*cos(theta)
y = 1082e5*sin(theta)
Venus.pos = [x,y,0]
x = 1496e5*cos(theta)
y = 1496e5*sin(theta)
Earth.pos = [x,y,0]
x = 2279e5*cos(theta)
y = 2279e5*sin(theta)
Mars.pos = [x,y,0]
x = 7785e5*cos(theta)
y = 7785e5*sin(theta)
Jupiter.pos = [x,y,0]
x = 14334e5*cos(theta)
y = 14334e5*sin(theta)
Saturn.pos = [x,y,0]
Related
I would like to check if two polygons built from coordinates touch or overlap. The following example should touch (they came from a simulation), however, the code doesn't detect they touch, hence, crash. Any clue?
import geopy
import geopy.distance
import math
from shapely import geometry
def bearing_calc(a_lat, a_lon, b_lat, b_lon): # a previous position b current position
# print(a)
# a.type
rlat1 = math.radians(a_lat)
# print(a_lat)
rlon1 = math.radians(a_lon)
rlat2 = math.radians(b_lat)
rlon2 = math.radians(b_lon)
dlon = math.radians(b_lon - a_lon)
b = math.atan2(math.sin(dlon) * math.cos(rlat2),
math.cos(rlat1) * math.sin(rlat2) - math.sin(rlat1) * math.cos(rlat2) * math.cos(
dlon)) # bearing calc
bd = math.degrees(b)
br, bn = divmod(bd + 360, 360) # the bearing remainder and final bearing
return bn
# To get a rotated rectangle at a bearing, you need to get the points of the the recatangle at that bearing
def get_rotated_points(coordinates, bearing, width, length):
start = geopy.Point(coordinates)
width = width / 1000
length = length / 1000
rectlength = geopy.distance.distance(kilometers=length)
rectwidth = geopy.distance.distance(kilometers=width)
halfwidth = geopy.distance.distance(kilometers=width / 2)
halflength = geopy.distance.distance(kilometers=length / 2)
pointAB = halflength.destination(point=start, bearing=bearing)
pointA = halfwidth.destination(point=pointAB, bearing=0 - bearing)
pointB = rectwidth.destination(point=pointA, bearing=180 - bearing)
pointC = rectlength.destination(point=pointB, bearing=bearing - 180)
pointD = rectwidth.destination(point=pointC, bearing=0 - bearing)
points = []
for point in [pointA, pointB, pointC, pointD]:
coords = (point.latitude, point.longitude)
points.append(coords)
return points
v1_id = 176
v1_coord_5 = [41.39129840757358, 2.1658667401858738]
v1_coord_4 = [41.391335226146495, 2.1658363842310404]
v1_length = 5 #meters
v1_width = 1.8 #meters
v1_bearing = bearing_calc(v1_coord_4[0], v1_coord_4[1],
v1_coord_5[0], v1_coord_5[1])
v1_points = get_rotated_points(tuple(v1_coord_5), v1_bearing,
v1_width, v1_length)
polygon1 = geometry.Polygon(v1_points)
v2_id = 210
v2_coord_5 = [41.39134056977012, 2.1658847515529804]
v2_coord_4 = [41.39127485461452, 2.165851515431892]
v2_length = 5 #meters
v2_width = 1.8 #meters
v2_bearing = bearing_calc(v2_coord_4[0], v2_coord_4[1],
v2_coord_5[0], v2_coord_5[1])
v2_points = get_rotated_points(tuple(v2_coord_5), v2_bearing,
v2_width, v2_length)
polygon2 = geometry.Polygon(v2_points)
if polygon1.intersection(polygon2).area > 0.0:
print("COLISION")
else:
print("NO COLISION")
I've also tried the function touches
polygon1.touches(polygon2)
Unfortunately, also, false. I don't know if there is a function to check if the two borders touch even in one points, minimally.
If I plot the two polygons, they do not collide, as shown above, however, they should. Is it because the code is not considering the earth surface curvature? How can I adapt the code to consider that factor.
I am trying to write a code for the orbit of the earth in SI using a symplectic integrator, my attempt is as follows:
import numpy as np
import matplotlib.pyplot as plt
#Set parameters
G = 6.67348e-11
mEar = 5.972e24
mSun = 1.989e30
def earth_orbit(x0, y0, vx0, vy0, N):
dt = 1/N #timestep
pos_arr = np.zeros((N,2)) #empty array to store position
vel_arr = np.zeros((N,2)) #empty array to store velocities
#Initial conditions
# x0 = x
# y0 = y
# vx0 = vx
# vy0 = vy
pos_arr[0] = (x0,y0) #set the intial positions in the array
vel_arr[0] = (vx0,vy0) #set the initial velocities in the array
#Implement Verlet Algorithm
for k in range (N-1):
pos_arr[k+1] = pos_arr[k] + vel_arr[k]*dt #update positions
force = -G * mSun * mEar * pos_arr[k+1] / (np.linalg.norm(pos_arr[k+1])**3) #force calculation
vel_arr[k+1] = vel_arr[k] + (force/mEar) * dt #update velocities
#Plot:
plt.plot(pos_arr, 'go', markersize = 1, label = 'Earth trajectory')
# plt.plot(0,0,'yo', label = 'Sun positon') # yellow marker
# plt.plot(pos_arr[0],'bo', label = 'Earth initial positon') # dark blue marker
plt.axis('equal')
plt.xlabel ('x')
plt.ylabel ('y')
return pos_arr, vel_arr
earth_orbit(149.59787e9, 0, 0, 29800, 1000)
The output is 2 dots and I can't figure out if this is a unit issue or a calculation issue?
Display the trajectory
pos_arr contains the x and y coordinates in its columns. To display the whole trajectory, plt.plot(pos_arr[:,0], pos_arr[:,1]) can thus be used. I would prefer to use plt.plot(*pos_arr.T) as a shorter alternative. The line that displays the trajectory must be replaced by:
plt.plot(*pos_arr.T, 'g', label = 'Earth trajectory')
Change the timestep
Here the timestep (in second) is chosen as 1/N, where N is the number of iterations. So, the total duration of the simulation is equal to timestep * N = 1 second ! For N=1000, you can instead try with timestep = 3600*12 (half-day), so that the total duration is a little less than 1.5 years. I suggest passing the duration as a parameter of the function earth_orbit and then setting timestep as duration / N.
def earth_orbit(x0, y0, vx0, vy0, N=1000, duration=3.15e7):
dt = duration / N
...
As said in the comments, this is not the Verlet algorithm, but the symplectic Euler algorithm. The difference is in the initialization, but in comparing against a more exact reference solution and with several step sizes, the difference in the orders, 2 vs. 1, will be quite visible.
A short change to the time loop ensuring that the velocities are at the half-time steps as required for Leapfrog Verlet could look like this:
def force(pos): return -G * mSun * mEar * pos_arr[k+1] / (np.linalg.norm(pos_arr[k+1])**3) #force calculation
pos_arr[0] = (x0,y0) #set the intial positions in the array
vel_arr[0] = (vx0,vy0) #set the initial velocities in the array
vel_arr[0] += (force(pos_arr[0])/mEar) * (0.5*dt) #correct for velocity at half-time
#Implement Verlet Algorithm
for k in range (N-1):
pos_arr[k+1] = pos_arr[k] + vel_arr[k] * dt #update positions
vel_arr[k+1] = vel_arr[k] + (force(pos_arr[k+1])/mEar) * dt #update velocities
I try to understand this code and I actually understood the whole thing except these 2 line:
f_grav = gravity * sun.mass * earth.mass * (sun.pos - earth.pos).norm() / (sun.pos - earth.pos).mag2
earth.vel = earth.vel + (f_grav/earth.mass) * dt
Why couldn't it just be: f_grav = gravity * sun.mass * earth.mass / (sun.pos-earth.pos)**2
I also dont get the role of .norm() and .mag2
Here is the whole code snippet of the program(GlowScript):
sunRadius = 10 * realSunRadius # the size for our solar system
earthRadius = sunRadius * 0.25 # note: real value would be sunRadius * 0.01, a good choice for sim is * 0.25
astronomicalUnit = 212.7 * realSunRadius # the distance from Sun to Earth - the Sun is about 100 Sun diameters away
gravity = 6.6e-11 # sets the strength of the gravitational constant to 6.6x10-11 Newton x meters squared per kilograms squared
# create the Sun object
sun = sphere( radius = sunRadius, opacity = 0.7, emissive = True, texture = "http://i.imgur.com/yoEzbtg.jpg" )
sun.mass = 2e30 # mass of the Sun in kilograms is 2,000,000,000,000,000,000,000,000,000,000 kg
sun.pos = vec(0,0,0)
sun.vel = vec(0,0,0)
# place a few sources of light at the same position as the Sun to illuminate the Earth and Moon objects
sunlight = local_light( pos = vec(0,0,0), color=color.white )
more_sunlight = local_light( pos = vec(0,0,0), color=color.white ) # I found adding two lights was about right
# create the Earth object
earth = sphere ( radius = earthRadius, texture = "http://i.imgur.com/rhFu01b.jpg",make_trail=True)
earth.mass = 6e24 # mass of Earth in kilograms
earth.pos = vec(astronomicalUnit, 0, 0)
earth.vel = vec(0,0,-30000) # the Earth is moving around 30000 m/s
dt = 10000
# below is the main loop of the program - everything above is "setup" and now we are in the main "loop" where all the action occurs
while (True): # this will make it loop forever
rate(100) # this limits the animation rate so that it won't depend on computer/browser processor speed
# calculate the force of gravity on each object
f_grav = gravity * sun.mass * earth.mass * (sun.pos - earth.pos).norm() / (sun.pos - earth.pos).mag2
earth.vel = earth.vel + (f_grav/earth.mass) * dt
# update the position of the Earth and Moon by using simple circle trigonometry
earth.pos = earth.pos + earth.vel * dt
(sun.pos-earth.pos) is a vector. I don't think you can do (sun.pos-earth.pos)**2 because you can't square a vector. Unless you're trying to do a dot product of the vector with itself? But the result of a dot product is a scalar, so f_grav would be a scalar. Forces are vectors, so it doesn't make sense to use a dot product there.
In comparison, f_grav = gravity * sun.mass * earth.mass * (sun.pos - earth.pos).norm() / (sun.pos - earth.pos).mag2 makes sense because you're multiplying (sun.pos - earth.pos).norm(), a vector, by three scalars, and dividing by one scalar. So the result is a vector as desired.
.norm() returns a unit vector so that the result is a vector not a scalar. This is the vector form of Newtonian gravity. (See Wikipedia)
.mag2 does the same thing as what is expected from **2, however in general, powers of vectors are not defined, so it wouldn't make sense to define the exponentiation operator on the vector class.
I am trying to plot a curved path for a robot to follow using the following as a guide: http://rossum.sourceforge.net/papers/CalculationsForRobotics/CirclePath.htm
The code i have does not create a path that ends at the destination. I am expecting the path to curve left or right depending on the quadrant the destination is in (+x+y,+x-y,-x+y,-x-y)
import math
start = [400,500]
dest = [200,300]
speed = 10
startangle = 0
rc =0
rotv =0
rads =0
def getPos(t):
ang = (rotv*t)+rads
x = start[0] - rc * math.sin(rads) + rc * math.sin(rotv*(t)+rads)
y = start[1] + rc * math.cos(rads) - rc * math.cos(rotv*(t)+rads)
return (int(x),int(y), ang)
dx = dest[0] - start[0]
dy = dest[1] - start[1]
rads = math.atan2(-dy,dx)
rads %= 2*math.pi
distance = (dx**2 + dy**2)**.5 #rg
bangle = 2*rads
rc = distance /(2 * math.sin(rads))
if rads > (math.pi/2):
bangle = 2*(rads-math.pi)
rc= -rc
if rads < -(math.pi/2):
bangle = 2*(rads+math.pi)
rc= -rc
pathlength = rc * bangle
xc = start[0] - rc * math.sin(rads)
yc = start[1] + rc * math.cos(rads)
rotcenter = [xc,yc]
traveltime = pathlength/speed
rotv = bangle/traveltime
for p in range(int(traveltime)):
pos = getPos(p)
Start: Blue, End: Red, Rotation Point: Purple
UPDATE:
I have added code to allow positive and negative x/y values. I have updated the image.
To answer your question I first read through the article you linked. I think it is very interesting and explains the ideas behind the formulas pretty well, althought it lacks the formulas for when the starting position is not at the origin and when the starting angle is not 0.
It took a little while to come up with these formulas, but now it works for every case I could think of. To be able to use the formulas given in the linked article, I used the names of the variables given there. Notice that I also used the notation with t_0 as the starting time, which you just ignored. You can easily remove any instance of t_0 or set t_0 = 0.
The last part of the following code is used for testing and creates a little red turtle that traces the path of the computed arc in the specified direction. The black turtle indicates the goal position. Both turtles are close to each other at the end of the animation, but they are not directly above each other, because I am only iterating over integers and it is possible that t_1 is not an integer.
from math import pi, hypot, sin, cos, atan2, degrees
def norm_angle(a):
# Normalize the angle to be between -pi and pi
return (a+pi)%(2*pi) - pi
# Given values
# named just like in http://rossum.sourceforge.net/papers/CalculationsForRobotics/CirclePath.htm
x_0, y_0 = [400,500] # initial position of robot
theta_0 = -pi/2 # initial orientation of robot
s = 10 # speed of robot
x_1, y_1 = [200,300] # goal position of robot
t_0 = 0 # starting time
# To be computed:
r_G = hypot(x_1 - x_0, y_1 - y_0) # relative polar coordinates of the goal
phi_G = atan2(y_1 - y_0, x_1 - x_0)
phi = 2*norm_angle(phi_G - theta_0) # angle and
r_C = r_G/(2*sin(phi_G - theta_0)) # radius (sometimes negative) of the arc
L = r_C*phi # length of the arc
if phi > pi:
phi -= 2*pi
L = -r_C*phi
elif phi < -pi:
phi += 2*pi
L = -r_C*phi
t_1 = L/s + t_0 # time at which the robot finishes the arc
omega = phi/(t_1 - t_0) # angular velocity
x_C = x_0 - r_C*sin(theta_0) # center of rotation
y_C = y_0 + r_C*cos(theta_0)
def position(t):
x = x_C + r_C*sin(omega*(t - t_0) + theta_0)
y = y_C - r_C*cos(omega*(t - t_0) + theta_0)
return x, y
def orientation(t):
return omega*(t - t_0) + theta_0
#--------------------------------------------
# Just used for debugging
#--------------------------------------------
import turtle
screen = turtle.Screen()
screen.setup(600, 600)
screen.setworldcoordinates(0, 0, 600, 600)
turtle.hideturtle()
turtle.shape("turtle")
turtle.penup()
turtle.goto(x_1, y_1)
turtle.setheading(degrees(orientation(t_1)))
turtle.stamp()
turtle.goto(x_0, y_0)
turtle.color("red")
turtle.showturtle()
turtle.pendown()
for t in range(t_0, int(t_1)+1):
turtle.goto(*position(t))
turtle.setheading(degrees(orientation(t)))
I am not sure at which point your code failed, but I hope this works for you. If you intend to use this snippet multiple times in you code consider encapsulating it in a function that takes in the given values as parameters and returns the position function (and if you like rotation function as well).
I have this triangle in pygame
triangle = pygame.draw.polygon(window, (210,180,140), [[x, y], [x -10, y -10], [x + 10, y - 10]], 5)
that i need to rotate towards the mouse, very much like the center arrow in this gif: http://i.stack.imgur.com/yxsV1.gif. Pygame doesn't have a built in function for rotating polygons, so I'll need to manually move the three points in a circle, with the lowermost point [x,y] pointing towards the coords of the mouse. The variables I have are:
the distance between the center of the triangle and the circle i want it to rotate along (i.e. the radius)
the distance from the center to the mouse coordinates
the coordinates of the lowermost point of the triangle [x,y] and the other two sides
with this information, how can I use trigonometry to rotate all three sides of the triangle so that the bottom point allways faces the mouse position?
EDIT: this is what I've got so far, but it only manages to move the triangle back and forth along a diagonal instead of rotating.
def draw(self):
curx,cury = cur
#cur is a global var that is mouse coords
angle = math.atan2(self.x - curx, self.y - cury)
distance = math.sqrt(200 - (200 * math.cos(angle)))
x = self.x + distance
y = self.y + distance
triangle = pygame.draw.polygon(window, (210,180,140), [[x, y], [x - 10,y - 10], [x + 10,y - 10]], 5)
Edit: Thinking about this again this morning there's another way to do this since the polygon is a triangle. Also the math is potentially easier to understand, and it requires less calculation for each point.
Let Cx and Cy be the center of the circle inscribing the triangle. We can describe the equation of a circle using the parametric equation:
F(t) = { x = Cx + r * cos(t)
{ y = Cy + r * sin(t)
Where r is the radius of the circle, and t represents the angle along the circle.
Using this equation we can describe the triangle using the points that touch the circle, in this case we'll use t = { 0, 3 * pi / 4, 5 * pi / 4 } as our points.
We also need to calculate the angle that we need to rotate the triangle so that the point that was at t = (0) is on a line from (Cx, Cy) to the mouse location. The angle between two (normalized) vectors can be calculated by:
t = acos(v1 . v2) = acos(<x1, y1> . <x2, y2>) = acos(x1 * x2 + y1 * y2)
where . represents the dot product, and acos is the inverse cosine (arccos or cos^-1).
From these two equations we can easily create a python function which, given the center of the triangle/circle, the radius of the circle, and the location of the mouse, returns a list of tuples representing the x-y coordinates of the triangle. (For the example the center and mouse position are tuples of the form (x, y))
def get_points(center, radius, mouse_position):
# calculate the normalized vector pointing from center to mouse_position
length = math.hypot(mouse_position[0] - center[0], mouse_position[1] - center[1])
# (note we only need the x component since y falls
# out of the dot product, so we won't bother to calculate y)
angle_vector_x = (mouse_position[0] - center[0]) / length
# calculate the angle between that vector and the x axis vector (aka <1,0> or i)
angle = math.acos(angle_vector_x)
# list of un-rotated point locations
triangle = [0, (3 * math.pi / 4), (5 * math.pi / 4)]
result = list()
for t in triangle:
# apply the circle formula
x = center[0] + radius * math.cos(t + angle)
y = center[1] + radius * math.sin(t + angle)
result.append((x, y))
return result
Calling this function like this:
from pprint import pprint
center = (0,0)
radius = 10
mouse_position = (50, 50)
points = get_points(center, radius, mouse_position)
pprint(points)
produces:
[(7.071067811865475, 7.0710678118654755),
(-10.0, 1.2246467991473533e-15),
(-1.8369701987210296e-15, -10.0)]
which is the three points (x, y) of the triangle.
I'm going to leave the original method below, since it's the way that modern computer graphics systems (OpenGL, DirectX, etc.) do it.
Rotation about the centroid of a arbitrary polygon is a sequence of three distinct matrix operations, Translating the object so that the centroid is at the origin (0,0), applying a rotation, and translating back to the original position.
Calculating the centroid for an arbitrary n-gon is probably outside the scope of an answer here, (Google will reveal many options), but it could be done completely by hand using graph paper. Call that point C.
To simplify operations, and to enable all transformations to be applied using simple matrix multiplications, we use so called Homogeneous coordinates, which are of the form:
[ x ]
p = | y |
[ 1 ]
for 2d coordinates.
Let
[ Cx ]
C = | Cy |
[ 1 ]
The general form of the translation matrix is:
[ 1 0 Vx ]
T = | 0 1 Vy |
[ 0 0 1 ]
Where <Vx, Vy> represents the translation vector. Since the goal of the translation is to move the centroid C to the origin, Vx = -Cx and Vy = -Cy. The inverse translation T' is simply Vx = Cx, Vy = Cy
Next the rotation matrix is needed. Let r be the desired clockwise rotation angle, and R be the general form of the rotation matrix. Then,
[ cos(r) sin(r) 0 ]
R = | -sin(r) cos(r) 0 |
[ 0 0 1 ]
The final transformation matrix is therefore:
[ 1 0 -Cx ] [ cos(r) sin(r) 0 ] [ 1 0 Cx ]
TRT' = | 0 1 -Cy | * | -sin(r) cos(r) 0 | * | 0 1 Cy |
[ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ]
Which simplifies to:
[ cos(r) sin(r) cos(r)*Cx-Cx+Cy*sin(r) ]
|-sin(r) cos(r) cos(r)*Cy-Cy-Cx*sin(r) |
[ 0 0 1 ]
Applying this to a point p = (x,y) we obtain the following equation:
p' = { x' = Cx*cos(r)-Cx+Cy*sin(r)+x*cos(r)+y*sin(r)
{ y' = -Cx*sin(r)+Cy*cos(r)-Cy-x*sin(r)+y*cos(r)
In Python:
def RotatePoint(c, p, r):
x = c[0]*math.cos(r)-c[0]+c[1]*math.sin(r)+p[0]*math.cos(r)+p[1]*math.sin(r)
y = -c[0]*math.sin(r)+c[1]*math.cos(r)-c[1]-p[0]*math.sin(r)+p[1]*math.cos(r)
return (x, y)
After typing all that I realize that your object may already be centered on the origin, in which case the function above simplifies to x=p[0]*math.cos(r)+p[1]*math.sin(r) and y=p[0]*math.sin(r)+p[1]*math.cos(r)
I put some faith in Wolfram Alpha here, rather than multiplying everything out by hand. If anyone notices any issues, feel free to make the edit.