Водный волновой эффект Python и Pygame, из видео кодирования поезда - PullRequest
2 голосов
/ 21 февраля 2020

В настоящее время я пытаюсь воспроизвести в python / pygame (из java, p5 из видео с кодирующим поездом: https://www.youtube.com/watch?v=BZUdGqeOD0w) эффект водной ряби.

Я не могу чтобы заставить это работать, экран остается пустым (черный: цвет фона), или я выдаю мне ошибку:

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

, что я на самом деле считаю нормальным, так как мы подставляем положительное число от 0 до цвета пикселя:

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

: previous[i-1][j], previous[i+1][j], previous[i][j-1], previous[i][j+1] равны 0, тогда как current[I][j] является положительным число, так что по логике он должен давать отрицательное значение цвета. Может быть, это связано с тем, как массивы работают в Python против Java?

Для получения дополнительной помощи, вот сайт поезда кодирования, который использовал для создания своего видео: https://web.archive.org/web/20160418004149/http: //freespace.virgin.net/hugo.elias/graphics/x_water.htm

Вот мой код:

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()

Вот код поезда кодирования (в java P5): (Примечание: он использовал mousedraft, а не mouseTouch (в видео он использует mousetouch) но это не важно.)

// 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 Ответ

2 голосов
/ 21 февраля 2020

Прежде всего

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

не является двумерным массивом. Он генерирует один список, а затем создается внешний список, где каждый элемент ссылается на один и тот же внутренний список.

Если вы хотите создать список, где каждый элемент представляет собой отдельный список, то вам необходимо:
(внутренний размер должен быть строками, например current[col][row] / current[x][y])

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

В пигаме значения для цветового канала должны быть целыми значениями в диапазоне [0, 255] :

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

Кроме того, событие MOUSEBUTTONDOWN происходит только один раз при нажатии кнопки мыши. Вы должны оценить, нажата ли текущая мышь на 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

Я рекомендую использовать pygame.Surface соответственно pygame.PixelArray для улучшения производительности. Это похоже loadPixels() / updatePixels() в Обработка :

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))

К сожалению, все еще невероятно медленно. Полный пример:

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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...