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.

In the initial moments, all particles will be falling into the centre, or decelerating while pulling away from the centre.
After reaching the centre all the particles will be gradually be drawn back to the centre however the surrounding particles will slightly influence their trajectory.
After running for a little while the particles begin to develop their own distinct ellipses, as the particles converge on one another they will influence the rotation of one anothers ellipse, As well as lengthening their path, or shortening it into a more circular shape.

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.

In the initial moments, all particles will be falling into the centre, or decelerating while pulling away from the centre.
After reaching the centre all the particles will be gradually be drawn back to the centre however the surrounding particles will slightly influence their trajectory.

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.

As the planets orbit intersects close to the star it also pulls it out of place.

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
Back to top