Incorrect Rotation of Shape in Python - python

I'm trying to create a rotating plane in pygame. I'm converting 3d (x, y, z) coordinates to screen coordinates (x, y), and then rotating the screen coordinates. This seems to work when its rotating on both the x and y axis, but when it's rotating on only one axis (I commented out the y axis rotation) it is slanted. I can't seem to figure out why?
import pygame
import math
red = (255, 0, 0)
class Vector3:
def __init__(self, _x, _y, _z):
self.x = _x
self.y = _y
self.z = _z
class Vector2:
def __init__(self, _x, _y):
self.x = _x
self.y = _y
class Plane:
def draw(self, screen, value):
scale = 25
points = []
vertices = [Vector3(0, 1, 0),
Vector3(1, 1, 0),
Vector3(1, 0, 0),
Vector3(0, 0, 0)]
for vert in vertices:
x, y = vec3to2(vert)
points.append(Vector2(x * scale + 40, y * scale + 100))
print((x, y))
centerx = (points[0].x + points[1].x + points[2].x + points[3].x) / 4
centery = (points[0].y + points[1].y + points[2].y + points[3].y) / 4
for point in points:
rotx, roty = vec3rot(point, math.radians(value), centerx, centery)
point.x = rotx
#point.y = roty
pygame.draw.line(screen, red, (points[0].x, points[0].y), (points[1].x, points[1].y))
pygame.draw.line(screen, red, (points[1].x, points[1].y), (points[2].x, points[2].y))
pygame.draw.line(screen, red, (points[0].x, points[0].y), (points[3].x, points[3].y))
pygame.draw.line(screen, red, (points[3].x, points[3].y), (points[2].x, points[2].y))
pygame.draw.circle(screen, red, (int(centerx), int(centery)), 1)
def vec3to2(vect3):
try:
_x = vect3.x / vect3.z
except ZeroDivisionError:
_x = vect3.x
try:
_y = vect3.y / vect3.z
except ZeroDivisionError:
_y = vect3.y
return(_x, _y)
def vec3rot(vect3, theta, centerx, centery):
_x = centerx + (vect3.x - centerx) * math.cos(theta) - (vect3.y - centery) * math.sin(theta)
_y = centery + (vect3.x - centerx) * math.sin(theta) + (vect3.y - centery) * math.cos(theta)
return(_x, _y)
def main():
pygame.init()
screen = pygame.display.set_mode((640, 480))
v = 0
plane = Plane()
running = True
while running:
screen.fill((0, 0, 0))
plane.draw(screen, v)
pygame.display.flip()
v += 0.1
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
main()

Related

How to obtain the precise values of the streets of a city with OSMnx?

