예제 #1
0
class OpenGLRenderer(ABC):
    def __init__(self, src_fbuffer, src_default):
        self.default_prog = None
        self.fbuffer_prog = None

        self.fbuffer = None
        self.fbuffer_tex_front = None
        self.fbuffer_tex_back = None

        self.vertex_buffer = None
        self.index_buffer = None

        # Renderer Globals: STYLE/MATERIAL PROPERTIES
        #
        self.style = Style()

        # Renderer Globals: Curves
        self.stroke_weight = 1
        self.stroke_cap = ROUND
        self.stroke_join = MITER

        # Renderer Globals
        # VIEW MATRICES, ETC
        #
        self.viewport = None
        self.texture_viewport = None
        self.transform_matrix = np.identity(4)
        self.projection_matrix = np.identity(4)

        # Renderer Globals: RENDERING
        self.draw_queue = []

        # Shaders
        self.fbuffer_prog = Program(src_fbuffer.vert, src_fbuffer.frag)
        self.default_prog = Program(src_default.vert, src_default.frag)

    def initialize_renderer(self):
        self.fbuffer = FrameBuffer()

        vertices = np.array(
            [[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]],
            np.float32)
        texcoords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]],
                             dtype=np.float32)

        self.fbuf_vertices = VertexBuffer(data=vertices)
        self.fbuf_texcoords = VertexBuffer(data=texcoords)

        self.fbuffer_prog['texcoord'] = self.fbuf_texcoords
        self.fbuffer_prog['position'] = self.fbuf_vertices

        self.vertex_buffer = VertexBuffer()
        self.index_buffer = IndexBuffer()

    def render_default(self, draw_type, draw_queue):
        # 1. Get the maximum number of vertices persent in the shapes
        # in the draw queue.
        #
        if len(draw_queue) == 0:
            return

        num_vertices = 0
        for vertices, _, _ in draw_queue:
            num_vertices = num_vertices + len(vertices)

        # 2. Create empty buffers based on the number of vertices.
        #
        data = np.zeros(num_vertices,
                        dtype=[('position', np.float32, 3),
                               ('color', np.float32, 4)])

        # 3. Loop through all the shapes in the geometry queue adding
        # it's information to the buffer.
        #
        sidx = 0
        draw_indices = []
        for vertices, idx, color in draw_queue:
            num_shape_verts = len(vertices)

            data['position'][sidx:(sidx +
                                   num_shape_verts), ] = np.array(vertices)

            color_array = np.array([color] * num_shape_verts)
            data['color'][sidx:sidx + num_shape_verts, :] = color_array

            draw_indices.append(sidx + idx)

            sidx += num_shape_verts

        self.vertex_buffer.set_data(data)
        self.index_buffer.set_data(np.hstack(draw_indices))

        # 4. Bind the buffer to the shader.
        #
        self.default_prog.bind(self.vertex_buffer)

        # 5. Draw the shape using the proper shape type and get rid of
        # the buffers.
        #
        self.default_prog.draw(draw_type, indices=self.index_buffer)

    def cleanup(self):
        """Run the clean-up routine for the renderer.

        This method is called when all drawing has been completed and the
        program is about to exit.

        """
        self.default_prog.delete()
        self.fbuffer_prog.delete()
        self.fbuffer.delete()

    def _transform_vertices(self, vertices, local_matrix, global_matrix):
        return np.dot(np.dot(vertices, local_matrix.T), global_matrix.T)[:, :3]
