diff options
| -rw-r--r-- | Gemfile | 11 | ||||
| -rw-r--r-- | Gemfile.lock | 16 | ||||
| -rw-r--r-- | README.mdown | 3 | ||||
| -rw-r--r-- | main.rb | 238 | ||||
| -rw-r--r-- | run.rb | 245 |
5 files changed, 240 insertions, 273 deletions
diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 94ab5da..0000000 --- a/Gemfile +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } - -# gem "rails" - -gem "ruby2d", "~> 0.10.0" - -gem "ruby2d-camera", "~> 1.1" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 8b212cb..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,16 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - ruby2d (0.10.0) - ruby2d-camera (1.1.0) - ruby2d (~> 0.10) - -PLATFORMS - x86_64-linux - -DEPENDENCIES - ruby2d (~> 0.10.0) - ruby2d-camera (~> 1.1) - -BUNDLED WITH - 2.2.22 diff --git a/README.mdown b/README.mdown index 48dabbe..13dc108 100644 --- a/README.mdown +++ b/README.mdown @@ -1,3 +1,4 @@ ### Working implementation of SAT(Seperating Axis Theorem) in ruby -working code, needs to be cleaned up +It works but the math could have some optimizations and cleanup done to it. Recently ported to work with mruby-raylib bindings. + @@ -0,0 +1,238 @@ +GameName = 'Hextest' +Rl.init_window(800, 600, GameName) + +include FECS + +source_shine = Rl::Rectangle.new(7, + 128, + 111, + 128) +source = Rl::Rectangle.new(7, + 0, + 111, + 128) + +offset_x = 100 +offset_y = 100 + +Cmp.new('Position', x: 0, y: 0) +#Cmp.new('Shape', radius: 50, line_thickness: 5, sides: 3) +Cmp.new('Shape', :obj) +Cmp.new('ArraySpot', :x, :y) +Cmp.new('ShapeColor', :color) +Cmp.new('BorderColor', :color) + +ShapeSize = 6.0 # needs to be float + +Sys.new('InitGrid') do + arry = Array.new(5) do |x| + Array.new(5) do |y| + x_thingie = 90 + Ent.new( + Cmp::Position.new( + x: (x * x_thingie) + (y * (x_thingie/2)) + offset_x, + y: (y * 50) + (y * 30) + offset_y, + ), + Cmp::Shape.new(sides: 6), + Cmp::ArraySpot.new(x: x, y: y) + ) + end + end +end + + +#Sys::InitGrid.call + +Sys.new('InitShape') do + xoff = 350 + yoff = 350 + multi = 100 + #shape = Cmp::Shape.new(sides:6) + ShapeSize.to_i.times do |point| + Ent.new( + Cmp::Position.new( + x: Math.sin((point/ShapeSize) * Math::PI * 2) * multi + xoff, + y: Math.cos((point/ShapeSize) * Math::PI * 2) * multi + yoff + ), + Cmp::Shape.new(sides:ShapeSize) + ) + end +end + +#Sys::InitShape.call + + +class Shape + attr_reader :angle, :size, :x, :y, :sides + def initialize(angle: 0, size: 0, x: 0, y: 0, sides: 3) + @sides = sides + @angle = angle + @size = size + @x = x + @y = y + #self.points = Array.new(6) do |point_num| + # { x: Math.sin(((point_num/6.0) * Math::PI * 2) + angle) * size + x, + # y: Math.cos(((point_num/6.0) * Math::PI * 2) + angle) * size + y } + #end + update + end + + def points + @points ||= [] + end + def angle=(angle) + @angle = angle + self.update + end + def size=(size) + @size = size + self.update + end + def x=(x) + @x = x + self.update + end + def y=(y) + @y = y + self.update + end + private + def update + sides.times do |point_num| + points[point_num] ||= Hash.new + points[point_num][:x] = Math.sin(((point_num/sides.to_f) * Math::PI * 2) + angle) * size + x + points[point_num][:y] = Math.cos(((point_num/sides.to_f) * Math::PI * 2) + angle) * size + y + end + [sides - points.length, 0].max.times do + points.pop # strip extra points + end + end +end + +MouseFollow = Cmp::Shape.new(obj: Shape.new(sides: 6, size: 100)) +Target = Cmp::Shape.new(obj: Shape.new(sides: 6, size: 100, x: 300, y: 300)) + +Ent.new( + MouseFollow, + Cmp::ShapeColor.new(color: Rl::Color.dodger_blue), + Cmp::BorderColor.new(color: Rl::Color.dodger_blue) +) + +Ent.new( + Target, + Cmp::ShapeColor.new(color: Rl::Color.medium_orchid), + Cmp::BorderColor.new(color: Rl::Color.medium_orchid) +) + +Sys.new('DrawShape') do + Ent.group(Cmp::Shape, Cmp::ShapeColor, Cmp::BorderColor) do |shape_cmp, color_cmp, border_color_cmp, entity| + shape = shape_cmp.obj + array_spot = false + #if !entity.components[Cmp::ArraySpot].nil? + # array_spot = entity.component[Cmp::ArraySpot] + #end + Rl.draw_poly(center: Rl::Vector2.new(shape.x, shape.y), + radius: shape.size, + sides: shape.sides, + color: color_cmp.color) + Rl.draw_poly_lines(center: Rl::Vector2.new(shape.x, shape.y), + radius: shape.size, + sides: shape.sides, + color: border_color_cmp.color, + line_thickness: 7) + if array_spot + "x: #{array_spot.x}".draw(x: position.x - 30, y: position.y - 20, color: Rl::Color.dark_violet) + "y: #{array_spot.y}".draw(x: position.x - 30, y: position.y, color: Rl::Color.dark_violet) + end + end +end + +module SAT + class << self + # The hitbox logic + def hitbox_check(shape_a, shape_b) + # Get normals of both shapes + inverted = build_inverted_edges(shape_a) + inverted.concat(build_inverted_edges(shape_b)) + + #DEBUG + #debug_outer_loop(inverted) + + inverted.each_with_index do |line, line_index| + # Determine max and min of a and b shapes + amax, amin = calculate_minmax(shape_a, line) + bmax, bmin = calculate_minmax(shape_b, line) + + #DEBUG + #debug_inner_loop(shape_a, shape_b, line_index, amax, amin, bmax, bmin) + + if ((amin <= bmax) && (amin >= bmin)) || ((bmin <= amax) && (bmin >= amin)) + #next + else + # The logic should end the calculations early once it detects lack of collision + # however for debug purposes this is disabled + return false + end + end + true + end + + # Creates edges out using coordinates and then gets the normal + def build_inverted_edges(shape) + edges = [] + shape.each_with_index do |vertex_start, index| + vertex_end = if index == shape.length - 1 + shape[0] + else + shape[index + 1] + end + edges.push [vertex_end[1] - vertex_start[1], + -(vertex_end[0] - vertex_start[0])] + end + edges + end + + # Dot product + def vecDotProd(a, b) + (a[0] * b[0]) + (a[1] * b[1]) + end + + # Calculates the minimum point and maximum point projected onto the line + def calculate_minmax(shape, line) + min = vecDotProd(shape.first, line) + max = vecDotProd(shape.first, line) + shape.each_with_index do |vertex, _vertex_index| + dot = vecDotProd(vertex, line) + if dot > max + max = dot + elsif dot < min + min = dot + end + end + [max, min] + end + end +end + +Rl.target_fps = 60 +Rl.while_window_open do + Rl.draw(clear_color: Rl::Color.black) do + MouseFollow.obj.x = Rl.mouse_x + MouseFollow.obj.y = Rl.mouse_y + if SAT.hitbox_check( + Array.new(MouseFollow.obj.sides) do |side| + [MouseFollow.obj.points[side][:x], + MouseFollow.obj.points[side][:y]] + end, + Array.new(Target.obj.sides) do |side| + [Target.obj.points[side][:x], + Target.obj.points[side][:y]] + end + ) + MouseFollow.entity.component[Cmp::ShapeColor].color = Rl::Color.fire_brick + else + MouseFollow.entity.component[Cmp::ShapeColor].color = Rl::Color.lime_green + end + Sys::DrawShape.call + end +end @@ -1,245 +0,0 @@ -require 'ruby2d' -require 'ruby2d/camera' - -# SOURCES FOR MATH USED -# here are the websites I tried to look at, understand, and implement the math from(unsuccessfully) -# -# Currently Main Source: -# http://programmerart.weebly.com/separating-axis-theorem.html -# -# Other sources read through: -# https://dyn4j.org/2010/01/sat/ -# https://www.sevenson.com.au/programming/sat/ -# -# more stuff near the bottom of this link: -# https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection - -#DEBUG: used to colour all debug lines unique colours -$colors = %w[blue teal green lime - yellow orange red fuchsia] - -set width: 800 -set height: 800 - -#DEBUG: used to draw, store and update debug lines -class DebugLines - class << self - def [](index) - data[index] ||= Camera::Line.new(color: 'red', z: 99, width: 5) - end - - def data - @data ||= [] - end - end -end - -# The coordinates of the 2 shapes being tested for collision -svA = { x1: 230.0, y1: 100.0, - x2: 200.0, y2: 250.0, - x3: 50.0, y3: 200.0, - x4: 100.0, y4: 100.0 } - -svB = { x1: 275.0 - 275.0, y1: 175.0 - 175.0, - x2: 375.0 - 275.0, y2: 225.0 - 175.0, - x3: 300.0 - 275.0, y3: 350.0 - 175.0, - x4: 250.0 - 275.0, y4: 250.0 - 175.0 } - -# The 2 shapes being tested for collision - -sA = Camera::Quad.new( - **svA, - color: 'olive', - z: 10 -) - -sB = Camera::Quad.new( - **svB, - color: 'aqua', - z: 10 -) - -# The hitbox logic -def hitbox_check(shape_a, shape_b) - # Get normals of both shapes - inverted = build_inverted_edges(shape_a) - inverted.concat(build_inverted_edges(shape_b)) - - #DEBUG - #debug_outer_loop(inverted) - - inverted.each_with_index do |line, line_index| - # Determine max and min of a and b shapes - amax, amin = calculate_minmax(shape_a, line) - bmax, bmin = calculate_minmax(shape_b, line) - - #DEBUG - #debug_inner_loop(shape_a, shape_b, line_index, amax, amin, bmax, bmin) - - if ((amin <= bmax) && (amin >= bmin)) || ((bmin <= amax) && (bmin >= amin)) - #next - else - # The logic should end the calculations early once it detects lack of collision - # however for debug purposes this is disabled - return false - end - end - true -end - -# Creates edges out using coordinates and then gets the normal -def build_inverted_edges(shape) - edges = [] - shape.each_with_index do |vertex_start, index| - vertex_end = if index == shape.length - 1 - shape[0] - else - shape[index + 1] - end - edges.push [vertex_end[1] - vertex_start[1], - -(vertex_end[0] - vertex_start[0])] - end - edges -end - -# Dot product -def vecDotProd(a, b) - (a[0] * b[0]) + (a[1] * b[1]) -end - -# Calculates the minimum point and maximum point projected onto the line -def calculate_minmax(shape, line) - min = vecDotProd(shape.first, line) - max = vecDotProd(shape.first, line) - shape.each_with_index do |vertex, _vertex_index| - dot = vecDotProd(vertex, line) - if dot > max - max = dot - elsif dot < min - min = dot - end - end - [max, min] -end - -# Displays debug info(used inside the inverted.each loop) -def debug_inner_loop(shape_a, shape_b, line_index, amax, amin, bmax, bmin) - #DEBUG: display the lines(uninverted), transluscent if they are not "seperating" - # If all lines are transluscent then the logic believes the - # shapes are colliding - if line_index < shape_a.length - DebugLines[line_index].x1 = shape_a[line_index][0] - DebugLines[line_index].y1 = shape_a[line_index][1] - if shape_a[line_index + 1].nil? - DebugLines[line_index].x2 = shape_a[0][0] - DebugLines[line_index].y2 = shape_a[0][1] - else - DebugLines[line_index].x2 = shape_a[line_index + 1][0] - DebugLines[line_index].y2 = shape_a[line_index + 1][1] - end - else - DebugLines[line_index].x1 = shape_b[line_index - shape_a.length][0] - DebugLines[line_index].y1 = shape_b[line_index - shape_a.length][1] - if shape_a[line_index - shape_a.length + 1].nil? - DebugLines[line_index].x2 = shape_b[0][0] - DebugLines[line_index].y2 = shape_b[0][1] - else - DebugLines[line_index].x2 = shape_b[line_index - shape_a.length + 1][0] - DebugLines[line_index].y2 = shape_b[line_index - shape_a.length + 1][1] - end - end - DebugLines[line_index].color = $colors[line_index % $colors.length] - - #DEBUG: print out line information - if $i == 0 - puts - puts $colors[line_index % $colors.length] - puts line_index - puts "x1 #{DebugLines[line_index].x1}" - puts "y1 #{DebugLines[line_index].y1}" - puts "x2 #{DebugLines[line_index].x2}" - puts "y2 #{DebugLines[line_index].y2}" - puts "(((#{amin} < #{bmax}) && (#{amin} > #{bmin})) || ((#{bmin} < #{amax}) && (#{bmin} > #{amin})))" - end - DebugLines[line_index].color.a = if ((amin <= bmax) && (amin >= bmin)) || ((bmin <= amax) && (bmin >= amin)) - 0.2 - else - 1.0 - end - - #DEBUG: make the debug lines effectively infinitly long - tempx1 = DebugLines[line_index].x1 - tempx2 = DebugLines[line_index].x2 - tempy1 = DebugLines[line_index].y1 - tempy2 = DebugLines[line_index].y2 - DebugLines[line_index].x1 = (tempx1 * (1 + 1000) / 2) + (tempx2 * (1 - 1000) / 2) - DebugLines[line_index].y1 = (tempy1 * (1 + 1000) / 2) + (tempy2 * (1 - 1000) / 2) - DebugLines[line_index].x2 = (tempx2 * (1 + 1000) / 2) + (tempx1 * (1 - 1000) / 2) - DebugLines[line_index].y2 = (tempy2 * (1 + 1000) / 2) + (tempy1 * (1 - 1000) / 2) -end - -# Displays debug info(used outside the inverted.each loop) -def debug_outer_loop(inverted) - if $i == 0 - puts - puts 'debug of inverted edges:' - pp inverted - end -end - -# Move camera -on :key_held do |event| - Camera.y -= 5 if event.key == 'w' - Camera.y += 5 if event.key == 's' - Camera.x -= 5 if event.key == 'a' - Camera.x += 5 if event.key == 'd' -end - -# Initialize frame counter -# resets to 0 periodically by a set amount -$i = 0 - -# "Game" loop -update do - # Advance frame - $i += 1 - - # Reset every 5 frames - $i %= 5 - - # Update shape 1 position to mouse - sB.x = Camera.coordinate_to_worldspace(get(:mouse_x), 0)[0] - 25 - sB.y = Camera.coordinate_to_worldspace(0, get(:mouse_y))[1] - 75 - - # Check hitboxes - a = hitbox_check( - [[sA.x1, sA.y1], - [sA.x2, sA.y2], - [sA.x3, sA.y3], - [sA.x4, sA.y4]], - [[sB.x1 + sB.x, sB.y1 + sB.y], - [sB.x2 + sB.x, sB.y2 + sB.y], - [sB.x3 + sB.x, sB.y3 + sB.y], - [sB.x4 + sB.x, sB.y4 + sB.y]] - ) - - if a - sB.color = 'red' - else - sB.color = 'aqua' - end - - #DEBUG - if $i == 0 - pp [[sA.x1, sA.y1], - [sA.x2, sA.y2], - [sA.x3, sA.y3], - [sA.x4, sA.y4]] - pp [[sB.x1 + sB.x, sB.y1 + sB.y], - [sB.x2 + sB.x, sB.y2 + sB.y], - [sB.x3 + sB.x, sB.y3 + sB.y], - [sB.x4 + sB.x, sB.y4 + sB.y]] - end -end - -show |
