Converting pygame 2d water ripple to pyOpenGL
Asked Answered
E

1

6

I have a 2d pygame water simulation thingy that I followed a tutorial to make. I also found the answer to this question to fix issues with the tutorial: Pygame water physics not working as intended

I have since been trying to convert this program over to using pyopengl to render things. However, I have been struggling to: A: Draw the water polygon B: texture the water polygon with a tiled texture

Here is my (rather poor) attempt at converting this code to pyopengl.

import pygame, random
import math as m

from pygame import *
from OpenGL import *
from OpenGL.GLU import *
from OpenGL.GL import *

pygame.init()

WINDOW_SIZE = (854, 480)
 
screen = pygame.display.set_mode(WINDOW_SIZE,0,32,DOUBLEBUF|OPENGL) # initiate the window

clock = pygame.time.Clock()


def draw_polygon(polygon_points):
    glBegin(GL_POLYGON);
    for i in polygon_points:
        glVertex3fv(i)
    #glEnd()
    
class surface_water_particle():
    
    def __init__(self, x,y):
        self.x_pos = x
        self.y_pos = y
        self.target_y = y
        self.velocity = 0
        self.k = 0.04  
        self.d = 0.08
        self.time = 1

    def update(self):
        x =  self.y_pos - self.target_y
        a = -(self.k * x + self.d * self.velocity)

        if self.y_pos > self.target_y:
            self.y_pos -= 0.1
        if self.y_pos < self.target_y:
            self.y_pos += 0.1
        self.velocity = round(self.velocity)

        self.y_pos += self.velocity
        self.velocity += a

        self.time += 1

class water_tile():
    def __init__(self, x_start, x_end, y_start, y_end, segment_length):
        self.springs = []
        self.x_start = x_start
        self.y_start = y_start
        self.x_end = x_end
        self.y_end = y_end - 10
        for i in range(abs(x_end - x_start) // segment_length):
            self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))

    def update(self, spread):
        passes = 4  # more passes = more splash spreading
        for i in range(len(self.springs)):
            self.springs[i].update() 

        leftDeltas = [0] * len(self.springs)
        rightDeltas = [0] * len(self.springs)
        for p in range(passes):  
            for i in range(0, len(self.springs)):
                if i > 0:  
                    leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
                    self.springs[i - 1].velocity += leftDeltas[i]
                if i < len(self.springs):
                    rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[(i + 1)%len(self.springs)].y_pos)
                    self.springs[(i + 1)%len(self.springs)].velocity += rightDeltas[i]

            for i in range(0, len(self.springs)):
                if round (leftDeltas[i],12) == 0 or round (rightDeltas[i],12) == 0:
                    self.springs[i - 1].y_pos = self.y_end+10
                if i > 0:
                    self.springs[i - 1].y_pos += leftDeltas[i]  # you were updating velocity here!
                if i < len(self.springs):
                    self.springs[(i + 1)%len(self.springs)].y_pos += rightDeltas[i]
                
    def splash(self, index, speed):
        if index >= 0 and index < len(self.springs):
            self.springs[index].velocity = speed 

    def draw(self):
        water_surface = pygame.Surface((abs(self.x_end-self.x_start), abs(self.y_start - self.y_end)), depth=8).convert_alpha()
        polygon_points = []
        polygon_points.append((self.x_start, self.y_start,0))
        for spring in range(len(self.springs)):
            polygon_points.append((self.springs[spring].x_pos, self.springs[spring].y_pos,0))
        polygon_points.append((self.springs[len(self.springs) - 1].x_pos, self.y_start,0))

        draw_polygon(polygon_points)
        
        return water_surface

class water_object:
    def __init__(self, x_start, x_end, y_start, y_end, segment_length, x_pos, y_pos):
        self.water = water_tile(x_start,x_end,y_start,y_end,segment_length)
        self.image = self.water.draw()
        self.rect = self.image.get_rect()
        self.rect.x = x_pos
        self.rect.y = y_pos

    def update(self):
        self.water.update(0.1)
        self.image = self.water.draw()
        
water_list = [water_object(0,276+16,64,0,16,0,20)]

while True:
    screen.fill((0,0,0))
    for water in water_list:
        gluPerspective(45, (WINDOW_SIZE[0]/WINDOW_SIZE[1]), 0.1, 50.0)
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        water.update()
        #screen.blit(water.image, (water.rect.x,water.rect.y))
        #water_test.x_start = water_test.x_start + 1
        
        #if random.randint(0,8) == 1:
            #water_test.splash(random.randint(0, len(water_test.springs) - 1),2)
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
            if event.type == MOUSEBUTTONDOWN:
                print (len(water.water.springs))
                water.water.splash(random.randint(0, len(water.water.springs) - 1),50)

    pygame.display.update()

    clock.tick(60)

