How to join two objects to share values but maintain the original name? Aka N-body merger
Question:
As the title says, I am working on an N-body simulation I found on YouTube. The current script has gravity turned off when radius is small enough. However, I’d like to join the two objects (body and otherbody) into a new one which behaves according to the same rules, it just has the properties of both added. How do I do this? Here is the code in question.
import pygame
from sys import exit
import math as m
import itertools
import random
pygame.init()
WINDOW_SIZE = (1500,1500)
WINDOW = pygame.display.set_mode(WINDOW_SIZE)
CLOCK = pygame.time.Clock()
G=1
BACKGROUND = pygame.Surface(WINDOW_SIZE)
BACKGROUND.fill("Black")
BACKGROUND_RECT = BACKGROUND.get_rect(center=(500,500))
class Body(pygame.sprite.Sprite):
def __init__(self, mass, radius, init_position, color, vx, vy):
super().__init__()
self.image = pygame.Surface([(radius*2),(radius*2)])
self.image.fill("Black")
self.rect = self.image.get_rect(center=init_position)
pygame.draw.circle(self.image, color, (radius,radius), radius, 0)
self.mass = mass
self.radius = radius
self.x_pos = init_position[0]
self.y_pos = init_position[1]
self.vx = vx
self.ax = 0
self.vy = vy
self.ay = 0
def set_vy(self,value):
self.vy = value
def set_xv(self,value):
self.vx = value
def set_ay(self,value):
self.ay = value
def set_ax(self,value):
self.ax=value
def change_x_pos(self,value):
self.x_pos+=value
def change_y_pos(self,value):
self.y_pos+=value
def change_vx(self,value):
self.vx+=value
def change_vy(self,value):
self.vy+=value
def update_pos(self):
self.rect.center = (round(self.x_pos), round(self.y_pos))
def animate(self):
self.change_vx(self.ax)
self.change_vy(self.ay)
self.change_x_pos(self.vx)
self.change_y_pos(self.vy)
self.update_pos()
def gravity(self, otherbody):
dx = self.x_pos - otherbody.x_pos
dy = self.y_pos - otherbody.y_pos
if dx < self.radius*2 and dy < self.radius*2:
pass
else:
try:
r = m.sqrt(dx**2 + dy**2)
a = G*otherbody.mass/r**2
theta = m.asin(dy/r)
if self.y_pos > otherbody.y_pos:
self.set_ay(-m.sin(theta)*a)
else:
self.set_ay(m.sin(theta)*a)
if self.x_pos > otherbody.x_pos:
self.set_ax(-m.cos(theta)*a)
else:
self.set_ax(m.cos(theta)*a)
except ZeroDivisionError:
pass
body_group = pygame.sprite.Group()
BODYCOUNT=3
for i in range(BODYCOUNT):
body_group.add(Body(10, 3, (random.randrange(100,900), random.randrange(100,900)), "White", 0,0))
body_list = list(body_group)
body_pairs = list(itertools.combinations(body_list,2))
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
WINDOW.blit(BACKGROUND, BACKGROUND_RECT)
body_group.draw(WINDOW)
for body, otherbody in body_pairs:
body.gravity(otherbody)
otherbody.gravity(body)
body.animate()
otherbody.animate()
pygame.display.update()
CLOCK.tick(60)
The part in question is if dx < self.radius*2 and dy < self.radius*2: pass
How do I join the objects body and otherbody into a new body?
Also the current version of the script only has one object being attracted to the other, not equal attraction. If you see why, can you point it out?
Answers:
The condition is wrong. You need to calculate and compare the square of the Euclidean distance. Use pygame.sprite.Sprite.kill
to remove the otherbody
from all groups and increase the size and mass of the body by the size and mass of the otherbody
. Additionally there are some problems in the function gravity
, which can be simplified anyway.
class Body(pygame.sprite.Sprite):
# [...]
def join(self, otherbody):
dx = self.x_pos - otherbody.x_pos
dy = self.y_pos - otherbody.y_pos
if (dx*dx + dy*dy) < self.radius*self.radius:
otherbody.kill()
self.mass += otherbody.mass
self.radius = m.sqrt(self.radius**2 + otherbody.radius**2)
self.image = pygame.Surface([(self.radius*2),(self.radius*2)])
self.image.fill("Black")
self.rect = self.image.get_rect(center=self.rect.center)
pygame.draw.circle(self.image, self.color, (self.radius,self.radius), self.radius, 0)
def gravity(self, otherbody):
dx = self.x_pos - otherbody.x_pos
dy = self.y_pos - otherbody.y_pos
r = m.sqrt(dx*dx + dy*dy)
a = G*otherbody.mass/r**2
self.set_ax(a * -dx / r)
self.set_ay(a * -dy / r)
Since the objects in body_group
can change, body_pairs
must be regenerated in each frame. animate
each body only once and join the bodies after moving.
while True:
# [...]
body_list = list(body_group)
body_pairs = list(itertools.combinations(body_list,2))
for body, otherbody in body_pairs:
body.gravity(otherbody)
otherbody.gravity(body)
for body in body_list:
body.animate()
for body, otherbody in body_pairs:
body.join(otherbody)
As the title says, I am working on an N-body simulation I found on YouTube. The current script has gravity turned off when radius is small enough. However, I’d like to join the two objects (body and otherbody) into a new one which behaves according to the same rules, it just has the properties of both added. How do I do this? Here is the code in question.
import pygame
from sys import exit
import math as m
import itertools
import random
pygame.init()
WINDOW_SIZE = (1500,1500)
WINDOW = pygame.display.set_mode(WINDOW_SIZE)
CLOCK = pygame.time.Clock()
G=1
BACKGROUND = pygame.Surface(WINDOW_SIZE)
BACKGROUND.fill("Black")
BACKGROUND_RECT = BACKGROUND.get_rect(center=(500,500))
class Body(pygame.sprite.Sprite):
def __init__(self, mass, radius, init_position, color, vx, vy):
super().__init__()
self.image = pygame.Surface([(radius*2),(radius*2)])
self.image.fill("Black")
self.rect = self.image.get_rect(center=init_position)
pygame.draw.circle(self.image, color, (radius,radius), radius, 0)
self.mass = mass
self.radius = radius
self.x_pos = init_position[0]
self.y_pos = init_position[1]
self.vx = vx
self.ax = 0
self.vy = vy
self.ay = 0
def set_vy(self,value):
self.vy = value
def set_xv(self,value):
self.vx = value
def set_ay(self,value):
self.ay = value
def set_ax(self,value):
self.ax=value
def change_x_pos(self,value):
self.x_pos+=value
def change_y_pos(self,value):
self.y_pos+=value
def change_vx(self,value):
self.vx+=value
def change_vy(self,value):
self.vy+=value
def update_pos(self):
self.rect.center = (round(self.x_pos), round(self.y_pos))
def animate(self):
self.change_vx(self.ax)
self.change_vy(self.ay)
self.change_x_pos(self.vx)
self.change_y_pos(self.vy)
self.update_pos()
def gravity(self, otherbody):
dx = self.x_pos - otherbody.x_pos
dy = self.y_pos - otherbody.y_pos
if dx < self.radius*2 and dy < self.radius*2:
pass
else:
try:
r = m.sqrt(dx**2 + dy**2)
a = G*otherbody.mass/r**2
theta = m.asin(dy/r)
if self.y_pos > otherbody.y_pos:
self.set_ay(-m.sin(theta)*a)
else:
self.set_ay(m.sin(theta)*a)
if self.x_pos > otherbody.x_pos:
self.set_ax(-m.cos(theta)*a)
else:
self.set_ax(m.cos(theta)*a)
except ZeroDivisionError:
pass
body_group = pygame.sprite.Group()
BODYCOUNT=3
for i in range(BODYCOUNT):
body_group.add(Body(10, 3, (random.randrange(100,900), random.randrange(100,900)), "White", 0,0))
body_list = list(body_group)
body_pairs = list(itertools.combinations(body_list,2))
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
WINDOW.blit(BACKGROUND, BACKGROUND_RECT)
body_group.draw(WINDOW)
for body, otherbody in body_pairs:
body.gravity(otherbody)
otherbody.gravity(body)
body.animate()
otherbody.animate()
pygame.display.update()
CLOCK.tick(60)
The part in question is if dx < self.radius*2 and dy < self.radius*2: pass
How do I join the objects body and otherbody into a new body?
Also the current version of the script only has one object being attracted to the other, not equal attraction. If you see why, can you point it out?
The condition is wrong. You need to calculate and compare the square of the Euclidean distance. Use pygame.sprite.Sprite.kill
to remove the otherbody
from all groups and increase the size and mass of the body by the size and mass of the otherbody
. Additionally there are some problems in the function gravity
, which can be simplified anyway.
class Body(pygame.sprite.Sprite):
# [...]
def join(self, otherbody):
dx = self.x_pos - otherbody.x_pos
dy = self.y_pos - otherbody.y_pos
if (dx*dx + dy*dy) < self.radius*self.radius:
otherbody.kill()
self.mass += otherbody.mass
self.radius = m.sqrt(self.radius**2 + otherbody.radius**2)
self.image = pygame.Surface([(self.radius*2),(self.radius*2)])
self.image.fill("Black")
self.rect = self.image.get_rect(center=self.rect.center)
pygame.draw.circle(self.image, self.color, (self.radius,self.radius), self.radius, 0)
def gravity(self, otherbody):
dx = self.x_pos - otherbody.x_pos
dy = self.y_pos - otherbody.y_pos
r = m.sqrt(dx*dx + dy*dy)
a = G*otherbody.mass/r**2
self.set_ax(a * -dx / r)
self.set_ay(a * -dy / r)
Since the objects in body_group
can change, body_pairs
must be regenerated in each frame. animate
each body only once and join the bodies after moving.
while True:
# [...]
body_list = list(body_group)
body_pairs = list(itertools.combinations(body_list,2))
for body, otherbody in body_pairs:
body.gravity(otherbody)
otherbody.gravity(body)
for body in body_list:
body.animate()
for body, otherbody in body_pairs:
body.join(otherbody)