more efficient wind tunnel simulation in Pygame, using numpy - python

I am an aerospace student working on a school project for our python programming course. The assignment is create a program only using Pygame and numpy. I decided to create a wind tunnel simulation that simulates the airflow over a two dimensional wing. I was wondering if there is a more efficient way of doing the computation from a programming perspective. I will explain the program:
I have attached an image here:
The (steady) flow field is modeled using the vortex panel method. Basically, I am using a grid of Nx times Ny points where at each point a velocity (u,v) vector is given. Then using Pygame I map these grid points as circles, so that they resemble an area of influence. The grid points are the grey circles in the following image:
I create N particles and determine their velocities by iterating as follows:
create a list of particles.
create a grid list.
for each gridpoint in grid list:
  for each particle in list of particles:
  if particle A is within the area of influence of grid point n (xn,yn):    particle A its velocity = velocity at grid point n.
Visualize everything in Pygame.
this basic way was the only way I could think of visualizing the flow in Pygame. The simulation works pretty well, but If I increase the number of grid points(increase the accuracy of the flow field), the performance decreases. My question is if there is a more efficient way to do this just using pygame and numpy?
I have attached the code here:
import pygame,random,sys,numpy
from Flow import Compute
from pygame.locals import *
import random, math, sys
#from PIL import Image
pygame.init()
Surface = pygame.display.set_mode((1000,600))
#read the airfoil geometry from a dat file
with open ('./resources/naca0012.dat') as file_name:
x, y = numpy.loadtxt(file_name, dtype=float, delimiter='\t', unpack=True)
#parameters used to describe the flow
Nx=30# 30 column grid
Ny=10#10 row grid
N=20#number of panels
alpha=0#angle of attack
u_inf=1#freestream velocity
#compute the flow field
u,v,X,Y= Compute(x,y,N,alpha,u_inf,Nx,Ny)
#The lists used for iteration
Circles = []
Particles= []
Velocities=[]
#Scaling factors used to properly map the potential flow datapoints into Pygame
magnitude=400
vmag=30
umag=30
panel_x= numpy.multiply(x,magnitude)+315
panel_y= numpy.multiply(-y,magnitude)+308
#build the grid suited for Pygame
grid_x= numpy.multiply(X,magnitude)+300
grid_y= numpy.multiply(Y,-1*magnitude)+300
grid_u =numpy.multiply(u,umag)
grid_v =numpy.multiply(v,-vmag)
panelcoordinates= zip(panel_x, panel_y)
# a grid area
class Circle:
def __init__(self,xpos,ypos,vx,vy):
self.radius=16
self.x = xpos
self.y = ypos
self.speedx = 0
self.speedy = 0
#create the grid list
for i in range(Ny):
for s in range(Nx):
Circles.append(Circle(int(grid_x[i][s]),int(grid_y[i][s]),grid_u[i][s],grid_v[i][s]))
Velocities.append((grid_u[i][s],grid_v[i][s]))
#a particle
class Particle:
def __init__(self,xpos,ypos,vx,vy):
self.image = pygame.Surface([10, 10])
self.image.fill((150,0,0))
self.rect = self.image.get_rect()
self.width=4
self.height=4
self.radius =2
self.x = xpos
self.y = ypos
self.speedx = 30
self.speedy = 0
#change particle velocity if collision with grid point
def CircleCollide(Circle,Particle):
Particle.speedx = int(Velocities[Circles.index((Circle))][0])
Particle.speedy = int(Velocities[Circles.index((Circle))][1])
#movement of particles
def Move():
for Particle in Particles:
Particle.x += Particle.speedx
Particle.y += Particle.speedy
#create particle streak
def Spawn(number_of_particles):
for i in range(number_of_particles):
i=i*(300/number_of_particles)
Particles.append(Particle(0, 160+i,1,0))
#create particles again if particles are out of wake
def Respawn(number_of_particles):
for Particle in Particles:
if Particle.x >1100:
Particles.remove(Particle)
if Particles==[]:
Spawn(number_of_particles)
#Collsion detection using pythagoras and distance formula
def CollisionDetect():
for Circle in Circles:
for Particle in Particles:
if Particle.y >430 or Particle.y<160:
Particles.remove(Particle)
if math.sqrt( ((Circle.x-Particle.x)**2) + ((Circle.y-Particle.y)**2) ) <= (Circle.radius+Particle.radius):
CircleCollide(Circle,Particle)
#draw everything
def Draw():
Surface.fill((255,255,255))
#Surface.blit(bg,(-300,-83))
for Circle in Circles:
pygame.draw.circle(Surface,(245,245,245),(Circle.x,Circle.y),Circle.radius)
for Particle in Particles:
pygame.draw.rect(Surface,(150,0,0),(Particle.x,Particle.y,Particle.width,Particle.height),0)
#pygame.draw.rect(Surface,(245,245,245),(Circle.x,Circle.y,1,16),0)
for i in range(len(panelcoordinates)-1):
pygame.draw.line(Surface,(0,0,0),panelcoordinates[i],panelcoordinates[i+1],3)
pygame.display.flip()
def GetInput():
keystate = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == QUIT or keystate[K_ESCAPE]:
pygame.quit();sys.exit()
def main():
#bg = pygame.image.load("pressure.png")
#bg = pygame.transform.scale(bg,(1600,800))
#thesize= bg.get_rect()
#bg= bg.convert()
number_of_particles=10
Spawn(number_of_particles)
clock = pygame.time.Clock()
while True:
ticks = clock.tick(60)
GetInput()
CollisionDetect()
Move()
Respawn(number_of_particles)
Draw()
if __name__ == '__main__': main()
The code requires another script that computes the flow field itself. It also reads datapoints from a textfile to get the geometry of the wing.
I have not provided these two files, but I can add them if necessary. Thank you in advance.

