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

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

Related

center an object/image rect

Ok so I have this code which in def draw, in if self.part == 1: I blit the main image in the middle, but this doesn´t center the image as I want, it just makes the spawning point in the middle, and the image starts from there, so the image always appears on the bottom right side. I want it to blit it in the middle, like the whole thing:
class GameScene(Scene):
def __init__(self, game, images, main_image, next_scene):
super().__init__(next_scene)
self.game = game
self.main_image = main_image
self.game_images = images
# Fade effect set-up
self.fade = False
self.fade_time = 0
self.current_alpha = 255
self.part = 1
self.record_text = font.render('Atiende',True, PURPLE)
self.record_text_rect = self.record_text.get_rect(center=(SCREEN_WIDTH/2, 70))
self.correct_image_rect = None
# Trying to use colliderect so it doesnt overlap
# this is the same code as before but adapted to use the gameimage class and the rects stored there
self.rects = []
# this is the fade stuff from before that was in draw. It really belongs here tbh
def update(self, dt):
if len(self.rects) < len(self.game_images):
i = len(self.rects)
x = random.randint(100,950)
y = random.randint(100,600)
self.game_images[i].rect.x = x
self.game_images[i].rect.y = y
margin = 5
rl = [rect.inflate(margin*2, margin*2) for rect in self.rects]
if len(self.rects) == 0 or self.game_images[i].rect.collidelist(rl) < 0:
self.rects.append(self.game_images[i].rect)
if self.part == 1 and self.fade:
self.fade_time += dt
if self.fade_time > fade_timer:
self.fade_time = 0
self.main_image.set_alpha(self.current_alpha)
self.record_text.set_alpha(self.current_alpha)
# Speed whichin the image dissapears
self.current_alpha -= 5
if self.current_alpha <= 0:
self.fade = False
self.part = 2
else:
# we reset the main image alpha otherwise it will be invisible on the next screen (yeah, this one caught me out lol!)
self.main_image.set_alpha(255)
# draw is similar to before, but a bit more streamlined as the fade stuff is not in update
def draw(self, screen):
super().draw(screen)
if self.part == 1:
screen.blit(self.record_text, self.record_text_rect)
# x = self.main_image.rect.x.center
# y = self.main_image.rect.y.center
screen.blit(self.main_image.image, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
else:
# Second half
text2 = font.render('¿Qué has visto?',True, PURPLE)
screen.blit(text2, (400,5))
# Show all similar images
cont = 0
for game_image in self.game_images:
game_image.draw(screen)
cont += 1
# We associate the correct rect to the correct image, to pass it later to the CORRECT Screen
self.correct_image_rect = self.game_images[self.game_images.index(self.main_image)].rect
The thing is, that the main_image that it comes through a parameter, its a proper class:
class GameImage(object):
def __init__(self, image):
self.image = image
self.rect = self.image.get_rect()
# this class has the set_alpha so the alpha can be passed on to its image
def set_alpha(self, alpha):
self.image.set_alpha(alpha)
# we pass the draw method the surface we want the image drawn on
def draw(self, surf):
surf.blit(self.image, self.rect)
So, as you can see, the GameImage class it´s an object that gets its rect as well, but I´m struggling to center that main image rect and blit into the screen the way I want. So yeah, I know how to do it with texts, as you can see on the self.recrod_text_rect, but I don´t know how to do it with this object and pass it to the screen.blit of the draw def.
You need to set the center of the image by the center of target Surface:
class GameImage(object):
def __init__(self, image):
self.image = image
self.rect = self.image.get_rect()
# this class has the set_alpha so the alpha can be passed on to its image
def set_alpha(self, alpha):
self.image.set_alpha(alpha)
# we pass the draw method the surface we want the image drawn on
def draw(self, surf):
self.rect.center = surf.get_rect().center # <---
surf.blit(self.image, self.rect)
See also How to Center Text in Pygame

Python programming trouble

I was programming a game and I did not know how to program in python 3.7 so I had to get a book called Code This Game, book from OddDot. And When I got to chapter 6 I was learning how to create a group for the enemy sprites. and every time I ran the program all it drew was the background image and the grid(because the book is not only teaching me how to code in Python, while I am learning I am making a game from the book while I learn from the book.) it did not draw the group of Vampire Pizzas in the right column if fact it doesn't even draw them at all. I have tried redoing the chapter over and over again and I can't do it
here is my code
#Import Libraries
import pygame
from pygame import *
from random import randint
#Initialize pygame
pygame.init()
#Define constant variables
WINDOW_WIDTH = 1100
WINDOW_HEIGHT = 600
WINDOW_RES = (WINDOW_WIDTH, WINDOW_HEIGHT)
#Define Tile Parameters
WIDTH = 100
HEIGHT = 100
#Define Tile colors
WHITE = (255, 255, 255)
#Set up rates
SPAWN_RATE = 360
#This is the code where the game window will show up
GAME_WINDOW = display.set_mode(WINDOW_RES)
display.set_caption('Attack of the Vampire Pizzaas!')
#---------------------------------------------------------
#Set up the enemy image
pizza_img = image.load('vampire.png')
pizza_surf = Surface.convert_alpha(pizza_img)
VAMPIRE_PIZZA = transform.scale(pizza_surf, (HEIGHT, WIDTH))
#Create a subclass of Sprite called VampireSprite
class VampireSprite(sprite.Sprite):
#Set up enemy instances
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
#Set up the background image
background_img = image.load('restaurant.jpg')
background_surf = Surface.convert_alpha(background_img)
BACKGROUND = transform.scale(background_surf, WINDOW_RES)
#------------------------------------------------------
all_vampires = sprite.Group()
#Initialile and draw background grid
tile_color = WHITE
for row in range(6):
for column in range(11):
draw.rect(BACKGROUND, tile_color, (WIDTH * column,
HEIGHT * row, WIDTH, HEIGHT),1)
GAME_WINDOW.blit(BACKGROUND, (0, 0))
#---------------------------------------------------------
#Start The Main Game Loop
#Game Loop
game_running = True
while game_running:
#Check for Events
for event in pygame.event.get():
if event.type == QUIT:
game_running = False
#Spawn vampire pizza sprites
if randint(1, SPAWN_RATE) == 1:
VampireSprite()
#Update displays
for vampire in all_vampires:
vampire.update(GAME_WINDOW)
display.update()
#End of the Main game loop
#---------------------------------------------------------
#Clean up game
pygame.quit()
I don't know if I am doing something wrong or what
If my eyes are working fine(I guess) the code is valid
Make sure that the pygame version you are using is the one compatible with Python 3.7 and not older versions such as Python 2.
I had the same issue while working my way through this fun coding book. Comparing the code from the book's website and my own code I found the issue was incorrect spacing while defining the update() function within the VampireSprite class.
# Create a subclass of Sprite called VampireSprite
class VampireSprite(sprite.Sprite):
# Set up enemy instances
def __init__(self):
super().__init__()
self.Speed = 2
self. Lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
The correct code is:
# Create an enemy class
class VampireSprite(sprite.Sprite):
# This function creates an instance of the enemy
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center=(1100, y))
# This function moves the enemies from right to left and destroys them after they've left the screen
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))