I want to make a traffic simulator of a city and for that I need to get the exact location of the streets and then plot them.
To draw the map I'm taking the streets of omsnx, but these have some flaws.Here I show how it looks like using the omsnx.plot_graph() function.
But using the values of G.edges() and building the graph myself there are inconsistencies like extra streets. This is an example of what the graph looks like using exactly the values provided by the edges.
Note that both images show the same traffic circle.
So the question is how to obtain the real street values without noise, i.e. without several streets playing the role of one.
This is the code I use to plot with pygame.
import osmnx as ox
address_name='Ciudad Deportiva, Havana, Cuba'
#Import graph
point = (23.1021, -82.3936)
G = ox.graph_from_point(point, dist=1000, retain_all=True, simplify=True, network_type='drive')
G = ox.project_graph(G)
import pygame
from map import Map
RED = (255, 0, 0)
BLUE = (0, 0, 20)
GRAY = (215,215,215)
WHITE = (255, 255, 255)
inc = 0.8
m = Map(1400, 800, lng=inc*2555299.469922482, lat=inc*356731.10053785384, i_zoom=0.1)
last_x = -1
last_y = -1
i=0
while True:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 4:
m.inc_zoom()
elif event.button == 5:
m.dec_zoom()
if event.type == pygame.MOUSEMOTION:
if event.buttons[0] == 1:
if last_x == last_y == -1:
last_x, last_y = event.pos
else:
m.x += (event.pos[0] - last_x)/2
m.y += (event.pos[1] - last_y)/2
last_x, last_y = event.pos
if event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
last_x = last_y = -1
m.fill(GRAY)
# Here I take the coordinates of the streets and paint them
for _, _, data in G.edges(data=True):
try:
m.draw_road(data['geometry'], inc, BLUE)
except:
pass
m.update()
Other functions and objects I use for plotting. This is the code of map.py
from typing import List, Tuple
from window import Window
from pygame import gfxdraw
from shapely.geometry import LineString
def build_rect(
start: Tuple[float,float],
end: Tuple[float,float],
width: float = 3
) -> List[Tuple[float,float]]:
x0, y0 = start
x1, y1 = end
if x0**2 + y0**2 > x1**2 + y1**2:
x0, y0 = end
x1, y1 = start
# vector from start to end
vX, vY = x1 - x0, y1 - y0
# normal vector
nX, nY = -vY, vX
# normalize
n = (nX**2 + nY**2)**0.5
nX, nY = width/n * nX, width/n * nY
# third vector
x2, y2 = x1 + nX, y1 + nY
# fourth vector
x3, y3 = x0 + nX, y0 + nY
return [(x0, y0), (x1, y1), (x2, y2), (x3, y3)]
class Map(Window):
def __init__(self, width, height, **kwargs):
super().__init__(width, height, **kwargs)
def draw_road(self, st: LineString, inc: float, color: Tuple[float,float,float]):
last = None
for c in st.__geo_interface__['coordinates']:
c = (inc*c[0], inc*c[1])
if last == None:
last = (c[0] - self.lat,c[1] - self.lng)
continue
lat = c[0] - self.lat
lng = c[1] - self.lng
pts = build_rect(last, (lat, lng))
gfxdraw.filled_polygon(self.screen, [
(self.x + lat * self.zoom, self.y + lng * self.zoom)
for lat, lng in pts
], color)
And this is the code of window.py.
import pygame
from pygame import gfxdraw
from pygame.locals import *
class Window:
def __init__(self, width, height, **kwargs):
self.width = width
self.height = height
self.zoom = 1
self.x = self.y = 0
self.i_zoom = 0.001
self.__dict__.update(kwargs)
pygame.init()
self.screen = pygame.display.set_mode((width, height))
def inc_zoom(self):
self.zoom += self.i_zoom
def dec_zoom(self):
self.zoom -= self.i_zoom
self.zoom = max(0, self.zoom)
def draw_polygon(self, points, color):
gfxdraw.filled_polygon(self.screen, [
(self.x + pt[0] * self.zoom, self.y + pt[1] * self.zoom)
for pt in points], color)
def fill(self, color):
self.screen.fill(color)
def update(self):
pygame.display.update()

My parabola is working fine alone, but it's wrong in Pygame

