I am generating diagrams in Turtle, and as part of my program, I identify certain coordinates from my diagrams. I would like to be able to hide the complete turtle window, since I only care about the coordinates, is that possible?
Edit:
QUESTION 2:
This isn't really an answer, but a few other questions.
I got my program working to some extent, if you run it in IDLE and type "l" it will give you the list with the coordinates.
import Tkinter
import turtle
from turtle import rt, lt, fd # Right, Left, Forward
size = 10
root = Tkinter.Tk()
root.withdraw()
c = Tkinter.Canvas(master = root)
t = turtle.RawTurtle(c)
t.speed("Fastest")
# List entire coordinates
l = []
def findAndStoreCoords():
x = t.xcor()
y = t.ycor()
x = round(x, 0) # Round x to the nearest integer
y = round(y, 0) # Round y to the nearest integer
# Integrate coordinates into sub-list
l.append([x, y])
def hilbert(level, angle):
if level == 0:
return
t.rt(angle)
hilbert(level - 1, -angle)
t.fd(size)
findAndStoreCoords()
t.lt(angle)
hilbert(level - 1, angle)
t.fd(size)
findAndStoreCoords()
hilbert(level - 1, angle)
t.lt(angle)
t.fd(size)
findAndStoreCoords()
hilbert(level - 1, -angle)
t.rt(angle)
The problem is that Turtle is so SLOW! Is there any package that is just like Turtle but can do commands much faster?
I reimplemented the turtle class as suggested by thirtyseven. It is consistent with the api. (i.e. when you turn right in this class, it is the same as turning right in turtle.
This does not implement all the methods in the api, only common ones. (And the ones you used).
However, it's short and fairly straightforward to extend. Also, it keeps track of all of the points it has been to. It does this by adding an entry to pointsVisited every time you call forward, backward, or setpos (or any of the aliases for those functions).
import math
class UndrawnTurtle():
def __init__(self):
self.x, self.y, self.angle = 0.0, 0.0, 0.0
self.pointsVisited = []
self._visit()
def position(self):
return self.x, self.y
def xcor(self):
return self.x
def ycor(self):
return self.y
def forward(self, distance):
angle_radians = math.radians(self.angle)
self.x += math.cos(angle_radians) * distance
self.y += math.sin(angle_radians) * distance
self._visit()
def backward(self, distance):
self.forward(-distance)
def right(self, angle):
self.angle -= angle
def left(self, angle):
self.angle += angle
def setpos(self, x, y = None):
"""Can be passed either a tuple or two numbers."""
if y == None:
self.x = x[0]
self.y = y[1]
else:
self.x = x
self.y = y
self._visit()
def _visit(self):
"""Add point to the list of points gone to by the turtle."""
self.pointsVisited.append(self.position())
# Now for some aliases. Everything that's implemented in this class
# should be aliased the same way as the actual api.
fd = forward
bk = backward
back = backward
rt = right
lt = left
setposition = setpos
goto = setpos
pos = position
ut = UndrawnTurtle()
Yes, this is possible. The simplest way is to instantiate a root Tkinter window, withdraw it, and then use it as the master window for a RawTurtle's Canvas instance.
Example:
import Tkinter
import turtle
root=Tkinter.Tk()
root.withdraw()
c=Tkinter.Canvas(master=root)
t=turtle.RawTurtle(c)
t.fd(5)
print t.xcor() # outputs 5.0
Unfortunately, this still initiates the graphics system, but no window will appear.
The problem is that Turtle is so SLOW! Is there any package that is
just like Turtle but can do commands much faster?
Yes, turtle can. If we add a TurtleScreen to the tkinter implementation, and use it's tracer() functionality, we can speed things up more than turtle's speed() method. And we can simplify the code greatly by tossing the customizations and simply use turtle's own begin_poly(), end_poly() and get_poly() methods:
from tkinter import Tk, Canvas
from turtle import TurtleScreen, RawTurtle
SIZE = 10
def hilbert(level, angle):
if level == 0:
return
turtle.right(angle)
hilbert(level - 1, -angle)
turtle.forward(SIZE)
turtle.left(angle)
hilbert(level - 1, angle)
turtle.forward(SIZE)
hilbert(level - 1, angle)
turtle.left(angle)
turtle.forward(SIZE)
hilbert(level - 1, -angle)
turtle.right(angle)
root = Tk()
root.withdraw()
canvas = Canvas(master=root)
screen = TurtleScreen(canvas)
screen.tracer(False) # turn off turtle animation
turtle = RawTurtle(screen)
turtle.begin_poly() # start tracking movements
hilbert(5, 90)
turtle.end_poly() # end tracking movements
print(turtle.get_poly())
This prints all the points in a level 5 Hilbert curve in about 1/3 of a second on my system. Your posted code toke nearly 9 seconds to output a level 4.
Related
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:
I am trying to make a simple galaga type game where the ship moves back and forth along the bottom of the screen and shoots automatically. It runs as expected at first but slows down drastically pretty quickly. I thought it was maybe slowing as it had more and more bullets to keep track of, but limiting the number of bullets on-screen didn't seem to help at all.
The main loop:
while game_is_on:
screen.update()
ship.slide()
bullet_manager.shoot(ship.xcor())
bullet_manager.move_bullets()
from bullet_manager:
def shoot(self, xcor):
self.chance +=1
if self.chance %6 == 0:
new_bullet = Turtle("square")
new_bullet.color("red")
new_bullet.shapesize(stretch_wid=.1)
new_bullet.pu()
new_bullet.seth(90)
new_bullet.goto(xcor, -200)
self.all_bullets.append(new_bullet)
def move_bullets(self):
for bullet in self.all_bullets:
bullet.forward(10)
self.all_bullets = self.all_bullets[-10:]
self.all_bullets = self.all_bullets[-10:] prunes turtles from your local list, but not from the application's memory. There are still turtles being managed by the turtle module even if they're not in your list. You can take a peek at turtle.turtles() to see how many turtles are being tracked internally.
Here's a minimal reproduction:
import turtle
from random import randint
from time import sleep
turtle.tracer(0)
turtles = []
while True:
t = turtle.Turtle()
turtles.append(t)
if len(turtles) > 10:
turtles = turtles[-10:]
for t in turtles:
if randint(0, 9) < 1:
t.left(randint(0, 360))
t.forward(randint(2, 10))
print(len(turtles), len(turtle.turtles()))
turtle.update()
sleep(0.1)
You'll see the screen flood with turtles, and while your list stays at length 10, the turtle module's length keeps going up.
The first thought might be to chop off the turtle's internal list, but I prefer not to mess with library-managed resources without being given permission to do so. A quick attempt using memory stat code from this answer leaks:
import os, psutil
import turtle
from random import randint
from time import sleep
turtle.tracer(0)
while True:
t = turtle.Turtle()
if len(turtle.turtles()) > 10:
turtle.turtles()[:] = turtle.turtles()[:10]
for t in turtle.turtles():
if randint(0, 9) < 1:
t.left(randint(0, 360))
t.forward(randint(2, 10))
print(len(turtle.turtles()))
process = psutil.Process(os.getpid())
print(process.memory_info().rss) # in bytes
turtle.update()
sleep(0.1)
How to fully delete a turtle is the canonical resource for deleting turtles, but a probably better solution for this use case is to pre-allocate a turtle pool and recycle turtles from it. When a bullet (or any other entity) is created, borrow a turtle from the pool and store it as a property on your object. When the bullet leaves the screen (or meets some other termination condition), return the turtle that the bullet instance borrowed back to the pool.
Here's an example, possibly overengineered for your use case, but you can use the pool as a library or adapt the high-level concept.
import turtle
from random import randint
class TurtlePool:
def __init__(self, initial_size=8, capacity=32):
if initial_size > capacity:
raise ArgumentError("initial_size cannot be greater than capacity")
self.capacity = capacity
self.allocated = 0
self.dead = []
for _ in range(initial_size):
self._allocate()
def available(self):
return bool(self.dead) or self.allocated < self.capacity
def acquire(self):
if not self.dead:
if self.allocated < self.capacity:
self._allocate()
return self.dead.pop()
def give_back(self, t):
self.dead.append(t)
self._clean_turtle(t)
def _allocate(self):
t = turtle.Turtle()
self.allocated += 1
assert self.allocated == len(turtle.turtles())
self.dead.append(t)
self._clean_turtle(t)
def _clean_turtle(self, t):
t.reset()
t.hideturtle()
t.penup()
class Bullet:
def __init__(self, x, y, speed, angle):
self.speed = speed
self.turtle = turtle_pool.acquire()
self.turtle.goto(x, y)
self.turtle.setheading(angle)
self.turtle.showturtle()
def forward(self):
self.turtle.forward(self.speed)
def destroy(self):
turtle_pool.give_back(self.turtle)
def in_bounds(self):
x, y = self.turtle.pos()
return (
x >= w / -2 and x < w / 2 and
y >= h / -2 and y < h / 2
)
def tick():
if randint(0, 1) == 0 and turtle_pool.available():
bullets.append(Bullet(
x=0,
y=0,
speed=8,
angle=randint(0, 360)
))
next_gen = []
for b in bullets:
b.forward()
if b.in_bounds():
next_gen.append(b)
else:
b.destroy()
bullets[:] = next_gen
turtle.update()
turtle.Screen().ontimer(tick, frame_delay_ms)
frame_delay_ms = 1000 // 30
turtle.tracer(0)
turtle_pool = TurtlePool()
w = turtle.window_width()
h = turtle.window_height()
bullets = []
tick()
turtle.exitonclick()
Keep in mind, this doesn't reuse Bullet objects and allocates a new list per frame, so there's room for further optimization.
import turtle as t
from random import randint, random
def draw_star(points, size, col, x, y):
t.penup()
t.goto(x, y)
t.pendown()
angle = 180 - (180 / points)
t.color(col)
t.begin_fill()
for i in range(points):
t.forward(size)
t.right(angle)
t.end_fill()
# Main code
while True:
ranPts = randint(2, 5) * 2 + 1
ranSize = randint(10, 50)
ranCol = (random(), random(), random())
ranX = randint(-350, 300)
ranY = randint(-250, 250)
draw_star(ranPts, ranSize, ranCol, ranX, ranY)
Question:
How could I know the maximum values of coordinates of my screen? So I can have a better idea on how to set the values of ranX and ranY?
Thanks.
You could use t.setworldcoordinates(llx, lly, urx, ury)
The parameters:
llx = x of lower left corner
lly = y of lower left corner
urx= x of upper right corner
ury = y of upper right corner
You can create a function and find the values of coordinates yourself by clicking on the screen like this:
# turtle library
import turtle
#This to make turtle object
tess=turtle.Turtle()
# self defined function to print coordinate
def buttonclick(x,y):
print("You clicked at this coordinate({0},{1})".format(x,y))
#onscreen function to send coordinate
turtle.onscreenclick(buttonclick,1)
turtle.listen() # listen to incoming connections
turtle.speed(10) # set the speed
turtle.done() # hold the screen
This will print everytime you click on the screen and print the coordinates out.
The screensize() function returns the canvas width and the canvas height as a tuple.
You can use this to find the max coordinates of the canvas.
screenSize = t.screensize() #returns (width, height)
# Main code
while True:
ranPts = randint(2, 5) * 2 + 1
ranSize = randint(10, 50)
ranCol = (random(), random(), random())
ranX = randint(50-screenSize[0], screenSize[0] - 100)
ranY = randint(50-screenSize[1], screenSize[1] - 100)
draw_star(ranPts, ranSize, ranCol, ranX, ranY)
I found out this is what I need: t.window_width() and t.window_height().
How would I go about finding the current position of the mouse pointer in the turtle screen? I want it so I have the mouse position before I click and while im moving the cursor. I have searched google and this website can't find anything besides how to get the position after a click.
turtle.getcanvas() returns a Tkinter Canvas.
Like with a Tkinter window, you can get the current mouse pointer coordinates by winfo_pointerx and .winfo_pointery on it:
canvas = turtle.getcanvas()
x, y = canvas.winfo_pointerx(), canvas.winfo_pointery()
# or
# x, y = canvas.winfo_pointerxy()
If you want to only react to movement instead of e.g. polling mouse pointer positions in a loop, register an event:
def motion(event):
x, y = event.x, event.y
print('{}, {}'.format(x, y))
canvas = turtle.getcanvas()
canvas.bind('<Motion>', motion)
Note that events only fire while the mouse pointer hovers over the turtle canvas.
All these coordinates will be window coordinates (with the origin (0, 0) at the top left of the turtle window), not turtle coordinates (with the origin (0, 0) at the canvas center), so if you want to use them for turtle positioning or orientation, you'll have to do some transformation.
Following up on this answer, here's an example of one way to normalize the Tkinter coordinates for turtle using the canvas.bind('<Motion>', motion) approach (canvas.winfo_pointerxy() didn't provide values that made sense to me, although I didn't look into it much):
import turtle
def on_motion(event):
x = event.x - turtle.window_width() / 2
y = -event.y + turtle.window_height() / 2
turtle.goto(x, y)
turtle.stamp()
print(x, y)
turtle.tracer(0)
turtle.penup()
turtle.getcanvas().bind("<Motion>", on_motion)
turtle.exitonclick()
Using this in a real-time app rather than only on motion:
import turtle
def on_motion(event):
global mouse_x, mouse_y
mouse_x = event.x - turtle.window_width() / 2
mouse_y = -event.y + turtle.window_height() / 2
def tick():
print(mouse_x, mouse_y)
turtle.goto(mouse_x, mouse_y)
turtle.stamp()
win.ontimer(tick, frame_delay_ms)
turtle.tracer(0)
mouse_x, mouse_y = 0, 0
turtle.getcanvas().bind("<Motion>", on_motion)
turtle.shape("circle")
turtle.penup()
frame_delay_ms = 1000 // 30
win = turtle.Screen()
tick()
turtle.exitonclick()
Putting it into a more full-featured app without global:
import turtle
from math import sin
def add_mouse_listener():
def on_motion(event):
nonlocal x, y
x = event.x - turtle.window_width() / 2
y = -event.y + turtle.window_height() / 2
turtle.getcanvas().bind("<Motion>", on_motion)
x, y = 0, 0
return lambda: (x, y)
def color(c, a):
return sin(c + a) / 2 + 0.5
def colors(r, ra, g, ga, b, ba):
return color(r, ra), color(g, ga), color(b, ba)
def main():
ra = 0
ba = 0
ga = 0
r = 0.5
b = 0
g = 1
frame_delay_ms = 1000 // 30
turtle.tracer(0)
turtle.pensize(40)
mouse_pos = add_mouse_listener()
win = turtle.Screen()
def tick():
nonlocal ra, ba, ga
turtle.color(colors(r, ra, g, ga, b, ba))
ra += 0.03
ba += 0.0311
ga += 0.032
x, y = mouse_pos()
turtle.setheading(turtle.towards(x, y))
turtle.forward(10)
turtle.update()
win.ontimer(tick, frame_delay_ms)
tick()
turtle.exitonclick()
if __name__ == "__main__":
main()
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.