builtins.AttributeError: 'module' object has no attribute 'one_tile'

HI I keep getting that error message from the line below
pygame.one_tile.blit(self.i_list[img_index], pos)
and this is from the function below
def create_grid(self, grid_size):
self.grid = [ ]
tile_width = self.surface.get_width() / grid_size
tile_height = self.surface.get_height() / grid_size
# this for loop creates each row in our grid
for i in range(grid_size):
one_row = [ ]
img_index = 0
for j in range(grid_size):
y = i * tile_height
x = j * tile_width
pos = (x,y)
one_tile = Tile(pos, tile_width, tile_height, self.surface)
pygame.one_tile.blit(self.i_list[img_index], pos)
img_index += 1
one_row.append(one_tile)
self.grid.append(one_row)
I'm writing a code for the memory game version 1 (the game that has 8 pairs of images and you need to memorize which image was on which card and match a pair) and I keep get that error message but I don;t really know what I should do to solve it. Any help would be appreciated very much. Thank you!!
and my full code is
import pygame, random
# User-defined functions
def main():
# initialize all pygame modules (some need initialization)
pygame.init()
# create a pygame display window
pygame.display.set_mode((500, 400))
# set the title of the display window
pygame.display.set_caption('A template for graphical games with two moving dots')
# get the display surface
w_surface = pygame.display.get_surface()
# create a game object
game = Game(w_surface)
# start the main game loop by calling the play method on the game object
game.play()
# quit pygame and clean up the pygame window
pygame.quit()
# User-defined classes
class Game:
# An object in this class represents a complete game.
def __init__(self, surface):
# Initialize a Game.
# - self is the Game to initialize
# - surface is the display window surface object
# === objects that are part of every game that we will discuss
self.surface = surface
self.bg_color = pygame.Color('black')
self.FPS = 60
self.game_Clock = pygame.time.Clock()
self.close_clicked = False
self.continue_game = True
# === game specific objects
self.max_frames = 150
self.frame_counter = 0
self.i_list = []
self.images = ["image1.bmp", "image2.bmp", "image3.bmp", "image4.bmp", "image5.bmp", "image6.bmp", "image7.bmp", "image8.bmp"]
for i in self.images:
pygame.image.load(i)
self.i_list.append(i)
self.i_list.append(i)
random.shuffle(self.i_list)
self.create_grid(4)
def create_grid(self, grid_size):
self.grid = [ ]
tile_width = self.surface.get_width() / grid_size
tile_height = self.surface.get_height() / grid_size
# this for loop creates each row in our grid
for i in range(grid_size):
one_row = [ ]
img_index = 0
for j in range(grid_size):
y = i * tile_height
x = j * tile_width
pos = (x,y)
one_tile = Tile(pos, tile_width, tile_height, self.surface)
pygame.one_tile.blit(self.i_list[img_index], pos)
img_index += 1
one_row.append(one_tile)
self.grid.append(one_row)
def play(self):
# Play the game until the player presses the close box.
# - self is the Game that should be continued or not.
while not self.close_clicked: # until player clicks close box
# play frame
self.handle_events()
self.draw()
if self.continue_game:
self.update()
self.decide_continue()
self.game_Clock.tick(self.FPS) # run at most with FPS Frames Per Second
def handle_events(self):
# Handle each user event by changing the game state appropriately.
# - self is the Game whose events will be handled
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
self.close_clicked = True
def draw(self):
# Draw all game objects.
# - self is the Game to draw
self.surface.fill(self.bg_color) # clear the display surface first
for row in self.grid:
for tile in row:
tile.draw()
pygame.display.update()
def update(self):
# Update the game objects for the next frame.
# - self is the Game to update
pass
def decide_continue(self):
# Check and remember if the game should continue
# - self is the Game to check
return True
class Tile:
# A tile represents one location on a grid. Tiles hold content
# (in this case, an X or an O).
def __init__(self, screen_position, width, height, surface):
# initialize one instance of our Tile class. Tiles represent
# one 'position' in our tic-tac-toe board.
# - self: the tile being initialized
# - screen_position: the [x, y] coordinates to draw the tile at
# - surface: the surface on which to draw the tile
# - height: the height of the tile when it is drawn
# - width: the width of the tile when it is drawn
self.screen_position = screen_position
self.surface = surface
self.content = ''
self.height = height
self.width = width
def draw(self):
# draw the contents of a tile to its surface.
tile_boundary = pygame.Rect(self.screen_position[0],
self.screen_position[1],
self.width,
self.height)
pygame.draw.rect(self.surface, pygame.Color("white"), tile_boundary, 2)
main()
I recommend to add an .image attribute and .visible attribute to the class Tile. Each tile knows the associated image and has a state if the image in on the tile is visible:
class Tile:
# A tile represents one location on a grid. Tiles hold content
# (in this case, an X or an O).
def __init__(self, screen_position, width, height, surface, image):
# initialize one instance of our Tile class. Tiles represent
# one 'position' in our tic-tac-toe board.
# - self: the tile being initialized
# - screen_position: the [x, y] coordinates to draw the tile at
# - surface: the surface on which to draw the tile
# - height: the height of the tile when it is drawn
# - width: the width of the tile when it is drawn
self.screen_position = screen_position
self.surface = surface
self.content = ''
self.height = height
self.width = width
self.image = image
self.visible = False
def show(self, visible):
self.visible = visible
def draw(self):
# draw the contents of a tile to its surface.
tile_boundary = pygame.Rect(self.screen_position[0],
self.screen_position[1],
self.width,
self.height)
pygame.draw.rect(self.surface, pygame.Color("white"), tile_boundary, 2)
if self.visible:
img_rect = self.image.get_rect(center = tile_boundary.center)
self.surface.blit(self.image, img_rect.topleft)
To create an image list, you've to load the image. The return value of pygame.image.load is a pygame.Surface object which can be append to i_list:
self.i_list = []
self.images = ["image1.bmp", "image2.bmp", "image3.bmp", "image4.bmp", "image5.bmp", "image6.bmp", "image7.bmp", "image8.bmp"]
for imgname in self.images:
img = pygame.image.load(imgname)
self.i_list.append(img)
random.shuffle(self.i_list)
Pass the image to the constructor of Tile:
for i in range(grid_size):
one_row = [ ]
img_index = 0
for j in range(grid_size):
pos = (j * tile_width, i * tile_height)
one_tile = Tile(pos, tile_width, tile_height, self.surface, self.i_list[img_index])
img_index += 1
one_row.append(one_tile)
self.grid.append(one_row)
Note, for debug reasons you can all the state of all the images "visible" (self.visible = True in the constructor of Tiles).