So I created this parabola class which can be instantiated with 3 parameters (a, b and c) or with 3 points belonging to the parabola. The punti() function returns all the points belonging to the parabola in a range defined by n and m. Here's the code (Most of this is in Italian, sorry):
class Parabola:
def __init__(self, tipo=0, *params):
'''
Il tipo è 0 per costruire la parabola con a, b, c; 1 per costruire la parabola con
tre punti per la quale passa
'''
if tipo == 0:
self.__a = params[0]
self.__b = params[1]
self.__c = params[2]
self.__delta = self.__b ** 2 - (4 * self.__a * self.__c)
elif tipo == 1:
matrix_a = np.array([
[params[0][0]**2, params[0][0], 1],
[params[1][0]**2, params[1][0], 1],
[params[2][0]**2, params[2][0], 1]
])
matrix_b = np.array([params[0][1], params[1][1], params[2][1]])
matrix_c = np.linalg.solve(matrix_a, matrix_b)
self.__a = round(matrix_c[0], 2)
self.__b = round(matrix_c[1], 2)
self.__c = round(matrix_c[2], 2)
self.__delta = self.__b ** 2 - (4 * self.__a * self.__c)
def trovaY(self, x):
y = self.__a * x ** 2 + self.__b * x + self.__c
return y
def punti(self, n, m, step=1):
output = []
for x in range(int(min(n, m)), int(max(n, m)) + 1, step):
output.append((x, self.trovaY(x)))
return output
Now my little game is about shooting targets with a bow and i have to use the parabola for the trajectory and it passes by 3 points:
The player center
A point with the cursor's x and player's y
A point in the middle with the cursors's y
The trajectory is represented by a black line but it clearly doesn't work and I can't understand why. Here's the code of the game (Don't mind about the bow's rotation, I still have to make it function properly):
import os
import sys
import pygame
from random import randint
sys.path.insert(
1, __file__.replace("pygame-prototype\\" + os.path.basename(__file__), "coniche\\")
)
import parabola
# Initialization
pygame.init()
WIDTH, HEIGHT = 1024, 576
screen = pygame.display.set_mode((WIDTH, HEIGHT))
# Function to rotate without losing quality
def rot_from_zero(surface, angle):
rotated_surface = pygame.transform.rotozoom(surface, angle, 1)
rotated_rect = rotated_surface.get_rect()
return rotated_surface, rotated_rect
# Function to map a range of values to another
def map_range(value, leftMin, leftMax, rightMin, rightMax):
# Figure out how 'wide' each range is
leftSpan = leftMax - leftMin
rightSpan = rightMax - rightMin
# Convert the left range into a 0-1 range (float)
valueScaled = float(value - leftMin) / float(leftSpan)
# Convert the 0-1 range into a value in the right range.
return rightMin + (valueScaled * rightSpan)
# Player class
class Player:
def __init__(self, x, y, width=64, height=64):
self.rect = pygame.Rect(x, y, width, height)
self.dirx = 0
self.diry = 0
def draw(self):
rectangle = pygame.draw.rect(screen, (255, 0, 0), self.rect)
# Target class
class Target:
def __init__(self, x, y, acceleration=0.25):
self.x, self.y = x, y
self.image = pygame.image.load(
__file__.replace(os.path.basename(__file__), "target.png")
)
self.speed = 0
self.acceleration = acceleration
def draw(self):
screen.blit(self.image, (self.x, self.y))
def update(self):
self.speed -= self.acceleration
self.x += int(self.speed)
if self.speed < -1:
self.speed = 0
player = Player(64, HEIGHT - 128)
# Targets init
targets = []
targets_spawn_time = 3000
previous_ticks = pygame.time.get_ticks()
# Ground animation init
ground_frames = []
for i in os.listdir(__file__.replace(os.path.basename(__file__), "ground_frames")):
ground_frames.append(
pygame.image.load(
__file__.replace(os.path.basename(__file__), "ground_frames\\" + i)
)
) # Load all ground frames
ground_frame_counter = 0 # Keep track of the current ground frame
frame_counter = 0
# Bow
bow = pygame.image.load(__file__.replace(os.path.basename(__file__), "bow.png"))
angle = 0
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# Spawning the targets
current_ticks = pygame.time.get_ticks()
if current_ticks - previous_ticks >= targets_spawn_time:
targets.append(Target(WIDTH, randint(0, HEIGHT - 110)))
previous_ticks = current_ticks
screen.fill((101, 203, 214))
player.draw()
for i, e in list(enumerate(targets))[::-1]:
e.draw()
e.update()
if e.x <= -e.image.get_rect().width:
del targets[i]
# Calculating the angle of the bow
mouse_pos = pygame.Vector2(pygame.mouse.get_pos())
angle = map_range(mouse_pos.x, 0, WIDTH, 90, 0)
# Rotate the bow
rotated_bow, rotated_bow_rect = rot_from_zero(bow, angle)
rotated_bow_rect.center = player.rect.center
screen.blit(rotated_bow, rotated_bow_rect)
# Animate the ground
if frame_counter % 24 == 0:
ground_frame_counter += 1
if ground_frame_counter >= len(ground_frames):
ground_frame_counter = 0
for i in range(round(WIDTH / ground_frames[ground_frame_counter].get_rect().width)):
screen.blit(
ground_frames[ground_frame_counter],
(
ground_frames[ground_frame_counter].get_rect().width * i,
HEIGHT - ground_frames[ground_frame_counter].get_rect().height,
),
)
# Calculating the trajectory
mouse_pos.x = (
mouse_pos.x if mouse_pos.x != rotated_bow_rect.centerx else mouse_pos.x + 1
)
# print(mouse_pos, rotated_bow_rect.center)
v_x = rotated_bow_rect.centerx + ((mouse_pos.x - rotated_bow_rect.centerx) / 2)
trajectory_parabola = parabola.Parabola(
1,
rotated_bow_rect.center,
(mouse_pos.x, rotated_bow_rect.centery),
(v_x, mouse_pos.y),
)
trajectory = [(i[0], int(i[1])) for i in trajectory_parabola.punti(0, WIDTH)]
pygame.draw.lines(screen, (0, 0, 0), False, trajectory)
pygame.draw.ellipse(
screen, (128, 128, 128), pygame.Rect(v_x - 15, mouse_pos.y - 15, 30, 30)
)
pygame.draw.ellipse(
screen,
(128, 128, 128),
pygame.Rect(mouse_pos.x - 15, rotated_bow_rect.centery - 15, 30, 30),
)
pygame.display.update()
if frame_counter == 120:
for i in trajectory:
print(i)
frame_counter += 1
You can run all of this and understand what's wrong with it, help?
You round the values of a, b and c to 2 decimal places. This is too inaccurate for this application:
self.__a = round(matrix_c[0], 2)
self.__b = round(matrix_c[1], 2)
self.__c = round(matrix_c[2], 2)
self.__a = matrix_c[0]
self.__b = matrix_c[1]
self.__c = matrix_c[2]
Similar to answer above... rounding is the issue here. This is magnified when the scale of the coordinates gets bigger.
However, disagree with other solution: It does not matter what order you pass the coordinates into your parabola construction. Any order works fine. points are points.
Here is a pic of your original parabola function "drooping" because of rounding error:
p1 = (0, 10) # left
p2 = (100, 10) # right
p3 = (50, 100) # apex
p = Parabola(1, p3, p2, p1)
traj = p.punti(0, 100)
xs, ys = zip(*traj)
plt.scatter(xs, ys)
plt.plot([0, 100], [10, 10], color='r')
plt.show()