One bottleneck in your code is likely collision detection. CollisionDetect() computes the distance between each particle and each circle. Then, if a collision is detected, CircleCollide() finds the index of the circle in Circles (a linear search), so that the velocities can be retrieved from the same index in Velocities. Clearly this is ripe for improvement.
First, the Circle class already has the velocities in the speedx/speedy attributes, so Velocities can be eliminated .
Second, because the circles are at fixed locations, you can calculate which circle is closest to any given particle from the position of the particle.
# You may already have these values from creating grid_x etc.
# if not, you only need to calculated them once, because the
# circles don't move
circle_spacing_x = Circles[1].x - Circles[0].x
circle_spacing_y = Circles[Nx].y - Circles[0].y
circle_first_x = Circles[0].x - circle_spacing_x / 2
circle_first_y = Circles[0].y - circle_spacing_y / 2
Then CollisionDetect() becomes:
def CollisionDetect():
for particle in Particles:
if particle.y >430 or particle.y<160:
Particles.remove(particle)
continue
c = (particle.x - circle_first_x) // circle_spacing_x
r = (particle.y - circle_first_y) // circle_spacing_y
circle = Circles[r*Nx + c]
if ((circle.x - particle.x)**2 + (circle.y - particle.y)**2
<= (circle.radius+particle.radius)**2):
particle.speedx = int(circle.speedx)
particle.speedy = int(circle.speedy)

I've tidied up your code and made a few changes, namely adding scope to your classes and introducing a couple more. Without further knowledge of Flow I cannot test this fully, but if you could get back to me I can do some more. I'm assuming here that the 'flow field' can be simulated by the numpy.meshgrid function.
import pygame,numpy,sys
import pygame.locals
import math
class Particle:
def __init__(self,xpos,ypos,vx,vy):
self.size = numpy.array([4,4])
self.radius =2
self.pos = numpy.array([xpos,ypos])
self.speed = numpy.array([30,0])
self.rectangle = numpy.hstack((self.pos,self.size))
def move(self):
self.pos += self.speed
self.rectangle = numpy.hstack((self.pos,self.size))
def distance(self,circle1):
return math.sqrt(numpy.sum((circle1.pos - self.pos)**2))
def collision(self,circle1):
result = False
if self.pos[1] >430 or self.pos[1]<160:
result = True
if self.distance(circle1) <= (circle1.radius+self.radius):
self.speed = circle1.speed
return result
class Particles:
def __init__(self,num_particles):
self.num = num_particles
self.particles =[]
self.spawn()
def spawn(self):
for i in range(self.num):
i=i*(300/self.num)
self.particles.append(Particle(0, 160+i,1,0))
def move(self):
for particle in self.particles:
particle.move()
if particle.pos[0] >1100:
self.particles.remove(particle)
if not self.particles: self.spawn()
def draw(self):
for particle in self.particles:
pygame.draw.rect(Surface,(150,0,0),particle.rectangle,0)
def collisiondetect(self,circle1):
for particle in self.particles:
if particle.collision(circle1):
self.particles.remove(particle)
def GetInput():
keystate = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == pygame.locals.QUIT or keystate[pygame.locals.K_ESCAPE]:
pygame.quit()
sys.exit()
#draw everything
def Draw(sw,cir):
Surface.fill((255,255,255))
cir.draw()
for i in range(panelcoordinates.shape[1]):
pygame.draw.line(Surface,(0,0,0),panelcoordinates[0,i-1],panelcoordinates[0,i],3)
sw.draw()
pygame.display.flip()
# a grid area
class Circle:
def __init__(self,xpos,ypos,vx,vy):
self.radius=16
self.pos = numpy.array([xpos,ypos])
self.speed = numpy.array([vx,vy])
class Circles:
def __init__(self,columns,rows):
self.list = []
grid_x,grid_y = numpy.meshgrid(numpy.linspace(0,1000,columns),numpy.linspace(200,400,rows))
grid_u,grid_v = numpy.meshgrid(numpy.linspace(20,20,columns),numpy.linspace(-1,1,rows))
for y in range(rows):
for x in range(columns):
c1= Circle(int(grid_x[y,x]),int(grid_y[y,x]),grid_u[y,x],grid_v[y,x])
self.list.append(c1)
def draw(self):
for circle in self.list:
pygame.draw.circle(Surface,(245,245,245),circle.pos,circle.radius)
def detectcollision(self,parts):
for circle in self.list:
parts.collisiondetect(circle)
if __name__ == '__main__':
#initialise variables
number_of_particles=10
Nx=30
Ny=10
#circles and particles
circles1 = Circles(Nx,Ny)
particles1 = Particles(number_of_particles)
#read the airfoil geometry
panel_x = numpy.array([400,425,450,500,600,500,450,425,400])
panel_y = numpy.array([300,325,330,320,300,280,270,275,300])
panelcoordinates= numpy.dstack((panel_x,panel_y))
#initialise PyGame
pygame.init()
clock = pygame.time.Clock()
Surface = pygame.display.set_mode((1000,600))
while True:
ticks = clock.tick(6)
GetInput()
circles1.detectcollision(particles1)
particles1.move()
Draw(particles1,circles1)
I've also made some a crude stab at drawing a aerofoil, as again I don't have the knowledge of the data file with the coordinates.

