def _cache_vao(self, ref_sprites, total_width, max_height, atlas, tex_map):
        sprite_count = len(ref_sprites)
        sprite_positions = []
        sprite_colors = []
        sprite_angles = []
        sprite_sizes = []
        for sprite in ref_sprites:
            sprite_positions.append([sprite.center_x, sprite.center_y])
            sprite_colors.append(sprite.color + (sprite.alpha, ))
            sprite_angles.append(math.radians(sprite.angle))
            sprite_sizes.append([sprite.width / 2, sprite.height / 2])

        sprite_coords = self._map_textures_to_atlas(total_width, max_height,
                                                    atlas, tex_map)

        #
        # Creating OpenGL data buffer
        #
        sprite_data = np.zeros(sprite_count,
                               dtype=np.dtype([('position', '2f4'),
                                               ('angle', 'f4'),
                                               ('size', '2f4'),
                                               ('sub_tex_coords', '4f4'),
                                               ('color', '4B')]))
        sprite_data['position'] = sprite_positions
        sprite_data['angle'] = sprite_angles
        sprite_data['size'] = sprite_sizes
        sprite_data['sub_tex_coords'] = sprite_coords
        sprite_data['color'] = sprite_colors
        self.sprite_data = sprite_data.tobytes()
        self.gl_data_buf = shader.buffer(self.sprite_data, usage='stream')
        #
        #
        #

        self.vbo_buf = shader.buffer(
            GLShader.get_triangle_strip_vertices().tobytes())
        vbo_buf_desc = shader.BufferDescription(self.vbo_buf, '2f 2f',
                                                ('in_vert', 'in_texture'))
        pos_angle_scale_buf_desc = shader.BufferDescription(
            self.gl_data_buf,
            '2f 1f 2f 4f 4B', ('in_pos', 'in_angle', 'in_scale',
                               'in_sub_tex_coords', 'in_color'),
            normalized=['in_color'],
            instanced=True)

        vao_content = [vbo_buf_desc, pos_angle_scale_buf_desc]
        self.vao = shader.vertex_array(self.program, vao_content)
Exemple #2
0
def _generic_draw_line_strip(point_list: PointList,
                             color: Color,
                             mode: int = gl.GL_LINE_STRIP):
    """
    Draw a line strip. A line strip is a set of continuously connected
    line segments.

    :param point_list: List of points making up the line. Each point is
         in a list. So it is a list of lists.
    :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
         RGBA format.
    """
    # Cache the program. But not on linux because it fails unit tests for some reason.
    # if not _generic_draw_line_strip.program or sys.platform == "linux":

    _generic_draw_line_strip.program = shader.program(
        vertex_shader=_line_vertex_shader,
        fragment_shader=_line_fragment_shader,
    )

    c4 = get_four_byte_color(color)
    c4e = c4 * len(point_list)
    a = array.array('B', c4e)
    color_buf = shader.buffer(a.tobytes())
    color_buf_desc = shader.BufferDescription(
        color_buf,
        '4B',
        ['in_color'],
        normalized=['in_color'],
    )

    def gen_flatten(my_list):
        return [item for sublist in my_list for item in sublist]

    vertices = array.array('f', gen_flatten(point_list))

    vbo_buf = shader.buffer(vertices.tobytes())
    vbo_buf_desc = shader.BufferDescription(vbo_buf, '2f', ['in_vert'])

    vao_content = [vbo_buf_desc, color_buf_desc]

    vao = shader.vertex_array(_generic_draw_line_strip.program, vao_content)
    with vao:
        _generic_draw_line_strip.program['Projection'] = get_projection(
        ).flatten()
        vao.render(mode=mode)
Exemple #3
0
def create_line(start_x: float,
                start_y: float,
                end_x: float,
                end_y: float,
                color: Color,
                line_width: float = 1) -> Shape:
    """
    Create a line to be rendered later. This works faster than draw_line because
    the vertexes are only loaded to the graphics card once, rather than each frame.
    """

    program = shader.program(
        vertex_shader='''
            #version 330
            uniform mat4 Projection;
            in vec2 in_vert;
            in vec4 in_color;
            out vec4 v_color;
            void main() {
               gl_Position = Projection * vec4(in_vert, 0.0, 1.0);
               v_color = in_color;
            }
        ''',
        fragment_shader='''
            #version 330
            in vec4 v_color;
            out vec4 f_color;
            void main() {
                f_color = v_color;
            }
        ''',
    )

    buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')])
    data = np.zeros(2, dtype=buffer_type)
    data['vertex'] = (start_x, start_y), (end_x, end_y)
    data['color'] = get_four_byte_color(color)

    vbo = shader.buffer(data.tobytes())
    vao_content = [
        shader.BufferDescription(vbo,
                                 '2f 4B', ('in_vert', 'in_color'),
                                 normalized=['in_color'])
    ]

    vao = shader.vertex_array(program, vao_content)
    with vao:
        program['Projection'] = get_projection().flatten()

    shape = Shape()
    shape.vao = vao
    shape.vbo = vbo
    shape.program = program
    shape.mode = gl.GL_LINE_STRIP
    shape.line_width = line_width

    return shape