However, despite my attempt, I couldnt get anything to display on screen at all. How can I fix this/how can I attain the 2 things I have been struggling with?

Elka answered 3/9, 2020 at 22:59 Comment(0)
M
2

You cannot draw an OpenGL primitive to a pygame.Surface. Anyway there is no need to do so. For the best performance, directly draw to the default framebuffer (window).

Since you want to draw a line, you have to use a Line primitive type. GL_POLYGON would draw a filed convex polygon. Use the primitive type GL_LINE_STRIP:

def draw_polygon(polygon_points):
    glBegin(GL_LINE_STRIP)
    for pt in polygon_points:
        glVertex2f(*pt)
    glEnd()

Before you draw the line, ser the current color by glColor:

glColor3f(0, 0, 1)
draw_polygon(polygon_points)

The vertex coordinates of the lie are specified in window space. Hence you have to setup an Orthographic projection rather than a Perspective projection. Specify the current matrix by [glMatrixMode] and set the projection matrix by glOrtho. Since the matrix operations do not set a matrix, but multiply the current matrix by the specified matrix, I recommend to load the identity matrix before (glLoadIdentity):

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, WINDOW_SIZE[0], WINDOW_SIZE[1], 0, -1, 1)

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()

Before you draw the line you have to clear the framebuffer by glClear. The clear color can be defined by glClearColor:

glClearColor(1, 1, 1, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

Complete example:

import pygame
from OpenGL import *
from OpenGL.GL import *

def draw_polygon(surf_rect, polygon_points):
    glBegin(GL_LINE_STRIP)
    #glBegin(GL_TRIANGLE_STRIP)
    for pt in polygon_points:
        glVertex2f(*pt)
        glVertex2f(pt[0], surf_rect.height)
    glEnd()
    
class WaterParticle():
    def __init__(self, x, y):
        self.x, self.y = x, y
        self.target_y = y
        self.velocity = 0
        self.k = 0.04  
        self.d = 0.08
    def update(self):
        x = self.y - self.target_y
        a = -(self.k * x + self.d * self.velocity)
        #self.p[1] += -0.1 if x > 0 else 0.1 if x < 0 else 0
        self.y += self.velocity
        self.velocity += a

class Water():
    def __init__(self, x_start, x_end, y_start, segment_length, passes, spread):
        n = abs(x_end - x_start + segment_length - 1) // segment_length + 1
        self.particles = [WaterParticle(i * segment_length + x_start, y_start) for i in range(n)]
        self.passes = passes
        self.spread = spread

    def update(self):
        for particle in self.particles:
            particle.update() 

        left_deltas = [0] * len(self.particles)
        right_deltas = [0] * len(self.particles)
        for _ in range(self.passes):  
            for i in range(len(self.particles)):
                if i > 0:  
                    left_deltas[i] = self.spread * (self.particles[i].y - self.particles[i - 1].y)
                    self.particles[i - 1].velocity += left_deltas[i]
                if i < len(self.particles)-1:
                    right_deltas[i] = self.spread * (self.particles[i].y - self.particles[i + 1].y)
                    self.particles[i + 1].velocity += right_deltas[i]
            for i in range(len(self.particles)):
                if i > 0:
                    self.particles[i-1].y += left_deltas[i]
                if i < len(self.particles) - 1:
                    self.particles[i+1].y += right_deltas[i]
 
    def splash(self, index, speed):
        if index > 0 and index < len(self.particles):
            self.particles[index].velocity += speed
                
    def draw(self, surf_rect):
        polygon_points = []
        for spring in range(len(self.particles)):
            polygon_points.append((self.particles[spring].x, self.particles[spring].y))
        glColor3f(0, 0, 1)
        draw_polygon(surf_rect, polygon_points)
        
pygame.init()
window = pygame.display.set_mode((640, 480), pygame.DOUBLEBUF | pygame.OPENGL)
clock = pygame.time.Clock()

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, *window.get_size(), 0, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glClearColor(1, 1, 1, 1)

water_line_y = window.get_height() // 2
water = Water(0, window.get_width(), window.get_height() // 2, 3, 8, 0.025)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
        if event.type == pygame.MOUSEBUTTONDOWN:
            velocity = water_line_y - event.pos[1]
            if velocity > 0:
                index = int(len(water.particles) * event.pos[0] / window.get_width())
                water.splash(index, velocity)
    
    water.update()

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    water.draw(window.get_rect())
    pygame.display.flip()
    clock.tick(50)
Moresque answered 4/9, 2020 at 15:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.