Related

Class angle value is reset after running move function

I am working on a little project to have autonomous cells move around and eventually be a little game of life simulation. Currently I'm having an issue with randomizing the cells movement. I have the cell as a class and set the starting angle in the init then in a move function the angle is updated. For some reason the updated angle is reset the next time the move function is called. To handle the simulation window and physics I'm using Python Arcade with pymunk physics.
Cell Class
import arcade
import random
import math
from dice import Dice
cell_types = ["Plant", "Animal"] # add fungus and virus later
d20 = Dice(20, 1)
count = 0
class Cell(arcade.SpriteCircle):
""" Cell Sprite """
def __init__(self, radius, color, soft, mass, x, y):
""" Init """
# initialize SpriteCircle parent class
super().__init__(radius, color, soft)
# body
self.mass = radius * mass
self.speed = radius
self.center_x = x
self.center_y = y
self.angle = random.randint(0, 360)
self.hit_box_algorithm = "Simple"
# characteristics
self.type = random.choice(cell_types)
def move(self):
# roll a d20
roll = d20.roll()
print(f"roll: {roll}")
# if d20 is 15 or more turn right
# if d20 is 5 or less turn left
print(f"old angle: {self.angle}")
if roll >= 15:
self.angle -= 90
elif roll <= 5:
self.angle += 90
print(f"new angle: {self.angle}")
# convert angle to radians
angle_rad = math.radians(self.angle)
# find next coordinates and save as a tuple
print(f"old x pos: {self.center_x}")
print(f"old y pos: {self.center_y}")
self.center_x += self.speed * math.cos(angle_rad)
self.center_y += self.speed * math.sin(angle_rad)
print(f"new x pos: {self.center_x}")
print(f"new y pos: {self.center_y}")
# return the tuple for apply force function
movement_vector = (self.center_x, self.center_y)
return movement_vector
Dice class for reference, it's just a way to have a randrange as an object instead of typing out the function each time and does function as expected otherwise the results later in the post would not have any variance between the old and new angles.
import random
class Dice:
""" Create a die specifying sides and how many dice"""
def __init__(self, sides, count=1):
self.sides = sides
self.count = count
def roll(self):
""" Roll the set of dice"""
total = 0
for i in range(self.count):
total += random.randrange(1, self.sides)
return total
The class is initialized as a "new_cell" and added into a spritelist and when the on_update function runs, the move function is called.
Relevant code for main.py using arcades boilerplate window template, segments of boilerplate not in use have been cut.
https://api.arcade.academy/en/stable/examples/starting_template.html#starting-template
import arcade
import random
from typing import Optional
from cell import Cell
from dice import Dice
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Autonomous Cells"
STARTING_CELL_COUNT = 1
SPRITE_SIZE = 32
SPRITE_SCALING = .15
CELL_SIZE = SPRITE_SIZE * SPRITE_SCALING
CELL_SIZE_MIN_MULTIPLIER = 1
CELL_SIZE_MAX_MULTIPLIER = 5
DEFAULT_DAMPING = .5
CELL_DAMPING = 0.4
CELL_FRICTION = 0.5
DEFAULT_CELL_MASS = 1.0
CELL_MAX_SPEED = 50
class MyGame(arcade.Window):
"""
Main application class.
NOTE: Go ahead and delete the methods you don't need.
If you do need a method, delete the 'pass' and replace it
with your own code. Don't leave 'pass' in this program.
"""
def __init__(self, width, height, title):
super().__init__(width, height, title)
arcade.set_background_color(arcade.color.DARK_BLUE_GRAY)
# If you have sprite lists, you should create them here,
# and set them to None
self.cell_sprite_list = None
# physics engine
self.physics_engine = Optional[arcade.PymunkPhysicsEngine]
def setup(self):
""" Set up the game variables. Call to re-start the game. """
# Create your sprites and sprite lists here
self.cell_sprite_list = arcade.SpriteList()
for i in range(STARTING_CELL_COUNT):
new_color = (random.randrange(256),
random.randrange(256),
random.randrange(256)
)
new_cell = Cell(radius=(random.randint(CELL_SIZE_MIN_MULTIPLIER,
CELL_SIZE_MAX_MULTIPLIER
) * int(CELL_SIZE)),
color=new_color,
soft=False,
mass=DEFAULT_CELL_MASS,
x=SCREEN_WIDTH / 2 + random.randint(3, 10),
y=SCREEN_HEIGHT / 2 + random.randint(3, 10)
)
self.cell_sprite_list.append(new_cell)
# physics engine setup
damping = DEFAULT_DAMPING
self.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,
gravity=(0, 0))
# add cell sprites to physics engine
for cell in self.cell_sprite_list:
self.physics_engine.add_sprite(cell,
friction=CELL_FRICTION,
collision_type="Cell",
damping=CELL_DAMPING,
max_velocity=CELL_MAX_SPEED)
def on_draw(self):
"""
Render the screen.
"""
# This command should happen before we start drawing. It will clear
# the screen to the background color, and erase what we drew last frame.
self.clear()
self.cell_sprite_list.draw()
# Call draw() on all your sprite lists below
def on_update(self, delta_time):
"""
All the logic to move, and the game logic goes here.
Normally, you'll call update() on the sprite lists that
need it.
"""
for cell in self.cell_sprite_list:
self.physics_engine.apply_force(cell, cell.move())
self.physics_engine.step()
def main():
""" Main function """
game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
game.setup()
arcade.run()
if __name__ == "__main__":
main()
move function called for each cell in the sprite list after this loop finishes the cells initial angle resets to the value at instantiation rather than retaining the newly applied value from the move function.
The function seems to work but after the for loop exits each cells angle variable is reset to its original state. Console log output shows that while in the for loop the angle is updated but after exiting the loop and running the next time the angle has been reset to the original value.
Angle before move called: 303.0
roll: 17
old angle: 303.0
new angle: 213.0
old x pos: 450.6516108053191
old y pos: 288.2189227316175
new x pos: 437.2328817181923
new y pos: 279.5046981713771
angle after move called: 213.0
Angle before move called: 303.0
roll: 3
old angle: 303.0
new angle: 393.0
old x pos: 451.40957273618153
old y pos: 287.87260184601035
new x pos: 464.8283018233083
new y pos: 296.5868264062508
angle after move called: 393.0
I have tried reworking the movement function and calling the movement function multiple times per update outside of the for loop. when called consecutively the angle does carry over to the next call but is reset to the original value by the time the next on_update function runs.
I was expecting the self.angle of the instanced cell in the cell_sprite_list to update to the new angle generated by the move function.
If you just need to move circle randomly, you can directly update its coordinates:
import arcade
import random
class Game(arcade.Window):
def __init__(self):
super().__init__()
self.x = 400
self.y = 300
self.x_direction = random.choice([-1, 1])
self.y_direction = random.choice([-1, 1])
def on_draw(self):
self.clear()
arcade.draw_circle_filled(self.x, self.y, 30, arcade.color.RED)
def on_update(self, delta_time):
self.x += 3 * self.x_direction
self.y += 3 * self.y_direction
if random.random() < 0.1:
self.x_direction = random.choice([-1, 1])
self.y_direction = random.choice([-1, 1])
Game()
arcade.run()
Output:

How do I get glsl shader to work with pymunk physics engine and camera?

Here are my observations of the problem:
The asteroids game uses a drawn texture sprite for the glow ball weapons. This is done in the GlowBall class. Weapon 7 uses a drawn image
The drawn sprite is with bullet_list.draw() but the shader: for bullet in bullet_list: bullet.draw().
The sprite is being drawn correctly but the shader is not.
The shader seems to move with the ship but faster. almost like it is being drawn to a larger area.
What I tried:
Using the player position minus the camera position which is what I
did to make a drawn image stay on the player - to draw a turret on
the top of the ship.
Tried to make a second version of Shadertoy.py so that I can use the
screen_rectangle instead of quad_2d_fs.
If I use the pymunk physics engine it can accelerate the shader but
causes it to be drawn in the way described above.
How can I use the camera with the shader class along with the pymunk physics engine to create plasma bullets and explosions?
These classes come from the arcade example: https://github.com/pythonarcade/asteroids
I am using a margin scrolling from example: https://api.arcade.academy/en/latest/examples/sprite_move_scrolling_box.html#sprite-move-scrolling-box
'''
class GlowBullet(arcade.Sprite):
def __init__(self, image_file=None, scale=1.0, shadertoy=None, player_no=1):
super().__init__(image_file, scale)
self.type = None
self.shadertoy = shadertoy
self.player_no = player_no
def draw(self):
pass
class GlowBall(GlowBullet):
def __init__(self, shadertoy, glowcolor, radius):
super().__init__(shadertoy=shadertoy)
self.type = None
self.shadertoy = shadertoy
self.glowcolor = glowcolor
self.texture = arcade.make_circle_texture(radius * 2, glowcolor)
self._points = self.texture.hit_box_points
def draw(self):
self.shadertoy.program['pos'] = self.position
self.shadertoy.program['color'] = arcade.get_three_float_color(self.glowcolor)
self.shadertoy.render()
'''
...
In the GameView class:
'''
class GameView(arcade.View):
def __init__(self):
super().__init__()
self.glowball_shadertoy = Shadertoy.create_from_file(self.window.get_size(), GLOW_BALL_IMAGE)
def player_blaster(self,x,y,blaster_filename):
self.click_x = self.world_mouse_x
self.click_y = self.world_mouse_y
r,diff_angle = self.cart_to_polar([self.player.center_x,self.player.center_y],[self.world_mouse_x,self.world_mouse_y])
bullet_sprite = GlowBall(glowcolor=bullet_color,
radius=5,
shadertoy=self.glowball_shadertoy)
glow_bullet = GlowBullet(glowcolor=(1000, 1000, 1000),radius=7,shadertoy=self.glowball_shadertoy)
player_size = max(self.player.width, self.player.height) / 2
bullet_size = max(bullet.width, bullet.height) / 2
glow_bullet_size = bullet_size
radius_start = player_size + bullet_size
glow_radius_start = 0
glow_bullet_x,glow_bullet_y = self.polar_to_cart(glow_radius_start,diff_angle,[self.player.center_x-self.camera_gui.position[0],self.player.center_y-self.camera_gui.position[1]])
glow_bullet_x,glow_bullet_y = self.polar_to_cart(glow_radius_start,diff_angle,[self.player.center_x,self.player.center_y])
glow_bullet.center_x=glow_bullet_x
glow_bullet.center_y=glow_bullet_y
glow_bullet.angle=math.degrees(diff_angle) - 90
bullet.damage = self.player.damage_attack
self.bullet_list.append(bullet)
self.glow_bullet_list.append(glow_bullet)
angle = math.pi/2
bullet_force_x = PLASMA_FORCE_1 * math.cos(angle)
bullet_force_y = PLASMA_FORCE_1 * math.sin(angle)
self.physics_engine.add_sprite(glow_bullet,
mass=0.01,
damping=1,
friction=0.01,
moment_of_intertia=100000,
elasticity=0.9)
self.physics_engine.apply_force(glow_bullet, (bullet_force_x,bullet_force_y))
def on_draw(self):
""" Draw everything """
self.clear()
# This controlls the GLSL shader
for bullet in self.glow_bullet_list:
bullet.draw()
# This draws the sprite
self.glow_bullet_list.draw()
'''
Did you run it in a HiDPI environment?
If so, you should use shader class with proper framebuffer size. Not window.get_size()
Like,
shader = ShaderToy(window.get_framebuffer_size())
render_scale = window.get_framebuffer_size()[0] / window.size[0]
shader.program['pos'] = position * render_scale