Exemple #4
0
def create_line_generic_with_colors(point_list: PointList,
                                    color_list: Iterable[Color],
                                    shape_mode: int,
                                    line_width: float=1) -> Shape:
    """
    This function is used by ``create_line_strip`` and ``create_line_loop``,
    just changing the OpenGL type for the line drawing.
    """
    program = shader.program(
        vertex_shader='''
            #version 330
            uniform mat4 Projection;
            in vec2 in_vert;
            in vec4 in_color;
            out vec4 v_color;
            void main() {
               gl_Position = Projection * vec4(in_vert, 0.0, 1.0);
               v_color = in_color;
            }
        ''',
        fragment_shader='''
            #version 330
            in vec4 v_color;
            out vec4 f_color;
            void main() {
                f_color = v_color;
            }
        ''',
    )

    buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')])
    data = np.zeros(len(point_list), dtype=buffer_type)
    data['vertex'] = point_list
    data['color'] = [get_four_byte_color(color) for color in color_list]

    vbo = shader.buffer(data.tobytes())
    vao_content = [
        shader.BufferDescription(
            vbo,
            '2f 4B',
            ('in_vert', 'in_color'),
            normalized=['in_color']
        )
    ]

    vao = shader.vertex_array(program, vao_content)
    with vao:
        program['Projection'] = get_projection().flatten()

    shape = Shape()
    shape.vao = vao
    shape.vbo = vbo
    shape.program = program
    shape.mode = shape_mode
    shape.line_width = line_width

    return shape
Exemple #5
0
def _generic_draw_line_strip(point_list: PointList,
                             color: Color,
                             line_width: float=1,
                             mode: int=moderngl.LINE_STRIP):
    """
    Draw a line strip. A line strip is a set of continuously connected
    line segments.

    Args:
        :point_list: List of points making up the line. Each point is
         in a list. So it is a list of lists.
        :color: color, specified in a list of 3 or 4 bytes in RGB or
         RGBA format.
        :border_width: Width of the line in pixels.
    Returns:
        None
    Raises:
        None
    """
    program = shader.program(
        vertex_shader=line_vertex_shader,
        fragment_shader=line_fragment_shader,
    )
    buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')])
    data = np.zeros(len(point_list), dtype=buffer_type)

    data['vertex'] = point_list

    color = get_four_byte_color(color)
    data['color'] = color

    vbo = shader.buffer(data.tobytes())
    vbo_desc = shader.BufferDescription(
        vbo,
        '2f 4B',
        ('in_vert', 'in_color'),
        normalized=['in_color']
    )

    vao_content = [vbo_desc]

    vao = shader.vertex_array(program, vao_content)
    with vao:
        program['Projection'] = get_projection().flatten()

        gl.glLineWidth(line_width)
        gl.glPointSize(line_width)

        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
        gl.glEnable(gl.GL_LINE_SMOOTH)
        gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST)
        gl.glHint(gl.GL_POLYGON_SMOOTH_HINT, gl.GL_NICEST)

        vao.render(mode=mode)
Exemple #6
0
        def _calculate_angle_buffer():
            self._sprite_angle_data = array.array('f')
            for sprite in self.sprite_list:
                self._sprite_angle_data.append(math.radians(sprite.angle))

            self._sprite_angle_buf = shader.buffer(
                self._sprite_angle_data.tobytes(), usage=usage)
            variables = ['in_angle']
            self._sprite_angle_desc = shader.BufferDescription(
                self._sprite_angle_buf, '1f', variables, instanced=True)
            self._sprite_angle_changed = False
Exemple #7
0
        def _calculate_size_buffer():
            self._sprite_size_data = array.array('f')
            for sprite in self.sprite_list:
                self._sprite_size_data.append(sprite.width)
                self._sprite_size_data.append(sprite.height)

            self._sprite_size_buf = shader.buffer(
                self._sprite_size_data.tobytes(), usage=usage)
            variables = ['in_size']
            self._sprite_size_desc = shader.BufferDescription(
                self._sprite_size_buf, '2f', variables, instanced=True)
            self._sprite_size_changed = False