Collision detection between an ellipse and a circle

I want to make a collision detection between an ellipse and circle. The way I am doing this is:
Calculate angle from centre of the ellipse to the circle
Calculate where the point lies in the ellipse at that angle
Check for collision against that point
However, I am having one small problem. When I calculate the angle, it seems like it is 90 degrees off. I did a dirty fix by simply adding 1.5 radians to account for the 90 degrees, and it kind of works but there are inconsistencies and doesn't work properly at certain angles, particularly at around 0.7 and -2.6 radians . Here is the code (all the collision stuff is in collision method in Ellipse class)
import pygame
from math import sin, cos, atan2, radians
pygame.init()
SW = 1200
SH = 600
WIN = pygame.display
D = WIN.set_mode((SW, SH))
class Circle:
def __init__(self, radius):
self.x = 0
self.y = 0
self.radius = radius
def update(self, pos):
self.x = pos[0]
self.y = pos[1]
def draw(self, display):
pygame.draw.circle(display, (255, 0, 0), (int(self.x), int(self.y)), self.radius, 2)
circle = Circle(30)
class Ellipse:
def __init__(self, centre, rx, ry):
self.centre = centre
self.collided = False
self.rx = rx
self.ry = ry
def draw(self, display):
angle = 0
while angle < 6.28:
angle += 0.001
x = self.centre[0] + sin(angle)* self.rx
y = self.centre[1] + cos(angle)* self.ry
if self.collided:
display.set_at((int(x), int(y)), (255, 0, 0))
else:
display.set_at((int(x), int(y)), (0, 0, 255))
pygame.draw.circle(D, (0, 255, 0), (int(self.centre[0]), int(self.centre[1])), 5)
def collision(self, circle):
#angle to the circle
dx = circle.x - self.centre[0]
dy = circle.y - self.centre[1]
angle = atan2(-dy, dx)
print(angle)
#where the point lies in the ellipse at that angle
x = sin(angle + 1.5)* self.rx + self.centre[0]
y = cos(angle + 1.5)* self.ry + self.centre[1]
#print(x, y)
#drawing the point just to make sure its working
# (debugging)
pygame.draw.circle(D, (0, 255, 0), (int(x), int(y)), 5)
# distance between the point we just
# calculated and the circle's centre
distance = ((x-circle.x)**2 + (y-circle.y)**2)**0.5
#print(distance)
#collision condition
if distance < circle.radius:
self.collided = True
else:
self.collided = False
ellipse = Ellipse([600, 300], 300, 200)
while True:
events = pygame.event.get()
mousePos = pygame.mouse.get_pos()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
D.fill((255, 255, 255))
circle.update(mousePos)
circle.draw(D)
ellipse.draw(D)
ellipse.collision(circle)
pygame.display.flip()
The fist mistake is, that 1.5 is not equal to pi/2:
from math import pi
x = sin(angle + 1.5)* self.rx + self.centre[0]
y = cos(angle + 1.5)* self.ry + self.centre[1]
x = sin(angle + pi/2)* self.rx + self.centre[0]
y = cos(angle + pi/2)* self.ry + self.centre[1]
Second, calculating the point on an ellipse by an angle is a little more complicated. See the answers to the questions How to get a point on an ellipse's outline given an angle? and Calculating a Point that lies on an Ellipse given an Angle:
from math import pi, sin, cos, atan2, radians, copysign, sqrt
class Ellipse:
# [...]
def pointFromAngle(self, a):
c = cos(a)
s = sin(a)
ta = s / c ## tan(a)
tt = ta * self.rx / self.ry ## tan(t)
d = 1. / sqrt(1. + tt * tt)
x = self.centre[0] + copysign(self.rx * d, c)
y = self.centre[1] - copysign(self.ry * tt * d, s)
return x, y
def collision(self, circle):
# [...]
#where the point lies in the ellipse at that angle
x, y = self.pointFromAngle(angle)
# [...]
Minimal example: repl.it/#Rabbid76/PyGame-IntersectCircleEllipse
import math
import pygame
class Circle:
def __init__(self, center_x, center_y, radius):
self.center = center_x, center_y
self.radius = radius
def update(self, center_x, center_y):
self.center = center_x, center_y
def draw(self, surface):
pygame.draw.circle(surface, (255, 0, 0), (round(self.center[0]), round(self.center[1])), self.radius, 3)
class Ellipse:
def __init__(self, center, vertex):
self.center = center
self.collided = False
self.vertex = vertex
def draw(self, surface):
bounding_rect = pygame.Rect(0, 0, self.vertex[0] * 2, self.vertex[1] * 2)
bounding_rect.center = round(self.center[0]), round(self.center[1])
pygame.draw.ellipse(surface, (0, 255, 0), bounding_rect, 3)
def pointFromAngle(self, a):
c = math.cos(a)
s = math.sin(a)
ta = s / c ## tan(a)
tt = ta * self.vertex[0] / self.vertex[1] ## tan(t)
d = 1. / math.sqrt(1. + tt * tt)
x = self.center[0] + math.copysign(self.vertex[0] * d, c)
y = self.center[1] - math.copysign(self.vertex[1] * tt * d, s)
return x, y
def intersect_circle_ellipse(circle, ellipse):
dx = circle.center[0] - ellipse.center[0]
dy = circle.center[1] - ellipse.center[1]
angle = math.atan2(-dy, dx)
x, y = ellipse.pointFromAngle(angle)
distance = math.hypot(x - circle.center[0], y-circle.center[1])
return distance <= circle.radius, (x, y)
pygame.init()
window = pygame.display.set_mode((500, 300))
circle = Circle(0, 0, 30)
ellipse = Ellipse(window.get_rect().center, (150, 100))
run = True
while run:
events = pygame.event.get()
mousePos = pygame.mouse.get_pos()
for event in events:
if event.type == pygame.QUIT:
run = False
circle.update(*mousePos)
isect = intersect_circle_ellipse(circle, ellipse)
window.fill((255, 255, 255))
circle.draw(window)
ellipse.draw(window)
color = (255, 0, 255) if isect[0] else (0, 0, 255)
pygame.draw.circle(window, color, (round(isect[1][0]), round(isect[1][1])), 5)
pygame.display.flip()
pygame.quit()
exit()