Pygame for possible trajectories planning for a car and make the car follow one of that path/trajectory

I have to create a visualization where I have to create a car (I considered to represent it as a rectangle) and develop trajectories for that car depending on its lateral & longitudinal acceleration. My problem is I am unable to create trajectories. I am doing this as a part of own project. Hence I have no idea if I am in the right direction or not. Guide me if I am wrong.
Problem1 : How do I show predicted path trajectories (beginner step to create only 8 trajectories and not many) ? Is a possible way to also create trajectories also like a curve using Pygame? If not, can I use a mix pf pygame and tkinter to create a mix of curved and straight line trajectories?
Problem2 : Make my rectangular car run along the chosen trajectory (curved line or straight line) or make it follow the path defined in trajectory.
Solutions Tried:
I have taken a rectangular sprite as a car with an idea that I can make it move around the desired trajectory. I am successful with sprite creation and moving it. But I am unable to show display any trajectory curve and make the car follow the path. I am not sure if I have to take trajectories also as Sprite or only car as Sprite is sufficient and trajectories as lines/curves?
Code of the above same explanation is as follows:
My Sprite Class :
class Vehicle(pygame.sprite.Sprite):
def __init__(self,x,y,slip_angle,length=4):
super().__init__()
width = 50
height = 50
self.image = pygame.Surface([width,height])
#Create a car
self.image.fill(RED)
self.image.set_colorkey(RED)
pygame.draw.rect(self.image, WHITE, [0, 0, width, height])
self.rect = self.image.get_rect()
#Parameters of car
self.position = position = Vector2(x, y)
self.velocity = Vector2(0.0, 0.0)
self.slip_angle = slip_angle
self.length = length
self.max_velocity = 20
self.brake_deceleration = 10
self.free_deceleration = 2
self.initial_velocity = 0
self.long_acceleration = 0.0
self.lat_acceleration = 0.0
self.acceleration = 0.0
self.steering = 0.0
def update(self,dt):
self.velocity += (self.acceleration * dt, 0)
self.velocity.x = max(-self.max_velocity, min(self.velocity.x, self.max_velocity))
if self.steering:
turning_radius = self.length / sin(radians(self.steering))
angular_velocity = self.velocity.x / turning_radius
else:
angular_velocity = 0
self.position += self.velocity.rotate(-self.angle) * dt
self.slip_angle += degrees(angular_velocity) * dt
Trajectory Class:
class trajectory(object):
def __init__(self,x,y):
self.x = x
self.y = y
def draw():
x_value = []
time = []
for acc in range(10):
for dt in range(0,10,0.05):
x = vehicle1.initial_velocity + (vehicle1.velocity*dt) + (0.5 * vehicle1.long_acceleration * dt* dt)
x_value.append(x)
time.append(dt)
coordinates = (x_value,time)
canvas_1 = Canvas(root,700,600,background='pink')
canvas_1.grid(row=0,column=1)
x1 = coordinates[acc]
y1 = coordinates[time]
canvas_1.create_line(x1,y1)
#pygame.draw.line(screen,GREEN,list(coordinates),(700, 600))
def update(self):
#self.angle = vehicle1.slip_angle*pi / 180
self.velocity_h = vehicle1.velocity*cos(angle)
self.velocity_v = vehicle1.velocity*sin(angle)
Pygame Main Logic :
#main Logic
proceed = True
#Capturing events till exit
while proceed:
for event in pygame.event.get():
if event.type == pygame.QUIT:
proceed = False
vehicle1.update(dt)
sprites_list.update(dt)
screen.fill(WHITE)
#screen.pygame.Surface.fill(color, rect=None, special_flags=0)
pygame.draw.line(screen, BLACK, [0, 0], [700, 300], 5)
# self.screen.blit(rotated, [20,30])
sprites_list.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
I thank you for your advices and support. Happy to accept any guidance
A simple re-working of your trajectory.draw() should sort it out. I noticed the initial x and y passed to the constructor don't seem to be used. Is it intended that the trajectory is always from ( 0, 0 ) or suchlike?
def draw( screen, initial_vel, vel, accel ):
coordinates = []
for acc in range(10):
for dt in range( 0, 10, 0.05 ):
x = initial_vel + ( vel * dt ) + ( 0.5 * accel * dt * dt )
coordinates.append( ( x, dt ) )
if ( len( coordinates ) > 1 ):
PINK = ( 255, 192, 203 )
pygame.draw.lines( screen, PINK, False, coordinates )
Pass your Window and Vehicle Velocities as a parameters when calling trajectory.draw(), rather than relying on global variables.
my_trajectory.draw( screen, vehicle1.initial_velocity, vehicle1.velocity, vehicle1.long_acceleration )