Exemple #8
0
        def _calculate_pos_buffer():
            self._sprite_pos_data = array.array('f')
            # print("A")
            for sprite in self.sprite_list:
                self._sprite_pos_data.append(sprite.center_x)
                self._sprite_pos_data.append(sprite.center_y)

            self._sprite_pos_buf = shader.buffer(
                self._sprite_pos_data.tobytes(), usage=usage)
            variables = ['in_pos']
            self._sprite_pos_desc = shader.BufferDescription(
                self._sprite_pos_buf, '2f', variables, instanced=True)
            self._sprite_pos_changed = False
Exemple #9
0
        def _calculate_colors():
            self._sprite_color_data = array.array('B')
            for sprite in self.sprite_list:
                self._sprite_color_data.append(int(sprite.color[0]))
                self._sprite_color_data.append(int(sprite.color[1]))
                self._sprite_color_data.append(int(sprite.color[2]))
                self._sprite_color_data.append(int(sprite.alpha))

            self._sprite_color_buf = shader.buffer(
                self._sprite_color_data.tobytes(), usage=usage)
            variables = ['in_color']
            self._sprite_color_desc = shader.BufferDescription(
                self._sprite_color_buf,
                '4B',
                variables,
                normalized=['in_color'],
                instanced=True)
            self._sprite_color_changed = False
Exemple #10
0
def _generic_draw_line_strip(point_list: PointList,
                             color: Color,
                             mode: int = gl.GL_LINE_STRIP):
    """
    Draw a line strip. A line strip is a set of continuously connected
    line segments.

    :param point_list: List of points making up the line. Each point is
         in a list. So it is a list of lists.
    :param Color color: color, specified in a list of 3 or 4 bytes in RGB or
         RGBA format.
    """
    program = shader.program(
        vertex_shader=line_vertex_shader,
        fragment_shader=line_fragment_shader,
    )
    buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')])
    data = np.zeros(len(point_list), dtype=buffer_type)

    data['vertex'] = point_list

    color = get_four_byte_color(color)
    data['color'] = color

    vbo = shader.buffer(data.tobytes())
    vbo_desc = shader.BufferDescription(
        vbo,
        '2f 4B',
        ('in_vert', 'in_color'),
        normalized=['in_color']
    )

    vao_content = [vbo_desc]

    vao = shader.vertex_array(program, vao_content)
    with vao:
        program['Projection'] = get_projection().flatten()

        vao.render(mode=mode)
