diff --git a/README.md b/README.md index 56ca872..073d83b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ # CyberarmEngine -Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/cyberarm_engine`. To experiment with that code, run `bin/console` for an interactive prompt. +Yet Another Game Engine On Top Of Gosu -TODO: Delete this and the text above, and describe your gem +## Features +* [Shoes-like](http://shoesrb.com) GUI support +* OpenGL Shader support (requires [opengl-bindings](https://github.com/vaiorabbit/ruby-opengl) gem) +* Includes classes for handling Vectors, Rays, Bounding Boxes, and Transforms +* GameState system +* Monolithic GameObjects ## Installation @@ -22,7 +27,32 @@ Or install it yourself as: ## Usage -TODO: Write usage instructions here +```ruby +require "cyberarm_engine" + +class Hello < CyberarmEngine::GuiState + def setup + stack do + label "Hello World!" + + button "close" do + window.close + end + end + end +end + +class Window < CyberarmEngine::Engine + def initialize + super + self.show_cursor = true + + push_state(Hello) + end +end + +Window.new.show +``` ## Development diff --git a/lib/cyberarm_engine/game_state.rb b/lib/cyberarm_engine/game_state.rb index 044316a..21f9809 100644 --- a/lib/cyberarm_engine/game_state.rb +++ b/lib/cyberarm_engine/game_state.rb @@ -3,7 +3,7 @@ module CyberarmEngine include Common attr_accessor :options, :global_pause - attr_reader :game_objects, :containers + attr_reader :game_objects def initialize(options={}) @options = options diff --git a/lib/cyberarm_engine/shader.rb b/lib/cyberarm_engine/shader.rb index 7ac1399..3054cef 100644 --- a/lib/cyberarm_engine/shader.rb +++ b/lib/cyberarm_engine/shader.rb @@ -2,13 +2,29 @@ module CyberarmEngine # Ref: https://github.com/vaiorabbit/ruby-opengl/blob/master/sample/OrangeBook/brick.rb class Shader include OpenGL - @@shaders = {} - PREPROCESSOR_CHARACTER = "@" + @@shaders = {} # Cache for {Shader} instances + PREPROCESSOR_CHARACTER = "@".freeze # magic character for preprocessor phase of {Shader} compilation + # add instance of {Shader} to cache + # + # @param name [String] + # @param instance [Shader] def self.add(name, instance) @@shaders[name] = instance end + ## + # runs _block_ using {Shader} with _name_ + # + # @example + # + # CyberarmEngine::Shader.use("blur") do |shader| + # shader.uniform_float("radius", 20.0) + # # OpenGL Code that uses shader + # end + # + # @param name [String] name of {Shader} to use + # @return [void] def self.use(name, &block) shader = @@shaders.dig(name) if shader @@ -18,22 +34,37 @@ module CyberarmEngine end end + # returns whether {Shader} with _name_ is in cache + # + # @param name [String] + # @return [Boolean] def self.available?(name) @@shaders.dig(name).is_a?(Shader) end + # returns instance of {Shader}, if it exists + # + # @param name [String] + # @return [Shader?] def self.get(name) @@shaders.dig(name) end + # returns currently active {Shader}, if one is active + # + # @return [Shader?] def self.active_shader @active_shader end + # sets currently active {Shader} + # + # @param instance [Shader] instance of {Shader} to set as active def self.active_shader=(instance) @active_shader = instance end + # stops using currently active {Shader} def self.stop shader = Shader.active_shader @@ -44,11 +75,18 @@ module CyberarmEngine end end + # returns location of OpenGL Shader uniform + # + # @param variable [String] def self.attribute_location(variable) raise RuntimeError, "No active shader!" unless Shader.active_shader Shader.active_shader.attribute_location(variable) end + # sets _variable_ to _value_ + # + # @param variable [String] + # @param value def self.set_uniform(variable, value) raise RuntimeError, "No active shader!" unless Shader.active_shader Shader.active_shader.set_uniform(variable, value) @@ -90,10 +128,17 @@ module CyberarmEngine end end + # whether vertex and fragment files exist on disk + # + # @return [Boolean] def shader_files_exist?(vertex:, fragment:) File.exist?(vertex) && File.exist?(fragment) end + # creates an OpenGL Shader of _type_ using _source_ + # + # @param type [Symbol] valid values are: :vertex, :fragment + # @param source [String] source code for shader def create_shader(type:, source:) _shader = nil @@ -103,7 +148,7 @@ module CyberarmEngine when :fragment _shader = glCreateShader(GL_FRAGMENT_SHADER) else - warn "Unsupported shader type: #{type.inspect}" + raise ArgumentError, "Unsupported shader type: #{type.inspect}" end processed_source = preprocess_source(source: source) @@ -115,6 +160,23 @@ module CyberarmEngine @data[:shaders][type] =_shader end + # evaluates shader preprocessors + # + # currently supported preprocessors: + # + # @include "file/path" "another/file/path" # => Replace line with contents of file; Shader includes_dir must be specified in constructor + # + # @example + # # Example Vertex Shader # + # # #version 330 core + # # @include "material_struct" + # # void main() { + # # gl_Position = vec4(1, 1, 1, 1); + # # } + # + # Shader.new(name: "model_renderer", includes_dir: "path/to/includes", vertex: "path/to/vertex_shader.glsl") + # + # @param source shader source code def preprocess_source(source:) lines = source.lines @@ -141,6 +203,9 @@ module CyberarmEngine lines.join end + # compile OpenGL Shader of _type_ + # + # @return [Boolean] whether compilation succeeded def compile_shader(type:) _compiled = false _shader = @data[:shaders][type] @@ -166,6 +231,11 @@ module CyberarmEngine return _compiled end + # link compiled OpenGL Shaders in to a OpenGL Program + # + # @note linking must succeed or shader cannot be used + # + # @return [Boolean] whether linking succeeded def link_shaders @program = glCreateProgram @data[:shaders].values.each do |_shader| @@ -187,7 +257,10 @@ module CyberarmEngine @compiled = linked == 0 ? false : true end - # Returns the location of a uniform variable + # Returns the location of a uniform _variable_ + # + # @param variable [String] + # @return [Integer] location of uniform def variable(variable) loc = glGetUniformLocation(@program, variable) if (loc == -1) @@ -197,6 +270,7 @@ module CyberarmEngine return loc end + # @see Shader.use Shader.use def use(&block) return unless compiled? raise "Another shader is already in use! #{Shader.active_shader.name.inspect}" if Shader.active_shader @@ -210,49 +284,93 @@ module CyberarmEngine end end + # stop using shader, if shader is active def stop Shader.active_shader = nil if Shader.active_shader == self glUseProgram(0) end + # @return [Boolean] whether {Shader} successfully compiled def compiled? @compiled end + # returns location of a uniform _variable_ + # + # @note Use {#variable} for friendly error handling + # @see #variable Shader#variable + # + # @param variable [String] + # @return [Integer] def attribute_location(variable) glGetUniformLocation(@program, variable) end + # send {Transform} to {Shader} + # + # @param variable [String] + # @param value [Transform] + # @param location [Integer] + # @return [void] def uniform_transform(variable, value, location = nil) attr_loc = location ? location : attribute_location(variable) glUniformMatrix4fv(attr_loc, 1, GL_FALSE, value.to_gl.pack("F16")) end + # send Boolean to {Shader} + # + # @param variable [String] + # @param value [Boolean] + # @param location [Integer] + # @return [void] def uniform_boolean(variable, value, location = nil) attr_loc = location ? location : attribute_location(variable) glUniform1i(attr_loc, value ? 1 : 0) end + # send Integer to {Shader} + # @param variable [String] + # @param value [Integer] + # @param location [Integer] + # @return [void] def uniform_integer(variable, value, location = nil) attr_loc = location ? location : attribute_location(variable) glUniform1i(attr_loc, value) end + # send Float to {Shader} + # + # @param variable [String] + # @param value [Float] + # @param location [Integer] + # @return [void] def uniform_float(variable, value, location = nil) attr_loc = location ? location : attribute_location(variable) glUniform1f(attr_loc, value) end + # send {Vector} (x, y, z) to {Shader} + # + # @param variable [String] + # @param value [Vector] + # @param location [Integer] + # @return [void] def uniform_vec3(variable, value, location = nil) attr_loc = location ? location : attribute_location(variable) glUniform3f(attr_loc, *value.to_a[0..2]) end + # send {Vector} to {Shader} + # + # @param variable [String] + # @param value [Vector] + # @param location [Integer] + # @return [void] def uniform_vec4(variable, value, location = nil) attr_loc = location ? location : attribute_location(variable) diff --git a/lib/cyberarm_engine/vector.rb b/lib/cyberarm_engine/vector.rb index 65a2f5b..8d9b884 100644 --- a/lib/cyberarm_engine/vector.rb +++ b/lib/cyberarm_engine/vector.rb @@ -1,25 +1,61 @@ module CyberarmEngine class Vector + ## + # Creates a up vector + # + # Vector.new(0, 1, 0) + # + # @return [CyberarmEngine::Vector] def self.up Vector.new(0, 1, 0) end + ## + # Creates a down vector + # + # Vector.new(0, -1, 0) + # + # @return [CyberarmEngine::Vector] def self.down Vector.new(0, -1, 0) end + ## + # Creates a left vector + # + # Vector.new(-1, 0, 0) + # + # @return [CyberarmEngine::Vector] def self.left Vector.new(-1, 0, 0) end + ## + # Creates a right vector + # + # Vector.new(1, 0, 0) + # + # @return [CyberarmEngine::Vector] def self.right Vector.new(1, 0, 0) end + ## + # Creates a forward vector + # + # Vector.new(0, 0, 1) + # + # @return [CyberarmEngine::Vector] def self.forward Vector.new(0, 0, 1) end + ## + # Creates a backward vector + # + # Vector.new(0, 0, -1) + # + # @return [CyberarmEngine::Vector] def self.backward Vector.new(0, 0, -1) end @@ -43,6 +79,7 @@ module CyberarmEngine alias w weight alias w= weight= + # @return [Boolean] def ==(other) if other.is_a?(Numeric) @x == other && @@ -57,11 +94,13 @@ module CyberarmEngine end end + # Create a new vector using {x} and {y} values + # @return [CyberarmEngine::Vector] def xy Vector.new(@x, @y) end - # Performs math operation, excluding @weight + # Performs math operation, excluding {weight} private def operator(function, other) if other.is_a?(Numeric) Vector.new( @@ -78,22 +117,26 @@ module CyberarmEngine end end - # Adds Vector and Numberic or Vector and Vector, excluding @weight + # Adds Vector and Numeric or Vector and Vector, excluding {weight} + # @return [CyberarmEngine::Vector] def +(other) operator("+", other) end - # Subtracts Vector and Numberic or Vector and Vector, excluding @weight + # Subtracts Vector and Numeric or Vector and Vector, excluding {weight} + # @return [CyberarmEngine::Vector] def -(other) operator("-", other) end - # Multiplies Vector and Numberic or Vector and Vector, excluding @weight + # Multiplies Vector and Numeric or Vector and Vector, excluding {weight} + # @return [CyberarmEngine::Vector] def *(other) operator("*", other) end - # Divides Vector and Numberic or Vector and Vector, excluding @weight + # Divides Vector and Numeric or Vector and Vector, excluding {weight} + # @return [CyberarmEngine::Vector] def /(other) # Duplicated to protect from DivideByZero if other.is_a?(Numeric) @@ -111,6 +154,8 @@ module CyberarmEngine end end + # dot product of {Vector} + # @return [Integer|Float] def dot(other) product = 0 @@ -124,6 +169,8 @@ module CyberarmEngine return product end + # cross product of {Vector} + # @return [CyberarmEngine::Vector] def cross(other) a = self.to_a b = other.to_a @@ -136,24 +183,40 @@ module CyberarmEngine end # returns degrees + # @return [Float] def angle(other) Math.acos( self.normalized.dot(other.normalized) ) * 180 / Math::PI end # returns magnitude of Vector, ignoring #weight + # @return [Float] def magnitude Math.sqrt((@x * @x) + (@y * @y) + (@z * @z)) end + ## + # returns normalized {Vector} + # + # @example + # CyberarmEngine::Vector.new(50, 21.2, 45).normalized + # # => + # + # @return [CyberarmEngine::Vector] def normalized mag = magnitude self / Vector.new(mag, mag, mag) end + + # returns a direction {Vector} + # + # z is pitch + # + # y is yaw + # + # x is roll + # @return [CyberarmEngine::Vector] def direction - # z is pitch - # y is yaw - # x is roll _x = -Math.sin(@y.degrees_to_radians) * Math.cos(@z.degrees_to_radians) _y = Math.sin(@z.degrees_to_radians) _z = Math.cos(@y.degrees_to_radians) * Math.cos(@z.degrees_to_radians) @@ -161,41 +224,63 @@ module CyberarmEngine Vector.new(_x, _y, _z) end + # returns an inverse {Vector} + # @return [CyberarmEngine::Vector] def inverse Vector.new(1.0 / @x, 1.0 / @y, 1.0 / @z) end + # Adds up values of {x}, {y}, and {z} + # @return [Integer|Float] def sum @x + @y + @z end + ## + # Linear interpolation: smoothly transition between two {Vector} + # + # CyberarmEngine::Vector.new(100, 100, 100).lerp( CyberarmEngine::Vector.new(0, 0, 0), 0.75 ) + # # => + # + # @param other [CyberarmEngine::Vector | Integer | Float] value to subtract from + # @param factor [Float] how complete transition to _other_ is, in range [0.0..1.0] + # @return [CyberarmEngine::Vector] def lerp(other, factor) (self - other) * factor.clamp(0.0, 1.0) end # 2D distance using X and Y + # @return [Float] def distance(other) Math.sqrt((@x-other.x)**2 + (@y-other.y)**2) end # 2D distance using X and Z + # @return [Float] def gl_distance2d(other) Math.sqrt((@x-other.x)**2 + (@z-other.z)**2) end # 3D distance using X, Y, and Z + # @return [Float] def distance3d(other) Math.sqrt((@x-other.x)**2 + (@y-other.y)**2 + (@z-other.z)**2) end + # Converts {Vector} to Array + # @return [Array] def to_a [@x, @y, @z, @weight] end + # Converts {Vector} to String + # @return [String] def to_s "X: #{@x}, Y: #{@y}, Z: #{@z}, Weight: #{@weight}" end + # Converts {Vector} to Hash + # @return [Hash] def to_h {x: @x, y: @y, z: @z, weight: @weight} end