summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorrealtradam <[email protected]>2022-02-22 18:07:06 -0500
committerrealtradam <[email protected]>2022-02-22 18:07:06 -0500
commit72afb175a1d0196588959b3ddea4c8b9e06ccf3e (patch)
treec25063ab98a1892970f1098fa10c908214e36346
parent108b0c6a98ebf691b855978df9286f1e6b07c640 (diff)
downloadarbitrary-polygon-collision-72afb175a1d0196588959b3ddea4c8b9e06ccf3e.tar.gz
arbitrary-polygon-collision-72afb175a1d0196588959b3ddea4c8b9e06ccf3e.zip
ported to mruby-raylib
-rw-r--r--Gemfile11
-rw-r--r--Gemfile.lock16
-rw-r--r--README.mdown3
-rw-r--r--main.rb238
-rw-r--r--run.rb245
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.
+
diff --git a/main.rb b/main.rb
new file mode 100644
index 0000000..5c744f0
--- /dev/null
+++ b/main.rb
@@ -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
diff --git a/run.rb b/run.rb
deleted file mode 100644
index eea11f1..0000000
--- a/run.rb
+++ /dev/null
@@ -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