Exemple #11
0
    def _calculate_sprite_buffer(self):

        if len(self.sprite_list) == 0:
            return

        # Loop through each sprite and grab its position, and the texture it will be using.
        array_of_positions = []
        array_of_sizes = []
        array_of_colors = []
        array_of_angles = []

        for sprite in self.sprite_list:
            array_of_positions.append([sprite.center_x, sprite.center_y])
            array_of_angles.append(math.radians(sprite.angle))
            size_h = sprite.height / 2
            size_w = sprite.width / 2
            array_of_sizes.append([size_w, size_h])
            array_of_colors.append(sprite.color + (sprite.alpha, ))

        new_array_of_texture_names = []
        new_array_of_images = []
        new_texture = False
        if self.array_of_images is None:
            new_texture = True

        # print()
        # print("New texture start: ", new_texture)

        for sprite in self.sprite_list:

            if sprite._texture is None:
                raise Exception(
                    "Error: Attempt to draw a sprite without a texture set.")

            name_of_texture_to_check = sprite._texture.name
            if name_of_texture_to_check not in self.array_of_texture_names:
                new_texture = True
                # print("New because of ", name_of_texture_to_check)

            if name_of_texture_to_check not in new_array_of_texture_names:
                new_array_of_texture_names.append(name_of_texture_to_check)
                image = sprite._texture.image
                new_array_of_images.append(image)

        # print("New texture end: ", new_texture)
        # print(new_array_of_texture_names)
        # print(self.array_of_texture_names)
        # print()

        if new_texture:
            # Add back in any old textures. Chances are we'll need them.
            for index, old_texture_name in enumerate(
                    self.array_of_texture_names):
                if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None:
                    new_array_of_texture_names.append(old_texture_name)
                    image = self.array_of_images[index]
                    new_array_of_images.append(image)

            self.array_of_texture_names = new_array_of_texture_names

            self.array_of_images = new_array_of_images
            # print(f"New Texture Atlas with names {self.array_of_texture_names}")

        # Get their sizes
        widths, heights = zip(*(i.size for i in self.array_of_images))

        # Figure out what size a composite would be
        total_width = sum(widths)
        max_height = max(heights)

        if new_texture:

            # TODO: This code isn't valid, but I think some releasing might be in order.
            # if self.texture is not None:
            #     shader.Texture.release(self.texture_id)

            # Make the composite image
            new_image = Image.new('RGBA', (total_width, max_height))

            x_offset = 0
            for image in self.array_of_images:
                new_image.paste(image, (x_offset, 0))
                x_offset += image.size[0]

            # Create a texture out the composite image
            self._texture = shader.texture((new_image.width, new_image.height),
                                           4, np.asarray(new_image))

            if self.texture_id is None:
                self.texture_id = SpriteList.next_texture_id

        # Create a list with the coordinates of all the unique textures
        tex_coords = []
        start_x = 0.0
        for image in self.array_of_images:
            end_x = start_x + (image.width / total_width)
            normalized_width = image.width / total_width
            start_height = 1 - (image.height / max_height)
            normalized_height = image.height / max_height
            tex_coords.append(
                [start_x, start_height, normalized_width, normalized_height])
            start_x = end_x

        # Go through each sprite and pull from the coordinate list, the proper
        # coordinates for that sprite's image.
        array_of_sub_tex_coords = []
        for sprite in self.sprite_list:
            index = self.array_of_texture_names.index(sprite._texture.name)
            array_of_sub_tex_coords.append(tex_coords[index])

        # Create numpy array with info on location and such
        buffer_type = np.dtype([('position', '2f4'), ('angle', 'f4'),
                                ('size', '2f4'), ('sub_tex_coords', '4f4'),
                                ('color', '4B')])
        self.sprite_data = np.zeros(len(self.sprite_list), dtype=buffer_type)
        self.sprite_data['position'] = array_of_positions
        self.sprite_data['angle'] = array_of_angles
        self.sprite_data['size'] = array_of_sizes
        self.sprite_data['sub_tex_coords'] = array_of_sub_tex_coords
        self.sprite_data['color'] = array_of_colors

        if self.is_static:
            usage = 'static'
        else:
            usage = 'stream'

        self.sprite_data_buf = shader.buffer(self.sprite_data.tobytes(),
                                             usage=usage)

        vertices = np.array(
            [
                #  x,    y,   u,   v
                -1.0,
                -1.0,
                0.0,
                0.0,
                -1.0,
                1.0,
                0.0,
                1.0,
                1.0,
                -1.0,
                1.0,
                0.0,
                1.0,
                1.0,
                1.0,
                1.0,
            ],
            dtype=np.float32)
        self.vbo_buf = shader.buffer(vertices.tobytes())
        vbo_buf_desc = shader.BufferDescription(self.vbo_buf, '2f 2f',
                                                ('in_vert', 'in_texture'))
        pos_angle_scale_buf_desc = shader.BufferDescription(
            self.sprite_data_buf,
            '2f 1f 2f 4f 4B', ('in_pos', 'in_angle', 'in_scale',
                               'in_sub_tex_coords', 'in_color'),
            normalized=['in_color'],
            instanced=True)

        vao_content = [vbo_buf_desc, pos_angle_scale_buf_desc]

        # Can add buffer to index vertices
        self.vao = shader.vertex_array(self.program, vao_content)
