Skip to content

Axis Aligned Bounding Box

Martin Prout edited this page Aug 15, 2015 · 7 revisions

Currently this a convenience class and there may be no simple way of creating a jruby extension without giving up the keyword arguments (availability of required keyword args is what attracted me to create class in first place). There are two ways to create an instance of AABB:-

  1. AABB.new(center: vec1, extent: vec2) where vec1 and vec2 are 2D vectors ( use Vec2D for sanity, ducks allowed but may be incompetent )

  2. AABB.from_min_max(min: vec1, max: vec2) where vec1 and vec2 are 2D vectors ( use Vec2D for sanity, note ducks must support +, - and * operations )

# Axis aligned bounding box
class AABB
  attr_reader :center, :extent

  def initialize(center:, extent:)
    @center = center
    @extent = extent
  end

  def self.from_min_max(min:, max:)
    new(center: (min + max) * 0.5, extent: max - min)
  end

  def position(vec)
    return @center = vec unless block_given?
    @center = vec if yield
  end

  def scale(d)
    @extent *= d
  end

  def contains?(vec)
    rad = extent * 0.5
    return false unless (center.x - rad.x..center.x + rad.x).cover? vec.x
    (center.y - rad.y..center.y + rad.y).cover? vec.y
  end
end

Here is some example usage:-

# Click on the box and drag it across the screen.

attr_reader :block, :block_locked, :over_block, :renderer, :bounds
BLOCK_WIDTH = 150

def setup
  sketch_title 'AABB Example'
  @block = Block.new(
    center: Vec2D.new(width / 2, height / 2),
    size: Vec2D.new(BLOCK_WIDTH, BLOCK_WIDTH))
  @locked = false
  @over_block = false
  @bounds = AABB.new(
    center: Vec2D.new(width / 2, height / 2),
    extent: Vec2D.new(width - BLOCK_WIDTH, height - BLOCK_WIDTH))
  @renderer = AppRender.new(self)
end

def draw
  background 0
  fill 153
  if block.over?(Vec2D.new(mouse_x, mouse_y))
    @over_block = true
    stroke 255
    fill 255 if block_locked?
  else
    @over_block = false
    stroke 153
  end
  # Draw the box as a shape
  begin_shape
  block.points_array.each { |vec| vec.to_vertex(renderer) }
  end_shape(CLOSE)
end

def block_locked?
  block_locked
end

def over_block?
  over_block
end

def mouse_pressed
  if over_block?
    @block_locked = true
    fill 255
  else
    @block_locked = false
  end
end

def mouse_dragged
  return unless block_locked?
  position = Vec2D.new(mouse_x, mouse_y)
  block.new_position(position) { bounds.contains? position }
end

def mouse_released
  @block_locked = false
end

def settings
  size 640, 360
  smooth 4
end

# Use class to contain block behaviour
class Block
  attr_reader :aabb

  def initialize(center:, size:)
    @aabb = AABB.new(center: center, extent: size)
  end

  # passing ruby block on to @aabb.position
  def new_position(center, &block)
    @aabb.position(center.dup, &block)
  end

  def over?(vec)
    aabb.contains? vec
  end

  # use for shape
  def points_array
    a = aabb.center - aabb.extent * 0.5
    c = aabb.center + aabb.extent * 0.5
    b = Vec2D.new(c.x, a.y)
    d = Vec2D.new(a.x, c.y)
    [a, b, c, d]
  end

  # use for rect
  def points
    [aabb.center, aabb.extent]
  end
end

If you want efficiency (and extensibility) over awesomeness checkout toxicgem (hope I haven't created a name space clash oops?)