예제 #2
0
파일: renderer2d.py 프로젝트: zostercr/p5
class Renderer2D:
    def __init__(self):
        self.default_prog = None
        self.fbuffer_prog = None
        self.texture_prog = None
        self.line_prog = None

        self.fbuffer = None
        self.fbuffer_tex_front = None
        self.fbuffer_tex_back = None

        self.vertex_buffer = None
        self.index_buffer = None

        ## Renderer Globals: USEFUL CONSTANTS
        self.COLOR_WHITE = (1, 1, 1, 1)
        self.COLOR_BLACK = (0, 0, 0, 1)
        self.COLOR_DEFAULT_BG = (0.8, 0.8, 0.8, 1.0)

        ## Renderer Globals: STYLE/MATERIAL PROPERTIES
        ##
        self.background_color = self.COLOR_DEFAULT_BG

        self.fill_color = self.COLOR_WHITE
        self.fill_enabled = True

        self.stroke_color = self.COLOR_BLACK
        self.stroke_enabled = True

        self.tint_color = self.COLOR_BLACK
        self.tint_enabled = False

        ## Renderer Globals: Curves
        self.stroke_weight = 1
        self.stroke_cap = 2
        self.stroke_join = 0

        ## Renderer Globals
        ## VIEW MATRICES, ETC
        ##
        self.viewport = None
        self.texture_viewport = None
        self.transform_matrix = np.identity(4)
        self.modelview_matrix = np.identity(4)
        self.projection_matrix = np.identity(4)

        ## Renderer Globals: RENDERING
        self.draw_queue = []

    def initialize_renderer(self):
        self.fbuffer = FrameBuffer()

        vertices = np.array(
            [[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]],
            np.float32)
        texcoords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]],
                             dtype=np.float32)

        self.fbuf_vertices = VertexBuffer(data=vertices)
        self.fbuf_texcoords = VertexBuffer(data=texcoords)

        self.fbuffer_prog = Program(src_fbuffer.vert, src_fbuffer.frag)
        self.fbuffer_prog['texcoord'] = self.fbuf_texcoords
        self.fbuffer_prog['position'] = self.fbuf_vertices

        self.vertex_buffer = VertexBuffer()
        self.index_buffer = IndexBuffer()

        self.default_prog = Program(src_default.vert, src_default.frag)
        self.texture_prog = Program(src_texture.vert, src_texture.frag)
        self.texture_prog['texcoord'] = self.fbuf_texcoords

        self.reset_view()

    def reset_view(self):
        self.viewport = (
            0,
            0,
            int(builtins.width * builtins.pixel_x_density),
            int(builtins.height * builtins.pixel_y_density),
        )
        self.texture_viewport = (
            0,
            0,
            builtins.width,
            builtins.height,
        )

        gloo.set_viewport(*self.viewport)

        cz = (builtins.height / 2) / math.tan(math.radians(30))
        self.projection_matrix = matrix.perspective_matrix(
            math.radians(60), builtins.width / builtins.height, 0.1 * cz,
            10 * cz)
        self.modelview_matrix = matrix.translation_matrix(-builtins.width / 2, \
                    builtins.height / 2, \
                    -cz)
        self.modelview_matrix = self.modelview_matrix.dot(
            matrix.scale_transform(1, -1, 1))

        self.transform_matrix = np.identity(4)

        self.default_prog['modelview'] = self.modelview_matrix.T.flatten()
        self.default_prog['projection'] = self.projection_matrix.T.flatten()

        self.texture_prog['modelview'] = self.modelview_matrix.T.flatten()
        self.texture_prog['projection'] = self.projection_matrix.T.flatten()

        self.line_prog = Program(src_line.vert, src_line.frag)

        self.line_prog['modelview'] = self.modelview_matrix.T.flatten()
        self.line_prog['projection'] = self.projection_matrix.T.flatten()
        self.line_prog["height"] = builtins.height

        self.fbuffer_tex_front = Texture2D(
            (builtins.height, builtins.width, 3))
        self.fbuffer_tex_back = Texture2D((builtins.height, builtins.width, 3))

        for buf in [self.fbuffer_tex_front, self.fbuffer_tex_back]:
            self.fbuffer.color_buffer = buf
            with self.fbuffer:
                self.clear()

    def clear(self, color=True, depth=True):
        """Clear the renderer background."""
        gloo.set_state(clear_color=self.background_color)
        gloo.clear(color=color, depth=depth)

    def _comm_toggles(self, state=True):
        gloo.set_state(blend=state)
        gloo.set_state(depth_test=state)

        if state:
            gloo.set_state(blend_func=('src_alpha', 'one_minus_src_alpha'))
            gloo.set_state(depth_func='lequal')

    @contextmanager
    def draw_loop(self):
        """The main draw loop context manager.
		"""

        self.transform_matrix = np.identity(4)

        self.default_prog['modelview'] = self.modelview_matrix.T.flatten()
        self.default_prog['projection'] = self.projection_matrix.T.flatten()

        self.fbuffer.color_buffer = self.fbuffer_tex_back

        with self.fbuffer:
            gloo.set_viewport(*self.texture_viewport)
            self._comm_toggles()
            self.fbuffer_prog['texture'] = self.fbuffer_tex_front
            self.fbuffer_prog.draw('triangle_strip')

            yield

            self.flush_geometry()
            self.transform_matrix = np.identity(4)

        gloo.set_viewport(*self.viewport)
        self._comm_toggles(False)
        self.clear()
        self.fbuffer_prog['texture'] = self.fbuffer_tex_back
        self.fbuffer_prog.draw('triangle_strip')

        self.fbuffer_tex_front, self.fbuffer_tex_back = self.fbuffer_tex_back, self.fbuffer_tex_front

    def _transform_vertices(self, vertices, local_matrix, global_matrix):
        return np.dot(np.dot(vertices, local_matrix.T), global_matrix.T)[:, :3]

    def _add_to_draw_queue_simple(self, stype, vertices, idx, fill, stroke,
                                  stroke_weight, stroke_cap, stroke_join):
        """Adds shape of stype to draw queue
		"""
        if stype == 'lines':
            self.draw_queue.append(
                (stype, (vertices, idx, stroke, stroke_weight, stroke_cap,
                         stroke_join)))
        else:
            self.draw_queue.append((stype, (vertices, idx, fill)))

    def render(self, shape):
        fill = shape.fill.normalized if shape.fill else None
        stroke = shape.stroke.normalized if shape.stroke else None
        stroke_weight = shape.stroke_weight
        stroke_cap = shape.stroke_cap
        stroke_join = shape.stroke_join

        obj_list = get_render_primitives(shape)
        for obj in obj_list:
            stype, vertices, idx = obj
            # Transform vertices
            vertices = self._transform_vertices(
                np.hstack([vertices, np.ones((len(vertices), 1))]),
                shape._matrix, self.transform_matrix)
            # Add to draw queue
            self._add_to_draw_queue_simple(stype, vertices, idx, fill, stroke,
                                           stroke_weight, stroke_cap,
                                           stroke_join)

    def flush_geometry(self):
        """Flush all the shape geometry from the draw queue to the GPU.
		"""
        current_queue = []
        for index, shape in enumerate(self.draw_queue):
            current_shape = self.draw_queue[index][0]
            current_queue.append(self.draw_queue[index][1])

            if current_shape == "lines":
                self.render_line(current_queue)
            else:
                self.render_default(current_shape, current_queue)

            current_queue = []

        self.draw_queue = []

    def render_default(self, draw_type, draw_queue):
        # 1. Get the maximum number of vertices persent in the shapes
        # in the draw queue.
        #
        if len(draw_queue) == 0:
            return

        num_vertices = 0
        for vertices, _, _ in draw_queue:
            num_vertices = num_vertices + len(vertices)

        # 2. Create empty buffers based on the number of vertices.
        #
        data = np.zeros(num_vertices,
                        dtype=[('position', np.float32, 3),
                               ('color', np.float32, 4)])

        # 3. Loop through all the shapes in the geometry queue adding
        # it's information to the buffer.
        #
        sidx = 0
        draw_indices = []
        for vertices, idx, color in draw_queue:
            num_shape_verts = len(vertices)

            data['position'][sidx:(sidx + num_shape_verts), ] = vertices

            color_array = np.array([color] * num_shape_verts)
            data['color'][sidx:sidx + num_shape_verts, :] = color_array

            draw_indices.append(sidx + idx)

            sidx += num_shape_verts

        self.vertex_buffer.set_data(data)
        self.index_buffer.set_data(np.hstack(draw_indices))

        # 4. Bind the buffer to the shader.
        #
        self.default_prog.bind(self.vertex_buffer)

        # 5. Draw the shape using the proper shape type and get rid of
        # the buffers.
        #
        self.default_prog.draw(draw_type, indices=self.index_buffer)

    def render_line(self, queue):
        '''
		This rendering algorithm works by tesselating the line into
		multiple triangles.

		Reference: https://blog.mapbox.com/drawing-antialiased-lines-with-opengl-8766f34192dc
		'''

        if len(queue) == 0:
            return

        pos = []
        posPrev = []
        posCurr = []
        posNext = []
        markers = []
        side = []

        linewidth = []
        join_type = []
        cap_type = []
        color = []

        for line in queue:
            if len(line[1]) == 0:
                continue

            for segment in line[1]:
                for i in range(
                        len(segment) -
                        1):  # the data is sent to renderer in line segments
                    for j in [0, 0, 1, 0, 1,
                              1]:  # all the vertices of triangles
                        if i + j - 1 >= 0:
                            posPrev.append(line[0][segment[i + j - 1]])
                        else:
                            posPrev.append(line[0][segment[i + j]])

                        if i + j + 1 < len(segment):
                            posNext.append(line[0][segment[i + j + 1]])
                        else:
                            posNext.append(line[0][segment[i + j]])

                        posCurr.append(line[0][segment[i + j]])

                    markers.extend(
                        [1.0, -1.0, -1.0, -1.0, 1.0,
                         -1.0])  # Is the vertex up/below the line segment
                    side.extend([1.0, 1.0, -1.0, 1.0, -1.0,
                                 -1.0])  # Left or right side of the segment
                    pos.extend([line[0][segment[i]]] *
                               6)  # Left vertex of each segment
                    linewidth.extend([line[3]] * 6)
                    join_type.extend([line[5]] * 6)
                    cap_type.extend([line[4]] * 6)
                    color.extend([line[2]] * 6)

        if len(pos) == 0:
            return

        posPrev = np.array(posPrev, np.float32)
        posCurr = np.array(posCurr, np.float32)
        posNext = np.array(posNext, np.float32)
        markers = np.array(markers, np.float32)
        side = np.array(side, np.float32)
        pos = np.array(pos, np.float32)
        linewidth = np.array(linewidth, np.float32)
        join_type = np.array(join_type, np.float32)
        cap_type = np.array(cap_type, np.float32)
        color = np.array(color, np.float32)

        self.line_prog['pos'] = gloo.VertexBuffer(pos)
        self.line_prog['posPrev'] = gloo.VertexBuffer(posPrev)
        self.line_prog['posCurr'] = gloo.VertexBuffer(posCurr)
        self.line_prog['posNext'] = gloo.VertexBuffer(posNext)
        self.line_prog['marker'] = gloo.VertexBuffer(markers)
        self.line_prog['side'] = gloo.VertexBuffer(side)
        self.line_prog['linewidth'] = gloo.VertexBuffer(linewidth)
        self.line_prog['join_type'] = gloo.VertexBuffer(join_type)
        self.line_prog['cap_type'] = gloo.VertexBuffer(cap_type)
        self.line_prog["color"] = gloo.VertexBuffer(color)

        self.line_prog.draw('triangles')

    def render_image(self, image, location, size):
        """Render the image.

		:param image: image to be rendered
		:type image: builtins.Image

		:param location: top-left corner of the image
		:type location: tuple | list | builtins.Vector

		:param size: target size of the image to draw.
		:type size: tuple | list | builtins.Vector
		"""
        self.flush_geometry()

        self.texture_prog[
            'fill_color'] = self.tint_color if self.tint_enabled else self.COLOR_WHITE
        self.texture_prog['transform'] = self.transform_matrix.T.flatten()

        x, y = location
        sx, sy = size
        imx, imy = image.size
        data = np.zeros(4,
                        dtype=[('position', np.float32, 2),
                               ('texcoord', np.float32, 2)])
        data['texcoord'] = np.array(
            [[0.0, 1.0], [1.0, 1.0], [0.0, 0.0], [1.0, 0.0]], dtype=np.float32)
        data['position'] = np.array(
            [[x, y + sy], [x + sx, y + sy], [x, y], [x + sx, y]],
            dtype=np.float32)

        self.texture_prog['texture'] = image._texture
        self.texture_prog.bind(VertexBuffer(data))
        self.texture_prog.draw('triangle_strip')

    def cleanup(self):
        """Run the clean-up routine for the renderer.

		This method is called when all drawing has been completed and the
		program is about to exit.

		"""
        self.default_prog.delete()
        self.fbuffer_prog.delete()
        self.line_prog.delete()
        self.fbuffer.delete()