Exemple #12
0
        def _calculate_sub_tex_coords():

            new_array_of_texture_names = []
            new_array_of_images = []
            new_texture = False
            if self.array_of_images is None:
                new_texture = True

            # print()
            # print("New texture start: ", new_texture)

            for sprite in self.sprite_list:

                # noinspection PyProtectedMember
                if sprite.texture is None:
                    raise Exception(
                        "Error: Attempt to draw a sprite without a texture set."
                    )

                name_of_texture_to_check = sprite.texture.name
                if name_of_texture_to_check not in self.array_of_texture_names:
                    new_texture = True
                    # print("New because of ", name_of_texture_to_check)

                if name_of_texture_to_check not in new_array_of_texture_names:
                    new_array_of_texture_names.append(name_of_texture_to_check)
                    if sprite.texture is None:
                        raise ValueError(f"Sprite has no texture.")
                    if sprite.texture.image is None:
                        raise ValueError(
                            f"Sprite texture {sprite.texture.name} has no image."
                        )
                    image = sprite.texture.image
                    new_array_of_images.append(image)

            # print("New texture end: ", new_texture)
            # print(new_array_of_texture_names)
            # print(self.array_of_texture_names)
            # print()

            if new_texture:
                # Add back in any old textures. Chances are we'll need them.
                for index, old_texture_name in enumerate(
                        self.array_of_texture_names):
                    if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None:
                        new_array_of_texture_names.append(old_texture_name)
                        image = self.array_of_images[index]
                        new_array_of_images.append(image)

                self.array_of_texture_names = new_array_of_texture_names

                self.array_of_images = new_array_of_images
                # print(f"New Texture Atlas with names {self.array_of_texture_names}")

            # Get their sizes
            widths, heights = zip(*(i.size for i in self.array_of_images))

            # Figure out what size a composite would be
            total_width = sum(widths)
            max_height = max(heights)

            if new_texture:

                # TODO: This code isn't valid, but I think some releasing might be in order.
                # if self.texture is not None:
                #     shader.Texture.release(self.texture_id)

                # Make the composite image
                new_image = Image.new('RGBA', (total_width, max_height))

                x_offset = 0
                for image in self.array_of_images:
                    new_image.paste(image, (x_offset, 0))
                    x_offset += image.size[0]

                # Create a texture out the composite image
                texture_bytes = new_image.tobytes()
                self._texture = shader.texture(
                    (new_image.width, new_image.height), 4, texture_bytes)

                if self.texture_id is None:
                    self.texture_id = SpriteList.next_texture_id

            # Create a list with the coordinates of all the unique textures
            tex_coords = []
            start_x = 0.0
            for image in self.array_of_images:
                end_x = start_x + (image.width / total_width)
                normalized_width = image.width / total_width
                start_height = 1 - (image.height / max_height)
                normalized_height = image.height / max_height
                tex_coords.append([
                    start_x, start_height, normalized_width, normalized_height
                ])
                start_x = end_x

            # Go through each sprite and pull from the coordinate list, the proper
            # coordinates for that sprite's image.
            array_of_sub_tex_coords = array.array('f')
            for sprite in self.sprite_list:
                index = self.array_of_texture_names.index(sprite.texture.name)
                for coord in tex_coords[index]:
                    array_of_sub_tex_coords.append(coord)

            self._sprite_sub_tex_buf = shader.buffer(
                array_of_sub_tex_coords.tobytes(), usage=usage)

            self._sprite_sub_tex_desc = shader.BufferDescription(
                self._sprite_sub_tex_buf,
                '4f', ['in_sub_tex_coords'],
                instanced=True)
            self._sprite_sub_tex_changed = False
