Water ripple effect Python and Pygame, from coding train video

試著忘記壹切 提交于 2020-12-12 10:12:37

问题


I am currently trying to replicate in python/pygame (from java, p5 from the coding train video: https://www.youtube.com/watch?v=BZUdGqeOD0w) the water ripple effect.

I am unable to make it work, the screen stays blank (black: background color) or I gives me the error:

Traceback (most recent call last):
  File "/Users/vicki/Documents/Atom Test/Water Ripple.py", line 39, in <module>
    screen.fill((current[i][j],current[i][j],current[i][j]),(i,j,1,1))
TypeError: invalid color argument

which I actually find to be normal since we substrate a positive number from 0 to get the color of the pixel:

current[i][j] = (previous[i-1][j]+previous[i+1][j]+ previous[i][j+1]+ previous[i][j-1])/ 2 - current[i][j]

The: previous[i-1][j], previous[i+1][j], previous[i][j-1], previous[i][j+1] are all equal to 0, whereas the current[I][j] is a positive number, so logically it should give a negative color value. Maybe it has to do with how the arrays work in Python vs Java?

For more help, here is the website coding train has used to make his video: https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm

Here is my code:

import pygame

pygame.init()

width = 200
height = 200

cols = width
rows = height

#dampening = how fast the ripple effect stops.
dampening = 0.999


#Arrays that hold the colors of the screen.
current = [[0]*cols]*rows
previous = [[0]*cols]*rows


screen = pygame.display.set_mode((width,height))
#Sets the initial background to black
screen.fill((0,0,0))

#Mainloop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
        if event.type == pygame.MOUSEBUTTONDOWN:
            #if the user clicks the screen, it sets the pixel the user clicked upon white
            mouse_pos = pygame.mouse.get_pos()
            current[mouse_pos[0]][mouse_pos[1]] = 255



    #This part seems to be the problem
    for i in range(1,rows-1):
        for j in range(1,cols-1):
            #This computation is weird: we are substracting positive/zero number from a null number which should normally give us a negative number. A color value (RGB) cannot be a negative number!
            current[i][j] = (previous[i-1][j]+previous[i+1][j]+ previous[i][j+1]+ previous[i][j-1])/ 2 - current[i][j]
            current[i][j] *= dampening
            #This line is where it gives me the error
            screen.fill((current[i][j],current[i][j],current[i][j]),(i,j,1,1))

    #Switching the arrays, have also tried: previous, current = current, previous (doesn't work either)
    temp = previous
    previous = current
    current = temp
    pygame.display.flip()

Here is the code of coding train (in java P5): (Note: he used mousedraft and not mouseTouch (in the video he uses mousetouch) but it isn't important.)

// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain

// 2D Water Ripples
// Video: https://youtu.be/BZUdGqeOD0w
// Algorithm: https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm

int cols;
int rows;
float[][] current;// = new float[cols][rows];
float[][] previous;// = new float[cols][rows];

float dampening = 0.99;

void setup() {
  size(600, 400);
  cols = width;
  rows = height;
  current = new float[cols][rows];
  previous = new float[cols][rows];
}

void mouseDragged() {
  previous[mouseX][mouseY] = 500;
}

void draw() {
  background(0);

  loadPixels();
  for (int i = 1; i < cols-1; i++) {
    for (int j = 1; j < rows-1; j++) {
      current[i][j] = (
        previous[i-1][j] + 
        previous[i+1][j] +
        previous[i][j-1] + 
        previous[i][j+1]) / 2 -
        current[i][j];
      current[i][j] = current[i][j] * dampening;
      int index = i + j * cols;
      pixels[index] = color(current[i][j]);
    }
  }
  updatePixels();

  float[][] temp = previous;
  previous = current;
  current = temp;
}

回答1:


First of all

current = [[0]*cols]*rows
previous = [[0]*cols]*rows

is not a 2 dimensional array. It generate a single list and than a outer list is generated where each element refers to the same inner list.

If you want to generate a list, where each element is a separate list, then you have to:
(The inner dimension should be rows e.g. current[col][row]/current[x][y])

current = [[0]*rows for col in range(cols)]
previous = [[0]*rows for col in range(cols)]

In pygame the values for the color channel have to be integral values in range [0, 255]:

val = min(255, max(0, round(current[i][j])))
screen.fill((val, val, val), (i,j,1,1)) 

Furthermore the MOUSEBUTTONDOWN event occurs only once when a mouse button is pressed. u have to evaluate if the current state of the mouse is pressed by pygame.mouse.get_pressed():

while True:
    # [...]
    
    if any(pygame.mouse.get_pressed()):
        mouse_pos = pygame.mouse.get_pos()
        previous[mouse_pos[0]][mouse_pos[1]] = 500

I recommend to use pygame.Surface respectively pygame.PixelArray to improve the performance. That's similar loadPixels()/updatePixels() in Processing:

img = pygame.Surface((width, height))
while True:
    # [...]

    pixelArray = pygame.PixelArray(img)
    for i in range(1,cols-1):
        for j in range(1,rows-1):
            current[i][j] = (
                previous[i-1][j] + 
                previous[i+1][j] +
                previous[i][j-1] + 
                previous[i][j+1]) / 2 - current[i][j]
            current[i][j] *= dampening
            val = min(255, max(0, round(current[i][j])))
            pixelArray[i, j] = (val, val, val)
    pixelArray.close()
    screen.blit(img, (0, 0))

Sadly that all is still incredibly slow. Full example:

import pygame
    
pygame.init()

width = 300
height = 200

cols = width
rows = height

#dampening = how fast the ripple effect stops.
dampening = 0.999

#Arrays that hold the colors of the screen.
current = [[0]*rows for col in range(cols)]
previous = [[0]*rows for col in range(cols)]

print(current[0][0], current[199][199])

screen = pygame.display.set_mode((width,height))
#Sets the initial background to black
screen.fill((0,0,0))

img = pygame.Surface((width, height))

#Mainloop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()         

    if any(pygame.mouse.get_pressed()):
        mouse_pos = pygame.mouse.get_pos()
        previous[mouse_pos[0]][mouse_pos[1]] = 500

    #This part seems to be the problem
    pixelArray = pygame.PixelArray(img)
    for i in range(1,cols-1):
        for j in range(1,rows-1):
            current[i][j] = (
                previous[i-1][j] + 
                previous[i+1][j] +
                previous[i][j-1] + 
                previous[i][j+1]) / 2 - current[i][j]
            current[i][j] *= dampening
            val = min(255, max(0, round(current[i][j])))
            pixelArray[i, j] = (val, val, val)
    pixelArray.close()

    # Switching the arrays
    previous, current = current, previous

    screen.blit(img, (0, 0))
    pygame.display.flip()

For really good performance, you must be using NumPy. In the following example I have throttled the frames per second with pygame.time.Clock.tick:

import numpy
import pygame
import scipy
pygame.init()

window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

size = window.get_size()
dampening = 0.999

current = numpy.zeros(size, numpy.float32)
previous = numpy.zeros(size, numpy.float32)
kernel = numpy.array([[0.0, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0]])

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False    
        
    if any(pygame.mouse.get_pressed()):
        mouse_pos = pygame.mouse.get_pos()
        previous[mouse_pos] = 1000

    # either:
    # current = (scipy.ndimage.convolve(previous, kernel) - current) * dampening
    # or:
    current[1:size[0]-1, 1:size[1]-1] = (
        (previous[0:size[0]-2, 0:size[1]-2] + 
         previous[2:size[0], 0:size[1]-2] + 
         previous[0:size[0]-2, 2:size[1]] + 
         previous[2:size[0], 2:size[1]]) / 2 - 
        current[1:size[0]-1, 1:size[1]-1]) * dampening

    array = numpy.transpose(255 - numpy.around(numpy.clip(current, 0, 255)))
    array = numpy.repeat(array.reshape(*size, 1).astype('uint8'), 3, axis = 2)
    image = pygame.image.frombuffer(array.flatten(), size, 'RGB')

    previous, current = current, previous

    window.blit(image, (0, 0))
    pygame.display.flip()

pygame.quit()
exit()    


来源:https://stackoverflow.com/questions/60336688/water-ripple-effect-python-and-pygame-from-coding-train-video

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!