Depth issue with 3D graphics

I copied a code off YouTube recently, about 3d graphics in Python without the use of 3D modules, like OpenGL. The video worked with only cubes, so I tried adding a tetrahedron to the shapes, and things started popping up where they weren't supposed to. The code is as follows:
import pygame, sys, math, random
def rotate2d(pos, rad): x,y=pos; s,c = math.sin(rad),math.cos(rad); return x*c-y*s,y*c+x*s
class Cam:
def __init__(self, pos=(0,0,0),rot=(0,0)):
self.pos = list(pos)
self.rot = list(rot)
def events(self, event):
if event.type == pygame.MOUSEMOTION:
x, y = event.rel; x/=200; y/=200
self.rot[0]+=y; self.rot[1]+=x
def update(self, dt, key):
s = dt*10
if key[pygame.K_q]: self.pos[1]+=s
if key[pygame.K_e]: self.pos[1]-=s
x,y = s*math.sin(self.rot[1]),s*math.cos(self.rot[1])
if key[pygame.K_w]: self.pos[0]+=x; self.pos[2]+=y
if key[pygame.K_s]: self.pos[0]-=x; self.pos[2]-=y
if key[pygame.K_a]: self.pos[0]-=y; self.pos[2]+=x
if key[pygame.K_d]: self.pos[0]+=y; self.pos[2]-=x
if key[pygame.K_r]: self.pos[0]=0; self.pos[1]=0;\
self.pos[2]=-5; self.rot[0]=0; self.rot[1]=0
class Cube:
faces = (0,1,2,3),(4,5,6,7),(0,1,5,4),(2,3,7,6),(0,3,7,4),(1,2,6,5)
colors = (255,0,0),(255,128,0),(255,255,0),(255,255,255),(0,0,255),(0,255,0)
def __init__(self,pos=(0,0,0),v0=(-1,-1,-1),v1=(1,-1,-1),v2=(1,1,-1),v3=(-1,1,-1),v4=(-1,-1,1),v5=(1,-1,1),v6=(1,1,1),v7=(-1,1,1)):
self.vertices = (v0,v1,v2,v3,v4,v5,v6,v7)
x,y,z = pos
self.verts = [(x+X/2,y+Y/2,z+Z/2) for X,Y,Z in self.vertices]
class Tetrahedron:
faces = (1,2,3),(0,1,2),(0,1,3),(0,2,3)
colors = (255,0,0),(255,128,0),(255,255,0),(255,255,255)
def __init__(self,pos=(0,0,0),v0=(0,0,.866),v1=(-.866,-1,-1),v2=(-.866,1,-1),v3=(.866,0,-1)):
self.vertices = (v0,v1,v2,v3)
x,y,z = pos
self.verts = [(x+X/2,y+Y/2,z+Z/2) for X,Y,Z in self.vertices]
pygame.init()
w,h = 400,400; cx,cy = w//2, h//2
screen = pygame.display.set_mode((w,h))
clock = pygame.time.Clock()
cam = Cam((0,0,-5))
pygame.event.get(); pygame.mouse.get_rel()
pygame.mouse.set_visible(0); pygame.event.set_grab(1)
objects = [Cube((0,0,0)),Cube((0,0,2)),Tetrahedron((0,0,1))]
while True:
dt = clock.tick()/1000
for event in pygame.event.get():
if event.type == pygame.QUIT: pygame.quit(); sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE: pygame.quit(); sys.exit()
cam.events(event)
screen.fill((0,0,0))
face_list = []; face_color = []; depth = []
for obj in objects:
vert_list = []; screen_coords = []
for x,y,z in obj.verts:
x-=cam.pos[0]; y-=cam.pos[1];z-=cam.pos[2]
x,z=rotate2d((x,z),cam.rot[1])
y,z = rotate2d((y,z),cam.rot[0])
vert_list += [(x,y,z)]
f = 200/z
x,y = x*f,y*f
screen_coords+=[(cx+int(x),cy+int(y))]
for f in range(len(obj.faces)):
face = obj.faces[f]
on_screen = False
for i in face:
x,y = screen_coords[i]
if vert_list[i][2]>0 and x>0 and x<w and y>0 and y<h: on_screen = True; break
if on_screen:
coords = [screen_coords[i] for i in face]
face_list += [coords]
face_color += [obj.colors[f]]
depth += [sum(sum(vert_list[j][i] for j in face)**2 for i in range(3))]
order = sorted(range(len(face_list)),key=lambda i:depth[i],reverse=1)
for i in order:
try: pygame.draw.polygon(screen,face_color[i],face_list[i])
except: pass
key = pygame.key.get_pressed()
pygame.display.flip()
cam.update(dt,key)
My problem is that the tetrahedron part is showing up through the cubes on either side, when the cam.pos is at least 7.5 away from the object:
As you can see, the tetrahedron is supposed to be buried under the cube, but it's still showing up. Can someone please suggest an edit to the code which will stop the tetrahedron from showing up through any objects?
What you actually try to do is to sort the faces by the square of the Euclidean distance to the vertices of the face.
But sum(vert_list[j][i] for j in face)**2 does not compute the square of the euclidean distance. It computes the square of the sum of the components of a coordinate. The square of the euclidean distance is
vert_list[j][0]**2 + vert_list[j][1]**2 + vert_list[j][2]**2
That is not the same as
(vert_list[j][0] + vert_list[j][1] + vert_list[j][2])**2
Furthermore you do not take into account the number of vertices of a face, because the squared distances are summed.
Note, the faces of the cube are quads and have 4 vertices, but the faces of the tetrahedron are triangles and have 3 vertices.
Correct the computation of the squared distance and divide by the number of vertices, to solve the issue:
depth += [sum(sum(vert_list[j][i] for j in face)**2 for i in range(3))]
depth += [sum(sum(vert_list[j][i]**2 for i in range(3)) for j in face) / len(face)]
But note, if faces are close to another there still may occur an issue (e.g. covering faces in the same plane). That is not an exact algorithm. It is not designed for "touching" objects.