Exemple #13
0
    def _calculate_sprite_buffer(self):

        if self.is_static:
            usage = 'static'
        else:
            usage = 'stream'

        def _calculate_pos_buffer():
            self._sprite_pos_data = array.array('f')
            # print("A")
            for sprite in self.sprite_list:
                self._sprite_pos_data.append(sprite.center_x)
                self._sprite_pos_data.append(sprite.center_y)

            self._sprite_pos_buf = shader.buffer(
                self._sprite_pos_data.tobytes(), usage=usage)
            variables = ['in_pos']
            self._sprite_pos_desc = shader.BufferDescription(
                self._sprite_pos_buf, '2f', variables, instanced=True)
            self._sprite_pos_changed = False

        def _calculate_size_buffer():
            self._sprite_size_data = array.array('f')
            for sprite in self.sprite_list:
                self._sprite_size_data.append(sprite.width)
                self._sprite_size_data.append(sprite.height)

            self._sprite_size_buf = shader.buffer(
                self._sprite_size_data.tobytes(), usage=usage)
            variables = ['in_size']
            self._sprite_size_desc = shader.BufferDescription(
                self._sprite_size_buf, '2f', variables, instanced=True)
            self._sprite_size_changed = False

        def _calculate_angle_buffer():
            self._sprite_angle_data = array.array('f')
            for sprite in self.sprite_list:
                self._sprite_angle_data.append(math.radians(sprite.angle))

            self._sprite_angle_buf = shader.buffer(
                self._sprite_angle_data.tobytes(), usage=usage)
            variables = ['in_angle']
            self._sprite_angle_desc = shader.BufferDescription(
                self._sprite_angle_buf, '1f', variables, instanced=True)
            self._sprite_angle_changed = False

        def _calculate_colors():
            self._sprite_color_data = array.array('B')
            for sprite in self.sprite_list:
                self._sprite_color_data.append(int(sprite.color[0]))
                self._sprite_color_data.append(int(sprite.color[1]))
                self._sprite_color_data.append(int(sprite.color[2]))
                self._sprite_color_data.append(int(sprite.alpha))

            self._sprite_color_buf = shader.buffer(
                self._sprite_color_data.tobytes(), usage=usage)
            variables = ['in_color']
            self._sprite_color_desc = shader.BufferDescription(
                self._sprite_color_buf,
                '4B',
                variables,
                normalized=['in_color'],
                instanced=True)
            self._sprite_color_changed = False

        def _calculate_sub_tex_coords():

            new_array_of_texture_names = []
            new_array_of_images = []
            new_texture = False
            if self.array_of_images is None:
                new_texture = True

            # print()
            # print("New texture start: ", new_texture)

            for sprite in self.sprite_list:

                # noinspection PyProtectedMember
                if sprite.texture is None:
                    raise Exception(
                        "Error: Attempt to draw a sprite without a texture set."
                    )

                name_of_texture_to_check = sprite.texture.name
                if name_of_texture_to_check not in self.array_of_texture_names:
                    new_texture = True
                    # print("New because of ", name_of_texture_to_check)

                if name_of_texture_to_check not in new_array_of_texture_names:
                    new_array_of_texture_names.append(name_of_texture_to_check)
                    if sprite.texture is None:
                        raise ValueError(f"Sprite has no texture.")
                    if sprite.texture.image is None:
                        raise ValueError(
                            f"Sprite texture {sprite.texture.name} has no image."
                        )
                    image = sprite.texture.image
                    new_array_of_images.append(image)

            # print("New texture end: ", new_texture)
            # print(new_array_of_texture_names)
            # print(self.array_of_texture_names)
            # print()

            if new_texture:
                # Add back in any old textures. Chances are we'll need them.
                for index, old_texture_name in enumerate(
                        self.array_of_texture_names):
                    if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None:
                        new_array_of_texture_names.append(old_texture_name)
                        image = self.array_of_images[index]
                        new_array_of_images.append(image)

                self.array_of_texture_names = new_array_of_texture_names

                self.array_of_images = new_array_of_images
                # print(f"New Texture Atlas with names {self.array_of_texture_names}")

            # Get their sizes
            widths, heights = zip(*(i.size for i in self.array_of_images))

            # Figure out what size a composite would be
            total_width = sum(widths)
            max_height = max(heights)

            if new_texture:

                # TODO: This code isn't valid, but I think some releasing might be in order.
                # if self.texture is not None:
                #     shader.Texture.release(self.texture_id)

                # Make the composite image
                new_image = Image.new('RGBA', (total_width, max_height))

                x_offset = 0
                for image in self.array_of_images:
                    new_image.paste(image, (x_offset, 0))
                    x_offset += image.size[0]

                # Create a texture out the composite image
                texture_bytes = new_image.tobytes()
                self._texture = shader.texture(
                    (new_image.width, new_image.height), 4, texture_bytes)

                if self.texture_id is None:
                    self.texture_id = SpriteList.next_texture_id

            # Create a list with the coordinates of all the unique textures
            tex_coords = []
            start_x = 0.0
            for image in self.array_of_images:
                end_x = start_x + (image.width / total_width)
                normalized_width = image.width / total_width
                start_height = 1 - (image.height / max_height)
                normalized_height = image.height / max_height
                tex_coords.append([
                    start_x, start_height, normalized_width, normalized_height
                ])
                start_x = end_x

            # Go through each sprite and pull from the coordinate list, the proper
            # coordinates for that sprite's image.
            array_of_sub_tex_coords = array.array('f')
            for sprite in self.sprite_list:
                index = self.array_of_texture_names.index(sprite.texture.name)
                for coord in tex_coords[index]:
                    array_of_sub_tex_coords.append(coord)

            self._sprite_sub_tex_buf = shader.buffer(
                array_of_sub_tex_coords.tobytes(), usage=usage)

            self._sprite_sub_tex_desc = shader.BufferDescription(
                self._sprite_sub_tex_buf,
                '4f', ['in_sub_tex_coords'],
                instanced=True)
            self._sprite_sub_tex_changed = False

        if len(self.sprite_list) == 0:
            return

        _calculate_pos_buffer()
        _calculate_size_buffer()
        _calculate_angle_buffer()
        _calculate_sub_tex_coords()
        _calculate_colors()

        vertices = array.array(
            'f',
            [
                #  x,    y,   u,   v
                -1.0,
                -1.0,
                0.0,
                0.0,
                -1.0,
                1.0,
                0.0,
                1.0,
                1.0,
                -1.0,
                1.0,
                0.0,
                1.0,
                1.0,
                1.0,
                1.0,
            ])
        self.vbo_buf = shader.buffer(vertices.tobytes())
        vbo_buf_desc = shader.BufferDescription(self.vbo_buf, '2f 2f',
                                                ('in_vert', 'in_texture'))

        # Can add buffer to index vertices
        vao_content = [
            vbo_buf_desc, self._sprite_pos_desc, self._sprite_size_desc,
            self._sprite_angle_desc, self._sprite_sub_tex_desc,
            self._sprite_color_desc
        ]
        self._vao1 = shader.vertex_array(self.program, vao_content)
    def _calculate_sprite_buffer(self):
        if len(self._queue) == 0:
            return