How create a simulation (pendulum, projectile motion) in pygame

I created a program that generates the placement of the pendulum, length, position of the ball, velocity, angles, and trajectory. The program's task is to find a solution where the ball can land safely through a 'cave'. The pendulum is inside an 85.75 by 66.75 area, length < 65, ball radius = 1.25
I want to create a simulation of the experiment in pygame, that will run my 1st program to generate all the parameters, and then display the solution and path the ball will follow. I have spent the past couple days learning pygame, but can't figure out how to 'transfer' my first program. Ive looked at other pendulum simulators, and tried to change it to work for my experiment, but I got lost and decided to come to StackOverflow for advice. If anyone could show me where I went wrong in making the simulation, it would be very appreciated.
first program
import math as m
import numpy as np
# Variables
c = 28.5
Wx = 20
Wy = 30
d = 85.75
f = 66.75
g = 385.826772
ay = -g
# Calculations
for theta in np.arange(1, 90, .01):
l = Wx + (m.tan(m.radians(theta)) * (f - Wy))
if Wx <= l <= d:
phi = 90 - theta
v = (d - l) / m.sin(m.radians(phi))
vc = v - 1.25
if (f - Wy) <= v <= 65:
h = f - (m.cos(m.radians(phi)) * v)
a = v * m.sin(m.radians(theta))
b = v * m.cos(m.radians(theta))
by = f - b
bx = l - a
if h <= f and by <= c:
vel = m.sqrt((2 * g) * (h - by)) * .95
velx = vel * m.cos(m.radians(theta))
vely = vel * m.sin(m.radians(theta))
y = (-vely**2) / (2 * ay)
Ymax = y + by
if m.isclose(Ymax, c, abs_tol= .01):
t1 = -vely / ay
t2 = m.sqrt((2 * Ymax) / -ay)
T = t1 + t2
x = velx * T
print(' l: {0} v: {1} vc: {2} h: {3}\n bx: {4} by: {5}\n vel: {6} velx: {7} vely: {8}\n y: {9} Ymax: {10} x: {11} T: {12}\n theta: {13} phi: {14}\n'
.format(l, v, vc, h, bx, by, vel, velx, vely, y, Ymax, x, T, theta, phi))
Simulator
import pygame
import numpy as np
import math as m
from math import pi
# Tarzan Variables
c = 28.5
Wy = 30
Wx = 20
d = 85.75
f = 66.75
# Colors
black = (0, 0, 0)
red = (255, 0, 0)
white = (255, 255, 255)
green = (0, 255, 0)
# Pygame Variables
theta = 0
v = 0
vel = 0
acc = 0
# Start Pygame
width, height = 900, 700
pygame.init()
background = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
# Tarzan
class Pendulum(object):
def __init__(self, XY, l, radius):
self.x = XY[0]
self.y = XY[1]
self.l = l
self.radius = radius
def draw(self, bg):
pygame.draw.line(bg, white, (self.l, 0), (self.x, self.y), 4)
pygame.draw.circle(bg, red, (self.x, self.y), self.radius)
pygame.draw.line(bg, green, (Wx, height), (Wx, (height - Wy)), 4)
# pygame.draw.circle(bg, white, (self.l, 0), int(v)) --- to see if pendulum is following an arc
def theta_v():
v = m.sqrt(m.pow(pendulum.x - (width / 2), 2) + m.pow(pendulum.y, 2))
theta = m.asin(((pendulum.x - (width / 2)) / v))
return theta, v
def get_path(theta, v):
pendulum.x = round(pendulum.l + (v * m.sin(theta)))
pendulum.y = round(v * m.cos(theta))
pendulum.l = pendulum.x - (v * m.sin(m.radians(theta)))
def redraw():
background.fill(black)
pendulum.draw(background)
pygame.display.update()
pendulum = Pendulum((75, 67), 500, 15)
# Close Pygame
stop = False
acceleration = False
while not stop:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
stop = True
if event.type == pygame.MOUSEBUTTONDOWN:
pendulum = Pendulum(pygame.mouse.get_pos(), 500, 15)
theta, v = theta_v()
acceleration = True
if acceleration:
acc = -.005 * m.sin(theta)
vel += acc
vel *= .995
theta += vel
get_path(theta, v)
print(pendulum.x, pendulum.y, (theta * (180 / pi)), v, vel, pendulum.l)
redraw()
pygame.quit()
quit()
I suppose you wanted this program to create a hanging pendulum swinging around correctly. There were a few things wrong with your code:
In order to calculate your pendulum.l value, you converted theta to radians first. As your theta value was already in radians, This was absolutely not nessary, and makes your pendulum.l value almost not change.
When you now execute your code, you might see that the attachment point of the pendulum is changing. This is because you are changing pendulum.l, and use it for drawingg the pendulum at the same time. This can be easily fixed by saving your first pendulum.l value, and use that value to draw the pendulum.
Your program keeps attampting to move the pendulum forever, giving sometimes unexpected results. You should add some way to figure out whether the pendulum is still moving (probably by chenking whether one of the variables is not changing enough - I had not enough time to figure it out)
Rounding your x and y values causes a buildup if rounding errors on the end, making the pendulium coming to a stop while still being tilted a bit. You can just round while drawing the penduluim to fix this.
The entire code should look like this:
import time
import pygame
import numpy as np
import math as m
from math import pi
# Tarzan Variables
c = 28.5
Wy = 30
Wx = 20
d = 85.75
f = 66.75
# Colors
black = (0, 0, 0)
red = (255, 0, 0)
white = (255, 255, 255)
green = (0, 255, 0)
# Pygame Variables
theta = 0
v = 0
vel = 0
acc = 0
# Start Pygame
width, height = 900, 700
pygame.init()
background = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
# Tarzan
class Pendulum(object):
def __init__(self, XY, l, radius):
self.x = XY[0]
self.y = XY[1]
self.l = l
self.lfixed = l
self.radius = radius
def draw(self, bg):
pygame.draw.line(bg, white, (self.lfixed, 0), (self.x, self.y), 4)
pygame.draw.circle(bg, red, (round(self.x), round(self.y)), self.radius)
pygame.draw.line(bg, green, (Wx, height), (Wx, (height - Wy)), 4)
# pygame.draw.circle(bg, white, (self.l, 0), int(v)) --- to see if pendulum is following an arc
def theta_v():
v = m.sqrt(m.pow(pendulum.x - (width / 2), 2) + m.pow(pendulum.y, 2))
theta = m.asin(((pendulum.x - (width / 2)) / v))
return theta, v
def get_path(theta, v):
pendulum.x = pendulum.l + (v * m.sin(theta))
pendulum.y = v * m.cos(theta)
pendulum.l = pendulum.x - (v * m.sin((theta)))
def redraw():
background.fill(black)
pendulum.draw(background)
pygame.display.update()
pendulum = Pendulum((75, 67), 500, 15)
# Close Pygame
stop = False
acceleration = False
while not stop:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
stop = True
if event.type == pygame.MOUSEBUTTONDOWN:
pendulum = Pendulum(pygame.mouse.get_pos(), 500, 15)
theta, v = theta_v()
acceleration = True
if acceleration:
acc = -.005 * m.sin(theta)
vel += acc
vel *= .995
theta += vel
get_path(theta, v)
print(pendulum.x, pendulum.y, (theta * (180 / pi)), v, vel, pendulum.l)
redraw()
time.sleep(0.1)
pygame.quit()
quit()

