Simulation is a great way to learn new concepts.
Whenever I’m confronted with some scary physics equation I can’t follow I usually try to find the equivalent concept in a syntax I know.
In the following post I will covering a few loose simulations of Newtonian inspired gravity.
It’s not true Newtonian gravity as I was hoping not only to speed things up a little, but also to reduce the impact of two bodies coming into close proximity.
First I started by defining the particles, each particle is an ordered list with 6 elements, in order they are, x_position, y_position, mass, velocity, last_x, and last_y.
Uniform distribution of particles
The following image ares made by adding particles at random.
The particles have an equal chance of being placed in all locations.
Symmetric distribution of particles
The following image ares made by adding pairs of particles.
First a particle of random mass is added randomly somewhere in the field, next we add a counter-particle of exact mass at the same height but on the exact opposite side of the screen.
The particles have an equal chance of being placed in all locations.
Small particles around a central mass
For this render I let the tracers run indefinitely, and faded them by 10% each frame.
In the first attempts the mass of the particles pulled the central body out of it’s position until it gradually drifted off the screen.
I tried increasing the mass of the central body but it only helped a little. The only way I could keep the star in shot was to manually initialise the planets with a velocity in a direction perpendicular to the line between it and the sun.
In the first attempts the mass of the particles pulled the central body out of it’s position until it gradually drifted off the screen.
By starting the planets already moving perpendicular to the star the orbits were stablised and the star took much longer to drift
Symmetric particles around a central mass
from math import atan2,sqrt,pi,cos,sin
from random import random
from PIL import Image,ImageDraw
def particle(x,y,mass,velocity=(0,0)):
return [x,y,mass,velocity,x,y] #first two elements are position -1 to 1
#last two elements the previous position
res=(1080,720)
rate=60
def iterate(particles):
nextparticles=[]
for i in particles:
velocity=i[3]
for j in particles:
if i != j:
mass=i[2]+j[2]
distance=3+sqrt((i[0]-j[0])**2+(i[1]-j[1])**2)*5
vector=(j[0]-i[0],j[1]-i[1])
velocity=(velocity[0]+vector[0]/distance**3/rate*mass,velocity[1]+vector[1]/distance**3/rate*mass)
nextparticles.append([i[0]+velocity[0]/rate,i[1]+velocity[1]/rate,i[2],velocity,i[0],i[1]])
return nextparticles
def midcol(a,b,r):
return (int(a[0]+(b[0]-a[0])*r),int(a[1]+(b[1]-a[1])*r),int(a[2]+(b[2]-a[2])*r),255)
def col(mass):
mass=mass/4
if mass<1:
col=(32,32,106,255)
elif mass<2.5:
r=(mass-1)/1.5
col=midcol((32,32,106),(64,196,64),r)
elif mass<5:
r=(mass-2.5)/2.5
col=midcol((64,196,64),(255,255,0),r)
elif mass<10:
r=(mass-5)/5
col=midcol((255,255,0),(255,0,0),r)
else:
col=(255,255,255,255)
return col
def draw(dr,particles):
w,h=im.size
midx=w/2
midy=h/2
for i in particles:
x=int(midx+i[0]*midx)
y=int(midy+i[1]*midy)
lastx=int(midx+i[4]*midx)
lasty=int(midy+i[5]*midy)
radius=(i[2]*10)**.5
dr.ellipse((x-radius,y-radius,x+radius,y+radius),fill=col(i[2]))
#The overlay iamge to darken each frame (is responsible for fading the trails.
overlay=Image.new("RGBA",res,(0,0,0,10))
n=0
p=[particle(0,0,200)]
particlecount=20
for i in range(particlecount):
p1=random()*2-1,random()*2-1
mass=random()**3*20
angle=atan2(p1[1],p1[0])+pi/2
dist=sqrt(p1[0]**2+p1[1]**2)
velocity=(cos(angle)*dist,sin(angle)*dist)
p.append(particle(p1[0],p1[1],mass,velocity))
##uncomment these lines to add a symetric counter particle
#p2=-p1[0],p1[1]
#p.append(particle(p2[0],p2[1],mass))
while 1:
if n==0:
#change this condition to if n%rate==0:
#with some rate if you want to periodically remove the trail.
im=Image.new("RGBA",res,(0,0,0,0))
dr=ImageDraw.Draw(im)
dr.rectangle((0,0,res[0],res[1]),fill=(0,0,0,255))
p=iterate(p)
draw(dr,p)
im.save("Frames/Z%04d.png"%n)
#darken image each frame remove these next two lines for a permanent trail.
im=Image.alpha_composite(im,overlay)
dr=ImageDraw.Draw(im)
n+=1
0