예제 #3
0
class VispyRenderer2D(OpenGLRenderer):
    def __init__(self):
        super().__init__(src_fbuffer, src_default)
        self.texture_prog = None
        self.line_prog = None
        self.modelview_matrix = np.identity(4)

    def initialize_renderer(self):
        super().initialize_renderer()
        self.texture_prog = Program(src_texture.vert, src_texture.frag)
        self.texture_prog['texcoord'] = self.fbuf_texcoords
        self.reset_view()

    def reset_view(self):
        self.viewport = (
            0,
            0,
            int(builtins.width * builtins.pixel_x_density),
            int(builtins.height * builtins.pixel_y_density),
        )
        self.texture_viewport = (
            0,
            0,
            builtins.width,
            builtins.height,
        )

        gloo.set_viewport(*self.viewport)  # pylint: disable=no-member

        cz = (builtins.height / 2) / math.tan(math.radians(30))
        self.projection_matrix = matrix.perspective_matrix(
            math.radians(60), builtins.width / builtins.height, 0.1 * cz,
            10 * cz)
        self.modelview_matrix = matrix.translation_matrix(
            -builtins.width / 2, builtins.height / 2, -cz)
        self.modelview_matrix = self.modelview_matrix.dot(
            matrix.scale_transform(1, -1, 1))

        self.transform_matrix = np.identity(4)

        self.default_prog['modelview'] = self.modelview_matrix.T.flatten()
        self.default_prog['projection'] = self.projection_matrix.T.flatten()

        self.texture_prog['modelview'] = self.modelview_matrix.T.flatten()
        self.texture_prog['projection'] = self.projection_matrix.T.flatten()

        self.line_prog = Program(src_line.vert, src_line.frag)

        self.line_prog['modelview'] = self.modelview_matrix.T.flatten()
        self.line_prog['projection'] = self.projection_matrix.T.flatten()
        self.line_prog["height"] = builtins.height

        self.fbuffer_tex_front = Texture2D(
            (builtins.height, builtins.width, 3))
        self.fbuffer_tex_back = Texture2D((builtins.height, builtins.width, 3))

        for buf in [self.fbuffer_tex_front, self.fbuffer_tex_back]:
            self.fbuffer.color_buffer = buf
            with self.fbuffer:
                self.clear()

    def clear(self, color=True, depth=True):
        """Clear the renderer background."""
        gloo.set_state(clear_color=self.style.background_color)  # pylint: disable=no-member
        gloo.clear(color=color, depth=depth)  # pylint: disable=no-member

    def _comm_toggles(self, state=True):
        gloo.set_state(blend=state)  # pylint: disable=no-member
        gloo.set_state(depth_test=state)  # pylint: disable=no-member

        if state:
            gloo.set_state(blend_func=('src_alpha', 'one_minus_src_alpha'))  # pylint: disable=no-member
            gloo.set_state(depth_func='lequal')  # pylint: disable=no-member

    @contextmanager
    def draw_loop(self):
        """The main draw loop context manager.
        """

        self.transform_matrix = np.identity(4)

        self.default_prog['modelview'] = self.modelview_matrix.T.flatten()
        self.default_prog['projection'] = self.projection_matrix.T.flatten()

        self.fbuffer.color_buffer = self.fbuffer_tex_back

        with self.fbuffer:
            gloo.set_viewport(*self.texture_viewport)  # pylint: disable=no-member
            self._comm_toggles()
            self.fbuffer_prog['texture'] = self.fbuffer_tex_front
            self.fbuffer_prog.draw('triangle_strip')

            yield

            self.flush_geometry()
            self.transform_matrix = np.identity(4)

        gloo.set_viewport(*self.viewport)  # pylint: disable=no-member
        self._comm_toggles(False)
        self.clear()
        self.fbuffer_prog['texture'] = self.fbuffer_tex_back
        self.fbuffer_prog.draw('triangle_strip')

        self.fbuffer_tex_front, self.fbuffer_tex_back = self.fbuffer_tex_back, self.fbuffer_tex_front

    def _add_to_draw_queue(self, stype, vertices, idx, fill, stroke,
                           stroke_weight, stroke_cap, stroke_join):
        """Adds shape of stype to draw queue
        """
        if stype == 'lines':
            self.draw_queue.append(
                (stype, (vertices, idx, stroke, stroke_weight, stroke_cap,
                         stroke_join)))
        else:
            self.draw_queue.append((stype, (vertices, idx, fill)))

    def render(self, shape):
        fill = shape.fill.normalized if shape.fill else None
        stroke = shape.stroke.normalized if shape.stroke else None
        stroke_weight = shape.stroke_weight
        stroke_cap = shape.stroke_cap
        stroke_join = shape.stroke_join

        obj_list = get_render_primitives(shape)
        for obj in obj_list:
            stype, vertices, idx = obj
            # Convert 2D vertices to 3D by adding "0" column, needed for further transformations
            if len(vertices[0]) == 2:
                vertices = np.hstack([vertices, np.zeros((len(vertices), 1))])
            # Transform vertices
            vertices = self._transform_vertices(
                np.hstack([vertices, np.ones((len(vertices), 1))]),
                shape._matrix, self.transform_matrix)
            # Add to draw queue
            self._add_to_draw_queue(stype, vertices, idx, fill, stroke,
                                    stroke_weight, stroke_cap, stroke_join)

    def flush_geometry(self):
        """Flush all the shape geometry from the draw queue to the GPU.
        """
        current_queue = []
        for index, shape in enumerate(self.draw_queue):
            current_shape = self.draw_queue[index][0]
            current_queue.append(self.draw_queue[index][1])

            if current_shape == "lines":
                self.render_line(current_queue)
            else:
                self.render_default(current_shape, current_queue)

            current_queue = []

        self.draw_queue = []

    def render_line(self, queue):
        '''
        This rendering algorithm works by tesselating the line into
        multiple triangles.

        Reference: https://blog.mapbox.com/drawing-antialiased-lines-with-opengl-8766f34192dc
        '''

        if len(queue) == 0:
            return

        pos = []
        posPrev = []
        posCurr = []
        posNext = []
        markers = []
        side = []

        linewidth = []
        join_type = []
        cap_type = []
        color = []

        stroke_cap_codes = {'PROJECT': 0, 'SQUARE': 1, 'ROUND': 2}

        stroke_join_codes = {'MITER': 0, 'BEVEL': 1, 'ROUND': 2}

        for line in queue:
            if len(line[1]) == 0:
                continue

            for segment in line[1]:
                for i in range(
                        len(segment) -
                        1):  # the data is sent to renderer in line segments
                    for j in [0, 0, 1, 0, 1,
                              1]:  # all the vertices of triangles
                        if i + j - 1 >= 0:
                            posPrev.append(line[0][segment[i + j - 1]])
                        else:
                            posPrev.append(line[0][segment[i + j]])

                        if i + j + 1 < len(segment):
                            posNext.append(line[0][segment[i + j + 1]])
                        else:
                            posNext.append(line[0][segment[i + j]])

                        posCurr.append(line[0][segment[i + j]])

                    # Is the vertex up/below the line segment
                    markers.extend([1.0, -1.0, -1.0, -1.0, 1.0, -1.0])
                    # Left or right side of the segment
                    side.extend([1.0, 1.0, -1.0, 1.0, -1.0, -1.0])
                    # Left vertex of each segment
                    pos.extend([line[0][segment[i]]] * 6)
                    linewidth.extend([line[3]] * 6)
                    join_type.extend([stroke_join_codes[line[5]]] * 6)
                    cap_type.extend([stroke_cap_codes[line[4]]] * 6)
                    color.extend([line[2]] * 6)

        if len(pos) == 0:
            return

        posPrev = np.array(posPrev, np.float32)
        posCurr = np.array(posCurr, np.float32)
        posNext = np.array(posNext, np.float32)
        markers = np.array(markers, np.float32)
        side = np.array(side, np.float32)
        pos = np.array(pos, np.float32)
        linewidth = np.array(linewidth, np.float32)
        join_type = np.array(join_type, np.float32)
        cap_type = np.array(cap_type, np.float32)
        color = np.array(color, np.float32)

        self.line_prog['pos'] = gloo.VertexBuffer(pos)
        self.line_prog['posPrev'] = gloo.VertexBuffer(posPrev)
        self.line_prog['posCurr'] = gloo.VertexBuffer(posCurr)
        self.line_prog['posNext'] = gloo.VertexBuffer(posNext)
        self.line_prog['marker'] = gloo.VertexBuffer(markers)
        self.line_prog['side'] = gloo.VertexBuffer(side)
        self.line_prog['linewidth'] = gloo.VertexBuffer(linewidth)
        self.line_prog['join_type'] = gloo.VertexBuffer(join_type)
        self.line_prog['cap_type'] = gloo.VertexBuffer(cap_type)
        self.line_prog["color"] = gloo.VertexBuffer(color)

        self.line_prog.draw('triangles')

    def render_image(self, image, location, size):
        """Render the image.

        :param image: image to be rendered
        :type image: builtins.Image

        :param location: top-left corner of the image
        :type location: tuple | list | builtins.Vector

        :param size: target size of the image to draw.
        :type size: tuple | list | builtins.Vector
        """
        self.flush_geometry()

        self.texture_prog[
            'fill_color'] = self.style.tint_color if self.style.tint_enabled else COLOR_WHITE
        self.texture_prog['transform'] = self.transform_matrix.T.flatten()

        x, y = location
        sx, sy = size
        imx, imy = image.size
        data = np.zeros(4,
                        dtype=[('position', np.float32, 2),
                               ('texcoord', np.float32, 2)])
        data['texcoord'] = np.array(
            [[0.0, 1.0], [1.0, 1.0], [0.0, 0.0], [1.0, 0.0]], dtype=np.float32)
        data['position'] = np.array(
            [[x, y + sy], [x + sx, y + sy], [x, y], [x + sx, y]],
            dtype=np.float32)

        self.texture_prog['texture'] = image._texture
        self.texture_prog.bind(VertexBuffer(data))
        self.texture_prog.draw('triangle_strip')

    def cleanup(self):
        """Run the clean-up routine for the renderer.

        This method is called when all drawing has been completed and the
        program is about to exit.

        """
        OpenGLRenderer.cleanup(self)
        self.line_prog.delete()

    def render_shape(self, shape):
        self.render(shape)
        for child_shape in shape.children:
            self.render_shape(child_shape)

    def line(self, *args):
        path = args[0]
        self.render_shape(PShape(vertices=path, shape_type=SType.LINES))

    def bezier(self, *args):
        vertices = args[0]
        self.render_shape(
            PShape(vertices=vertices, shape_type=SType.LINE_STRIP))

    def curve(self, *args):
        vertices = args[0]
        self.render_shape(
            PShape(vertices=vertices, shape_type=SType.LINE_STRIP))

    def triangle(self, *args):
        path = args[0]
        self.render_shape(PShape(vertices=path, shape_type=SType.TRIANGLES))

    def quad(self, *args):
        path = args[0]
        self.render_shape(PShape(vertices=path, shape_type=SType.QUADS))

    def arc(self, *args):
        center = args[0]
        dim = args[1]
        start_angle = args[2]
        stop_angle = args[3]
        mode = args[4]

        self.render_shape(Arc(center, dim, start_angle, stop_angle, mode))

    def shape(self, vertices, contours, shape_type, *args):
        """Render a Pshape"""
        self.render_shape(
            PShape(vertices=vertices, contours=contours,
                   shape_type=shape_type))