#
# Initialize arrays
#
        array_of_positions = []
        array_of_sizes = []
        array_of_colors = []
        array_of_angles = []
        self.array_of_layers.clear()
        num_sprites = 0
        #
        #
        #
        for layer in self._queue:
            for sprite in layer._queue:
                array_of_positions.append([sprite.center_x, sprite.center_y])
                array_of_angles.append(math.radians(sprite.angle))
                size_h = sprite.height / 2
                size_w = sprite.width / 2
                array_of_sizes.append([size_w, size_h])
                array_of_colors.append(sprite.color + (sprite.alpha, ))
            num_sprites += len(layer._queue)

        new_array_of_texture_names = []
        new_array_of_images = []
        new_texture = False
        if self.array_of_images is None:
            new_texture = True

        for layer in self._queue:
            for sprite in layer._queue:

                if sprite._texture is None:
                    raise Exception(
                        "Error: Attempt to draw a sprite without a texture set."
                    )

                name_of_texture_to_check = sprite._texture.name
                if name_of_texture_to_check not in self.array_of_texture_names:
                    new_texture = True

                if name_of_texture_to_check not in new_array_of_texture_names:
                    new_array_of_texture_names.append(name_of_texture_to_check)
                    image = sprite._texture.image
                    new_array_of_images.append(image)

        if new_texture:
            # Add back in any old textures. Chances are we'll need them.
            for index, old_texture_name in enumerate(
                    self.array_of_texture_names):
                if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None:
                    new_array_of_texture_names.append(old_texture_name)
                    image = self.array_of_images[index]
                    new_array_of_images.append(image)

            self.array_of_texture_names = new_array_of_texture_names

            self.array_of_images = new_array_of_images
            # print(f"New Texture Atlas with names {self.array_of_texture_names}")

        # Get their sizes
        widths, heights = zip(*(i.size for i in self.array_of_images))

        # Figure out what size a composite would be
        total_width = sum(widths)
        max_height = max(heights)

        if new_texture:
            new_image = self._merge_down_to_image(total_width, max_height)
            # Create a texture out the composite image
            self._texture = shader.texture((new_image.width, new_image.height),
                                           4, np.asarray(new_image))
            if self.texture_id is None:
                self.texture_id = SpriteList.next_texture_id

        # Create a list with the coordinates of all the unique textures
        tex_coords = []
        start_x = 0.0
        for image in self.array_of_images:
            end_x = start_x + (image.width / total_width)
            normalized_width = image.width / total_width
            start_height = 1 - (image.height / max_height)
            normalized_height = image.height / max_height
            tex_coords.append(
                [start_x, start_height, normalized_width, normalized_height])
            start_x = end_x

        # Go through each sprite and pull from the coordinate list, the proper
        # coordinates for that sprite's image.
        array_of_sub_tex_coords = []
        for layer in self._queue:
            for sprite in layer._queue:
                index = self.array_of_texture_names.index(sprite._texture.name)
                array_of_sub_tex_coords.append(tex_coords[index])

        # Create numpy array with info on location and such
        buffer_type = np.dtype([('position', '2f4'), ('angle', 'f4'),
                                ('size', '2f4'), ('sub_tex_coords', '4f4'),
                                ('color', '4B')])
        self.sprite_data = np.zeros(num_sprites, dtype=buffer_type)
        self.sprite_data['position'] = array_of_positions
        self.sprite_data['angle'] = array_of_angles
        self.sprite_data['size'] = array_of_sizes
        self.sprite_data['sub_tex_coords'] = array_of_sub_tex_coords
        self.sprite_data['color'] = array_of_colors

        if self.is_static:
            usage = 'static'
        else:
            usage = 'stream'

        self.sprite_data_buf = shader.buffer(self.sprite_data.tobytes(),
                                             usage=usage)

        self.vbo_buf = shader.buffer(self.vertices.tobytes())
        vbo_buf_desc = shader.BufferDescription(self.vbo_buf, '2f 2f',
                                                ('in_vert', 'in_texture'))
        pos_angle_scale_buf_desc = shader.BufferDescription(
            self.sprite_data_buf,
            '2f 1f 2f 4f 4B', ('in_pos', 'in_angle', 'in_scale',
                               'in_sub_tex_coords', 'in_color'),
            normalized=['in_color'],
            instanced=True)

        vao_content = [vbo_buf_desc, pos_angle_scale_buf_desc]

        # Can add buffer to index vertices
        self.vao = shader.vertex_array(self.program, vao_content)