How to draw a Spiral & Arc Shape Curves using pygame? [duplicate]

I am trying to make realistic water in pygame:
This is till now my code:
from random import randint
import pygame
WIDTH = 700
HEIGHT = 500
win = pygame.display.set_mode((WIDTH, HEIGHT))
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
AQUA = 'aqua'
RADIUS = 1
x, y = 0, HEIGHT//2
K = 1
FORCE = 100
VELOCITY = 0.5
run = True
class Molecule:
def __init__(self, x, y, radius, force, k):
self.x = x
self.y = y
self.radius = radius
self.force = force
self.k = k
self.max_amplitude = y + force/k
self.min_amplitude = y - force/k
self.up = False
self.down = True
self.restore = False
def draw(self, win):
pygame.draw.circle(win, BLACK, (self.x, self.y), self.radius)
def oscillate(self):
if self.y <= self.max_amplitude and self.down == True:
self.y += VELOCITY
if self.y == self.max_amplitude or self.up:
self.up = True
self.down = False
self.y -= VELOCITY
if self.y == self.min_amplitude:
self.up = False
self.down = True
molecules = []
for i in range(100):
FORCE = randint(10, 20)
molecules.append(Molecule(x, y, RADIUS, FORCE, K))
x += 10
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill(WHITE)
for molecule in molecules:
molecule.draw(win)
molecule.oscillate()
for i in range(len(molecules)):
try:
pygame.draw.line(win, BLACK, (molecules[i].x, molecules[i].y), (molecules[i+1].x, molecules[i+1].y))
pygame.draw.line(win, AQUA, (molecules[i].x, molecules[i].y), (molecules[i+1].x, HEIGHT))
except:
pass
pygame.display.flip()
pygame.quit()
But as may expected the water curve is not smooth:
Look at it:
Sample Img1
I want to connect the two randomly added wave points using a set of circles not line like in this one so that a smooth curve could occur.
And in this way i could add the water color to it such that it will draw aqua lines or my desired color line from the point to the end of screen and all this will end up with smooth water flowing simulation.
Now the question is how could i make the points connect together smoothly into a smooth curve by drawing point circles at relative points?
I suggest sticking the segments with a Bézier curves. Bézier curves can be drawn with pygame.gfxdraw.bezier
Calculate the slopes of the tangents to the points along the wavy waterline:
ts = []
for i in range(len(molecules)):
pa = molecules[max(0, i-1)]
pb = molecules[min(len(molecules)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
Use the the tangents to define 4 control points for each segment and draw the curve with pygame.gfxdraw.bezier:
for i in range(len(molecules)-1):
p0 = molecules[i].x, molecules[i].y
p3 = molecules[i+1].x, molecules[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pygame.gfxdraw.bezier(win, [p0, p1, p2, p3], 4, BLACK)
Complete example:
from random import randint
import pygame
import pygame.gfxdraw
WIDTH = 700
HEIGHT = 500
win = pygame.display.set_mode((WIDTH, HEIGHT))
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
AQUA = 'aqua'
RADIUS = 1
x, y = 0, HEIGHT//2
K = 1
FORCE = 100
VELOCITY = 0.5
class Molecule:
def __init__(self, x, y, radius, force, k):
self.x = x
self.y = y
self.radius = radius
self.force = force
self.k = k
self.max_amplitude = y + force/k
self.min_amplitude = y - force/k
self.up = False
self.down = True
self.restore = False
def draw(self, win):
pygame.draw.circle(win, BLACK, (self.x, self.y), self.radius)
def oscillate(self):
if self.y <= self.max_amplitude and self.down == True:
self.y += VELOCITY
if self.y == self.max_amplitude or self.up:
self.up = True
self.down = False
self.y -= VELOCITY
if self.y == self.min_amplitude:
self.up = False
self.down = True
molecules = []
for i in range(50):
FORCE = randint(10, 20)
molecules.append(Molecule(x, y, RADIUS, FORCE, K))
x += 20
clock = pygame.time.Clock()
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill(WHITE)
for molecule in molecules:
molecule.draw(win)
molecule.oscillate()
ts = []
for i in range(len(molecules)):
pa = molecules[max(0, i-1)]
pb = molecules[min(len(molecules)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
for i in range(len(molecules)-1):
p0 = molecules[i].x, molecules[i].y
p3 = molecules[i+1].x, molecules[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pygame.gfxdraw.bezier(win, [p0, p1, p2, p3], 4, BLACK)
for i in range(len(molecules)-1):
pygame.draw.line(win, AQUA, (molecules[i].x, molecules[i].y), (molecules[i].x, HEIGHT))
pygame.display.flip()
pygame.quit()
If you want to "fill" the water, you must calculate the points along the Bézier line and draw a filled polygon. How to calculate a Bézier curve is explained in Trying to make a Bezier Curve on PyGame library How Can I Make a Thicker Bezier in Pygame? and "X". You can use the following function:
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(b, samples):
return [ptOnCurve(b, i/samples) for i in range(samples+1)]
Use the bezier to stitch the wavy water polygon:
ts = []
for i in range(len(molecules)):
pa = molecules[max(0, i-1)]
pb = molecules[min(len(molecules)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
pts = [(WIDTH, HEIGHT), (0, HEIGHT)]
for i in range(len(molecules)-1):
p0 = molecules[i].x, molecules[i].y
p3 = molecules[i+1].x, molecules[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pts += bezier([p0, p1, p2, p3], 4)
Draw the polygon with pygame.draw.polygon():
pygame.draw.polygon(win, AQUA, pts)
Complete example:
from random import randint
import pygame
class Node:
def __init__(self, x, y, force, k, v):
self.x = x
self.y = y
self.y0 = y
self.force = force
self.k = k
self.v = v
self.direction = 1
def oscillate(self):
self.y += self.v * self.direction
if self.y0 - self.force / self.k > self.y or self.y0 + self.force / self.k < self.y:
self.direction *= -1
def draw(self, surf):
pygame.draw.circle(surf, "black", (self.x, self.y), 3)
window = pygame.display.set_mode((700, 500))
clock = pygame.time.Clock()
width, height = window.get_size()
no_of_nodes = 25
dx = width / no_of_nodes
nodes = [Node(i*dx, height//2, randint(15, 30), 1, 0.5) for i in range(no_of_nodes+1)]
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(b, samples):
return [ptOnCurve(b, i/samples) for i in range(samples+1)]
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for molecule in nodes:
molecule.oscillate()
ts = []
for i in range(len(nodes)):
pa = nodes[max(0, i-1)]
pb = nodes[min(len(nodes)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
pts = [(width, height), (0, height)]
for i in range(len(nodes)-1):
p0 = nodes[i].x, nodes[i].y
p3 = nodes[i+1].x, nodes[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pts += bezier([p0, p1, p2, p3], 4)
window.fill("white")
pygame.draw.polygon(window, 'aqua', pts)
for molecule in nodes:
molecule.draw(window)
pygame.display.flip()
pygame.quit()
exit()

Categories