예제 #4
0
파일: renderer3d.py 프로젝트: yonghuming/p5
class Renderer3D(OpenGLRenderer):
    def __init__(self):
        super().__init__(src_fbuffer, src_default)
        self.style = Style3D()
        self.normal_prog = Program(src_normal.vert, src_normal.frag)
        self.phong_prog = Program(src_phong.vert, src_phong.frag)
        self.lookat_matrix = np.identity(4)

        # Camera position
        self.camera_pos = np.zeros(3)
        # Lights
        self.MAX_LIGHTS_PER_CATEGORY = 8
        self.ambient_light_color = GlslList(self.MAX_LIGHTS_PER_CATEGORY, 3,
                                            np.float32)
        self.directional_light_dir = GlslList(self.MAX_LIGHTS_PER_CATEGORY, 3,
                                              np.float32)
        self.directional_light_color = GlslList(self.MAX_LIGHTS_PER_CATEGORY,
                                                3, np.float32)
        self.directional_light_specular = GlslList(
            self.MAX_LIGHTS_PER_CATEGORY, 3, np.float32)
        self.point_light_color = GlslList(self.MAX_LIGHTS_PER_CATEGORY, 3,
                                          np.float32)
        self.point_light_pos = GlslList(self.MAX_LIGHTS_PER_CATEGORY, 3,
                                        np.float32)
        self.point_light_specular = GlslList(self.MAX_LIGHTS_PER_CATEGORY, 3,
                                             np.float32)
        self.const_falloff = GlslList(self.MAX_LIGHTS_PER_CATEGORY, 1,
                                      np.float32)
        self.linear_falloff = GlslList(self.MAX_LIGHTS_PER_CATEGORY, 1,
                                       np.float32)
        self.quadratic_falloff = GlslList(self.MAX_LIGHTS_PER_CATEGORY, 1,
                                          np.float32)
        self.curr_linear_falloff, self.curr_quadratic_falloff, self.curr_constant_falloff = 0.0, 0.0, 0.0
        self.light_specular = np.array([0.0] * 3)

    def initialize_renderer(self):
        super().initialize_renderer()
        self.reset_view()

    def reset_view(self):
        self.viewport = (
            0,
            0,
            int(builtins.width * builtins.pixel_x_density),
            int(builtins.height * builtins.pixel_y_density),
        )
        self.texture_viewport = (
            0,
            0,
            builtins.width,
            builtins.height,
        )

        gloo.set_viewport(*self.viewport)  # pylint: disable=no-member

        cz = (builtins.height / 2) / math.tan(math.radians(30))
        self.projection_matrix = matrix.perspective_matrix(
            math.radians(60), builtins.width / builtins.height, 0.1 * cz,
            10 * cz)

        self.transform_matrix = np.identity(4)
        self._update_shader_transforms()

        self.fbuffer_tex_front = Texture2D(
            (builtins.height, builtins.width, 3))
        self.fbuffer_tex_back = Texture2D((builtins.height, builtins.width, 3))
        self.fbuffer.depth_buffer = gloo.RenderBuffer(
            (builtins.height, builtins.width))

        for buf in [self.fbuffer_tex_front, self.fbuffer_tex_back]:
            self.fbuffer.color_buffer = buf
            with self.fbuffer:
                self.clear()

    def clear(self, color=True, depth=True):
        """Clear the renderer background."""
        gloo.set_state(clear_color=self.style.background_color)  # pylint: disable=no-member
        gloo.clear(color=color, depth=depth)  # pylint: disable=no-member

    def clear_lights(self):
        self.ambient_light_color.clear()
        self.directional_light_color.clear()
        self.directional_light_dir.clear()
        self.directional_light_specular.clear()
        self.point_light_color.clear()
        self.point_light_pos.clear()
        self.point_light_specular.clear()
        self.const_falloff.clear()
        self.linear_falloff.clear()
        self.quadratic_falloff.clear()

    def _comm_toggles(self, state=True):
        gloo.set_state(blend=state)  # pylint: disable=no-member
        gloo.set_state(depth_test=state)  # pylint: disable=no-member

        if state:
            gloo.set_state(blend_func=('src_alpha', 'one_minus_src_alpha'))  # pylint: disable=no-member
            gloo.set_state(depth_func='lequal')  # pylint: disable=no-member

    def _update_shader_transforms(self):
        # Default shader
        self.default_prog['projection'] = self.projection_matrix.T.flatten()
        self.default_prog['perspective_matrix'] = self.lookat_matrix.T.flatten(
        )

        # Normal shader
        self.normal_prog['projection'] = self.projection_matrix.T.flatten()
        self.normal_prog['perspective'] = self.lookat_matrix.T.flatten()
        # This is a no-op, meaning that the normals stay in world space, which
        # matches the behavior in p5.js
        normal_transform = np.identity(3)
        # I think the transformation below takes the vertices to camera space, but
        # the results are funky, so it's probably incorrect? - ziyaointl, 2020/07/20
        # normal_transform = np.linalg.inv(self.projection_matrix[:3, :3] @ self.lookat_matrix[:3, :3])
        self.normal_prog['normal_transform'] = normal_transform.flatten()

        # Blinn-Phong Shader
        self.phong_prog['projection'] = self.projection_matrix.T.flatten()
        self.phong_prog['perspective'] = self.lookat_matrix.T.flatten()

    @contextmanager
    def draw_loop(self):
        """The main draw loop context manager.
        """

        self.transform_matrix = np.identity(4)
        self._update_shader_transforms()
        self.fbuffer.color_buffer = self.fbuffer_tex_back

        with self.fbuffer:
            gloo.set_viewport(*self.texture_viewport)  # pylint: disable=no-member
            self._comm_toggles()
            self.fbuffer_prog['texture'] = self.fbuffer_tex_front
            self.fbuffer_prog.draw('triangle_strip')
            self.clear(color=False, depth=True)
            self.clear_lights()

            yield

            self.flush_geometry()
            self.transform_matrix = np.identity(4)

        gloo.set_viewport(*self.viewport)  # pylint: disable=no-member
        self._comm_toggles(False)
        self.clear()
        self.fbuffer_prog['texture'] = self.fbuffer_tex_back
        self.fbuffer_prog.draw('triangle_strip')

        self.fbuffer_tex_front, self.fbuffer_tex_back = self.fbuffer_tex_back, self.fbuffer_tex_front

    def _add_to_draw_queue_simple(self, stype, vertices, idx, color):
        """Adds shape of stype to draw queue
        """
        self.draw_queue.append((stype, (vertices, idx, color, None, None)))

    def tnormals(self, shape):
        """Obtain a list of vertex normals in world coordinates
        """
        if isinstance(shape.material,
                      BasicMaterial):  # Basic shader doesn't need this
            return None
        return shape.vertex_normals @ np.linalg.inv(
            to_3x3(self.transform_matrix) @ to_3x3(shape.matrix))

    def render(self, shape):
        if isinstance(shape, Geometry):
            n = len(shape.vertices)
            # Perform model transform
            # TODO: Investigate moving model transform from CPU to the GPU
            tverts = self._transform_vertices(
                np.hstack([shape.vertices, np.ones((n, 1))]), shape.matrix,
                self.transform_matrix)
            tnormals = self.tnormals(shape)

            edges = shape.edges
            faces = shape.faces

            self.add_to_draw_queue('poly', tverts, edges, faces,
                                   self.style.fill_color,
                                   self.style.stroke_color, tnormals,
                                   self.style.material)

        elif isinstance(shape, PShape):
            fill = shape.fill.normalized if shape.fill else None
            stroke = shape.stroke.normalized if shape.stroke else None

            obj_list = get_render_primitives(shape)
            for obj in obj_list:
                stype, vertices, idx = obj
                # Transform vertices
                vertices = self._transform_vertices(
                    np.hstack([vertices, np.ones((len(vertices), 1))]),
                    shape._matrix, self.transform_matrix)
                # Add to draw queue
                self._add_to_draw_queue_simple(
                    stype, vertices, idx, stroke if stype == 'lines' else fill)

    def add_to_draw_queue(self,
                          stype,
                          vertices,
                          edges,
                          faces,
                          fill=None,
                          stroke=None,
                          normals=None,
                          material=None):
        """Add the given vertex data to the draw queue.

        :param stype: type of shape to be added. Should be one of {'poly',
                'path', 'point'}
        :type stype: str

        :param vertices: (N, 3) array containing the vertices to be drawn.
        :type vertices: np.ndarray

        :param edges: (N, 2) array containing edges as tuples of indices
                into the vertex array. This can be None when not appropriate
                (eg. for points)
        :type edges: None | np.ndarray

        :param faces: (N, 3) array containing faces as tuples of indices
                into the vertex array. For 'point' and 'path' shapes, this can
                be None
        :type faces: np.ndarray

        :param fill: Fill color of the shape as a normalized RGBA tuple.
                When set to `None` the shape doesn't get a fill (default: None)
        :type fill: None | tuple

        :param stroke: Stroke color of the shape as a normalized RGBA
                tuple. When set to `None` the shape doesn't get stroke
                (default: None)
        :type stroke: None | tuple
        // TODO: Update documentation
        // TODO: Unite style-related attributes for both 2D and 3D under one material class
        """

        fill_shape = self.style.fill_enabled and not (fill is None)
        stroke_shape = self.style.stroke_enabled and not (stroke is None)

        if fill_shape and stype not in ['point', 'path']:
            idx = np.array(faces, dtype=np.uint32).ravel()
            self.draw_queue.append(
                ["triangles", (vertices, idx, fill, normals, material)])

        if stroke_shape:
            if stype == 'point':
                idx = np.arange(0, len(vertices), dtype=np.uint32)
                self.draw_queue.append(
                    ["points", (vertices, idx, stroke, normals, material)])
            else:
                idx = np.array(edges, dtype=np.uint32).ravel()
                self.draw_queue.append(
                    ["lines", (vertices, idx, stroke, normals, material)])

    def render_with_shaders(self, draw_type, draw_obj):
        vertices, idx, color, normals, material = draw_obj
        """Like render_default but is aware of shaders other than the basic one"""
        # 0. If material does not need normals nor extra info, strip them out
        # and use the method from superclass
        if material is None or isinstance(
                material, BasicMaterial) or draw_type in ['points', 'lines']:
            OpenGLRenderer.render_default(self, draw_type, [draw_obj[:3]])
            return

        # 1. Get the number of vertices
        num_vertices = len(vertices)

        # 2. Create empty buffers based on the number of vertices.
        #
        data = np.zeros(num_vertices,
                        dtype=[('position', np.float32, 3),
                               ('normal', np.float32, 3)])

        # 3. Loop through all the shapes in the geometry queue adding
        # it's information to the buffer.
        #
        draw_indices = []
        data['position'][0:num_vertices, ] = np.array(vertices)
        draw_indices.append(idx)
        data['normal'][0:num_vertices, ] = np.array(normals)
        self.vertex_buffer.set_data(data)
        self.index_buffer.set_data(np.hstack(draw_indices))

        if isinstance(material, NormalMaterial):
            # 4. Bind the buffer to the shader.
            #
            self.normal_prog.bind(self.vertex_buffer)

            # 5. Draw the shape using the proper shape type and get rid of
            # the buffers.
            #
            self.normal_prog.draw(draw_type, indices=self.index_buffer)
        elif isinstance(material, BlinnPhongMaterial):
            self.phong_prog.bind(self.vertex_buffer)
            self.phong_prog['u_cam_pos'] = self.camera_pos
            # Material attributes
            self.phong_prog['u_ambient_color'] = material.ambient
            self.phong_prog['u_diffuse_color'] = material.diffuse
            self.phong_prog['u_specular_color'] = material.specular
            self.phong_prog['u_shininess'] = material.shininess
            # Directional lights
            self.phong_prog[
                'u_directional_light_count'] = self.directional_light_color.size
            self.phong_prog[
                'u_directional_light_dir'] = self.directional_light_dir.data
            self.phong_prog[
                'u_directional_light_color'] = self.directional_light_color.data
            self.phong_prog[
                'u_directional_light_specular'] = self.directional_light_specular.data
            # Ambient lights
            self.phong_prog[
                'u_ambient_light_count'] = self.ambient_light_color.size
            self.phong_prog[
                'u_ambient_light_color'] = self.ambient_light_color.data
            # Point lights
            self.phong_prog[
                'u_point_light_count'] = self.point_light_color.size
            self.phong_prog[
                'u_point_light_color'] = self.point_light_color.data
            self.phong_prog['u_point_light_pos'] = self.point_light_pos.data
            self.phong_prog[
                'u_point_light_specular'] = self.point_light_specular.data
            # Point light falloffs
            self.phong_prog['u_const_falloff'] = self.const_falloff.data
            self.phong_prog['u_linear_falloff'] = self.linear_falloff.data
            self.phong_prog[
                'u_quadratic_falloff'] = self.quadratic_falloff.data
            # Draw
            self.phong_prog.draw(draw_type, indices=self.index_buffer)
        else:
            raise NotImplementedError("Material not implemented")

    def flush_geometry(self):
        """Flush all the shape geometry from the draw queue to the GPU.
        """
        for index, shape in enumerate(self.draw_queue):
            current_shape, current_obj = self.draw_queue[index][
                0], self.draw_queue[index][1]
            # If current_shape is lines, bring it to the front by epsilon
            # to resolve z-fighting
            if current_shape == 'lines':
                # line_transform is used whenever we render lines to break ties in depth
                # We transform the points to camera space, move them by
                # Z_EPSILON, and them move them back to world space
                line_transform = inv(self.lookat_matrix).dot(
                    translation_matrix(0, 0,
                                       Z_EPSILON).dot(self.lookat_matrix))
                vertices = current_obj[0]
                current_obj = (np.hstack([
                    vertices, np.ones((vertices.shape[0], 1))
                ]).dot(line_transform.T)[:, :3], *current_obj[1:])
            self.render_with_shaders(current_shape, current_obj)

        self.draw_queue = []

    def cleanup(self):
        super(Renderer3D, self).cleanup()
        self.normal_prog.delete()
        self.phong_prog.delete()

    def add_ambient_light(self, r, g, b):
        self.ambient_light_color.add(np.array((r, g, b)))

    def add_directional_light(self, r, g, b, x, y, z):
        self.directional_light_color.add(np.array((r, g, b)))
        self.directional_light_dir.add(np.array((x, y, z)))
        self.directional_light_specular.add(self.light_specular)

    def add_point_light(self, r, g, b, x, y, z):
        self.point_light_color.add(np.array((r, g, b)))
        self.point_light_pos.add(np.array((x, y, z)))
        self.point_light_specular.add(self.light_specular)
        self.const_falloff.add(self.curr_constant_falloff)
        self.linear_falloff.add(self.curr_linear_falloff)
        self.quadratic_falloff.add(self.curr_quadratic_falloff)