Exemple #15
0
        def _calculate_sub_tex_coords():
            """
            Create a sprite sheet, and set up subtexture coordinates to point
            to images in that sheet.
            """
            new_array_of_texture_names = []
            new_array_of_images = []
            new_texture = False
            if self.array_of_images is None:
                new_texture = True

            # print()
            # print("New texture start: ", new_texture)

            for sprite in self.sprite_list:

                # noinspection PyProtectedMember
                if sprite.texture is None:
                    raise Exception(
                        "Error: Attempt to draw a sprite without a texture set."
                    )

                name_of_texture_to_check = sprite.texture.name
                if name_of_texture_to_check not in self.array_of_texture_names:
                    new_texture = True
                    # print("New because of ", name_of_texture_to_check)

                if name_of_texture_to_check not in new_array_of_texture_names:
                    new_array_of_texture_names.append(name_of_texture_to_check)
                    if sprite.texture is None:
                        raise ValueError(f"Sprite has no texture.")
                    if sprite.texture.image is None:
                        raise ValueError(
                            f"Sprite texture {sprite.texture.name} has no image."
                        )
                    image = sprite.texture.image
                    new_array_of_images.append(image)

            # print("New texture end: ", new_texture)
            # print(new_array_of_texture_names)
            # print(self.array_of_texture_names)
            # print()

            if new_texture:
                # Add back in any old textures. Chances are we'll need them.
                for index, old_texture_name in enumerate(
                        self.array_of_texture_names):
                    if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None:
                        new_array_of_texture_names.append(old_texture_name)
                        image = self.array_of_images[index]
                        new_array_of_images.append(image)

                self.array_of_texture_names = new_array_of_texture_names

                self.array_of_images = new_array_of_images
                # print(f"New Texture Atlas with names {self.array_of_texture_names}")

            # Get their sizes
            widths, heights = zip(*(i.size for i in self.array_of_images))

            grid_item_width, grid_item_height = max(widths), max(heights)
            image_count = len(self.array_of_images)
            root = math.sqrt(image_count)
            grid_width = int(math.sqrt(image_count))
            # print(f"\nimage_count={image_count}, root={root}")
            if root == grid_width:
                # Perfect square
                grid_height = grid_width
                # print("\nA")
            else:
                grid_height = grid_width
                grid_width += 1
                if grid_width * grid_height < image_count:
                    grid_height += 1
                # print("\nB")

            # Figure out sprite sheet size
            MARGIN = 1

            sprite_sheet_width = (grid_item_width + MARGIN) * grid_width
            sprite_sheet_height = (grid_item_height + MARGIN) * grid_height

            if new_texture:

                # TODO: This code isn't valid, but I think some releasing might be in order.
                # if self.texture is not None:
                #     shader.Texture.release(self.texture_id)

                # Make the composite image
                new_image2 = Image.new(
                    'RGBA', (sprite_sheet_width, sprite_sheet_height))

                x_offset = 0
                for index, image in enumerate(self.array_of_images):

                    x = (index % grid_width) * (grid_item_width + MARGIN)
                    y = (index // grid_width) * (grid_item_height + MARGIN)

                    # print(f"Pasting {new_array_of_texture_names[index]} at {x, y}")

                    new_image2.paste(image, (x, y))
                    x_offset += image.size[0]

                # Create a texture out the composite image
                texture_bytes2 = new_image2.tobytes()
                self._texture = shader.texture(
                    (new_image2.width, new_image2.height), 4, texture_bytes2)

                if self.texture_id is None:
                    self.texture_id = SpriteList.next_texture_id

                # new_image2.save("sprites.png")

            # Create a list with the coordinates of all the unique textures
            tex_coords = []

            for index, image in enumerate(self.array_of_images):
                column = index % grid_width
                row = index // grid_width

                # Texture coordinates are reversed in y axis
                row = grid_height - row - 1

                x = column * (grid_item_width + MARGIN)
                y = row * (grid_item_height + MARGIN)

                # Because, coordinates are reversed
                y += (grid_item_height - (image.height - MARGIN))

                normalized_x = x / sprite_sheet_width
                normalized_y = y / sprite_sheet_height

                start_x = normalized_x
                start_y = normalized_y

                normalized_width = image.width / sprite_sheet_width
                normalized_height = image.height / sprite_sheet_height

                # print(f"Fetching {new_array_of_texture_names[index]} at {row}, {column} => {x}, {y} normalized to {start_x:.2}, {start_y:.2} size {normalized_width}, {normalized_height}")

                tex_coords.append(
                    [start_x, start_y, normalized_width, normalized_height])

            # Go through each sprite and pull from the coordinate list, the proper
            # coordinates for that sprite's image.
            array_of_sub_tex_coords = array.array('f')
            for sprite in self.sprite_list:
                index = self.array_of_texture_names.index(sprite.texture.name)
                for coord in tex_coords[index]:
                    array_of_sub_tex_coords.append(coord)

            self._sprite_sub_tex_buf = shader.buffer(
                array_of_sub_tex_coords.tobytes(), usage=usage)

            self._sprite_sub_tex_desc = shader.BufferDescription(
                self._sprite_sub_tex_buf,
                '4f', ['in_sub_tex_coords'],
                instanced=True)
            self._sprite_sub_tex_changed = False