danini-the-panini/mittsu-opengl

View on GitHub
lib/mittsu/opengl/plugins/sprite_plugin.rb

Summary

Maintainability
A
0 mins
Test Coverage
B
84%
module Mittsu
  class SpritePlugin
    include OpenGL::Helper

    VERTICES = [
      -0.5, -0.5, 0.0, 0.0,
       0.5, -0.5, 1.0, 0.0,
       0.5,  0.5, 1.0, 1.0,
      -0.5,  0.5, 0.0, 1.0
    ] # Float32Array

    FACES = [
      0, 1, 2,
      0, 2, 3
    ] # Uint16Array

    def initialize(renderer, sprites)
      @renderer = renderer
      @sprites = sprites
      @program = nil

      # for decomposing matrixWorld
      @sprite_position = Vector3.new
      @sprite_rotation = Quaternion.new
      @sprite_scale = Vector3.new
    end

    def render(scene, camera)
      return if @sprites.empty?

      init if @program.nil?
      setup_gl_for_render(camera)
      setup_fog(scene)

      update_positions_and_sort(camera)

      render_all_sprites(scene)

      GL.Enable(GL::CULL_FACE)
      @renderer.reset_gl_state
    end

    private

    def init
      create_vertex_array_object
      create_program

      init_attributes
      init_uniforms

      # TODO: canvas texture??
    end

    def create_vertex_array_object
      @vertex_array_object = GL.CreateVertexArray
      GL.BindVertexArray(@vertex_array_object)

      @vertex_buffer = GL.CreateBuffer
      @element_buffer = GL.CreateBuffer

      GL.BindBuffer(GL::ARRAY_BUFFER, @vertex_buffer)
      GL.BufferData_easy(GL::ARRAY_BUFFER, VERTICES, GL::STATIC_DRAW)

      GL.BindBuffer(GL::ELEMENT_ARRAY_BUFFER, @element_buffer)
      GL.BufferData_easy(GL::ELEMENT_ARRAY_BUFFER, FACES, GL::STATIC_DRAW)
    end

    def create_program
      @program = GL.CreateProgram

      vertex_shader = OpenGL::Shader.new(GL::VERTEX_SHADER, File.read(File.join(__dir__, 'sprite_vertex.glsl')))
      fragment_shader = OpenGL::Shader.new(GL::FRAGMENT_SHADER, File.read(File.join(__dir__, 'sprite_fragment.glsl')))

      GL.AttachShader(@program, vertex_shader.shader)
      GL.AttachShader(@program, fragment_shader.shader)

      GL.LinkProgram(@program)
    end

    def init_attributes
      @attributes = {
        position: GL.GetAttribLocation(@program, 'position'),
        uv: GL.GetAttribLocation(@program, 'uv')
      }
    end

    def init_uniforms
      @uniforms = {
        uvOffset: GL.GetUniformLocation(@program, 'uvOffset'),
        uvScale: GL.GetUniformLocation(@program, 'uvScale'),

        rotation: GL.GetUniformLocation(@program, 'rotation'),
        scale: GL.GetUniformLocation(@program, 'scale'),

        color: GL.GetUniformLocation(@program, 'color'),
        map: GL.GetUniformLocation(@program, 'map'),
        opacity: GL.GetUniformLocation(@program, 'opacity'),

        modelViewMatrix: GL.GetUniformLocation(@program, 'modelViewMatrix'),
        projectionMatrix: GL.GetUniformLocation(@program, 'projectionMatrix'),

        fogType: GL.GetUniformLocation(@program, 'fogType'),
        fogDensity: GL.GetUniformLocation(@program, 'fogDensity'),
        fogNear: GL.GetUniformLocation(@program, 'fogNear'),
        fogFar: GL.GetUniformLocation(@program, 'fogFar'),
        fogColor: GL.GetUniformLocation(@program, 'fogColor'),

        alphaTest: GL.GetUniformLocation(@program, 'alphaTest')
      }
    end

    def painter_sort_stable(a, b)
      if a.z != b.z
        b.z - a.z
      else
        b.id - a.id
      end
    end

    def setup_gl_for_render(camera)
      GL.UseProgram(@program)

      GL.Disable(GL::CULL_FACE)
      GL.Enable(GL::BLEND)

      GL.BindVertexArray(@vertex_array_object)

      GL.EnableVertexAttribArray(@attributes[:position])
      GL.EnableVertexAttribArray(@attributes[:uv])

      GL.BindBuffer(GL::ARRAY_BUFFER, @vertex_buffer)

      GL.VertexAttribPointer(@attributes[:position], 2, GL::FLOAT, GL::FALSE, 2 * 8, 0)
      GL.VertexAttribPointer(@attributes[:uv], 2, GL::FLOAT, GL::FALSE, 2 * 8, 8)

      GL.BindBuffer(GL::ELEMENT_ARRAY_BUFFER, @element_buffer)

      GL.UniformMatrix4fv(@uniforms[:projectionMatrix], 1, GL::FALSE, array_to_ptr_easy(camera.projection_matrix.elements))

      GL.ActiveTexture(GL::TEXTURE0)
      GL.Uniform1i(@uniforms[:map], 0)
    end

    def setup_fog(scene)
      @old_fog_type = 0
      @scene_fog_type = 0
      fog = scene.fog

      if fog
        GL.Uniform3f(@uniforms[:fogColor], fog.color.r, fog.color.g, fog.color.b)

        if fog.is_a?(Fog)
          GL.Uniform1f(@uniforms[:fogNear], fog.near)
          GL.Uniform1f(@uniforms[:fogFar], fog.far)

          GL.Uniform1i(@uniforms[:fogType], 1)
          @old_fog_type = 1
          @scene_fog_type = 1
        elsif fog.is_a?(FogExp2)
          GL.Uniform1f(@uniforms[:fogDensity], fog.density)

          GL.Uniform1i(@uniforms[:fogType], 2)
          @old_fog_type = 2
          @scene_fog_type = 2
        end
      else
        GL.Uniform1i(@uniforms[:fogType], 0)
        @old_fog_type = 0
        @scene_fog_type = 0
      end
    end

    def update_positions_and_sort(camera)
      @sprites.each do |sprite|
        sprite.model_view_matrix.multiply_matrices(camera.matrix_world_inverse, sprite.matrix_world)
        sprite.z = -sprite.model_view_matrix.elements[14]
      end

      @sprites.sort!(&self.method(:painter_sort_stable))
    end

    def render_all_sprites(scene)
      @sprites.each do |sprite|
        material = sprite.material

        set_fog_uniforms(material, scene)
        set_uv_uniforms(material)
        set_color_uniforms(material)
        set_transform_uniforms(sprite)
        set_blend_mode(material)

        # set texture
        if material.map && material.map.image && material.map.image.width
          material.map.set(0, @renderer)
        else
          # TODO: canvas texture?
          # texture.set(0, @renderer)
        end

        # draw elements
        GL.DrawElements(GL::TRIANGLES, 6, GL::UNSIGNED_INT, 0) # GL::UNSIGNED_SHORT
      end
    end

    def set_fog_uniforms(material, scene)
      fog_type = 0

      if scene.fog && material.fog
        fog_type = @scene_fog_type
      end

      if @old_fog_type != fog_type
        GL.Uniform1(@uniforms[:fogType], fog_type)
        @old_fog_type = fog_type
      end
    end

    def set_uv_uniforms(material)
      if !material.map.nil?
        GL.Uniform2f(@uniforms[:uvOffset], material.map.offset.x, material.map.offset.y)
        GL.Uniform2f(@uniforms[:uvScale], material.map.repeat.x, material.map.repeat.y)
      else
        GL.Uniform2f(@uniforms[:uvOffset], 0.0, 0.0)
        GL.Uniform2f(@uniforms[:uvScale], 1.0, 1.0)
      end
    end

    def set_color_uniforms(material)
      GL.Uniform1f(@uniforms[:opacity], material.opacity)
      GL.Uniform3f(@uniforms[:color], material.color.r, material.color.g, material.color.b)
      GL.Uniform1f(@uniforms[:alphaTest], material.alpha_test)
    end

    def set_transform_uniforms(sprite)
      GL.UniformMatrix4fv(@uniforms[:modelViewMatrix], 1, GL::FALSE, array_to_ptr_easy(sprite.model_view_matrix.elements))

      sprite.matrix_world.decompose(@sprite_position, @sprite_rotation, @sprite_scale)

      GL.Uniform1f(@uniforms[:rotation], sprite.material.rotation)
      GL.Uniform2fv(@uniforms[:scale], 1, array_to_ptr_easy([@sprite_scale.x, @sprite_scale.y]))
    end

    def set_blend_mode(material)
      @renderer.state.set_blending(material.blending, material.blend_equation, material.blend_src, material.blend_dst)
      @renderer.state.set_depth_test(material.depth_test)
      @renderer.state.set_depth_write(material.depth_write)
    end
  end
end