예제 #5
0
class Renderer3D:
    def __init__(self):
        self.default_prog = None

        self.fbuffer = None
        self.fbuffer_tex_front = None
        self.fbuffer_tex_back = None

        self.vertex_buffer = None
        self.index_buffer = None

        ## Renderer Globals: USEFUL CONSTANTS
        self.COLOR_WHITE = (1, 1, 1, 1)
        self.COLOR_BLACK = (0, 0, 0, 1)
        self.COLOR_DEFAULT_BG = (0.8, 0.8, 0.8, 1.0)

        ## Renderer Globals: STYLE/MATERIAL PROPERTIES
        ##
        self.background_color = self.COLOR_DEFAULT_BG

        self.fill_color = self.COLOR_WHITE
        self.fill_enabled = True

        self.stroke_color = self.COLOR_BLACK
        self.stroke_enabled = True

        self.tint_color = self.COLOR_BLACK
        self.tint_enabled = False

        ## Renderer Globals: Curves
        self.stroke_weight = 1
        self.stroke_cap = 2
        self.stroke_join = 0

        ## Renderer Globals
        ## VIEW MATRICES, ETC
        ##
        self.viewport = None
        self.texture_viewport = None
        self.transform_matrix = np.identity(4)
        self.projection_matrix = np.identity(4)
        self.lookat_matrix = np.identity(4)

        ## Renderer Globals: RENDERING
        self.draw_queue = []

    def initialize_renderer(self):
        self.fbuffer = FrameBuffer()

        vertices = np.array(
            [[-1.0, -1.0], [+1.0, -1.0], [-1.0, +1.0], [+1.0, +1.0]],
            np.float32)
        texcoords = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]],
                             dtype=np.float32)

        self.fbuf_vertices = VertexBuffer(data=vertices)
        self.fbuf_texcoords = VertexBuffer(data=texcoords)

        self.fbuffer_prog = Program(src_fbuffer.vert, src_fbuffer.frag)
        self.fbuffer_prog['texcoord'] = self.fbuf_texcoords
        self.fbuffer_prog['position'] = self.fbuf_vertices

        self.vertex_buffer = VertexBuffer()
        self.index_buffer = IndexBuffer()

        self.default_prog = Program(src_default.vert, src_default.frag)

        self.reset_view()

    def reset_view(self):
        self.viewport = (
            0,
            0,
            int(builtins.width * builtins.pixel_x_density),
            int(builtins.height * builtins.pixel_y_density),
        )
        self.texture_viewport = (
            0,
            0,
            builtins.width,
            builtins.height,
        )

        gloo.set_viewport(*self.viewport)

        cz = (builtins.height / 2) / math.tan(math.radians(30))
        self.projection_matrix = matrix.perspective_matrix(
            math.radians(60), builtins.width / builtins.height, 0.1 * cz,
            10 * cz)

        self.transform_matrix = np.identity(4)

        self.default_prog['projection'] = self.projection_matrix.T.flatten()
        self.default_prog['perspective_matrix'] = self.lookat_matrix.T.flatten(
        )

        self.fbuffer_tex_front = Texture2D(
            (builtins.height, builtins.width, 3))
        self.fbuffer_tex_back = Texture2D((builtins.height, builtins.width, 3))

        for buf in [self.fbuffer_tex_front, self.fbuffer_tex_back]:
            self.fbuffer.color_buffer = buf
            with self.fbuffer:
                self.clear()

        self.fbuffer.depth_buffer = gloo.RenderBuffer(
            (builtins.height, builtins.width))

    def clear(self, color=True, depth=True):
        """Clear the renderer background."""
        gloo.set_state(clear_color=self.background_color)
        gloo.clear(color=color, depth=depth)

    def _comm_toggles(self, state=True):
        gloo.set_state(blend=state)
        gloo.set_state(depth_test=state)

        if state:
            gloo.set_state(blend_func=('src_alpha', 'one_minus_src_alpha'))
            gloo.set_state(depth_func='lequal')

    @contextmanager
    def draw_loop(self):
        """The main draw loop context manager.
		"""

        self.transform_matrix = np.identity(4)

        self.default_prog['projection'] = self.projection_matrix.T.flatten()
        self.default_prog['perspective_matrix'] = self.lookat_matrix.T.flatten(
        )

        self.fbuffer.color_buffer = self.fbuffer_tex_back

        with self.fbuffer:
            gloo.set_viewport(*self.texture_viewport)
            self._comm_toggles()
            self.fbuffer_prog['texture'] = self.fbuffer_tex_front
            self.fbuffer_prog.draw('triangle_strip')

            yield

            self.flush_geometry()
            self.transform_matrix = np.identity(4)

        gloo.set_viewport(*self.viewport)
        self._comm_toggles(False)
        self.clear()
        self.fbuffer_prog['texture'] = self.fbuffer_tex_back
        self.fbuffer_prog.draw('triangle_strip')

        self.fbuffer_tex_front, self.fbuffer_tex_back = self.fbuffer_tex_back, self.fbuffer_tex_front

    def _transform_vertices(self, vertices, local_matrix, global_matrix):
        return np.dot(np.dot(vertices, local_matrix.T), global_matrix.T)[:, :3]

    def render(self, shape):
        if isinstance(shape, Geometry):
            n = len(shape.vertices)
            tverts = self._transform_vertices(
                np.hstack([shape.vertices, np.ones((n, 1))]), shape.matrix,
                self.transform_matrix)

            edges = shape.edges
            faces = shape.faces

            self.add_to_draw_queue('poly', tverts, edges, faces,
                                   self.fill_color, self.stroke_color)

        elif isinstance(shape, PShape):
            vertices = shape._draw_vertices
            n, _ = vertices.shape
            tverts = self._transform_vertices(
                np.hstack([vertices,
                           np.zeros((n, 1)),
                           np.ones((n, 1))]), shape._matrix,
                self.transform_matrix)

            fill = shape.fill.normalized if shape.fill else None
            stroke = shape.stroke.normalized if shape.stroke else None
            edges = shape._draw_edges
            faces = shape._draw_faces

            if edges is None:
                print(vertices)
                print("whale")
                exit()

            if 'open' in shape.attribs:
                overtices = shape._draw_outline_vertices
                no, _ = overtices.shape
                toverts = self._transform_vertices(
                    np.hstack([overtices,
                               np.zeros((no, 1)),
                               np.ones((no, 1))]), shape._matrix,
                    self.transform_matrix)

                self.add_to_draw_queue('poly', tverts, edges, faces, fill,
                                       None)
                self.add_to_draw_queue('path', toverts, edges[:-1], None, None,
                                       stroke)
            else:
                self.add_to_draw_queue(shape.kind, tverts, edges, faces, fill,
                                       stroke)

    def add_to_draw_queue(self,
                          stype,
                          vertices,
                          edges,
                          faces,
                          fill=None,
                          stroke=None):
        """Add the given vertex data to the draw queue.

		:param stype: type of shape to be added. Should be one of {'poly',
			'path', 'point'}
		:type stype: str

		:param vertices: (N, 3) array containing the vertices to be drawn.
		:type vertices: np.ndarray

		:param edges: (N, 2) array containing edges as tuples of indices
			into the vertex array. This can be None when not appropriate
			(eg. for points)
		:type edges: None | np.ndarray

		:param faces: (N, 3) array containing faces as tuples of indices
			into the vertex array. For 'point' and 'path' shapes, this can
			be None
		:type faces: np.ndarray

		:param fill: Fill color of the shape as a normalized RGBA tuple.
			When set to `None` the shape doesn't get a fill (default: None)
		:type fill: None | tuple

		:param stroke: Stroke color of the shape as a normalized RGBA
			tuple. When set to `None` the shape doesn't get stroke
			(default: None)
		:type stroke: None | tuple

		"""

        fill_shape = self.fill_enabled and not (fill is None)
        stroke_shape = self.stroke_enabled and not (stroke is None)

        if fill_shape and stype not in ['point', 'path']:
            idx = np.array(faces, dtype=np.uint32).ravel()
            self.draw_queue.append(["triangles", (vertices, idx, fill)])

        if stroke_shape:
            if stype == 'point':
                idx = np.arange(0, len(vertices), dtype=np.uint32)
                self.draw_queue.append(["points", (vertices, idx, stroke)])
            else:
                idx = np.array(edges, dtype=np.uint32).ravel()
                self.draw_queue.append(["lines", (vertices, idx, stroke)])

    def flush_geometry(self):
        """Flush all the shape geometry from the draw queue to the GPU.
		"""
        current_queue = []
        for index, shape in enumerate(self.draw_queue):
            current_shape, current_obj = self.draw_queue[index][
                0], self.draw_queue[index][1]
            # If current_shape is lines, bring it to the front by epsilon
            # to resolve z-fighting
            if current_shape == 'lines':
                # line_transform is used whenever we render lines to break ties in depth
                # We transform the points to camera space, move them by Z_EPSILON, and them move them back to world space
                line_transform = inv(self.lookat_matrix).dot(
                    translation_matrix(0, 0,
                                       Z_EPSILON).dot(self.lookat_matrix))
                vertices = current_obj[0]
                current_obj = (np.hstack(
                    [vertices, np.ones(
                        (vertices.shape[0], 1))]).dot(line_transform.T)[:, :3],
                               current_obj[1], current_obj[2])
            current_queue.append(current_obj)

            if index < len(self.draw_queue) - 1:
                if self.draw_queue[index][0] == self.draw_queue[index + 1][0]:
                    continue

            self.render_default(current_shape, current_queue)
            current_queue = []

        self.draw_queue = []

    def render_default(self, draw_type, draw_queue):
        # 1. Get the maximum number of vertices persent in the shapes
        # in the draw queue.
        #
        if len(draw_queue) == 0:
            return

        num_vertices = 0
        for vertices, _, _ in draw_queue:
            num_vertices = num_vertices + len(vertices)

        # 2. Create empty buffers based on the number of vertices.
        #
        data = np.zeros(num_vertices,
                        dtype=[('position', np.float32, 3),
                               ('color', np.float32, 4)])

        # 3. Loop through all the shapes in the geometry queue adding
        # it's information to the buffer.
        #
        sidx = 0
        draw_indices = []
        for vertices, idx, color in draw_queue:
            num_shape_verts = len(vertices)

            data['position'][sidx:(sidx +
                                   num_shape_verts), ] = np.array(vertices)

            color_array = np.array([color] * num_shape_verts)
            data['color'][sidx:sidx + num_shape_verts, :] = color_array

            draw_indices.append(sidx + idx)

            sidx += num_shape_verts

        self.vertex_buffer.set_data(data)
        self.index_buffer.set_data(np.hstack(draw_indices))

        # 4. Bind the buffer to the shader.
        #
        self.default_prog.bind(self.vertex_buffer)

        # 5. Draw the shape using the proper shape type and get rid of
        # the buffers.
        #
        self.default_prog.draw(draw_type, indices=self.index_buffer)

    def cleanup(self):
        """Run the clean-up routine for the renderer.

		This method is called when all drawing has been completed and the
		program is about to exit.

		"""
        self.default_prog.delete()
        self.fbuffer_prog.delete()
        self.fbuffer.delete()