Matthew Mitchell
Matthew Mitchell

Reputation: 13

Python OpenGL does not render every Triangle in a Quadrilateral

I have some code that renders a flat surface in OpenGL with a texture. The same framework lets me render cubes but flat surfaces seem to render three triangles that join in the middle of a square. The effect seems to be an envelope shape.

I have changing the texture image. I have tried loading the image with 3 channels instead of 4. The same framework renders cubes with no problem.

I can render the same square again and rotate it or use triangles instead but I would prefer to understand the problem.

## Python 3.7

## OpenGL objectsare visualised in pygame

from OpenGL.GL import *
from OpenGL.GLU import *
import pygame
from pygame.locals import *
import sys
import numpy as np
import cv2

cwd = 'C:/Users/berta/Desktop/Python/Open GL/'

coords = np.array([
[0.,1.],
[1.,1.],
[1.,0.],
[0.,0.]
])

graphic_names = [
    'ground'
]

ground_vertices = np.array([
    [-10, -1.5, 10],
    [10, -1.5, 10],
    [-10, -1.5, -300],
    [10, -1.5, -300]
], dtype = np.float64)

def loadGraphics(graphic_names):
    output_dict = {}
    for f in graphic_names:
        img = cv2.imread(cwd + 'Images/' + f + '.png', cv2.IMREAD_UNCHANGED)
        if img.shape[2] == 3:
            alpha = np.uint8(np.ones(img.shape[:2])*255)
            img = np.dstack((img,alpha))
        textureSurface = pygame.Surface(img.shape[:2], pygame.SRCALPHA)
        bv = textureSurface.get_buffer()
        bv.write(img.tostring(), 0)
        textureData = pygame.image.tostring(textureSurface, "RGBA", 1)
        width = textureSurface.get_width()
        height = textureSurface.get_height()
        output_dict[f] = (textureData, width, height)
    return output_dict

graphics = loadGraphics(graphic_names)

def loadTexture(textureData, width, height):
    glEnable(GL_BLEND)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

    glEnable(GL_TEXTURE_2D)

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
                    0, GL_RGBA, GL_UNSIGNED_BYTE, textureData)

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    return

class FlatSurface:

    def __init__(self, texture_name, input_vertices, n_x, n_z):
        self.texture_name = texture_name
        self.vertices = input_vertices
        # Determines the flat plane and creates tiled squares
        flat_dim_found = False
        for i in range(3):
            if np.all(self.vertices[:,i] == self.vertices[0,i]):
                flat_dim_found = True
                self.flat_dim = i
                break
        if not(flat_dim_found):
            print('No flat dimension has been found!')
            self.flat_dim = 1
        self.y_const = np.mean(self.vertices[:,self.flat_dim])
        x_not_done = True
        for i in range(3):
            if i != self.flat_dim:
                if x_not_done:
                    self.min_x = np.min(self.vertices[:,i])
                    max_x = np.max(self.vertices[:,i])
                    self.x_dim = i
                    x_not_done = False
                else:
                    self.min_z = np.min(self.vertices[:,i])
                    max_z = np.max(self.vertices[:,i])
                    self.z_dim = i
        self.tiles = []
        self.tile_x_width = float(max_x-self.min_x)/float(n_x)
        self.tile_z_width = float(max_z-self.min_z)/float(n_z)
        for x in range(n_x):
            for z in range(n_z):
                self.tiles.append(self.make_tile(x,z))
        self.tiles = np.array(self.tiles, dtype = np.float64)
        print(self.tiles)

    def make_tile(self,x,z):
        # Creates a square tile for a certain index in the bounded shape
        surf = []
        tile = np.array([0,0,0])
        tile[self.flat_dim] = self.y_const
        tile[self.x_dim] = self.min_x + x*self.tile_x_width
        tile[self.z_dim] = self.min_z + z*self.tile_z_width
        surf.append(tile.copy())
        tile[self.z_dim] += self.tile_z_width
        surf.append(tile.copy())
        tile[self.x_dim] += self.tile_x_width
        tile[self.z_dim] -= self.tile_z_width
        surf.append(tile.copy())
        tile[self.z_dim] += self.tile_z_width
        surf.append(tile.copy())
        return surf

    def draw(self):
        # loads texture and draws each square
        loadTexture(*graphics[self.texture_name])
        glBegin(GL_QUADS)
        for surf in self.tiles:
            for vertex, coord in zip(surf, coords):
                glTexCoord2f(*coord)
                glVertex3fv(vertex)
            ## A shameful hack to stop envelopes appearing
            #for vertex, coord in zip(surf[::-1], coords):
            #   glTexCoord2f(*coord)
            #   glVertex3fv(vertex)
        glEnd()


def main():
    pygame.init()
    display = (1200,800)
    pygame.display.set_mode(display, DOUBLEBUF|OPENGL|OPENGLBLIT)
    gluPerspective(45, (display[0]/display[1]), 0.99 , 20.0)
    glTranslatef( 0., 0., -10. )
    glRotate(0, 0, 0, 0)
    x_move = 0
    z_move = 0
    speed = 0.01

    ground = FlatSurface('ground', ground_vertices, 4, 62)

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

        m = glGetDoublev(GL_MODELVIEW_MATRIX) # perspective matrix

        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        ground.draw()
        glTranslate(x_move,0,z_move+speed)
        pygame.display.flip()
        pygame.time.wait(10)


main()

The coordinates of the quadrilaterals look like this and is as expected:

[[   5.   -1.   -5.]
[   5.   -1.    0.]
[  10.   -1.   -5.]
[  10.   -1.    0.]]

[[   5.   -1.    0.]
[   5.   -1.    5.]
[  10.   -1.    0.]
[  10.   -1.    5.]]

[[   5.   -1.    5.]
[   5.   -1.   10.]
[  10.   -1.    5.]
[  10.   -1.   10.]]]

The output is shown in the screenshot.

envelopes instead of squares

Upvotes: 1

Views: 264

Answers (2)

Rabbid76
Rabbid76

Reputation: 210909

The issue is the method make_tile in the class FlatSurface, which creates the vertices for a Quad primitive in the wrong order.
The vertices have to be in a circulating order around the quad. e.g.:

           1         2
       z    +--------+
       ^    |        |
       |    |        |
       |    |        |
            +--------+
min (x, z) 0          3
             -----> x

It has to be:

class FlatSurface

    # [...]

    def make_tile(self,x,z):
        # Creates a square tile for a certain index in the bounded shape
        surf = []
        tile = np.array([0,0,0])
        tile[self.flat_dim] = self.y_const
        tile[self.x_dim] = self.min_x + x*self.tile_x_width
        tile[self.z_dim] = self.min_z + z*self.tile_z_width
        surf.append(tile.copy())
        tile[self.z_dim] += self.tile_z_width
        surf.append(tile.copy())
        tile[self.x_dim] += self.tile_x_width
        surf.append(tile.copy())
        tile[self.z_dim] -= self.tile_z_width
        surf.append(tile.copy())
        return surf

Upvotes: 1

HolyBlackCat
HolyBlackCat

Reputation: 96091

Your vertices are in a wrong order.

You used following order for each quad:

1---2
|   |
3---4

But it should've been:

1---2
|   |
4---3

Upvotes: 1

Related Questions