Coulombs Law: Protons atract each other - python

I'am working on a simulation of the Coulomb's Law with pygame, but I have a problem. When I start the Simulation an set two Protons, they attract each other. But there is more!
When I set an electron and a proton, they both start to "travel" with a constant distance to each other in the same direction.
Here is my code:
import pygame
import numpy as np
import math
pygame.init()
# window-settings
WIDTH, HEIGHT = 800, 800
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Electric field simulation")
# constants
Pi = np.pi
epsilon0 = 8.85 * (10**(-12))
q = 1.6 * (10**-19)
# colors
WHITE = (255,255,255)
BLACK = (0,0,0)
BLUE = (100, 149, 237)
RED = (188, 39, 50)
class Charge:
TIMESTEP = 50000
def __init__(self, x, y, radius, color, q):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.q = q
self.x_vel = 0
self.y_vel = 0
def draw(self, win):
pygame.draw.circle(win, self.color, (self.x, self.y), self.radius)
def attraction(self, other):
other_x, other_y = other.x, other.y
distance_x = other_x - self.x
distance_y = other_y - self.y
distance = math.sqrt(distance_x ** 2 + distance_y ** 2)
force = (1 / (4 * Pi * epsilon0)) * self.q * other.q / (distance ** 2)
theta = math.atan2(distance_y, distance_x)
force_x = math.cos(theta) * force
force_y = math.sin(theta) * force
return force_x, force_y
def update_position(self, charges):
total_fx = total_fy = 0
for charge in charges:
if self == charge:
continue
fx, fy = self.attraction(charge)
total_fx += fx
total_fy += fy
self.x_vel += total_fx / self.q * self.TIMESTEP
self.y_vel += total_fy / self.q * self.TIMESTEP
self.x += self.x_vel * self.TIMESTEP
self.y += self.y_vel * self.TIMESTEP
def main():
run = True
clock = pygame.time.Clock()
proton = Charge(200, 200, 10, RED, q)
electron = Charge(300, 200, 10, BLUE, -q)
charges = [electron, proton]
while run:
clock.tick(60)
WIN.fill(WHITE)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN: # To set charges
if event.button == 1: # Sets positive charge
charges.append(Charge(pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1], 10, RED, q))
if event.button == 3: # Sets negitive charge
charges.append(Charge(pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1], 10, BLUE, -q))
for charge in charges:
charge.update_position(charges)
charge.draw(WIN)
pygame.display.update()
pygame.quit()
if __name__ == "__main__":
main()
I think the problem is the attraction-function, but I don't know how to solve it.

At the moment I have not the possibility to run your code but have you mixed up the direction of your force?
At least Wikipedia calculates r1-r2
Annother source of error might be the sin, cos etc. functions have you checked for your example that the calculated values are correct?
If I understand your code correct you update all positions one after eachother and apply the changes right away. Dependend on your application you might want to
Calculate all forces
Update all Values
Otherwise you have a multiple states of your system in just one time step.

Okay I solved the problem by calculating the direction of the force. With this I calculate at first the the force but return the absolute and then calculate the direction and speed.
Here is the code of my changes (I just changed something in the updating and attracting function):
def attraction(self, other):
other_x, other_y = other.x, other.y
distance_x = self.x - other_x
distance_y = self.y - other_y
distance = math.sqrt(distance_x ** 2 + distance_y ** 2)
if self.q != other.q and distance < 5:
return 0,0
force = (1 / (4 * Pi * epsilon0)) * self.q * other.q / (distance ** 2)
force_x = distance_x / distance*np.abs(force)
force_y = distance_y / distance*np.abs(force)
return force_x, force_y
def update_position(self, charges):
total_fx = total_fy = 0
for charge in charges:
if self == charge:
continue
fx, fy = self.attraction(charge)
total_fx += fx
total_fy += fy
self.x_vel += total_fx / self.q * self.TIMESTEP
self.y_vel += total_fy / self.q * self.TIMESTEP
if self != charge and self.q < 0 and self.q != charge.q:
x = abs(self.x - charge.x)
y = abs(self.y - charge.y)
self.direction = [x / np.sqrt((x ** 2) + (y ** 2)),y / np.sqrt((x ** 2) + (y ** 2))]
elif self != charge and self.q > 0 and self.q != charge.q:
x = abs(self.x - charge.x)
y = abs(self.y - charge.y)
self.direction = [-x / np.sqrt((x ** 2) + (y ** 2)), -y / np.sqrt((x ** 2) + (y ** 2))]
elif self != charge and self.q < 0 and self.q == charge.q:
x = abs(self.x - charge.x)
y = abs(self.y - charge.y)
self.direction = [-x / np.sqrt((x ** 2) + (y ** 2)), -y / np.sqrt((x ** 2) + (y ** 2))]
elif self != charge and self.q > 0 and self.q == charge.q:
x = abs(self.x - charge.x)
y = abs(self.y - charge.y)
self.direction = [x / np.sqrt((x ** 2) + (y ** 2)), y / np.sqrt((x ** 2) + (y ** 2))]
self.x += self.x_vel * self.TIMESTEP * self.direction[0]
self.y += self.y_vel * self.TIMESTEP * self.direction[1]
I know this code isn't that beautiful, but at first it works.

