From eabad4abd4ddbd39612ce24ba63ae27d95677d6d Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Sat, 3 May 2025 18:21:11 -0500 Subject: [PATCH] Refactored mesh handling, imported AABB tree implementation from I-MIC FPS --- Gemfile.lock | 24 ++++ assets/shaders/fragment/lighting.glsl | 124 +++++++++++------ assets/shaders/vertex/g_buffer.glsl | 2 +- lib/cyberarm_engine.rb | 8 -- lib/cyberarm_engine/model.rb | 11 +- .../model/{model_object.rb => mesh.rb} | 2 +- lib/cyberarm_engine/model/parser.rb | 4 +- lib/cyberarm_engine/opengl.rb | 15 ++- lib/cyberarm_engine/opengl/light.rb | 2 + .../opengl/perspective_camera.rb | 3 + .../opengl/renderer/g_buffer.rb | 2 + .../opengl/renderer/opengl_renderer.rb | 3 + lib/cyberarm_engine/opengl/shader.rb | 2 + lib/cyberarm_engine/trees/aabb_node.rb | 126 ++++++++++++++++++ lib/cyberarm_engine/trees/aabb_tree.rb | 55 ++++++++ lib/cyberarm_engine/trees/aabb_tree_debug.rb | 29 ++++ 16 files changed, 356 insertions(+), 56 deletions(-) create mode 100644 Gemfile.lock rename lib/cyberarm_engine/model/{model_object.rb => mesh.rb} (99%) create mode 100644 lib/cyberarm_engine/trees/aabb_node.rb create mode 100644 lib/cyberarm_engine/trees/aabb_tree.rb create mode 100644 lib/cyberarm_engine/trees/aabb_tree_debug.rb diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..c4d90a2 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,24 @@ +PATH + remote: . + specs: + cyberarm_engine (0.24.4) + gosu (~> 1.1) + +GEM + remote: https://rubygems.org/ + specs: + gosu (1.4.6) + minitest (5.25.5) + rake (13.2.1) + +PLATFORMS + x64-mingw-ucrt + +DEPENDENCIES + bundler (~> 2.2) + cyberarm_engine! + minitest (~> 5.0) + rake (~> 13.0) + +BUNDLED WITH + 2.5.3 diff --git a/assets/shaders/fragment/lighting.glsl b/assets/shaders/fragment/lighting.glsl index 7ecb3b2..755fd1c 100644 --- a/assets/shaders/fragment/lighting.glsl +++ b/assets/shaders/fragment/lighting.glsl @@ -1,14 +1,15 @@ #version 330 core +@include "light_struct" + out vec4 frag_color; -@include "light_struct" const int DIRECTIONAL = 0; const int POINT = 1; const int SPOT = 2; -flat in Light out_lights[7]; in vec2 out_tex_coords; flat in int out_light_count; +flat in Light out_lights[7]; uniform sampler2D diffuse, position, texcoord, normal, depth; @@ -27,43 +28,88 @@ vec4 directionalLight(Light light) { return vec4(_diffuse + _ambient + _specular, 1.0); } -vec4 pointLight(Light light) { - return vec4(0.25, 0.25, 0.25, 1); -} - -vec4 spotLight(Light light) { - return vec4(0.5, 0.5, 0.5, 1); -} - -vec4 calculateLighting(Light light) { - vec4 result; - - // switch(light.type) { - // case DIRECTIONAL: { - // result = directionalLight(light); - // } - // case SPOT: { - // result = spotLight(light); - // } - // default: { - // result = pointLight(light); - // } - // } - - if (light.type == DIRECTIONAL) { - result = directionalLight(light); - } else { - result = pointLight(light); - } - - return result; -} - void main() { - frag_color = vec4(0.0); + Light light; + light.type = DIRECTIONAL; - for(int i = 0; i < out_light_count; i++) - { - frag_color += texture(diffuse, out_tex_coords) * calculateLighting(out_lights[i]); - } + light.position = vec3(100, 100, 100); + + light.diffuse = vec3(0.5, 0.5, 0.5); + light.ambient = vec3(0.8, 0.8, 0.8); + light.specular = vec3(0.2, 0.2, 0.2); + + light.intensity = 1.0; + + frag_color = texture(diffuse, out_tex_coords) * directionalLight(light); } + +// #version 330 core +// @include "light_struct" + +// out vec4 frag_color; + +// const int DIRECTIONAL = 0; +// const int POINT = 1; +// const int SPOT = 2; + +// in vec2 out_tex_coords; +// flat in int out_light_count; +// flat in Light out_lights[7]; + +// uniform sampler2D diffuse, position, texcoord, normal, depth; + +// vec4 directionalLight(Light light) { +// vec3 norm = normalize(texture(normal, out_tex_coords).rgb); +// vec3 diffuse_color = texture(diffuse, out_tex_coords).rgb; +// vec3 frag_pos = texture(position, out_tex_coords).rgb; + +// vec3 lightDir = normalize(light.position - frag_pos); +// float diff = max(dot(norm, lightDir), 0); + +// vec3 _ambient = light.ambient; +// vec3 _diffuse = light.diffuse * diff; +// vec3 _specular = light.specular; + +// return vec4(_diffuse + _ambient + _specular, 1.0); +// } + +// vec4 pointLight(Light light) { +// return vec4(0.25, 0.25, 0.25, 1); +// } + +// vec4 spotLight(Light light) { +// return vec4(0.5, 0.5, 0.5, 1); +// } + +// vec4 calculateLighting(Light light) { +// vec4 result; + +// // switch(light.type) { +// // case DIRECTIONAL: { +// // result = directionalLight(light); +// // } +// // case SPOT: { +// // result = spotLight(light); +// // } +// // default: { +// // result = pointLight(light); +// // } +// // } + +// if (light.type == DIRECTIONAL) { +// result = directionalLight(light); +// } else { +// result = pointLight(light); +// } + +// return result; +// } + +// void main() { +// frag_color = vec4(0.0); + +// for(int i = 0; i < out_light_count; i++) +// { +// frag_color += texture(diffuse, out_tex_coords) * calculateLighting(out_lights[i]); +// } +// } diff --git a/assets/shaders/vertex/g_buffer.glsl b/assets/shaders/vertex/g_buffer.glsl index 5ea8709..42b0e6e 100644 --- a/assets/shaders/vertex/g_buffer.glsl +++ b/assets/shaders/vertex/g_buffer.glsl @@ -1,4 +1,4 @@ -# version 330 core +#version 330 core layout(location = 0) in vec3 in_position; layout(location = 1) in vec3 in_color; diff --git a/lib/cyberarm_engine.rb b/lib/cyberarm_engine.rb index b5e63d6..7562651 100644 --- a/lib/cyberarm_engine.rb +++ b/lib/cyberarm_engine.rb @@ -67,12 +67,4 @@ require_relative "cyberarm_engine/ui/elements/menu_item" require_relative "cyberarm_engine/game_state" require_relative "cyberarm_engine/ui/gui_state" -require_relative "cyberarm_engine/model" -require_relative "cyberarm_engine/model_cache" -require_relative "cyberarm_engine/model/material" -require_relative "cyberarm_engine/model/model_object" -require_relative "cyberarm_engine/model/parser" -require_relative "cyberarm_engine/model/parsers/wavefront_parser" -require_relative "cyberarm_engine/model/parsers/collada_parser" if RUBY_ENGINE != "mruby" && defined?(Nokogiri) - require_relative "cyberarm_engine/builtin/intro_state" diff --git a/lib/cyberarm_engine/model.rb b/lib/cyberarm_engine/model.rb index 0399f0b..d1df187 100644 --- a/lib/cyberarm_engine/model.rb +++ b/lib/cyberarm_engine/model.rb @@ -1,5 +1,8 @@ module CyberarmEngine class Model + include OpenGL + include CyberarmEngine + attr_accessor :objects, :materials, :vertices, :uvs, :texures, :normals, :faces, :colors, :bones, :material_file, :current_material, :current_object, :vertex_count, :smoothing attr_reader :position, :bounding_box, :textured_material, :file_path, :positions_buffer_id, :colors_buffer_id, @@ -49,9 +52,9 @@ module CyberarmEngine @objects.each { |o| @vertex_count += o.vertices.size } - # start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - # build_collision_tree - # puts " Building mesh collision tree took #{((Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_time) / 1000.0).round(2)} seconds" + start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) + build_collision_tree + puts " Building mesh collision tree took #{((Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_time) / 1000.0).round(2)} seconds" end def parse(parser) @@ -178,7 +181,7 @@ module CyberarmEngine end def build_collision_tree - @aabb_tree = IMICFPS::AABBTree.new + @aabb_tree = AABBTree.new @faces.each do |face| box = BoundingBox.new diff --git a/lib/cyberarm_engine/model/model_object.rb b/lib/cyberarm_engine/model/mesh.rb similarity index 99% rename from lib/cyberarm_engine/model/model_object.rb rename to lib/cyberarm_engine/model/mesh.rb index 3fca694..0d9a60e 100644 --- a/lib/cyberarm_engine/model/model_object.rb +++ b/lib/cyberarm_engine/model/mesh.rb @@ -1,6 +1,6 @@ module CyberarmEngine class Model - class ModelObject + class Mesh attr_reader :id, :name, :vertices, :uvs, :normals, :materials, :bounding_box, :debug_color attr_accessor :faces, :scale diff --git a/lib/cyberarm_engine/model/parser.rb b/lib/cyberarm_engine/model/parser.rb index 3aa803c..0327319 100644 --- a/lib/cyberarm_engine/model/parser.rb +++ b/lib/cyberarm_engine/model/parser.rb @@ -48,12 +48,12 @@ module CyberarmEngine if _model @model.current_object = _model else - raise "Couldn't find ModelObject!" + raise "Couldn't find Mesh!" end end def change_object(id, name) - @model.objects << Model::ModelObject.new(id, name) + @model.objects << Model::Mesh.new(id, name) @model.current_object = @model.objects.last end diff --git a/lib/cyberarm_engine/opengl.rb b/lib/cyberarm_engine/opengl.rb index 00f11a4..33b9501 100644 --- a/lib/cyberarm_engine/opengl.rb +++ b/lib/cyberarm_engine/opengl.rb @@ -1,5 +1,6 @@ begin require "opengl" + require "glu" rescue LoadError puts "Required gem is not installed, please install 'opengl-bindings' and try again." exit(1) @@ -8,7 +9,7 @@ end module CyberarmEngine def gl_error? e = glGetError - if e != GL_NO_ERROR + if e != OpenGL::GL_NO_ERROR warn "OpenGL error detected by handler at: #{caller[0]}" warn " #{gluErrorString(e)} (#{e})\n" exit if Window.instance&.exit_on_opengl_error? @@ -38,3 +39,15 @@ require_relative "opengl/renderer/g_buffer" require_relative "opengl/renderer/bounding_box_renderer" require_relative "opengl/renderer/opengl_renderer" require_relative "opengl/renderer/renderer" + +require_relative "trees/aabb_tree_debug" +require_relative "trees/aabb_node" +require_relative "trees/aabb_tree" + +require_relative "model" +require_relative "model_cache" +require_relative "model/material" +require_relative "model/mesh" +require_relative "model/parser" +require_relative "model/parsers/wavefront_parser" +require_relative "model/parsers/collada_parser" if RUBY_ENGINE != "mruby" && defined?(Nokogiri) diff --git a/lib/cyberarm_engine/opengl/light.rb b/lib/cyberarm_engine/opengl/light.rb index cbeea7e..06af199 100644 --- a/lib/cyberarm_engine/opengl/light.rb +++ b/lib/cyberarm_engine/opengl/light.rb @@ -1,5 +1,7 @@ module CyberarmEngine class Light + include OpenGL + DIRECTIONAL = 0 POINT = 1 SPOT = 2 diff --git a/lib/cyberarm_engine/opengl/perspective_camera.rb b/lib/cyberarm_engine/opengl/perspective_camera.rb index f4f571f..48c355a 100644 --- a/lib/cyberarm_engine/opengl/perspective_camera.rb +++ b/lib/cyberarm_engine/opengl/perspective_camera.rb @@ -1,5 +1,8 @@ module CyberarmEngine class PerspectiveCamera + include OpenGL + include GLU + attr_accessor :position, :orientation, :aspect_ratio, :field_of_view, :min_view_distance, :max_view_distance diff --git a/lib/cyberarm_engine/opengl/renderer/g_buffer.rb b/lib/cyberarm_engine/opengl/renderer/g_buffer.rb index e9c0eb2..526a164 100644 --- a/lib/cyberarm_engine/opengl/renderer/g_buffer.rb +++ b/lib/cyberarm_engine/opengl/renderer/g_buffer.rb @@ -1,5 +1,7 @@ module CyberarmEngine class GBuffer + include OpenGL + attr_reader :screen_vbo, :vertices, :uvs attr_reader :width, :height diff --git a/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb b/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb index 8ef223d..8777c84 100644 --- a/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb +++ b/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb @@ -1,5 +1,8 @@ module CyberarmEngine class OpenGLRenderer + include OpenGL + include CyberarmEngine + @@immediate_mode_warning = false attr_accessor :show_wireframe diff --git a/lib/cyberarm_engine/opengl/shader.rb b/lib/cyberarm_engine/opengl/shader.rb index 6f2b1bd..f667ae4 100644 --- a/lib/cyberarm_engine/opengl/shader.rb +++ b/lib/cyberarm_engine/opengl/shader.rb @@ -2,6 +2,7 @@ module CyberarmEngine # Ref: https://github.com/vaiorabbit/ruby-opengl/blob/master/sample/OrangeBook/brick.rb class Shader include OpenGL + @@shaders = {} # Cache for {Shader} instances PREPROCESSOR_CHARACTER = "@".freeze # magic character for preprocessor phase of {Shader} compilation @@ -298,6 +299,7 @@ module CyberarmEngine # @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 Shader.active_shader = self diff --git a/lib/cyberarm_engine/trees/aabb_node.rb b/lib/cyberarm_engine/trees/aabb_node.rb new file mode 100644 index 0000000..1500a48 --- /dev/null +++ b/lib/cyberarm_engine/trees/aabb_node.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +module CyberarmEngine + class AABBTree + class AABBNode + attr_accessor :bounding_box, :parent, :object + attr_reader :a, :b + + def initialize(parent:, object:, bounding_box:) + @parent = parent + @object = object + @bounding_box = bounding_box + + @a = nil + @b = nil + end + + def a=(leaf) + @a = leaf + @a.parent = self + end + + def b=(leaf) + @b = leaf + @b.parent = self + end + + def leaf? + @object + end + + def insert_subtree(leaf) + if leaf? + new_node = AABBNode.new(parent: nil, object: nil, bounding_box: @bounding_box.union(leaf.bounding_box)) + + new_node.a = self + new_node.b = leaf + + new_node + else + cost_a = @a.bounding_box.volume + @b.bounding_box.union(leaf.bounding_box).volume + cost_b = @b.bounding_box.volume + @a.bounding_box.union(leaf.bounding_box).volume + + if cost_a == cost_b + cost_a = @a.proximity(leaf) + cost_b = @b.proximity(leaf) + end + + if cost_b < cost_a + self.b = @b.insert_subtree(leaf) + else + self.a = @a.insert_subtree(leaf) + end + + @bounding_box = @bounding_box.union(leaf.bounding_box) + + self + end + end + + def search_subtree(collider, items = []) + if @bounding_box.intersect?(collider) + if leaf? + items << self + else + @a.search_subtree(collider, items) + @b.search_subtree(collider, items) + end + end + + items + end + + def remove_subtree(leaf) + if leaf + self + elsif leaf.parent == self + other_child = other(leaf) + other_child.parent = @parent + other_child + else + leaf.parent.disown_child(leaf) + self + end + end + + def other(leaf) + @a == leaf ? @b : @a + end + + def disown_child(leaf) + value = other(leaf) + raise "Can not replace child of a leaf!" if @parent.leaf? + raise "Node is not a child of parent!" unless leaf.child_of?(@parent) + + if @parent.a == self + @parent.a = value + else + @parent.b = value + end + + @parent.update_bounding_box + end + + def child_of?(leaf) + self == leaf.a || self == leaf.b + end + + def proximity(leaf) + (@bounding_box - leaf.bounding_box).sum.abs + end + + def update_bounding_box + node = self + + unless node.leaf? + node.bounding_box = node.a.bounding_box.union(node.b.bounding_box) + + while (node = node.parent) + node.bounding_box = node.a.bounding_box.union(node.b.bounding_box) + end + end + end + end + end +end diff --git a/lib/cyberarm_engine/trees/aabb_tree.rb b/lib/cyberarm_engine/trees/aabb_tree.rb new file mode 100644 index 0000000..ddea8f6 --- /dev/null +++ b/lib/cyberarm_engine/trees/aabb_tree.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module CyberarmEngine + class AABBTree + include AABBTreeDebug + + attr_reader :root, :objects, :branches, :leaves + + def initialize + @objects = {} + @root = nil + @branches = 0 + @leaves = 0 + end + + def insert(object, bounding_box) + raise "BoundingBox can't be nil!" unless bounding_box + raise "Object can't be nil!" unless object + # raise "Object already in tree!" if @objects[object] # FIXME + + leaf = AABBNode.new(parent: nil, object: object, bounding_box: bounding_box.dup) + @objects[object] = leaf + + insert_leaf(leaf) + end + + def insert_leaf(leaf) + @root = @root ? @root.insert_subtree(leaf) : leaf + end + + def update(object, bounding_box) + leaf = remove(object) + leaf.bounding_box = bounding_box + insert_leaf(leaf) + end + + # Returns a list of all objects that collided with collider + def search(collider, return_nodes = false) + items = [] + if @root + items = @root.search_subtree(collider) + items.map!(&:object) unless return_nodes + end + + items + end + + def remove(object) + leaf = @objects.delete(object) + @root = @root.remove_subtree(leaf) if leaf + + leaf + end + end +end diff --git a/lib/cyberarm_engine/trees/aabb_tree_debug.rb b/lib/cyberarm_engine/trees/aabb_tree_debug.rb new file mode 100644 index 0000000..9f22d77 --- /dev/null +++ b/lib/cyberarm_engine/trees/aabb_tree_debug.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module CyberarmEngine + # Gets included into AABBTree + module AABBTreeDebug + def inspect + @branches = 0 + @leaves = 0 + if @root + node = @root + + debug_search(node.a) + debug_search(node.b) + end + + puts "<#{self.class}:#{object_id}> has #{@branches} branches and #{@leaves} leaves" + end + + def debug_search(node) + if node.leaf? + @leaves += 1 + else + @branches += 1 + debug_search(node.a) + debug_search(node.b) + end + end + end +end