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.
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.
I'm coding in python. I've created a spiral with my current for loop below. I want to create tiny spiral off-shoots around every 10 degrees. The problem is I don't know how to do that. I'm trying to do something like this:
My question is how would I create the off-shoots? Is this even possible or not?
Any suggestions are greatly appreciated.
import turtle
import math
me = turtle.Turtle()
def miniSpiral():
for i in range(0,360):
x,y = me.position()
me.left(1)
if x%10==0:
x2,y2 = me.forward(((5*i)+5)*math.pi/360)
else:
x2,y2= me.forward(5*math.pi/360)
me.goto((x+x2),(y+y2))
for x2 in range(0,720):
me.left(1)
if x2%10==0:
me.forward(((10*x2)+10)*math.pi/360)
#miniSpiral()
me.forward(10*math.pi/360)
In general, the easiest way to draw a fractal programmatically is to use recursion. Start with the code to draw one "segment" of your fractal. In the image you linked to, that would be one 90-degree piece of the spiral (since that's the distance between the branchings).
Once you have code that can draw one segment, you add a recursive call to it. Give it some parameter (e.g. the initial size), and have the recursive call reduce the value passed on to the next call. Add a base case where a call with that parameter set too small gets skipped (e.g. if size < 1: return) so that the recursion doesn't go on forever.
Now you can add branching. Instead of just one recursive call, make two. You'll need to add some extra logic to move the position of the turtle in between the calls (so the second one starts at roughly the same spot as the first), but that shouldn't be too hard. To make the two branches distinct, vary their initial position or angle, or give them different parameters. In your example image, the "extra" branches all start going the oposite direction from the "main" branch, and they start smaller.
Here's a pseudo-code implementation of the spiral you want (I'm not adding actual turtle code because you seem to be using a different turtle module than the one I have from the standard library):
def spiral(size):
if size < 1: return # base case
draw_segment(size) # this draws a 90 degree piece of the spiral
position = getposition() # save state so we can back up after the first recursive call
angle = getangle()
spiral(size - 1) # main branch of the recursion
penup() # restore state (mostly)
setposition(position)
pendown()
setangle(angle + 180) # we want to start facing the other direction for the second branch
spiral(size - 2) # extra branch of the recursion
You can play around with the details (like how you modify the size for the recursive calls) to suit your tastes or the fractal design you're looking for. For instance, you could multiply the size by some factor (e.g. size * 0.75) rather than subtracting a fixed amount.
Each mini spiral is just a smaller version of the original spiral, as such you can carefully call the original function to make each smaller one.
import turtle
import math
def spiral(steps, left, forward, level):
for step in range(50, steps):
me.left(left)
me.forward(step / forward)
if step % 200 == 0 and level > 0:
x, y = me.position()
heading = me.heading()
spiral((steps * 2) / 3, -left * 1.2, forward * 1.2, level - 1)
me.up()
me.setpos(x, y)
me.setheading(heading)
me.down()
#turtle.tracer(5, 200) # to speed things up
me = turtle.Turtle()
spiral(800, 0.6, 200.0, 4)
Each time spiral() is called, the arguments are modified slightly and the level is reduced by 1. Also the left argument is made negative which has the effect of changing the direction for each sub spiral. Each sub spiral is called after 200 steps. Each sub spiral has 2/3rds of the original steps and so on.... lots of numbers to play with to see how they effect the outcome. When the sub spiral finishes drawing, it jumps back to the point where it started drawing it an continues with the original spiral.
Calling it with a level of 0 would give you a single simple spiral for example.
This would give you the following type of output:
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
Hi im working on a script were i spawn random instances of polycubes on the Maya grid. However I don't how to stop these cubes spawning on top of each or inside of each other. I have heard the word bounding box been thrown around on the internet but im not sure how to make one. Can someone help me I've been working on this for days. (Im coding in Python)
If you save the location of each cube as a tuple() you can keep those in a set. Then just check the set every time you roll the dice for a new location to and re-roll if that combination has already been used:
import random
already_seen = set()
created = []
while len(created) < 11:
x = random.randint(-10, 10)
y = random.randint(-10,10)
z = random.randint(-10,10)
pos = (x, y, z)
if pos not in already_seen:
already_seen.add(pos)
node, shape = cmds.polyCube()
cmds.xform(node, t = pos)
created.append(node)
This uses integer positions because that gets you off the hook for distance checking each new point against all previous ones. You could treat the integer positions as a 'cell' and add a smaller random offset inside that cell keep it less rigid if that matters.
I'm currently working with the turtle library of python.
I'm working on my midterm project for my coding class and my project is to draw cos, sin, and tangent curves using turtle as well as their inverse functions.
My problem is that when I'm coding inverse sin, the graph shows up way too small and is impossible to be seen by the user. I was wondering if there was a zoom function or a way to stretch the graph to make it bigger?
Here is my code for arcsin:
def drawarcsincurve(amplitude, period, horShift, verShift):
turtle.speed(0)
startPoint = -1
turtle.goto(startPoint, math.asin(startPoint))
turtle.pendown()
for angles in range(-1,1):
y = math.asin(angles)
turtle.goto(angles,y)
Your main problem here, I think, is the range over which your are iterating your angles variable. The line
for angles in range(-1,1):
will execute the loop only twice, with angle == 1 and angle == 0 - i.e. it is equivalent to using
for angles in [-1,0]:
Type range(-1,1) in a Python interpreter window to see what I mean.
You might be getting confused over names as well. You call your loop variable angles, but it's actually representing a ratio (the sine value whose inverse you are calculating).
What you probably want really is something that iterates over the range -1 to 1 in fairly small steps. Lets choose 0.01 as our step (that's arbitrary)
I've altered your code directly rather than doing my own implementation.
I've put in a scale factor (plot_scale) which is equivalent to the zoom that I think you want in your original question.
I've left your original function arguments in, although I don't use them. I thought you might want to play with them later.
def drawarcsincurve(amplitude, period, horShift, verShift):
plot_scale = 100 # Arbitrary value - up to you - similar to "zoom"
turtle.speed(1)
turtle.penup()
startPoint = -1
turtle.goto(plot_scale*startPoint, plot_scale*math.asin(startPoint))
turtle.pendown()
for angles in range(-100,100):
sinval = 1.0 * angles / 100 # this will run -1 to 1 in 0.01 steps
y = math.asin(sinval)
turtle.goto(plot_scale*sinval,plot_scale*y)
This outputs: