I wrote a program that computes and animates the orbit of pluto, and have begun rewriting it using classes because this seems like a sensible way of introducing more planets into the simulation. i.e have a class that defines the physics, and then feed in specific planet data to get the orbital data.
class Planet(object):
m_sun = 1.989*(10**30)
G = 6.67*(10**-11)
dt = 1
coords = []
def __init__(self, x, y, vx, vy, m):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.m = m
def genData(self):
while self.dt < 100000000:
r = ((self.x)**2 + (self.y)**2)**0.5
a = ((self.G*self.m_sun)/r**2)
ax = -a*((self.x)/r)
ay = -a*((self.y)/r)
self.vx = self.vx + ax*self.dt
self.vy = self.vy + ay*self.dt
self.x = self.x + self.vx*self.dt
self.y = self.y + self.vy*self.dt
coord = (self.x, self.y)
print coord
self.coords.append(coord)
self.dt = self.dt + 1000
pluto = Planet(4495978707000, 0, 0, 4670, 1.305*(10**22))
pluto.genData()
I'm sure it isn't perfect, but it appears to be working (this is the first class i've built on my own). My question is how do I extract the data from 'coords' into a list that I can work with outside of the class.
I want to generate data for each planet, and then use this data to create an animation in Pygame. For example, a list of (x,y) coordinates for pluto, earth, saturn etc. As it stands, it churns out the data, but it doesn't appear to be accessible from outside the class.
I hope my question makes sense.
Thanks!
Your question's been answered, but I have some info you should find useful to improve your program. Technically, this info belongs in a comment (since you didn't actually ask about this in your question), but it wouldn't fit. :)
Pluto's orbit is inclined quite a bit to the ecliptic, so if you want to plot it along with several other planets in the Solar System you need to work in 3D to be accurate. But I guess that's not a big deal for your application.
Earth's orbit is tiny compared to Pluto, so you'll probably need to implement zooming to see them both on one animation.
You can get more accuracy in your calculations by using the Standard gravitational parameter rather than using separate values of G and mass.
Your algorithm for calculating velocity and position is called Euler integration. It's equivalent to approximating the curve of the orbit by a polygon. It works, but it's not very accurate. So you need to make the time delta very small otherwise the orbit won't be very realistic and it may not even be a closed curve. And even that doesn't help a lot because the error is accumulative, so eventually the computed orbit will bear little resemblance to reality.
No technique of numerical integration is perfect (except on very simple functions), but a popular family of integration techniques that are more accurate (and hence work ok with a much larger time step) are the Runge-Kutta methods. You can find lots of example code of orbit calculation using a Runge-Kutta method; most code examples use the variant known as RK4.
However, I strongly urge you to try Leapfrog integration. It's quite easy to code the synchronized form of Leapfrog and it has a major benefit over Runge-Kutta in that it's symplectic, which (roughly) means that it conserves energy, so the error won't accumulate from orbit to orbit.
Did you try pluto.coords?
You can access members of a class from outside by using the instance followed by dot followed by the member name, i.e. attribute access. This is just as you have done when calling the genData() method.
BTW, you can define your constants using exponential notation:
m_sun = 1.989e+30
G = 6.67e-11
and
pluto = Planet(4495978707000, 0, 0, 4670, 1.305e+22)
which is more readable (important) and saves a few calculations for the definition of your class (less/not important).
Instead of storing the values in self.coords, yield the values:
def genData(self):
while self.dt < 100000000:
...
self.x = self.x + self.vx*self.dt
self.y = self.y + self.vy*self.dt
coord = (self.x, self.y)
yield coord
self.dt = self.dt + 1000
pluto = Planet(4495978707000, 0, 0, 4670, 1.305*(10**22))
for coord in pluto.genData():
print(coord)
Note that this makes genData a generator function.
To obtain a list of coords, you can accumulate the values in the loop:
coords = []
for coord in pluto.genData():
coords.append(coord)
or use coords = list(pluto.genData()).
By the way, it's usually a good policy to separate code-that-calculates from code-that-prints. That way you can call code-that-calculates many times, or include it in a chain of calculations without always emitting print statements.
I want to generate data for each planet, and then use this data to create an animation in Pygame.
It sounds like you don't need to accumulate the data. You can plot the current point for each planet without needing to know the planet's coordinate history. In that case, using generator functions would be more memory-efficient since they would allow you to generate the next coordinate for each planet without having to store all the coordinates in a list first:
# In Python2
import itertools as IT
for coords in IT.izip(*[planet.genData() for planet in [pluto, neptune, uranus, ...]]):
# plot coords
or
# In Python3
for coords in zip(*[planet.genData() for planet in [pluto, neptune, uranus, ...]]):
# plot coords
Actually it's very easy, just pluto.coords:
pluto = Planet(4495978707000, 0, 0, 4670, 1.305*(10**22))
pluto.genData()
print pluto.coords
Related
I've been trying to make a code with pygame to simulate simple gravity. At the moment, there is only one object (HOM) which is orbiting the sun. However, for reasons unknown to me, whenever I run the code, HOM travels round the sun in an orbit at the start, but then accelerates away from the sun when it reaches ~135 degrees from vertical.
Does anyone know why this is happening and how I can fix it? I have been printing some variables to try and source the problem, but have had no luck so far.
Code:
import pygame,sys,time
from math import *
screen=pygame.display.set_mode((800,600))
G = 5
class Object: #Just an object, like a moon or planet
def __init__(self,mass,init_cds,init_vel,orbit_obj='Sun'):
self.mass = mass
self.cds = init_cds
self.velocity = init_vel
self.accel = [0,0]
self.angle = 0
self.orb_obj = orbit_obj
def display(self):
int_cds = (round(self.cds[0]),round(self.cds[1]))#Stores its co-ordinates as floats, has to convert to integers for draw function
pygame.draw.circle(screen,(255,0,0),int_cds,10)
def calc_gravity(self):
if self.orb_obj == 'Sun':
c_x,c_y = 400,300
c_mass = 10000
else:
c_x,c_y = self.orb_obj.cds
c_mass = self.orb_obj.mass
d_x = self.cds[0]-c_x
d_y = self.cds[1]-c_y
dist = sqrt(d_x**2+d_y**2) #Find direct distance
angle = atan(d_x/d_y) #Find angle
print(d_x,d_y)
print(dist,degrees(angle))
if dist == 0:
acc = 0
else:
acc = G*c_mass/(dist**2) #F=G(Mm)/r^2, a=F/m -> a=GM/r^2
print(acc)
acc_x = acc*sin(angle) #Convert acceleration from magnitude+angle -> x and y components
acc_y = acc*cos(angle)
self.accel = [acc_x,acc_y]
print(self.accel)
self.velocity = [self.velocity[0]+self.accel[0],self.velocity[1]+self.accel[1]] #Add acceleration to velocity
print(self.velocity)
self.cds = (self.cds[0]+self.velocity[0],self.cds[1]+self.velocity[1]) #Change co-ordinates by velocity
print(self.cds)
print('-------------------') #For seperating each run of the function when printing variables
HOM = Object(1000000,(400,100),[10,0]) #The problem planet
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
screen.fill((0,0,0))
pygame.draw.circle(screen,(255,255,0),(400,300),25)
HOM.display()
HOM.calc_gravity()
clock.tick(30)
pygame.display.flip()
Your main issue has to do with this line:
angle = atan(d_x/d_y) #Find angle
The atan function is very limited in its ability to compute angles because it can't tell the signs of the coordinates you combined in your division. For instance, it will give the same result for atan(1/1) and atan(-1/-1), since both divisions compute the same slope (1).
Instead you should use atan2, and pass the coordinates separately. Since this will let the code see both coordinates, it can pick an angle in the right quadrant of the circle every time.
But there's an even better fix. Instead of computing an angle and then immediately converting it back to a unit vector (by calling sin and cos on it), why not compute the unit vector directly? You already have the original vector's length! Instead of:
acc_x = acc*sin(angle) #Convert acceleration from magnitude+angle -> x and y components
acc_y = acc*cos(angle)
Use:
acc_x = acc * d_x / distance
acc_y = acc * d_y / distance
The d_x / distance and d_y / distance values are the same as the sin and cos values you were getting before (for the angles when they were working correctly), but there's no need for the trigonometry. You can get rid of the line I quoted up top completely!
Note that you might need to reverse the way you're computing d_x and d_y, so that you get a vector that points from the orbiting object towards the object it's orbiting around (instead of pointing the other way, from the center of the orbit towards the orbiting object). I'm not sure if I'm reading your code correctly, but it looks to me like you have it the other way around right now. That means that you were actually getting the wrong results from atan in the cases where your current code was working the way you expected, and the bad behavior (flying off into nowhere) is the code working "correctly" (from a mathematical point of view). Alternatively, you could compute acc to be negative, rather than positive.
As several commenters mentioned, you may have other issues related to your choice of integration algorithm, but those errors are not going to be as large as the main issue with the acceleration angle. They'll crop up as you run your simulation over longer time periods (and try to use larger time steps to make the simulation go faster). Your current algorithm is good enough for an orbit or two, but if you're simulating dozens or hundreds of orbits, you'll start seeing errors accumulate and so you should pick a better integrator.
The plan for the use of this is in a text based Space RPG game. I want to generate random points (x,y coordinates) - which I have already done - followed by plotting them with connections shown on a 2d grid. As a side, how would I label these coordinates?
The code for the random generation is included below:
import time
import random
import math
radius = 200
rangeX = (0, 2500)
rangeY = (0, 2500)
qty = 40
deltas = set()
for x in range(-radius, radius+1):
for y in range(-radius, radius+1):
if x*x + y*y <= radius*radius:
deltas.add((x,y))
randPoints = []
excluded = set()
i = 0
while i<qty:
x = random.randrange(*rangeX)
y = random.randrange(*rangeY)
if (x,y) in excluded: continue
randPoints.append((x,y))
i += 1
excluded.update((x+dx, y+dy) for (dx,dy) in deltas)
time.sleep(0.001)
stars = {}
keyName = 1
for item in randPoints:
stars[str(keyName)] = [item, 0,]
keyName += 1
Instead of providing you with a direct answer I will point you in a general direction as the scope of this question seems rather big.
Consider using pygame; it provides you with drawing functions, has an event system that you can use to detect user input and has many other helpful features for creating games as the name suggests.
Strongly consider using classes; your code will become more readable and more flexible. Adding features to your stars such as a label, surface temperature, color of emitted (pulsing?) light, available raw materials.. etc. becomes much easier to handle.
To generate names you could put existing star names in a text file, load it and pick one at random. For added bonus points use a markov chain to generate seemingly random names.
https://www.pygame.org
https://docs.python.org/3/tutorial/classes.html
https://en.wikipedia.org/wiki/Markov_chain
NOTE: Because I am a new member and I apparently need 10 reputation to post images or more than 2 links, I will be referring to this imgur album for visuals. Sorry for any inconvenience.
I'm trying to make a program to visualize 3D objects in python, but I'm having a problem with the function that projects a 3D coordinate onto 2D screen coordinates. It works in some cases, but not all. When the camera is relatively far away from the point and about on the same level, the projection looks very nice, such as in this image depicting a decagon and rectangle, whose vertices are defined by 3D points (Figure 2). However, when the camera is looking down on the points, the object they depict is flattened disproportionately (Figure 3), and when the camera is close to the points it must project, the shape gets warped (Figure 4).
The function in question is Camera.projectPoint, and I have included all of the functions and classes that it uses in the following:
class Vector3:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
self.mag = math.sqrt(x**2+y**2+z**2)
self.yaw = math.atan2(self.y, self.x)
self.pitch = math.atan2(self.z, math.sqrt(self.x**2+self.y**2))
class Point:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def vectorTo(self, p):
return(Vector3(p.x-self.x, p.y-self.y, p.z-self.z))
class Camera:
def __init__(self, pos, yaw, pitch, FOV=math.pi/2):
self.pos = pos
self.yaw = yaw
self.pitch = pitch
self.FOV = FOV
def projectPoint(self, point):
## finding the vector from the camera position to the point
v = self.pos.vectorTo(point)
## setting alpha to the difference in yaw between the camera and the vector to the point
## and beta to the difference in pitch
alpha = v.yaw - self.yaw
beta = v.pitch - self.pitch
## making sure that the smallest angle between the two is chosen
## (difference between 300 degrees and 10 degrees is 70, not 290)
alpha = (alpha+math.pi)%(2*math.pi)-math.pi
beta = (beta+math.pi)%(2*math.pi)-math.pi
## Doing the operation pictured in the diagram
h = math.sin(self.FOV/2)/math.cos(self.FOV/2)
x1 = math.sin(alpha)/math.cos(alpha)
y1 = math.sin(beta)/math.cos(beta)
## adjusting for screen resolution
return(x1*1000/h+500, y1*1000/h+325)
I have looked around for algorithms to project a 3D coordinate onto the screen, and have found a lot of things such as what is depicted in Figure 1 (which is what my function is based off of), but it doesn't seem to be working very well. Is it a problem with the algorithm? The way I implimented it? One of the functions it uses? (I'm like 99% sure all of the other functions and classes are perfectly fine). Any ideas on what's wrong and how to fix it? Thank you.
So Panda3D has this option to update the starting position of a Lerp interval by using the parameter "bakeInStart", if we set it equal to 0. However, I can't find a solution to overcome this problem, which is updating the end HPR (yaw, pitch, roll) value each time there is a new iteration of the Lerp interval loop. I've managed to work around this to get the desired effect, but a whole screen of code had to be written (because of multiple intervals written in sequence). I've tried this so far to minimize code:
self.rotateTargets(0)
def rotateTargets(self, angle):
# Create Lerp intervals for each target orbit node to rotate all of the targets
# in a parallel sequence with easing
self.angle = angle
self.rotateTargets = Sequence(
Parallel(
LerpHprInterval(self.orbitNodes["orbit0"], 4, Point3(0, 0, self.angle+90), blendType = "easeInOut", bakeInStart = 0),
name = "rotateTargets0"), Func(self.targetReparent(self.orbitNodes["orbit0"].getR()))
self.rotateTargets.loop()
def targetReparent(self, newAngle):
# Define new angle
self.newAngle = newAngle
self.angle = self.newAngle
An assertion error appears in the console, but it is related to the imported functions, and not my code.
I've recently been tasked with writing a Python based app that generates an STL file (a stereolithography file format - basically a list of triangles). I'm new to python, and I'm trying to avoid using overly complicated libraries. The issue that I'm having is with 3D rotations in a vector class that I've created.
My rotation function in the vector class looks like the following:
def rotateY(self, amount):
'''Rotates a vertex on the Y axis around the origin by a given amount (in radians)'''
self.x = self.x * math.cos(amount) + self.z * math.sin(amount)
self.y = self.y
self.z = self.x * (-math.sin(amount)) + self.z * math.cos(amount)
As you may have guessed, the vector class has an x, y, and z component. I'm reasonably familiar with 3D transformations and math, and the code looks correct, as far as I can tell. I'd rather not have to pull in some other matrix library for the sake of keeping things simple.
The problem arises when I attempt to do something like this:
v = vector(50.0, 0.0, 0.0)
rotationAmount = math.radians(10.0)
for i in range(0, 36):
v.rotateY(rotationAmount)
#Draw something at (v.x, v.y, v.z),
#...or in my case, create a list of triangles to export to an STL file
As I rotate around the y axis, the distance from the origin to the point being rotated slowly decreases. Here's an example, where I'm creating a 3D sphere at the location of the vector after each rotation, and exporting it all as an STL (as show in Maya):
As you can see, the distance from the origin to the vector being rotated is definitely decreasing the more times the vector is rotated. This is also backed up by printing out the length of the vector after each rotation, as shown (the initial vector is set to a position of (50.0, 0.0, 0.0):
Just as s temporary hack, I tried finding the length of the vector, normalizing it, rotating it, normalizing it again, then scaling back by the original size, and this seemed to have worked, but it seems like a very dirty hack to get something as simple as this to work.
My question is: what is causing the length of the vector to slowly decrease?
EDIT: I should have been a little more clear. The vector class only supports rotations around the origin.
There is a problem in your rotateY function. You are modifying x then using that modified x to calculate your modified z. This is causing your spiraling doom. One way to fix it would be to store your x in a temp variable so you can calculate your modified z off the original x. This will maintain your magnitude.
def rotateY(self, amount):
'''Rotates a vertex on the Y axis around the origin by a given amount (in radians)'''
temp_x = self.x
self.x = self.x * math.cos(amount) + self.z * math.sin(amount)
self.z = temp_x * (-math.sin(amount)) + self.z * math.cos(amount)