Related

Trouble creating a line perpendicular to player -> cursor line

I am trying raycasting methods and want to simply draw a triangle in the direction of my cursor, with that triangle having a 90 degree angle at the cursor, so that it is a right angle triangle.
I am currently using solely the player's coordinates and the mouse's coordenates to calculate a coordinate of the point of the triangle. I use pygame to draw the lines and create this app.
Here is my existing python code :
import pygame as pg, sys, time, math, numpy as np
class RayCast:
def calc_bearing(
self, charX, charY, charW, charH
): # take position of character and mouse, calculate distance using pythagoras and then angle using sohcahtoa -> "toa" tan-1(change in y , change in x)
mouseX, mouseY = pg.mouse.get_pos()
charX = charX + charW // 2
charY = charY + charH // 2
deltaY = charY - mouseY
deltaX = -1 * (charX - mouseX)
distance = math.sqrt(((deltaY**2) + (deltaX**2)))
#print(f"{deltaX} <-- DeltaX | DeltaY --> {deltaY}")
if deltaY != 0:
ANGLE = abs(math.degrees(math.atan(deltaX / deltaY)))
if deltaY < 0 and deltaX > 0:
ANGLE = 180 - ANGLE
elif deltaX == 0 and deltaY < 0:
ANGLE = 180
elif deltaY < 0 and deltaX < 0:
ANGLE += 180
elif deltaY > 0 and deltaX < 0:`your text`
ANGLE = 360 - ANGLE
else:
if deltaX > 0:
ANGLE = 90
elif deltaX < 0:
ANGLE = 270
#print(
# ANGLE
#) # PROBLEM, if both are negative, then angle is positive and vice versa // SOLVED
# finds bearing of mouse relative to player - works
class Game(RayCast):
def __init__(self):
pg.init()
#clock = pg.time.Clock()
self.WIDTH, self.HEIGHT = 800, 800
self.charWidth, self.charHeight = 50, 50
self.character = pg.Rect(
(self.WIDTH // 2 - self.charWidth // 2),
(self.HEIGHT // 2 - self.charHeight // 2), self.charWidth,
self.charHeight) # left,top,width,height , start in center
self.WIN = pg.display.set_mode((self.WIDTH, self.HEIGHT))
pg.display.set_caption("Ray Cast")
self.start_time = time.time()
self.raycast = RayCast()
self.WHITE = (255, 255, 255)
while True:
self.mainLoop()
def mainLoop(self):
self.updateScreen()
self.handleMove()
self.raycast.calc_bearing(self.character.x, self.character.y,
self.charWidth, self.charHeight)
self.deltaTime = self.calcDeltaTime(
self.start_time) #calc delta time (change in time)
self.start_time = self.calcFPS(self.start_time)
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
sys.exit(0)
def calcFPS(self, start):
if time.time() - start != 0:
fps = 1 / (time.time() - start)
#print(f"FPS --> {fps}")
return time.time()
def updateScreen(self):
self.WIN.fill((0, 0, 0))
pg.draw.rect(self.WIN, self.WHITE, self.character)
self.draw_line()
self.draw_triangle()
pg.display.update()
def handleMove(self):
keys = pg.key.get_pressed()
if keys[pg.K_w]:
self.character.y -= 0.75 * self.deltaTime
if keys[pg.K_a]:
self.character.x -= 0.75 * self.deltaTime
if keys[pg.K_s]:
self.character.y += 1 * self.deltaTime
if keys[pg.K_d]:
self.character.x += 1 * self.deltaTime
def calcDeltaTime(self, start):
#print(f"DeltaTime --> {(time.time() - start) + 1}")
return ((time.time() - start) + 1)
def draw_line(self):
point1 = tuple([
self.character.x + self.charWidth // 2,
self.character.y + self.charHeight // 2
])
point2 = pg.mouse.get_pos()
#print(f"{point1} <-- POINT1 | POINT2 --> {point2}")
# surface, colour, start, end, width
pg.draw.line(self.WIN, self.WHITE, point1, point2, width=2)
def draw_triangle(self):
charX, charY = self.character.x + self.charWidth//2, self.character.y + self.charHeight//2
mouseX, mouseY = pg.mouse.get_pos()
deltax = abs(mouseX) - abs(charX)
deltay = abs(mouseY) - abs(charY)
if (deltax!= 0) and (deltay != 0):
# sqrt((sqrt(x1^2 + y1^2)^2) + 100^2) * (sin((sin-1(100/ sqrt(x1^2 + y1^2)^2 + 100^2))) + (180 - (tan-1(x/y)) - 90))
r = math.sqrt(math.sqrt((deltax**2) + (deltay**2))+100**2)
theta1 = np.arcsin((100/r))
z = (np.arctan(deltax / deltay))
theta2 = 90 - z
o = r * (np.sin(theta1 + theta2))
a = r * (np.cos(theta1 + theta2))
point1 = (mouseX + o, mouseY + a)
playerPos = (charX,charY)
pg.draw.line(self.WIN, self.WHITE, point1, pg.mouse.get_pos(), width=2)
pg.draw.line(self.WIN, self.WHITE, point1,playerPos, width = 2 )
#r2 = np.sqrt((deltax**2 + deltay**2))
# r1 = np.sqrt(r2**2 + 100**2)
#theta2 = math.degrees(np.arccos(deltax / r2)) # in radians
#theta1 = math.degrees(np.arccos(r2/r1)) # in radians
#o = r1 * np.sin(theta1 + theta2)
#a = r1 * np.cos(theta1 + theta2)
#point1 = (mouseX + o, mouseY + a)
#pg.draw.line(self.WIN, self.WHITE, point1, pg.mouse.get_pos(), width = 2 )
else:
if (charX - mouseX == 0 and mouseY > charY): # is below the player
pass
elif (charX - mouseX == 0) and (mouseY < charY): # above player
pass
elif (charY - mouseY == 0): # left of player
#pg.draw.line(self.WIN, self.WHITE,
# (self.character.x, self.character.y - oppLength // 2),
# (self.character.x, self.character.y + oppLength // 2))
pass
elif (charY - mouseY == 0): # right of player
#pg.draw.line(self.WIN, self.WHITE,
# (self.character.x, self.character.y - oppLength // 2),
# (self.character.x, self.character.y + oppLength // 2))
pass
#print(m)
game = Game()
the error is in the draw_triangle() function. Please help me figure this out.
I tried a few different equations but the one I decided to use was :
a = sqrt((sqrt(x1^2 + y1^2)^2) + 100^2) * (sin((sin-1(100/ sqrt(x1^2 + y1^2)^2 + 100^2))) + (180 - (tan-1(x/y)) - 90))
pg.drawline(self.WIN,self.WHITE,(playerposX + a, playerposY), mousePos, width = 2)
but this draws a line in the complete wrong place.

Trajectory plalnification

I have a program that generates circles and lines, where the circles can not collide with each other and the lines can not collide with the circles, the problem is that it only draws a line but not the others, it does not mark any error and as much as I think the reason I do not understand why, (I'm new to python, so excuse me if maybe the error is obvious)
I tried to remove the for from my CreaLin class and it does generate the lines but they all collide with the circles, I also thought that the collisionL function could be the problem since the method does not belong as such to the line class, but I need values from the circle class, so I don't know what would be another way to do it, I would like to know a method.
my code:
class CreaCir:
def __init__(self, figs):
self.figs = figs
def update(self):
if len(self.figs) <70:
choca = False
r = randint(5, 104)
x = randint(0, 600 + r)
y = randint(0, 400 + r)
creOne = Circulo(x, y, r)
for fig in (self.figs):
choca = creOne.colisionC(fig)
if choca == True:
break
if choca == False:
self.figs.append(creOne)
def dibujar(self, ventana):
pass
class CreaLin:
def __init__(self, lins):
self.lins = lins
def update(self):
if len(self.lins) <70:
choca = False
x = randint(0, 700)
y = randint(0, 500)
a = randint(0, 700)
b = randint(0, 500)
linOne = Linea(x, y, a, b)
for lin in (self.lins):
choca = linOne.colisionL(lin)
if choca == True:
break
if choca == False:
self.lins.append(linOne)
def dibujar(self, ventana):
pass
class Ventana:
def __init__(self, Ven_Tam= (700, 500)):
pg.init()
self.ven_tam = Ven_Tam
self.ven = pg.display.set_caption("Linea")
self.ven = pg.display.set_mode(self.ven_tam)
self.ven.fill(pg.Color('#404040'))
self.figs = []
self.lins = []
self.reloj = pg.time.Clock()
def check_events(self):
for event in pg.event.get():
if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
quit()
pg.display.flip()
def run(self):
cirCreater = CreaCir(self.figs)
linCreater = CreaLin(self.lins)
while True:
self.check_events()
cirCreater.update()
linCreater.update()
for fig in self.figs:
fig.dibujar(self.ven)
for lin in self.lins:
lin.dibujar(self.ven)
self.reloj.tick(60)
if __name__ == '__main__':
ven = Ventana()
ven.run()
class Circulo:
class Circulo(PosGeo):
def __init__(self, x, y, r):
self.x = x
self.y = y
self.radio = r
self.cx = x+r
self.cy = y+r
def __str__(self):
return f"Circulo, (X: {self.x}, Y: {self.y}), radio: {self.radio}"
def dibujar(self, ventana):
pg.draw.circle(ventana, "white", (self.cx, self.cy), self.radio, 1)
pg.draw.line(ventana, "white", (self.cx+2, self.cy+2),(self.cx-2, self.cy-2))
pg.draw.line(ventana, "white", (self.cx-2, self.cy+2),(self.cx+2, self.cy-2))
def update(self):
pass
def colisionC(self, c2):
return self.radio + c2.radio > sqrt(pow(self.cx - c2.cx, 2) + pow(self.cy - c2.cy, 2))
def colisionL(self, L2):
l0 = [L2.x, L2.y]
l1 = [L2.a, L2.b]
cp = [self.cx, self.cy]
x1 = l0[0] - cp[0]
y1 = l0[1] - cp[1]
x2 = l1[0] - cp[0]
y2 = l1[1] - cp[1]
dx = x2 - x1
dy = y2 - y1
dr = sqrt(dx*dx + dy*dy)
D = x1 * y2 - x2 * y1
discriminant = self.radio*self.radio*dr*dr - D*D
if discriminant < 0:
return False
else:
return True`
and finally my class line:
class Linea(PosGeo):
def __init__(self, x, y, a, b):
super().__init__(x, y)
self.x = x
self.y = y
self.a = a
self.b = b
def dibujar(self, ventana):
pg.draw.line(ventana, "#7B0000", (self.x, self.y), (self.a, self.b))
def update(self):
pass
def colisionL(self, l1):
pass
Result:
You need to implement the colisionC and colisionL methods in Linea and Circulo. See Problem with calculating line intersections for the line-line intersection algorithm. When checking for collisions between lines and circles, in addition to checking for collisions between circles and endless lines, you must also consider the beginning and end of the line segment:
class Linea:
# [...]
def colisionC(self, c2):
return c2.colisionL(self)
def colisionL(self, l1):
return Linea.intersect_line_line((self.x, self.y), (self.a, self.b), (l1.x, l1.y), (l1.a, l1.b))
def intersect_line_line(P0, P1, Q0, Q1):
d = (P1[0]-P0[0]) * (Q1[1]-Q0[1]) + (P1[1]-P0[1]) * (Q0[0]-Q1[0])
if d == 0:
return None
t = ((Q0[0]-P0[0]) * (Q1[1]-Q0[1]) + (Q0[1]-P0[1]) * (Q0[0]-Q1[0])) / d
u = ((Q0[0]-P0[0]) * (P1[1]-P0[1]) + (Q0[1]-P0[1]) * (P0[0]-P1[0])) / d
if 0 <= t <= 1 and 0 <= u <= 1:
return P1[0] * t + P0[0] * (1-t), P1[1] * t + P0[1] * (1-t)
return None
class Circulo
# [...]
def colisionC(self, c2):
return self.radio + c2.radio > sqrt(pow(self.cx - c2.cx, 2) + pow(self.cy - c2.cy, 2))
def colisionL(self, L2):
l0 = [L2.x, L2.y]
l1 = [L2.a, L2.b]
cp = [self.cx, self.cy]
x1 = l0[0] - cp[0]
y1 = l0[1] - cp[1]
x2 = l1[0] - cp[0]
y2 = l1[1] - cp[1]
if sqrt(x1*x1+y1*y1) < self.radio or sqrt(x2*x2+y2*y2) < self.radio:
return True
dx = x2 - x1
dy = y2 - y1
dr = sqrt(dx*dx + dy*dy)
D = x1 * y2 - x2 * y1
discriminant = self.radio*self.radio*dr*dr - D*D
if discriminant < 0:
return False
sign = lambda x: -1 if x < 0 else 1
xa = (D * dy + sign(dy) * dx * sqrt(discriminant)) / (dr * dr)
xb = (D * dy - sign(dy) * dx * sqrt(discriminant)) / (dr * dr)
ya = (-D * dx + abs(dy) * sqrt(discriminant)) / (dr * dr)
yb = (-D * dx - abs(dy) * sqrt(discriminant)) / (dr * dr)
ta = (xa-x1)*dx/dr + (ya-y1)*dy/dr
tb = (xb-x1)*dx/dr + (yb-y1)*dy/dr
return 0 < ta < dr or 0 < tb < dr
Put all objects into one container. Before you add a new Linea you have to check if the new "Line" intersects another object with "ColisionL". Before you add a new Circulo, you must check if the new "Line" intersects another object with CollisionC:
class CreaObjects:
def __init__(self, figs):
self.figs = figs
self.no_lines = 0
self.no_circles = 0
def update(self):
if self.no_circles <70:
r = randint(5, 104)
creOne = Circulo(randint(0, 600 - 2*r), randint(0, 400 - 2*r), r)
if not any(fig.colisionC(creOne) for fig in self.figs):
self.figs.append(creOne)
self.no_circles += 1
if self.no_lines <70:
linOne = Linea(randint(0, 700), randint(0, 500), randint(0, 700), randint(0, 500))
if not any(fig.colisionL(linOne) for fig in self.figs):
self.figs.append(linOne)
self.no_lines += 1
Complete example:
import pygame as pg
from random import randint
from math import sqrt, hypot
class Linea:
def __init__(self, x, y, a, b):
self.x = x
self.y = y
self.a = a
self.b = b
def dibujar(self, ventana):
pg.draw.line(ventana, "#7B0000", (self.x, self.y), (self.a, self.b))
def update(self):
pass
def colisionC(self, c2):
return c2.colisionL(self)
def colisionL(self, l1):
return Linea.intersect_line_line((self.x, self.y), (self.a, self.b), (l1.x, l1.y), (l1.a, l1.b))
def intersect_line_line(P0, P1, Q0, Q1):
d = (P1[0]-P0[0]) * (Q1[1]-Q0[1]) + (P1[1]-P0[1]) * (Q0[0]-Q1[0])
if d == 0:
return None
t = ((Q0[0]-P0[0]) * (Q1[1]-Q0[1]) + (Q0[1]-P0[1]) * (Q0[0]-Q1[0])) / d
u = ((Q0[0]-P0[0]) * (P1[1]-P0[1]) + (Q0[1]-P0[1]) * (P0[0]-P1[0])) / d
if 0 <= t <= 1 and 0 <= u <= 1:
return P1[0] * t + P0[0] * (1-t), P1[1] * t + P0[1] * (1-t)
return None
class Circulo:
def __init__(self, x, y, r):
self.x = x
self.y = y
self.radio = r
self.cx = x+r
self.cy = y+r
def __str__(self):
return f"Circulo, (X: {self.x}, Y: {self.y}), radio: {self.radio}"
def dibujar(self, ventana):
pg.draw.circle(ventana, "white", (self.cx, self.cy), self.radio, 1)
pg.draw.line(ventana, "white", (self.cx+2, self.cy+2),(self.cx-2, self.cy-2))
pg.draw.line(ventana, "white", (self.cx-2, self.cy+2),(self.cx+2, self.cy-2))
def update(self):
pass
def colisionC(self, c2):
return self.radio + c2.radio > sqrt(pow(self.cx - c2.cx, 2) + pow(self.cy - c2.cy, 2))
def colisionL(self, L2):
l0 = [L2.x, L2.y]
l1 = [L2.a, L2.b]
cp = [self.cx, self.cy]
x1 = l0[0] - cp[0]
y1 = l0[1] - cp[1]
x2 = l1[0] - cp[0]
y2 = l1[1] - cp[1]
if sqrt(x1*x1+y1*y1) < self.radio or sqrt(x2*x2+y2*y2) < self.radio:
return True
dx = x2 - x1
dy = y2 - y1
dr = sqrt(dx*dx + dy*dy)
D = x1 * y2 - x2 * y1
discriminant = self.radio*self.radio*dr*dr - D*D
if discriminant < 0:
return False
sign = lambda x: -1 if x < 0 else 1
xa = (D * dy + sign(dy) * dx * sqrt(discriminant)) / (dr * dr)
xb = (D * dy - sign(dy) * dx * sqrt(discriminant)) / (dr * dr)
ya = (-D * dx + abs(dy) * sqrt(discriminant)) / (dr * dr)
yb = (-D * dx - abs(dy) * sqrt(discriminant)) / (dr * dr)
ta = (xa-x1)*dx/dr + (ya-y1)*dy/dr
tb = (xb-x1)*dx/dr + (yb-y1)*dy/dr
return 0 < ta < dr or 0 < tb < dr
class CreaObjects:
def __init__(self, figs):
self.figs = figs
self.no_lines = 0
self.no_circles = 0
def update(self):
if self.no_circles <70:
r = randint(5, 104)
creOne = Circulo(randint(0, 600 - 2*r), randint(0, 400 - 2*r), r)
if not any(fig.colisionC(creOne) for fig in self.figs):
self.figs.append(creOne)
self.no_circles += 1
if self.no_lines <70:
linOne = Linea(randint(0, 700), randint(0, 500), randint(0, 700), randint(0, 500))
if not any(fig.colisionL(linOne) for fig in self.figs):
self.figs.append(linOne)
self.no_lines += 1
class Ventana:
def __init__(self, Ven_Tam= (700, 500)):
pg.init()
self.ven_tam = Ven_Tam
self.ven = pg.display.set_caption("Linea")
self.ven = pg.display.set_mode(self.ven_tam)
self.figs = []
self.reloj = pg.time.Clock()
def check_events(self):
for event in pg.event.get():
if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
quit()
def run(self):
cirCreater = CreaObjects(self.figs)
while True:
self.check_events()
cirCreater.update()
self.ven.fill(pg.Color('#404040'))
for fig in self.figs:
fig.dibujar(self.ven)
pg.display.flip()
self.reloj.tick(60)
if __name__ == '__main__':
ven = Ventana()
ven.run()

How to draw a dashed curved line with pygame?

I need to draw sine and cosine waves on a coordinate system exactly like in this picture. I did all the work well except I failed to represent dashed and curved line with pygame. I have smth similar to what I need, but how can I make it curved? Or how can I improve this to make it like pygame.draw.lines, not pygame.draw.line?
import pygame
import math
class Point:
# constructed using a normal tupple
def __init__(self, point_t = (0,0)):
self.x = float(point_t[0])
self.y = float(point_t[1])
# define all useful operators
def __add__(self, other):
return Point((self.x + other.x, self.y + other.y))
def __sub__(self, other):
return Point((self.x - other.x, self.y - other.y))
def __mul__(self, scalar):
return Point((self.x*scalar, self.y*scalar))
def __div__(self, scalar):
return Point((self.x/scalar, self.y/scalar))
def __len__(self):
return int(math.sqrt(self.x**2 + self.y**2))
# get back values in original tuple format
def get(self):
return (self.x, self.y)
def draw_dashed_line(surf, color, start_pos, end_pos, width=1, dash_length=4):
origin = Point(start_pos)
target = Point(end_pos)
displacement = target - origin
length = len(displacement)
slope = displacement.__div__(length)
for index in range(0, int(length/dash_length), 2):
start = origin + (slope * index * dash_length)
end = origin + (slope * (index + 1) * dash_length)
pygame.draw.line(surf, color, start.get(), end.get(), width)
pygame.init()
screen = pygame.display.set_mode((400, 300))
done = False
while not done:
draw_dashed_line(screen,(0,255,0),(0,0),(110,110))
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.display.flip()
Write a function that operates similar as pygame.draw.line() but draws a dashed straight line. The function has an additional argument prev_line_len which indicates where the line segment is within a consecutive curve. Compute the Euclidean distance between the points and the Unit vector that points from the beginning of the line segment to its end. Distribute the strokes along the line:
def draw_dashed_line(surf, color, p1, p2, prev_line_len, dash_length=8):
dx, dy = p2[0]-p1[0], p2[1]-p1[1]
if dx == 0 and dy == 0:
return
dist = math.hypot(dx, dy)
dx /= dist
dy /= dist
step = dash_length*2
start = (int(prev_line_len) // step) * step
end = (int(prev_line_len + dist) // step + 1) * step
for i in range(start, end, dash_length*2):
s = max(0, start - prev_line_len)
e = min(start - prev_line_len + dash_length, dist)
if s < e:
ps = p1[0] + dx * s, p1[1] + dy * s
pe = p1[0] + dx * e, p1[1] + dy * e
pygame.draw.line(surf, color, pe, ps
Write another function that behaves similarly to pygame.draw.lines(), but uses the former function (draw_dashed_line) to draw the dashed curve. Calculate the length from the beginning of the curve to the beginning of each line segment and pass it to the function:
def draw_dashed_lines(surf, color, points, dash_length=8):
line_len = 0
for i in range(1, len(points)):
p1, p2 = points[i-1], points[i]
dist = math.hypot(p2[0]-p1[0], p2[1]-p1[1])
draw_dashed_line(surf, color, p1, p2, line_len, dash_length)
line_len += dist
Minimal example:
repl.it/#Rabbid76/DashedLine
import pygame
import math
def draw_dashed_line(surf, color, p1, p2, prev_line_len, dash_length=8):
dx, dy = p2[0]-p1[0], p2[1]-p1[1]
if dx == 0 and dy == 0:
return
dist = math.hypot(dx, dy)
dx /= dist
dy /= dist
step = dash_length*2
start = (int(prev_line_len) // step) * step
end = (int(prev_line_len + dist) // step + 1) * step
for i in range(start, end, dash_length*2):
s = max(0, start - prev_line_len)
e = min(start - prev_line_len + dash_length, dist)
if s < e:
ps = p1[0] + dx * s, p1[1] + dy * s
pe = p1[0] + dx * e, p1[1] + dy * e
pygame.draw.line(surf, color, pe, ps)
def draw_dashed_lines(surf, color, points, dash_length=8):
line_len = 0
for i in range(1, len(points)):
p1, p2 = points[i-1], points[i]
dist = math.hypot(p2[0]-p1[0], p2[1]-p1[1])
draw_dashed_line(surf, color, p1, p2, line_len, dash_length)
line_len += dist
pygame.init()
screen = pygame.display.set_mode((400, 300))
done = False
line = [(i, 150 + math.sin(math.radians(i*2)) * 100) for i in range(400)]
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
draw_dashed_lines(screen, (255, 255, 255), line)
pygame.display.flip()

Pygame: How do I apply Separating Axis Theorem (SAT) to Rotating Squares?

I don't want the code (I really do want the code), but can someone explain to me how I can create the diagonal line to see if there's a gap? I know we have to use vectors, but I don't know how to do that using python
So, using the logic of Separating Axis Theorem that if you cant draw a line in between 2 squares then they are overlapping and colliding. I made something close, its not perfect but its close, I also haven't accounted for rotation of squares but if you find a way to find the vertices/corners of the square, then this could easily work. The way i did it was that i turned the squares into lines and drew a line directly in the middle of the squares and at the normal of the line in between the squares, its a bit confusing but it makes sense once you see it. I then used line intersecting maths to find if they intersect.
import pygame
from pygame.locals import *
from pygame import Vector2
pygame.init()
screen = pygame.display.set_mode((500,500))
#check if 2 lines are intersecting
def LineIntersect(line1, line2):
#the math is from wikipedia
x1 = line1[0].x
y1 = line1[0].y
x2 = line1[1].x
y2 = line1[1].y
x3 = line2[0].x
y3 = line2[0].y
x4 = line2[1].x
y4 = line2[1].y
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if den == 0:
return
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
if t > 0 and t < 1 and u > 0 and u < 1:
pt = Vector2()
pt.x = x1 + t * (x2 - x1)
pt.y = y1 + t * (y2 - y1)
return pt
return
#class for sqaure
class square:
def __init__(self,x,y,w):
self.x = x
self.y = y
self.w = w
self.centerx = self.x + w//2
self.centery = self.y + w//2
self.col = (255,0,0)
def Draw(self, outline = False):
if outline:
self.Outline()
else:
pygame.draw.rect(screen,self.col,(self.x,self.y,self.w,self.w))
def Outline(self):
for point1, point2 in self.Lines():
pygame.draw.line(screen,sqr2.col,point1,point2,1)
#get the lines that make up the square, the outline/perameter
def Lines(self):
lines = []
lines.append((Vector2(self.x,self.y),Vector2(self.x+self.w,self.y)))
lines.append((Vector2(self.x,self.y),Vector2(self.x,self.y + self.w)))
lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x+self.w,self.y)))
lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x,self.y + self.w)))
return lines
#draw a line inbetween the 2 squares
def DrawLineInBetween():
#draw a line between the 2 squares, get gradient
#to avoid divide by zero
if abs(sqr1.x - sqr2.x) == 0:
gradient = "infinity"
else:
#rise over run
#left - right = run
left = sqr1 if sqr1.x < sqr2.x else sqr2
right = sqr1 if left == sqr2 else sqr2
gradient = ((left.y - right.y)/abs(sqr1.x - sqr2.x))
#print("gradient:",gradient)
#get the middle point between the centers of the squares
middle = (max(sqr1.x + sqr1.w//2, sqr2.x + sqr2.w//2) - abs(sqr1.x - sqr2.x)//2,
max(sqr1.y + sqr1.w//2, sqr2.y + sqr2.w//2) - abs(sqr1.y - sqr2.y)//2)
#to avoid divide by 0
if gradient == 0:
point1 = Vector2(middle[0], middle[1] + 100)
point2 = Vector2(middle[0], middle[1] - 100)
elif gradient == "infinity":
point1 = Vector2(middle[0] - 100, middle[1])
point2 = Vector2(middle[0] + 100, middle[1])
else:
#get normal of line
gradient = -1/gradient
#print("normal:",gradient)
point1 = Vector2(middle[0] + 100, middle[1] + int(-100 * gradient))
point2 = Vector2(middle[0] - 100, middle[1] + int(100 * gradient))
#print(point1)
#print(point2)
#print(middle)
pygame.draw.line(screen,(0,255,0),point1,point2,1)
line = (point1, point2)
return line
sqr1 = square(100,100,50)
sqr2 = square(200,100,50)
Clock = pygame.time.Clock()
running = True
key = ""
while running:
screen.fill((0,0,0))
sqr1.Draw(outline=True)
sqr2.Draw()
line = DrawLineInBetween()
for sqr_line in sqr1.Lines():
pt = LineIntersect(line,sqr_line)
if pt:
pygame.draw.circle(screen,(0,255,255),(int(pt.x),int(pt.y)),5)
if key == "s":
sqr1.y += 1
elif key == "w":
sqr1.y -= 1
if key == "d":
sqr1.x += 1
if key == "a":
sqr1.x -= 1
pygame.display.update()
Clock.tick(60)
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit()
running = False
if e.type == MOUSEBUTTONDOWN:
print(e.pos)
if e.type == KEYDOWN:
key = e.unicode
if e.type == KEYUP:
key = ""
doing rotating squares:
added rotation variable in square class, i used this answer to find the corners of the square, then once i have the corners, used the line intersetion.
Here is new class:
#class for sqaure
class square:
def __init__(self,x,y,w):
self.x = x
self.y = y
self.w = w
self.centerx = self.x + w//2
self.centery = self.y + w//2
self.col = (255,0,0)
self.rotation_angle = 0
def Draw(self, outline = False):
if outline:
self.Outline()
else:
pygame.draw.rect(screen,self.col,(self.x,self.y,self.w,self.w))
#this used the normal coordinate of an unrotated square to find new coordinates of rotated sqaure
def GetCorner(self,tempX,tempY):
angle = math.radians(self.rotation_angle)
rotatedX = tempX*math.cos(angle) - tempY*math.sin(angle);
rotatedY = tempX*math.sin(angle) + tempY*math.cos(angle);
x = rotatedX + self.centerx;
y = rotatedY + self.centery;
return Vector2(x,y)
def Outline(self):
for point1, point2 in self.Lines():
pygame.draw.line(screen,sqr2.col,point1,point2,1)
#new lines method, only changed to GetCorner()
def Lines(self):
lines = []
top_left = self.GetCorner(self.x - self.centerx, self.y - self.centery)
top_right = self.GetCorner(self.x + self.w - self.centerx, self.y - self.centery)
bottom_left = self.GetCorner(self.x - self.centerx, self.y + self.w - self.centery)
bottom_right = self.GetCorner(self.x + self.w - self.centerx, self.y + self.w - self.centery)
lines.append((top_left,top_right))
lines.append((top_left,bottom_left))
lines.append((bottom_right,top_right))
lines.append((bottom_right,bottom_left))
return lines
#chnaged to this as rotation rotates around center, so need to update both x and centerx
def Move(self,x =None, y = None):
if x:
self.x += x
self.centerx += x
if y:
self.y += y
self.centery += y
#get the lines that make up the square, the outline/perameter
#def Lines(self):
#lines = []
#lines.append((Vector2(self.x,self.y),Vector2(self.x+self.w,self.y)))
#lines.append((Vector2(self.x,self.y),Vector2(self.x,self.y + self.w)))
#lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x+self.w,self.y)))
#lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x,self.y + self.w)))
#return lines

Colision Resolution between Circles - Slow Sliding

I am having trouble with circle to circle collision resolution.
First, i detect the collision, then if the balls collide, i separate them by the sum of their radii and set the velocities. That's easy.
My problem is when gravity is acting and a ball collides with another ball from above. It's suposed to bouce off but instead it slides very slowly until it drops in the ground.
Whats hapning is that afer the colision resolution, gravity pushes the ball down and causes another colision. I've tried separating the ball by the sum of their radii + x but it just slides a little faster.
You can watch the video at http://www.youtube.com/watch?v=tk7qQ9KDFp0&feature=youtu.be.
And here's the code that hadles colision:
for p in world.particle_list:
if not p == self:
if self.pos.sub(p.pos).get_length() <= self.radius * ppm + p.radius * ppm:
p_mass_ratio = float(self.mass) / (self.mass + p.mass)
self_mass_ratio = float(p.mass) / (self.mass + p.mass)
rel_pos = p.pos.sub(self.pos)
shift = rel_pos.set_length(- rel_pos.get_length() + self.radius * ppm + p.radius * ppm)
p.pos = p.pos.add(shift.scale(0.50))
self.pos = self.pos.add(shift.scale(-0.50))
p_speed = p.speed
self_speed = self.speed
self.speed = p_speed.add(self.speed.norm_reflect(rel_pos.set_angle(rel_pos.get_angle() + 90).scale(-self.friction))).scale(0.50 * self_mass_ratio)
p.speed = self_speed.add(p.speed.norm_reflect(rel_pos.set_angle(rel_pos.get_angle() + 90).scale(self.friction))).scale(0.50 * p_mass_ratio)
I made a vector class to handle this:
def dcos(x):
return cos(radians(x))
def dsin(x):
return sin(radians(x))
def dtan(x):
return tan(radians(x))
class Vec(object):
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
self.length = self.get_length()
self.angle = self.get_angle()
def get_length(self):
return sqrt(self.x ** 2 + self.y ** 2)
def get_angle(self):
return atan2(self.y, self.x) * 180 / pi
def add(self, vec1):
new_x = self.x + vec1.x
new_y = self.y + vec1.y
return Vec(new_x, new_y)
def sub(self, vec1):
new_x = self.x - vec1.x
new_y = self.y - vec1.y
return Vec(new_x, new_y)
def scale(self, k):
return Vec(self.x * k, self.y * k)
def set_angle(self, a):
new_x = self.length * dcos(a)
new_y = self.length * dsin(a)
if a == -90 or a == 90:
new_x = 0
if a == 180 or a == 0 or a == -180:
new_y = 0
return Vec(new_x, new_y)
def set_length(self, l):
new_x = l * dcos(self.angle)
new_y = l * dsin(self.angle)
return Vec(new_x, new_y)
def inverse(self):
return Vec(- self.x, - self.y)
def norm_reflect(self, vec1):
if self.get_angle == vec1.get_angle():
return Vec(self.x, self.y)
if vec1.get_angle() >= 0:
return self.set_angle(vec1.get_angle() - self.get_angle() + 90)
else:
return self.set_angle(vec1.get_angle() - self.get_angle() - 90)
(I don't know python, but I know physics and you aren't getting other answers so I'll take a crack at it.)
Look at
if self.pos.sub(p.pos).get_length() <= self.radius * ppm + p.radius * ppm:
...
rel_pos = p.pos.sub(self.pos)
shift = rel_pos.set_length(- rel_pos.get_length() + self.radius * ppm + p.radius * ppm)
p.pos = p.pos.add(shift.scale(0.50))
self.pos = self.pos.add(shift.scale(-0.50))
You're setting the length to a negative number, so you're moving the objects toward each other.
The norm_reflect() function doesn't make much sense, and I suspect it does something other than what you intend (in which case you're not testing your code).
These two lines:
self.speed = p_speed.add(self.speed.norm_reflect(rel_pos.set_angle(rel_pos.get_angle() + 90).scale(-self.friction))).scale(0.50 * self_mass_ratio)
p.speed = self_speed.add(p.speed.norm_reflect(rel_pos.set_angle(rel_pos.get_angle() + 90).scale(self.friction))).scale(0.50 * p_mass_ratio)
seem to be invoking norm_reflect() with only one argument, and you're bringing friction and mass ratios into the picture before the simple case is working, and you're reusing rel_pos after multiple transformations, and the physics is non-Newtonian (i.e. wrong enough to require starting over from a blank slate).

Categories