From 7b903fbdb9eb68aa08610b5c5015da00dcb47e75 Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Wed, 20 Feb 2019 10:49:56 -0600 Subject: [PATCH] Refactored GameObject to Entity, replaced @x,@y,@z with @position, added @velocity vector to Entity, bricked Player terrain interaction while authoring Axis Aligned Bounding Box Tree for CollisionManager to handle all collision interaction. Added PhysicsManager stub. --- i-mic-fps.rb | 20 +- lib/managers/collision_manager.rb | 32 ++-- lib/managers/entity_manager.rb | 28 +++ lib/managers/object_manager.rb | 22 --- lib/managers/physics_manager.rb | 4 + lib/math/vertex.rb | 3 + .../{game_objects => entities}/camera.rb | 54 +++--- .../{game_objects => entities}/player.rb | 51 ++---- .../{game_objects => entities}/skydome.rb | 6 +- lib/objects/entities/terrain.rb | 7 + .../{game_objects => entities}/test_object.rb | 2 +- lib/objects/entities/tree.rb | 7 + lib/objects/{game_object.rb => entity.rb} | 90 +++++---- lib/objects/game_objects/terrain.rb | 172 ------------------ lib/objects/game_objects/tree.rb | 20 -- lib/objects/light.rb | 6 +- lib/objects/model_loader.rb | 4 +- lib/renderer/bounding_box_renderer.rb | 6 +- lib/renderer/opengl_renderer.rb | 12 +- lib/renderer/renderer.rb | 2 +- lib/renderer/shader.rb | 2 +- lib/states/game_state.rb | 6 +- lib/states/game_states/game.rb | 28 +-- lib/states/game_states/loading_state.rb | 4 +- lib/trees/aabb_tree.rb | 49 +++++ lib/wavefront/model.rb | 24 +-- lib/wavefront/parser.rb | 12 +- 27 files changed, 288 insertions(+), 385 deletions(-) create mode 100644 lib/managers/entity_manager.rb delete mode 100644 lib/managers/object_manager.rb create mode 100644 lib/managers/physics_manager.rb create mode 100644 lib/math/vertex.rb rename lib/objects/{game_objects => entities}/camera.rb (80%) rename lib/objects/{game_objects => entities}/player.rb (74%) rename lib/objects/{game_objects => entities}/skydome.rb (73%) create mode 100644 lib/objects/entities/terrain.rb rename lib/objects/{game_objects => entities}/test_object.rb (72%) create mode 100644 lib/objects/entities/tree.rb rename lib/objects/{game_object.rb => entity.rb} (56%) delete mode 100644 lib/objects/game_objects/terrain.rb delete mode 100644 lib/objects/game_objects/tree.rb create mode 100644 lib/trees/aabb_tree.rb diff --git a/i-mic-fps.rb b/i-mic-fps.rb index 2ec72bc..92c2ec2 100644 --- a/i-mic-fps.rb +++ b/i-mic-fps.rb @@ -61,9 +61,13 @@ end $debug = ARGV.join.include?("--debug") ? true : false require_relative "lib/common_methods" + +require_relative "lib/math/vertex" +require_relative "lib/trees/aabb_tree" + require_relative "lib/managers/input_mapper" require_relative "lib/managers/shader_manager" -require_relative "lib/managers/object_manager" +require_relative "lib/managers/entity_manager" require_relative "lib/managers/light_manager" require_relative "lib/managers/network_manager" require_relative "lib/managers/collision_manager" @@ -81,16 +85,16 @@ require_relative "lib/states/menus/main_menu" require_relative "lib/objects/text" require_relative "lib/objects/multi_line_text" -require_relative "lib/objects/game_object" +require_relative "lib/objects/entity" require_relative "lib/objects/model_loader" require_relative "lib/objects/light" -require_relative "lib/objects/game_objects/camera" -require_relative "lib/objects/game_objects/player" -require_relative "lib/objects/game_objects/tree" -require_relative "lib/objects/game_objects/skydome" -require_relative "lib/objects/game_objects/test_object" -require_relative "lib/objects/game_objects/terrain" +require_relative "lib/objects/entities/camera" +require_relative "lib/objects/entities/player" +require_relative "lib/objects/entities/tree" +require_relative "lib/objects/entities/skydome" +require_relative "lib/objects/entities/test_object" +require_relative "lib/objects/entities/terrain" require_relative "lib/wavefront/model" diff --git a/lib/managers/collision_manager.rb b/lib/managers/collision_manager.rb index 18f490e..b948ca7 100644 --- a/lib/managers/collision_manager.rb +++ b/lib/managers/collision_manager.rb @@ -2,25 +2,33 @@ class IMICFPS class CollisionManager def initialize(game_state:) @game_state = game_state - # @aabb_tree = AABBTree.new + @aabb_tree = AABBTree.new + end + + def add(entity) + @aabb_tree.add(entity.normalized_bounding_box, entity) + end + + def remove(entity) + @aabb_tree.remove(entity) end def lazy_check_collisions # Expensive AABB collision detection - @game_state.game_objects.each do |object| - @game_state.game_objects.each do |b| - next if object == b - next if object.is_a?(Terrain) || b.is_a?(Terrain) + @game_state.entities.each do |entity| + @game_state.entities.each do |other| + next if entity == other + next if entity.is_a?(Terrain) || other.is_a?(Terrain) - if object.intersect(object, b) - object.debug_color = Color.new(1.0,0.0,0.0) - b.debug_color = Color.new(1.0,0.0,0.0) + if entity.intersect(other) + entity.debug_color = Color.new(1.0,0.0,0.0) + other.debug_color = Color.new(1.0,0.0,0.0) - # @game_state.game_objects.delete(object) unless object.is_a?(Player) - # puts "#{object} is intersecting #{b}" if object.is_a?(Player) + # @game_state.entities.delete(entity) unless entity.is_a?(Player) + # puts "#{entity} is intersecting #{b}" if entity.is_a?(Player) else - object.debug_color = Color.new(0,1,0) - b.debug_color = Color.new(0,1,0) + entity.debug_color = Color.new(0,1,0) + other.debug_color = Color.new(0,1,0) end end end diff --git a/lib/managers/entity_manager.rb b/lib/managers/entity_manager.rb new file mode 100644 index 0000000..7c28bf7 --- /dev/null +++ b/lib/managers/entity_manager.rb @@ -0,0 +1,28 @@ +class IMICFPS + TextureCoordinate = Struct.new(:u, :v, :weight) + Point = Struct.new(:x, :y) + Color = Struct.new(:red, :green, :blue, :alpha) + + module EntityManager # Get included into GameState context + def add_entity(entity) + @collision_manager.add(entity) if entity.collidable? + @entities << entity + end + + def find_entity(entity) + @entities.detect {|entity| entity == entity} + end + + def remove_entity(entity) + ent = @entities.detect {|entity| entity == entity} + if ent + @collision_manager.remove(entity) + @entities.delete(ent) + end + end + + def entities + @entities + end + end +end diff --git a/lib/managers/object_manager.rb b/lib/managers/object_manager.rb deleted file mode 100644 index 1c2d70c..0000000 --- a/lib/managers/object_manager.rb +++ /dev/null @@ -1,22 +0,0 @@ -class IMICFPS - TextureCoordinate = Struct.new(:u, :v, :weight) - Vertex = Struct.new(:x, :y, :z, :weight) - Point = Struct.new(:x, :y) - Color = Struct.new(:red, :green, :blue, :alpha) - - module ObjectManager # Get included into GameState context - def add_object(model) - @game_objects << model - end - - def find_object() - end - - def remove_object() - end - - def game_objects - @game_objects - end - end -end diff --git a/lib/managers/physics_manager.rb b/lib/managers/physics_manager.rb new file mode 100644 index 0000000..b677d1d --- /dev/null +++ b/lib/managers/physics_manager.rb @@ -0,0 +1,4 @@ +class IMICFPS + class PhysicsManager + end +end \ No newline at end of file diff --git a/lib/math/vertex.rb b/lib/math/vertex.rb new file mode 100644 index 0000000..8463a06 --- /dev/null +++ b/lib/math/vertex.rb @@ -0,0 +1,3 @@ +class IMICFPS + Vector = Struct.new(:x, :y, :z, :weight) +end \ No newline at end of file diff --git a/lib/objects/game_objects/camera.rb b/lib/objects/entities/camera.rb similarity index 80% rename from lib/objects/game_objects/camera.rb rename to lib/objects/entities/camera.rb index 6040240..e415c5b 100644 --- a/lib/objects/game_objects/camera.rb +++ b/lib/objects/entities/camera.rb @@ -4,10 +4,10 @@ class IMICFPS include OpenGL include GLU - attr_accessor :x,:y,:z, :field_of_view, :pitch, :yaw, :roll, :mouse_sensitivity - attr_reader :game_object, :broken_mouse_centering + attr_accessor :field_of_view, :pitch, :yaw, :roll, :mouse_sensitivity + attr_reader :entity, :position def initialize(x: 0, y: 0, z: 0, fov: 70.0, view_distance: 100.0) - @x,@y,@z = x,y,z + @position = Vector.new(x,y,z) @pitch = 0.0 @yaw = 0.0 @roll = 0.0 @@ -15,7 +15,7 @@ class IMICFPS @view_distance = view_distance @constant_pitch = 20.0 - @game_object = nil + @entity = nil @distance = 4 @origin_distance = @distance @@ -29,6 +29,7 @@ class IMICFPS InputMapper.set(:camera, :descend, [Gosu::KbLeftControl, Gosu::KbRightControl]) InputMapper.set(:camera, :release_mouse, [Gosu::KbLeftAlt, Gosu::KbRightAlt]) InputMapper.set(:camera, :capture_mouse, Gosu::MsLeft) + InputMapper.set(:camera, :turn_180, Gosu::KbX) InputMapper.set(:camera, :increase_mouse_sensitivity, Gosu::KB_NUMPAD_PLUS) InputMapper.set(:camera, :decrease_mouse_sensitivity, Gosu::KB_NUMPAD_MINUS) InputMapper.set(:camera, :reset_mouse_sensitivity, Gosu::KB_NUMPAD_MULTIPLY) @@ -36,13 +37,13 @@ class IMICFPS InputMapper.set(:camera, :decrease_view_distance, Gosu::MsWheelDown) end - def attach_to(game_object) - raise "Not a game object!" unless game_object.is_a?(GameObject) - @game_object = game_object + def attach_to(entity) + raise "Not a game object!" unless entity.is_a?(Entity) + @entity = entity end def detach - @game_object = nil + @entity = nil end def distance_from_object @@ -58,23 +59,23 @@ class IMICFPS end def position_camera - if defined?(@game_object.first_person_view) - if @game_object.first_person_view + if defined?(@entity.first_person_view) + if @entity.first_person_view @distance = 0 else @distance = @origin_distance end end - x_offset = horizontal_distance_from_object * Math.sin(@game_object.y_rotation.degrees_to_radians) - z_offset = horizontal_distance_from_object * Math.cos(@game_object.y_rotation.degrees_to_radians) - # p @game_object.x, @game_object.z;exit - @x = @game_object.x - x_offset - @y = @game_object.y + 2 - @z = @game_object.z - z_offset + x_offset = horizontal_distance_from_object * Math.sin(@entity.rotation.y.degrees_to_radians) + z_offset = horizontal_distance_from_object * Math.cos(@entity.rotation.y.degrees_to_radians) + # p @entity.x, @entity.z;exit + @position.x = @entity.position.x - x_offset + @position.y = @entity.position.y + 2 + @position.z = @entity.position.z - z_offset - # @yaw = 180 - @game_object.y_rotation - @game_object.y_rotation = -@yaw + 180 + # @yaw = 180 - @entity.y_rotation + @entity.rotation.y = -@yaw + 180 end def draw @@ -86,22 +87,22 @@ class IMICFPS glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) glRotatef(@pitch,1,0,0) glRotatef(@yaw,0,1,0) - glTranslatef(-@x, -@y, -@z) + glTranslatef(-@position.x, -@position.y, -@position.z) glMatrixMode(GL_MODELVIEW) # The modelview matrix is where object information is stored. glLoadIdentity - # if $debug && @game_object + # if $debug && @entity # glBegin(GL_LINES) # glColor3f(1,0,0) - # glVertex3f(@x, @y, @z) - # glVertex3f(@game_object.x, @game_object.y, @game_object.z) + # glVector3f(@x, @y, @z) + # glVector3f(@entity.x, @entity.y, @entity.z) # glEnd # end end def update if @mouse_captured - position_camera if @game_object + position_camera if @entity delta = Float(@true_mouse.x-self.mouse_x)/(@mouse_sensitivity*@field_of_view)*70 @yaw -= delta @@ -110,8 +111,8 @@ class IMICFPS @pitch -= Float(@true_mouse.y-self.mouse_y)/(@mouse_sensitivity*@field_of_view)*70 @pitch = @pitch.clamp(-90.0, 90.0) - @game_object.y_rotation += delta if @game_object - free_move unless @game_object + @entity.rotation.y += delta if @entity + free_move unless @entity self.mouse_x = $window.width/2 if self.mouse_x <= 1 || $window.mouse_x >= $window.width-1 self.mouse_y = $window.height/2 if self.mouse_y <= 1 || $window.mouse_y >= $window.height-1 @@ -176,6 +177,9 @@ class IMICFPS # @field_of_view = @field_of_view.clamp(1, 100) @view_distance -= 1 @view_distance = @view_distance.clamp(1, 1000) + elsif InputMapper.is?(:camera, :turn_180, id) + @rotation.y = @rotation.y+180 + @rotation.y %= 360 end end end diff --git a/lib/objects/game_objects/player.rb b/lib/objects/entities/player.rb similarity index 74% rename from lib/objects/game_objects/player.rb rename to lib/objects/entities/player.rb index e322865..07d890c 100644 --- a/lib/objects/game_objects/player.rb +++ b/lib/objects/entities/player.rb @@ -1,7 +1,7 @@ require "etc" class IMICFPS - class Player < GameObject + class Player < Entity attr_accessor :speed attr_reader :name, :bound_model, :first_person_view @@ -17,14 +17,12 @@ class IMICFPS InputMapper.set(:character, :jump, Gosu::KbSpace) InputMapper.set(:character, :sprint, [Gosu::KbLeftControl]) - InputMapper.set(:character, :turn_180, Gosu::KbX) InputMapper.set(:character, :toggle_first_person_view, Gosu::KbF) @speed = 2.5 # meter's per second @running_speed = 6.8 # meter's per second @old_speed = @speed @mass = 72 # kg - @y_velocity = 0 @floor = 0 @first_person_view = true @@ -99,8 +97,6 @@ class IMICFPS end def update - @floor = @terrain.height_at(self, 4.5) - relative_speed = @speed if InputMapper.down?(:character, :sprint) relative_speed = (@running_speed)*(delta_time) @@ -108,73 +104,64 @@ class IMICFPS relative_speed = @speed*(delta_time) end - relative_y_rotation = @y_rotation*-1 + relative_y_rotation = @rotation.y*-1 if InputMapper.down?(:character, :forward) - @z+=Math.cos(relative_y_rotation * Math::PI / 180)*relative_speed - @x-=Math.sin(relative_y_rotation * Math::PI / 180)*relative_speed + @position.z+=Math.cos(relative_y_rotation * Math::PI / 180)*relative_speed + @position.x-=Math.sin(relative_y_rotation * Math::PI / 180)*relative_speed end if InputMapper.down?(:character, :backward) - @z-=Math.cos(relative_y_rotation * Math::PI / 180)*relative_speed - @x+=Math.sin(relative_y_rotation * Math::PI / 180)*relative_speed + @position.z-=Math.cos(relative_y_rotation * Math::PI / 180)*relative_speed + @position.x+=Math.sin(relative_y_rotation * Math::PI / 180)*relative_speed end if InputMapper.down?(:character, :strife_left) - @z+=Math.sin(relative_y_rotation * Math::PI / 180)*relative_speed - @x+=Math.cos(relative_y_rotation * Math::PI / 180)*relative_speed + @position.z+=Math.sin(relative_y_rotation * Math::PI / 180)*relative_speed + @position.x+=Math.cos(relative_y_rotation * Math::PI / 180)*relative_speed end if InputMapper.down?(:character, :strife_right) - @z-=Math.sin(relative_y_rotation * Math::PI / 180)*relative_speed - @x-=Math.cos(relative_y_rotation * Math::PI / 180)*relative_speed + @position.z-=Math.sin(relative_y_rotation * Math::PI / 180)*relative_speed + @position.x-=Math.cos(relative_y_rotation * Math::PI / 180)*relative_speed end if InputMapper.down?(:character, :turn_left) - @y_rotation+=(relative_speed*1000)*delta_time + @rotation.y+=(relative_speed*1000)*delta_time end if InputMapper.down?(:character, :turn_right) - @y_rotation-=(relative_speed*1000)*delta_time + @rotation.y-=(relative_speed*1000)*delta_time end if @_time_in_air air_time = ((Gosu.milliseconds-@_time_in_air)/1000.0) - @y_velocity-=(IMICFPS::GRAVITY*air_time)*delta_time + @velocity.y-=(IMICFPS::GRAVITY*air_time)*delta_time end if InputMapper.down?(:character, :jump) && !@jumping @jumping = true @_time_in_air = Gosu.milliseconds - elsif !@jumping && @y > @floor + elsif !@jumping && @position.y > @floor @falling = true @_time_in_air ||= Gosu.milliseconds # FIXME else if @jumping - if @y <= @floor - @falling = false; @jumping = false; @y_velocity = 0 + if @position.y <= @floor + @falling = false; @jumping = false; @velocity.y = 0; @position.y = @floor end end end if @jumping && !@falling if InputMapper.down?(:character, :jump) - @y_velocity = 1.5 + @velocity.y = 1.5 @falling = true end end - @y+=@y_velocity*delta_time - - @y = @floor if @y < @floor - # distance = 2.0 - # x_offset = distance * Math.cos(@bound_model.y_rotation) - # z_offset = distance * Math.sin(@bound_model.y_rotation) + @position.y+=@velocity.y*delta_time if @position.y >= @floor # TEMP fix to prevent falling forever, collision/physics managers should fix this in time. super end def button_up(id) - if InputMapper.is?(:character, :turn_180, id) - @y_rotation = @y_rotation+180 - @y_rotation %= 360 - - elsif InputMapper.is?(:character, :toggle_first_person_view, id) + if InputMapper.is?(:character, :toggle_first_person_view, id) @first_person_view = !@first_person_view puts "First Person? #{@first_person_view}" end diff --git a/lib/objects/game_objects/skydome.rb b/lib/objects/entities/skydome.rb similarity index 73% rename from lib/objects/game_objects/skydome.rb rename to lib/objects/entities/skydome.rb index 8200105..372b09e 100644 --- a/lib/objects/game_objects/skydome.rb +++ b/lib/objects/entities/skydome.rb @@ -1,5 +1,5 @@ class IMICFPS - class Skydome < GameObject + class Skydome < Entity def setup bind_model("base", "skydome") end @@ -11,8 +11,8 @@ class IMICFPS end def update - @y_rotation+=0.01 - @y_rotation%=360 + @rotation.y += 0.01 + @rotation.y %= 360 super end end diff --git a/lib/objects/entities/terrain.rb b/lib/objects/entities/terrain.rb new file mode 100644 index 0000000..63abbaa --- /dev/null +++ b/lib/objects/entities/terrain.rb @@ -0,0 +1,7 @@ +class IMICFPS + class Terrain < Entity + def setup + bind_model("base", "randomish_terrain") + end + end +end \ No newline at end of file diff --git a/lib/objects/game_objects/test_object.rb b/lib/objects/entities/test_object.rb similarity index 72% rename from lib/objects/game_objects/test_object.rb rename to lib/objects/entities/test_object.rb index 86650f0..7444875 100644 --- a/lib/objects/game_objects/test_object.rb +++ b/lib/objects/entities/test_object.rb @@ -1,5 +1,5 @@ class IMICFPS - class TestObject < GameObject + class TestObject < Entity def setup bind_model("base", "war_factory") end diff --git a/lib/objects/entities/tree.rb b/lib/objects/entities/tree.rb new file mode 100644 index 0000000..2cd3e06 --- /dev/null +++ b/lib/objects/entities/tree.rb @@ -0,0 +1,7 @@ +class IMICFPS + class Tree < Entity + def setup + bind_model("base", "tree") + end + end +end diff --git a/lib/objects/game_object.rb b/lib/objects/entity.rb similarity index 56% rename from lib/objects/game_object.rb rename to lib/objects/entity.rb index 8ed6912..cdbd5a8 100644 --- a/lib/objects/game_object.rb +++ b/lib/objects/entity.rb @@ -2,39 +2,43 @@ class IMICFPS # A game object is any renderable thing - class GameObject + class Entity include OpenGL include GLU include CommonMethods - attr_accessor :x, :y, :z, :scale + attr_accessor :scale attr_accessor :visible, :renderable, :backface_culling - attr_accessor :x_rotation, :y_rotation, :z_rotation - attr_reader :model, :name, :debug_color, :terrain, :width, :height, :depth, :last_x, :last_y, :last_z, :normalized_bounding_box - def initialize(x: 0, y: 0, z: 0, bound_model: nil, scale: MODEL_METER_SCALE, backface_culling: true, auto_manage: true, terrain: nil, game_state: nil) - @x,@y,@z,@scale = x,y,z,scale + attr_reader :position, :rotation, :velocity + attr_reader :model, :name, :debug_color, :width, :height, :depth, :last_x, :last_y, :last_z, :normalized_bounding_box + def initialize(x: 0, y: 0, z: 0, bound_model: nil, scale: MODEL_METER_SCALE, backface_culling: true, auto_manage: true, manifest_file: nil) + @position = Vector.new(x, y, z) + @scale = scale @bound_model = bound_model @backface_culling = backface_culling @visible = true @renderable = true - @x_rotation,@y_rotation,@z_rotation = 0,0,0 - @debug_color = Color.new(0.0, 1.0, 0.0) - @terrain = terrain + @rotation = Vector.new(0, 0, 0) + @velocity = Vector.new(0, 0, 0) - @game_state = game_state + @debug_color = Color.new(0.0, 1.0, 0.0) + + @collidable = [:static, :dynamic] + @collision = :static # :dynamic, moves in response, :static, does not move ever, :none, entities can pass through + @physics = false + @mass = 100 # kg @width, @height, @depth = 0,0,0 @delta_time = Gosu.milliseconds - @last_x, @last_y, @last_z = @x, @y, @z + @last_position = Vector.new(@position.x, @position.y, @position.z) - game_state.add_object(self) if auto_manage && game_state setup if @bound_model - @bound_model.model.game_object = self + @bound_model.model.entity = self @bound_model.model.objects.each {|o| o.scale = self.scale} - @normalized_bounding_box = normalize_bounding_box_with_offset(model.bounding_box) + @normalized_bounding_box = normalize_bounding_box_with_offset - box = normalize_bounding_box(@bound_model.model.bounding_box) + box = normalize_bounding_box @width = box.max_x-box.min_x @height = box.max_y-box.min_y @depth = box.max_z-box.min_z @@ -43,16 +47,20 @@ class IMICFPS return self end + def collidable? + @collidable.include?(@collision) + end + def bind_model(package, name) - model = ModelLoader.new(manifest_file: IMICFPS.assets_path + "/#{package}/#{name}/#{name}.yaml", game_object: @dummy_game_object) + model = ModelLoader.new(manifest_file: IMICFPS.assets_path + "/#{package}/#{name}/#{name}.yaml", entity: @dummy_entity) raise "model isn't a model!" unless model.is_a?(ModelLoader) @bound_model = model - @bound_model.model.game_object = self + @bound_model.model.entity = self @bound_model.model.objects.each {|o| o.scale = self.scale} - @normalized_bounding_box = normalize_bounding_box_with_offset(@bound_model.model.bounding_box) + @normalized_bounding_box = normalize_bounding_box_with_offset - box = normalize_bounding_box(@bound_model.model.bounding_box) + box = normalize_bounding_box @width = box.max_x-box.min_x @height = box.max_y-box.min_y @depth = box.max_z-box.min_z @@ -79,7 +87,7 @@ class IMICFPS @delta_time = Gosu.milliseconds unless at_same_position? - @normalized_bounding_box = normalize_bounding_box_with_offset(@bound_model.model.bounding_box) if model + @normalized_bounding_box = normalize_bounding_box_with_offset if model end @last_x, @last_y, @last_z = @x, @y, @z @@ -90,27 +98,31 @@ class IMICFPS end def at_same_position? - @x == @last_x && - @y == @last_y && - @z == @last_z + @position == @last_position end # Do two Axis Aligned Bounding Boxes intersect? - def intersect(a, b) - a = a.normalized_bounding_box - b = b.normalized_bounding_box + def intersect(other) + me = normalized_bounding_box + other = other.normalized_bounding_box # puts "bounding boxes match!" if a == b - if (a.min_x <= b.max_x && a.max_x >= b.min_x) && - (a.min_y <= b.max_y && a.max_y >= b.min_y) && - (a.min_z <= b.max_z && a.max_z >= b.min_z) + if (me.min_x <= other.max_x && me.max_x >= other.min_x) && + (me.min_y <= other.max_y && me.max_y >= other.min_y) && + (me.min_z <= other.max_z && me.max_z >= other.min_z) return true else return false end end - def normalize_bounding_box(box) + def distance(vertex, other) + return Math.sqrt((vertex.x-other.x)**2 + (vertex.y-other.y)**2 + (vertex.z-other.z)**2) + end + + def normalize_bounding_box + box = @bound_model.model.bounding_box + temp = BoundingBox.new temp.min_x = box.min_x.to_f*scale temp.min_y = box.min_y.to_f*scale @@ -123,15 +135,17 @@ class IMICFPS return temp end - def normalize_bounding_box_with_offset(box) - temp = BoundingBox.new - temp.min_x = box.min_x.to_f*scale+x - temp.min_y = box.min_y.to_f*scale+y - temp.min_z = box.min_z.to_f*scale+z + def normalize_bounding_box_with_offset + box = @bound_model.model.bounding_box - temp.max_x = box.max_x.to_f*scale+x - temp.max_y = box.max_y.to_f*scale+y - temp.max_z = box.max_z.to_f*scale+z + temp = BoundingBox.new + temp.min_x = box.min_x.to_f*scale+@position.x + temp.min_y = box.min_y.to_f*scale+@position.y + temp.min_z = box.min_z.to_f*scale+@position.z + + temp.max_x = box.max_x.to_f*scale+@position.x + temp.max_y = box.max_y.to_f*scale+@position.y + temp.max_z = box.max_z.to_f*scale+@position.z return temp end diff --git a/lib/objects/game_objects/terrain.rb b/lib/objects/game_objects/terrain.rb deleted file mode 100644 index 6b54eef..0000000 --- a/lib/objects/game_objects/terrain.rb +++ /dev/null @@ -1,172 +0,0 @@ -class IMICFPS - class Terrain < GameObject - def setup - bind_model("base", "randomish_terrain") - # bind_model(ModelLoader.new(type: :obj, file_path: "/home/cyberarm/Documents/blends/untitled.obj", game_object: self)) - self.scale = 1 - @nearest_vertex_lookup = {} - - generate_optimized_lists - end - - def generate_optimized_lists - x_slot,y_slot = 0,0 - model.vertices.each do |vert| - x_slot = vert.x.round - y_slot = vert.y.round - - @nearest_vertex_lookup[x_slot] = {} unless @nearest_vertex_lookup[x_slot] - @nearest_vertex_lookup[x_slot][y_slot] = [] unless @nearest_vertex_lookup[x_slot][y_slot] - @nearest_vertex_lookup[x_slot][y_slot] << vert - end - end - - def height_at(vertex, max_distance = Float::INFINITY) - if vert = find_nearest_vertex(vertex, max_distance) - return vert.y - else - -1 - end - end - - def find_nearest_vertex(vertex, max_distance) - nearest = nil - smaller_list = [] - smaller_list << @nearest_vertex_lookup.dig(vertex.x.round-1, vertex.y.round-1) - smaller_list << @nearest_vertex_lookup.dig(vertex.x.round, vertex.y.round) - smaller_list << @nearest_vertex_lookup.dig(vertex.x.round+1, vertex.y.round+1) - smaller_list.flatten! - - smaller_list.each do |vert| - next if vert.nil? - if nearest - if distance(vert, vertex) < distance(vert, nearest) && distance(vert, vertex) <= max_distance - nearest = vert - end - end - - nearest = vert unless nearest && distance(vert, vertex) > max_distance - end - - return nearest - end - - def distance(vertex, other) - return Math.sqrt((vertex.x-other.x)**2 + (vertex.y-other.y)**2 + (vertex.z-other.z)**2) - end - end -end -# class IMICFPS -# class Terrain -# TILE_SIZE = 0.5 -# include OpenGL -# def initialize(size:, height: nil, width: nil, length: nil, heightmap: nil) -# @size = size -# @heightmap = heightmap -# @map = [] - -# @height = height ? height : 1 -# @width = width ? width : @size -# @length = length ? length : @size - -# @vertices = [] -# @normals = [] -# @colors = [] -# generate -# end - -# def generate -# #@width.times do |x| -# # @length.times do |z| -# # # TRIANGLE STRIP (BROKEN) -# # @map << Vertex.new((x+1)-@width.to_f/2, 0, z-@legth.to_f/2) -# # @map << Vertex.new(x-@width.to_f/2, 0, (z+1)-@length.to_f/2) -# # end -# #end -# @width.times do |x| -# @length.times do |z| -# # WORKING TRIANGLES -# @map << Vertex.new(x-@width.to_f/2, @height, z-@length.to_f/2) -# @map << Vertex.new((x+1)-@width.to_f/2, @height, z-@length.to_f/2) -# @map << Vertex.new(x-@width.to_f/2, @height, (z+1)-@length.to_f/2) -# # -# @map << Vertex.new(x-@width.to_f/2, @height, (z+1)-@length.to_f/2) -# @map << Vertex.new((x+1)-@width.to_f/2, @height, z-@length.to_f/2) -# @map << Vertex.new((x+1)-@width.to_f/2, @height, (z+1)-@length.to_f/2) -# end -# end - -# @map.size.times do |i| -# @vertices << @map[i].x -# @vertices << @map[i].y -# @vertices << @map[i].z -# normal = Vertex.new(0,1,0) -# @normals << normal.x -# @normals << normal.y -# @normals << normal.z -# color = Color.new(rand(0.10..0.30),0,0) -# @colors << color.red -# @colors << color.green -# @colors << color.blue -# end - -# @vertices_packed = @vertices.pack("f*") -# @normals_packed = @normals.pack("f*") -# @colors_packed = @colors.pack("f*") -# end - -# def draw -# new_draw -# # old_draw -# end - -# def old_draw -# glEnable(GL_COLOR_MATERIAL) - -# # glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) -# glPointSize(5) -# # glBegin(GL_LINES) -# # glBegin(GL_POINTS) -# glBegin(GL_TRIANGLES) -# @map.each_with_index do |vertex, index| -# glNormal3f(0,1,0) -# glColor3f(0.0, 0.5, 0) if index.even? -# glColor3f(0, 1.0, 0) if index.odd? -# glVertex3f(vertex.x, vertex.y, vertex.z) -# end -# glEnd - -# glDisable(GL_COLOR_MATERIAL) -# glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) -# end - -# def new_draw -# glEnable(GL_NORMALIZE) -# glPushMatrix - -# glEnable(GL_COLOR_MATERIAL) -# glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) -# glShadeModel(GL_FLAT) -# glEnableClientState(GL_VERTEX_ARRAY) -# glEnableClientState(GL_NORMAL_ARRAY) -# glEnableClientState(GL_COLOR_ARRAY) - -# glVertexPointer(3, GL_FLOAT, 0, @vertices_packed) -# glNormalPointer(GL_FLOAT, 0, @normals_packed) -# glColorPointer(3, GL_FLOAT, 0, @colors_packed) - -# glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) -# glDrawArrays(GL_TRIANGLES, 0, @vertices.size/3) -# glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) - -# # glDrawArrays(GL_TRIANGLE_STRIP, 0, @vertices.size/3) -# $window.number_of_faces+=@vertices.size/3 - -# glDisableClientState(GL_VERTEX_ARRAY) -# glDisableClientState(GL_NORMAL_ARRAY) -# glDisableClientState(GL_COLOR_ARRAY) - -# glPopMatrix -# end -# end -# end diff --git a/lib/objects/game_objects/tree.rb b/lib/objects/game_objects/tree.rb deleted file mode 100644 index 2e5ecc1..0000000 --- a/lib/objects/game_objects/tree.rb +++ /dev/null @@ -1,20 +0,0 @@ -class IMICFPS - class Tree < GameObject - def setup - bind_model("base", "tree") - vert = @terrain.find_nearest_vertex(self, 4.5) - if vert - self.x = vert.x - self.y = vert.y - self.z = vert.z - end - - # @y_rotation += rand(1..100) - end - - # def update - # super - # @y_rotation+=0.005 - # end - end -end diff --git a/lib/objects/light.rb b/lib/objects/light.rb index a152336..e926934 100644 --- a/lib/objects/light.rb +++ b/lib/objects/light.rb @@ -4,9 +4,9 @@ class IMICFPS attr_reader :ambient, :diffuse, :specular, :position, :light_id attr_accessor :x, :y, :z, :intensity def initialize(x:,y:,z:, game_state:, - ambient: Vertex.new(0.5, 0.5, 0.5, 1), - diffuse: Vertex.new(1, 0.5, 0, 1), specular: Vertex.new(0.2, 0.2, 0.2, 1), - position: Vertex.new(x, y, z, 0), intensity: 1) + ambient: Vector.new(0.5, 0.5, 0.5, 1), + diffuse: Vector.new(1, 0.5, 0, 1), specular: Vector.new(0.2, 0.2, 0.2, 1), + position: Vector.new(x, y, z, 0), intensity: 1) @x,@y,@z = x,y,z @game_state = game_state @intensity = intensity diff --git a/lib/objects/model_loader.rb b/lib/objects/model_loader.rb index e84fb86..5993767 100644 --- a/lib/objects/model_loader.rb +++ b/lib/objects/model_loader.rb @@ -8,7 +8,7 @@ class IMICFPS attr_reader :model, :name, :debug_color - def initialize(manifest_file:, game_object: nil) + def initialize(manifest_file:, entity: nil) @manifest = YAML.load(File.read(manifest_file)) # pp @manifest @file_path = File.expand_path("./../model/", manifest_file) + "/#{@manifest["model"]}" @@ -23,7 +23,7 @@ class IMICFPS unless load_model_from_cache case @type when :obj - @model = Wavefront::Model.new(file_path: @file_path, game_object: game_object) + @model = Wavefront::Model.new(file_path: @file_path, entity: entity) else raise "Unsupported model type, supported models are: #{@supported_models.join(', ')}" end diff --git a/lib/renderer/bounding_box_renderer.rb b/lib/renderer/bounding_box_renderer.rb index b4008f1..375ffc3 100644 --- a/lib/renderer/bounding_box_renderer.rb +++ b/lib/renderer/bounding_box_renderer.rb @@ -35,7 +35,7 @@ class IMICFPS @bounding_boxes[mesh_object_id] = {} @bounding_boxes[mesh_object_id] = {object: object, box: box, color: color, objects: []} - box = object.normalize_bounding_box(box) + box = object.normalize_bounding_box normals = mesh_normals colors = mesh_colors(color) @@ -50,7 +50,7 @@ class IMICFPS object.model.objects.each do |mesh| data = {} - box = object.normalize_bounding_box(mesh.bounding_box) + box = object.normalize_bounding_box normals = mesh_normals colors = mesh_colors(mesh.debug_color) @@ -217,7 +217,7 @@ class IMICFPS glPopMatrix - found = @game_state.game_objects.detect { |o| o == bounding_box[:object] } + found = @game_state.entities.detect { |o| o == bounding_box[:object] } unless found @vertex_count -= @bounding_boxes[key][:vertices_size] diff --git a/lib/renderer/opengl_renderer.rb b/lib/renderer/opengl_renderer.rb index 834df1b..a1e60ce 100644 --- a/lib/renderer/opengl_renderer.rb +++ b/lib/renderer/opengl_renderer.rb @@ -20,10 +20,10 @@ class IMICFPS glEnable(GL_NORMALIZE) glPushMatrix - glTranslatef(object.x, object.y, object.z) - glRotatef(object.x_rotation,1.0, 0, 0) - glRotatef(object.y_rotation,0, 1.0, 0) - glRotatef(object.z_rotation,0, 0, 1.0) + glTranslatef(object.position.x, object.position.y, object.position.z) + glRotatef(object.rotation.x, 1.0, 0, 0) + glRotatef(object.rotation.y, 0, 1.0, 0) + glRotatef(object.rotation.z, 0, 0, 1.0) handleGlError @@ -48,7 +48,7 @@ class IMICFPS def draw_mesh(model) model.objects.each_with_index do |o, i| - glEnable(GL_CULL_FACE) if model.game_object.backface_culling + glEnable(GL_CULL_FACE) if model.entity.backface_culling glEnable(GL_COLOR_MATERIAL) glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) glShadeModel(GL_FLAT) unless o.faces.first[4] @@ -100,7 +100,7 @@ class IMICFPS # glBindTexture(GL_TEXTURE_2D, 0) glDisable(GL_TEXTURE_2D) end - glDisable(GL_CULL_FACE) if model.game_object.backface_culling + glDisable(GL_CULL_FACE) if model.entity.backface_culling glDisable(GL_COLOR_MATERIAL) end end diff --git a/lib/renderer/renderer.rb b/lib/renderer/renderer.rb index b0157b8..72370a3 100644 --- a/lib/renderer/renderer.rb +++ b/lib/renderer/renderer.rb @@ -13,7 +13,7 @@ class IMICFPS end def draw - @game_state.game_objects.each do |object| + @game_state.entities.each do |object| if object.visible && object.renderable # Render bounding boxes before transformation is applied @bounding_box_renderer.create_bounding_box(object, object.model.bounding_box, object.debug_color, object.object_id) if $debug diff --git a/lib/renderer/shader.rb b/lib/renderer/shader.rb index fccd364..a135361 100644 --- a/lib/renderer/shader.rb +++ b/lib/renderer/shader.rb @@ -54,7 +54,7 @@ class IMICFPS log = ' ' * @error_buffer_size glGetShaderInfoLog(@vertex, @error_buffer_size, nil, log) puts "Shader Error: Program \"#{@name}\"" - puts " Vertex Shader InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n" + puts " Vector Shader InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n" puts " Shader Compiled status: #{compiled}" puts " NOTE: assignment of uniforms in shaders is illegal!" puts diff --git a/lib/states/game_state.rb b/lib/states/game_state.rb index 8e7d2ca..3ff2338 100644 --- a/lib/states/game_state.rb +++ b/lib/states/game_state.rb @@ -1,15 +1,15 @@ class IMICFPS class GameState include CommonMethods - include ObjectManager + include EntityManager include LightManager attr_reader :options def initialize(options = {}) @options = options @delta_time = Gosu.milliseconds - @game_objects = [] - @lights = [] + @entities = [] + @lights = [] setup end diff --git a/lib/states/game_states/game.rb b/lib/states/game_states/game.rb index 9aedc0e..d0aa038 100644 --- a/lib/states/game_states/game.rb +++ b/lib/states/game_states/game.rb @@ -6,17 +6,19 @@ class IMICFPS def setup @collision_manager = CollisionManager.new(game_state: self) @renderer = Renderer.new(game_state: self) - @terrain = Terrain.new(game_state: self)#(size: 170, height: 0) + add_entity(Terrain.new) @draw_skydome = true - @skydome = Skydome.new(scale: 0.08, backface_culling: false, auto_manage: false) + @skydome = Skydome.new(scale: 0.08, backface_culling: false) + add_entity(@skydome) 25.times do - Tree.new(x: rand(@terrain.width)-(@terrain.width/2.0), z: rand(@terrain.depth)-(@terrain.depth/2.0), terrain: @terrain, game_state: self) + add_entity(Tree.new) end - TestObject.new(terrain: @terrain, z: 10, game_state: self, scale: 1.0) + add_entity(TestObject.new(z: 10)) - @player = Player.new(x: 1, y: 0, z: -1, terrain: @terrain, game_state: self) + @player = Player.new(x: 1, y: 0, z: -1) + add_entity(@player) @camera = Camera.new(x: 0, y: -2, z: 1) @camera.attach_to(@player) @@ -86,7 +88,7 @@ class IMICFPS update_text @collision_manager.update - @game_objects.each(&:update) + @entities.each(&:update) @skydome.update if @skydome.renderable @@ -150,8 +152,8 @@ OpenGL Version: #{glGetString(GL_VERSION)} OpenGL Shader Language Version: #{glGetString(GL_SHADING_LANGUAGE_VERSION)} Camera pitch: #{@camera.pitch.round(2)} Yaw: #{@camera.yaw.round(2)} Roll #{@camera.roll.round(2)} -Camera X:#{@camera.x.round(2)} Y:#{@camera.y.round(2)} Z:#{@camera.z.round(2)} -#{if @camera.game_object then "Actor X:#{@camera.game_object.x.round(2)} Y:#{@camera.game_object.y.round(2)} Z:#{@camera.game_object.z.round(2)}";end} +Camera X:#{@camera.position.x.round(2)} Y:#{@camera.position.y.round(2)} Z:#{@camera.position.z.round(2)} +#{if @camera.entity then "Actor X:#{@camera.entity.position.x.round(2)} Y:#{@camera.entity.position.y.round(2)} Z:#{@camera.entity.position.z.round(2)}";end} Field Of View: #{@camera.field_of_view} Mouse Sesitivity: #{@camera.mouse_sensitivity} Last Frame: #{delta_time*1000.0}ms (#{Gosu.fps} fps) @@ -168,7 +170,7 @@ Unable to call glGetString! Camera pitch: #{@camera.pitch.round(2)} Yaw: #{@camera.yaw.round(2)} Roll #{@camera.roll.round(2)} Camera X:#{@camera.x.round(2)} Y:#{@camera.y.round(2)} Z:#{@camera.z.round(2)} -#{if @camera.game_object then "Actor X:#{@camera.game_object.x.round(2)} Y:#{@camera.game_object.y.round(2)} Z:#{@camera.game_object.z.round(2)}";end} +#{if @camera.entity then "Actor X:#{@camera.entity.x.round(2)} Y:#{@camera.entity.y.round(2)} Z:#{@camera.entity.z.round(2)}";end} Field Of View: #{@camera.field_of_view} Mouse Sesitivity: #{@camera.mouse_sensitivity} Last Frame: #{delta_time*1000.0}ms (#{Gosu.fps} fps) @@ -198,8 +200,8 @@ eos end InputMapper.keydown(id) - @game_objects.each do |object| - object.button_down(id) if defined?(object.button_down) + @entities.each do |entity| + entity.button_down(id) if defined?(entity.button_down) end end @@ -214,8 +216,8 @@ eos end InputMapper.keyup(id) - @game_objects.each do |object| - object.button_up(id) if defined?(object.button_up) + @entities.each do |entity| + entity.button_up(id) if defined?(entity.button_up) end @camera.button_up(id) diff --git a/lib/states/game_states/loading_state.rb b/lib/states/game_states/loading_state.rb index 2a20c22..a605685 100644 --- a/lib/states/game_states/loading_state.rb +++ b/lib/states/game_states/loading_state.rb @@ -7,7 +7,7 @@ class IMICFPS @state = Text.new("Preparing...", y: window.height/2-40, size: 40, alignment: :center) @percentage = Text.new("0%", y: window.height - 100 + 25, size: 50, alignment: :center) - @dummy_game_object = nil + @dummy_entity = nil @assets = [] @asset_index = 0 add_asset(:model, "base", "randomish_terrain") @@ -48,7 +48,7 @@ class IMICFPS hash = @assets[@asset_index] case hash[:type] when :model - ModelLoader.new(manifest_file: IMICFPS.assets_path + "/#{hash[:package]}/#{hash[:name]}/#{hash[:name]}.yaml", game_object: @dummy_game_object) + ModelLoader.new(manifest_file: IMICFPS.assets_path + "/#{hash[:package]}/#{hash[:name]}/#{hash[:name]}.yaml", entity: @dummy_entity) # when :shader else warn "Unknown asset: #{hash}" diff --git a/lib/trees/aabb_tree.rb b/lib/trees/aabb_tree.rb new file mode 100644 index 0000000..0f83bf2 --- /dev/null +++ b/lib/trees/aabb_tree.rb @@ -0,0 +1,49 @@ +class IMICFPS + class AABBTree + def initialize + @objects = {} + @root = nil + end + + def add(bounding_box, object) + raise "BoundingBox can't be nil!" unless bounding_box + raise "Object can't be nil!" unless object + + if @root + @root.insert_subtree(bounding_box, object) + else + @root = AABBNode.new(parent: nil, object: object, bounding_box: BoundingBox.new(0,0,0, 0,0,0)) + end + end + + def update(object) + end + + # Returns a list of all collided objects inside Bounding Box + def search(bounding_box) + items = [] + @root.search_subtree(bounding_box) + end + + def remove(object) + end + + class AABBNode + def initialize(parent:, object:, bounding_box:) + @parent = parent + @object = object + @bounding_box = bounding_box + end + + def insert_subtree(bounding_box, object) + p "#{bounding_box} -> #{object.class}" + end + + def remove_subtree(node) + end + + def search_subtree(bounding_box) + end + end + end +end \ No newline at end of file diff --git a/lib/wavefront/model.rb b/lib/wavefront/model.rb index 5b0861d..33caccb 100644 --- a/lib/wavefront/model.rb +++ b/lib/wavefront/model.rb @@ -11,16 +11,16 @@ class IMICFPS include Parser attr_accessor :objects, :materials, :vertices, :texures, :normals, :faces - attr_accessor :x, :y, :z, :scale, :game_object - attr_reader :bounding_box, :model_has_texture, :textured_material + attr_accessor :scale, :entity + attr_reader :position, :bounding_box, :model_has_texture, :textured_material attr_reader :normals_buffer, :uvs_buffer, :vertices_buffer attr_reader :vertices_buffer_data, :uvs_buffer_data, :normals_buffer_data attr_reader :vertex_array_id - def initialize(file_path:, game_object: nil) - @game_object = game_object - update if @game_object + def initialize(file_path:, entity: nil) + @entity = entity + update if @entity @file_path = file_path @file = File.open(file_path, 'r') @material_file = nil @@ -64,7 +64,7 @@ class IMICFPS # Allocate arrays for future use @vertex_array_id = nil buffer = " " * 4 - glGenVertexArrays(1, buffer) + glGenVectorArrays(1, buffer) @vertex_array_id = buffer.unpack('L2').first # Allocate buffers for future use @@ -101,17 +101,17 @@ class IMICFPS end def populate_arrays - glBindVertexArray(@vertex_array_id) + glBindVectorArray(@vertex_array_id) glBindBuffer(GL_ARRAY_BUFFER, @vertices_buffer) - glBindVertexArray(0) + glBindVectorArray(0) glBindBuffer(GL_ARRAY_BUFFER, 0) end def update - @x, @y, @z = @game_object.x, @game_object.y, @game_object.z - @scale = @game_object.scale - # if @scale != @game_object.scale - # puts "oops for #{self}: #{@scale} != #{@game_object.scale}" + @position = @entity.position + @scale = @entity.scale + # if @scale != @entity.scale + # puts "oops for #{self}: #{@scale} != #{@entity.scale}" # self.objects.each(&:reflatten) if self.objects && self.objects.count > 0 # end end diff --git a/lib/wavefront/parser.rb b/lib/wavefront/parser.rb index eae4e67..c2f3386 100644 --- a/lib/wavefront/parser.rb +++ b/lib/wavefront/parser.rb @@ -114,9 +114,9 @@ class IMICFPS @vertex_count+=1 vert = nil if array.size == 5 - vert = Vertex.new(Float(array[1]), Float(array[2]), Float(array[3]), Float(array[4])) + vert = Vector.new(Float(array[1]), Float(array[2]), Float(array[3]), Float(array[4])) elsif array.size == 4 - vert = Vertex.new(Float(array[1]), Float(array[2]), Float(array[3]), 1.0) + vert = Vector.new(Float(array[1]), Float(array[2]), Float(array[3]), 1.0) else raise end @@ -127,9 +127,9 @@ class IMICFPS def add_normal(array) vert = nil if array.size == 5 - vert = Vertex.new(Float(array[1]), Float(array[2]), Float(array[3]), Float(array[4])) + vert = Vector.new(Float(array[1]), Float(array[2]), Float(array[3]), Float(array[4])) elsif array.size == 4 - vert = Vertex.new(Float(array[1]), Float(array[2]), Float(array[3]), 1.0) + vert = Vector.new(Float(array[1]), Float(array[2]), Float(array[3]), 1.0) else raise end @@ -140,9 +140,9 @@ class IMICFPS def add_texture_coordinate(array) texture = nil if array.size == 4 - texture = Vertex.new(Float(array[1]), 1-Float(array[2]), Float(array[3])) + texture = Vector.new(Float(array[1]), 1-Float(array[2]), Float(array[3])) elsif array.size == 3 - texture = Vertex.new(Float(array[1]), 1-Float(array[2]), 1.0) + texture = Vector.new(Float(array[1]), 1-Float(array[2]), 1.0) else raise end