Adding gravity to a bouncing ball using vectors

I have a gravity vector (in the form [r, theta]) which I add to my ball's velocity vector. For some reason, the ball doesn't return to the same height after bouncing, but instead slowly loses height sporadically. I am guessing there's some rounding error or something in a calculation I'm using, but I can't isolate the issue.
Here is my code. You need both files and pygame to run it. Sorry if it's a little confusing. I can comment anything some more if you want.
I added a marker whenever the ball reaches its max height so you guys what I mean. I want the ball to return to exactly the same height every time it bounces.
I took a little bit of unnecessary code out. The full program is under the pastebin links.
https://pastebin.com/FyejMCmg - PhysicsSim
import pygame, sys, math, tools, random, time
from pygame.locals import *
clock = pygame.time.Clock()
lines = []
class Particle:
def __init__(self,screen,colour, mass, loc, vel):
self.screen = screen
self.colour = colour
self.mass = mass
self.x = loc[0]
self.y = loc[1]
self.location = self.x,self.y
self.speed = vel[0]
self.angle = vel[1]
def update(self):
global lines
# add gravity
self.speed,self.angle = tools.add_vectors2([self.speed,self.angle], tools.GRAVITY)
# update position
dt = clock.tick(60)
self.x += self.speed * tools.SCALE * math.cos(self.angle) * dt
self.y -= self.speed * tools.SCALE * math.sin(self.angle) * dt
self.location = int(self.x),int(self.y)
# border checking
do = False
n=[]
if ((self.y+self.mass) > tools.SCREEN_HEIGHT):
self.y = tools.SCREEN_HEIGHT-self.mass
n = [0,1]
do = True
# adds position to array so max height so max height can be recorded
if (self.speed==0):
lines.append([self.screen, self.location, self.mass])
# bounce
if do:
#init, convert everything to cartesian
v = tools.polarToCartesian([self.speed, self.angle])
#final -> initial minus twice the projection onto n, where n is the normal to the surface
a = tools.scalarP(2*abs(tools.dotP(v,n)),n) #vector to be added to v
v = tools.add_vectors(v,a)
self.angle = tools.cartesianToPolar(v)[1] # does not set magnitude
# drawing
pygame.draw.circle(self.screen, self.colour, self.location, self.mass, 0)
# draws max height line
def draw_line(l):
screen = l[0]
location = l[1]
radius = l[2]
pygame.draw.line(screen, tools.BLACK, [location[0] + 15, location[1]-radius],[location[0] - 15, location[1]-radius])
def main():
pygame.init()
DISPLAY = pygame.display.set_mode(tools.SCREEN_SIZE,0,32)
DISPLAY.fill(tools.WHITE)
particles = []
particles.append(Particle(DISPLAY, tools.GREEN, 10, [100,100], [0,0]))
done = False
while not done:
global lines
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
DISPLAY.fill(tools.WHITE)
for i in particles:
i.update()
for l in lines:
draw_line(l)
pygame.display.update()
main()
https://pastebin.com/Epgqka31 - tools
import math
#colours
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = ( 255, 0, 0)
BLACK = ( 0, 0, 0)
COLOURS = [WHITE,BLUE,GREEN,RED,BLACK]
#screen
SCREEN_SIZE = SCREEN_WIDTH,SCREEN_HEIGHT = 1000,700
#vectors
GRAVITY = [5.0, 3*math.pi/2] # not 9.8 because it seems too high
SCALE = 0.01
# converts polar coordinates to cartesian coordinates in R2
def polarToCartesian(v):
return [v[0]*math.cos(v[1]), v[0]*math.sin(v[1])]
# converts cartesian coordinates to polar coordinates in R2
def cartesianToPolar(v):
return [math.sqrt(v[0]**2 + v[1]**2), math.atan2(v[1],v[0])]
# dots two cartesian vectors in R2
def dotP(v1, v2):
return v1[0]*v2[0] + v1[1]*v2[1]
# multiplies cartesian vector v by scalar s in Rn
def scalarP(s,v):
v_=[]
for i in v:
v_.append(s*i)
return v_
# returns the sum of two cartesian vectors in R2
def add_vectors(v1, v2):
return [v1[0]+v2[0], v1[1]+v2[1]]
# returns the sum of two polar vectors in R2, equations from https://math.stackexchange.com/questions/1365622/adding-two-polar-vectors
def add_vectors2(v1,v2):
r1,r2,t1,t2 = v1[0],v2[0],v1[1],v2[1]
return [math.sqrt(r1**2 + r2**2 + 2*r1*r2*math.cos(t2-t1)), t1 + math.atan2(r2*math.sin(t2 - t1), r1 + r2*math.cos(t2 - t1))]
Your time interval, dt = clock.tick(60), is not a constant. If you change it to dt = 60 your program runs as expected.
Have a look a the Verlet Algorithm and implement it in your code. You are on the right track!

Categories