How to prevent fast moving objects passing through statics when calculating pi with colliding blocks

I am trying to implement a similar program to the following in Python using Pymunk and Pyglet. My current implementation works well at low velocities however at high speeds the block can pass through the static wall. This because in 1/60s clock cycle the block moves further than the thickness of the wall. I have seen others solve this by having a limiting speed however in my case this would not work as the velocity is important to calculate the value for PI. I would like to know if there is any way of preventing this from happening.
import pyglet
import pymunk
class Block:
"""
The class for a block
Mass: the mass the block
X: Initial x position
Y: Initial y position
PhysSpace: The physics space to add items to
RenderBatch: Batch to add block to
"""
def __init__(self, Mass, X, Y, PhysSpace, RenderBatch):
# Create body with given mass and infinite moment of inertia
self.Body = pymunk.Body(Mass, pymunk.inf)
# Set Body's position
self.Body.position = X, Y
# Create shape for body
BodyShape = pymunk.Poly.create_box(self.Body, size=(50, 50))
# Define shapes elasticity
BodyShape.elasticity = 1
# Add block to the physics space
PhysSpace.add(self.Body, BodyShape)
# Import block image
BlockImg = pyglet.image.load('res/sqr.png')
# Set anchor point of image to be the centre
BlockImg.anchor_x = BlockImg.width // 2
BlockImg.anchor_y = BlockImg.height // 2
# Create sprite for block
self.BlockSprite = pyglet.sprite.Sprite(BlockImg, x=self.Body.position.x, y=self.Body.position.y,
batch=RenderBatch)
def update(self):
# Set the position of the sprite to be equal to the position of the physics body
self.BlockSprite.position = self.Body.position
def give_velocity(self, velocity):
# Set velocity of the body
self.Body.velocity = (velocity, 0)
class Simulation(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Set background to be clear
pyglet.gl.glClearColor(1, 1, 1, 1)
# Set clock speed
pyglet.clock.schedule_interval(self.update, 1/60)
# Create batch to draw all the graphics with
self.Batch = pyglet.graphics.Batch()
# Create Title Label
self.TitleLabel = pyglet.text.Label(text='Block Collision Simulator', x=self.width / 2, y=self.height - 20,
batch=self.Batch, anchor_x='center', anchor_y='center', font_size=24,
color=(0, 0, 0, 255))
self.Counter = -2
self.CounterLabel = pyglet.text.Label('Counter = 0'.format(self.Counter), x=self.width / 2, y=self.height - 60, anchor_x='center',
anchor_y='center', font_size=24, color=(0, 0, 0, 255), batch=self.Batch)
# Initiate space for Physics engine
self.Space = pymunk.Space()
self.Handler = self.Space.add_default_collision_handler()
self.Handler.begin = self.coll_begin
# Create the ground in physics engine
Ground = pymunk.Poly.create_box(self.Space.static_body, size=(self.width, 20))
Ground.body.position = self.width / 2, 10
self.Space.add(Ground)
# Create the sprite for the ground
GroundImg = pyglet.image.load('res/ground.png')
self.GroundSprite = pyglet.sprite.Sprite(GroundImg, x=0, y=0, batch=self.Batch)
# Create Wall in physics engine
Wall = pymunk.Poly.create_box(self.Space.static_body, size=(20, self.height))
Wall.body.position = 10, self.height / 2
Wall.elasticity = 1
self.Space.add(Wall)
# Create the sprite for the wall
WallImg = pyglet.image.load('res/wall.png')
self.WallSprite = pyglet.sprite.Sprite(WallImg, x=0, y=0, batch=self.Batch)
self.BlockRight = Block(10000, 2 * (self.width / 3), 45, self.Space, self.Batch)
self.BlockRight.give_velocity(-100)
self.BlockLeft = Block(1, self.width / 3, 45, self.Space, self.Batch)
pyglet.app.run()
def coll_begin(self, arbiter, space, data):
self.Counter += 1
if self.Counter > 0:
self.CounterLabel.text = 'Counter: {}'.format(self.Counter)
return True
def on_draw(self):
self.clear()
self.Batch.draw()
def update(self, dt):
self.Space.step(dt)
self.BlockRight.update()
self.BlockLeft.update()
One way is as you write to limit the velocity. Another way is to call the step function with a smaller dt. (On the same note, you should almost always use a fixed value for the dt, that will help to keep the simulation stable).
One way to use smaller dt is to call the step function multiple times for each call to the update function. So you can try something like this:
def update(self, dt):
for _ in range(10):
self.Space.step(dt/10)
#self.Space.step(dt)
self.BlockRight.update()
self.BlockLeft.update()

more efficient wind tunnel simulation in Pygame, using numpy

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.

Categories