hardoncollider^ top

Collider = require "hardoncollider"

The main module.

HardonCollider will automatically handle - but not resolve - collisions. It uses search data structure - a spatial hash - to quickly find colliding shapes.

A spatial hash is a grid that is laid over the whole scene in which a shape can occupy several cells. To find shapes that may be colliding, you simply need to look which shapes occupy the same cell. You can specify the cell size in the new() function.

To get a less boring explanation on how to use this, see the tutorial.

Module overview

new()
Create a new collider instance.
HC:clear()
Clears collider data.
HC:setCallbacks()
Set callback functions.
HC:update()
Update collision detection.
HC:addPolygon()
Add polygon to the scene.
HC:addRectangle()
Add rectangle to the scene.
HC:addCircle()
Add circle to the scene.
HC:addPoint()
Add point to the scene.
HC:remove()
Remove a shape from scene.
HC:addToGroup()
Group shapes that should not collide.
HC:removeFromGroup()
Remove shape from a group.
HC:setPassive()
Flag a shape passive.
HC:setActive()
Flag a shape active.
HC:setGhost()
Stops shape from colliding.
HC:setSolid()
Make shape collidable.

function new(cell_size, callback_collide, callback_stop)^ top

Initializes the library. Call this in love.load(). All the parameters can be omitted.

Note: The cell size does not determine the granularity of the collision detection, but is an optimization device. Values that are too small or too big will have a negative impact on the detection speed. The meaning of too small and too big depends on the size of the shapes in the collision detection.

Parameters:
number cell_size (100)
Cell size for internal search structure.
function callback_collide (empty function)
Called when two shapes are colliding.
function callback_stop (empty function)
Called when two shapes were colliding in the last frame, but aren't now.
Returns:
Nothing
Example:
Collider = require 'hardoncollider'
function love.load()
    HC = Collider.new(150)
    -- or: HC = Collider(150)
end

function setCallbacks(collide,stop)^ top

function setCallbacks{collide = collide,stop = stop}

Sets the collision callbacks. The second calling style let's you specify the callbacks by name, see the example.

If nil is passed as any callback, the callback will not be changed.

The callbacks must have the following function prototype:

function callback(dt, shape_one, shape_two, mtv_x, mtv_y)
shape_one and shape_two are the colliding shapes. mtv_x and mtv_y define the minimum translation vector, i.e. the direction and magnitude shape_one has to be moved so that the collision will be resolved. Note that if one of the shapes is a point shape, the translation vector will be invalid.

Parameters:
function collide
Called when two shapes are colliding.
function stop
Called when two shapes were colliding in the last frame, but aren't now.
Returns:
Nothing
Example:
Collider = require 'hardoncollider'

function collide(dt, shape_one, shape_two, mtv_x, mtv_y)
    print('colliding:', shape_one, shape_two)
    print('mtv:', mtv_x, mtv_y)
    -- move both shape_one and shape_two to resolve the collision
    shape_one:move(mtv_x/2, mtv_y/2)
    shape_two:move(-mtv_x/2, -mtv_y/2)
end

function colliding_two(dt, shape_one, shape_two, mtv_x, mtv_y)
    print('colliding:', shape_one, shape_two)
    -- move only shape_one to resolve the collision
    shape_one:move(mtv_x, mtv_y)
end

-- ignore the translation vector
function stop(dt, shape_one, shape_two)
    print('collision resolved')
end

function love.load()
    HC = Collider()
    -- set initial callbacks
    HC:setCallbacks(collide, stop)
    -- change collide callback
    HC:setCallbacks{collide = collide_two}
end

function HC:update(dt)^ top

Checks for collisions and call callbacks. Use this in love.update(dt).

Note that the delta time has no effect on the collision detection itself, but will be passed to the callback functions.

Parameters:
number dt
The time since the last update.
Returns:
Nothing
Example:
function love.update(dt)
    HC:update(dt)
end

function HC:addPolygon(x1,y1, ..., xn,yn)^ top

Add a polygon to the collision detection system. Any non-intersection polygon will work, even convex polygons.

Note that if three consecutive points lie on a line, the middle point will be discarded. This means you cannot construct polygon shapes out of lines.

Parameters:
numbers x1,y1, ..., xn,yn
The corners of the polygon. At least three corners (that do not lie on a line) are needed.
Returns:
Shape
The polygon shape added to the scene.
Example:
shape = HC:addPolygon(10,10, 40,50, 70,10, 40,30)

function HC:addRectangle(x, y, w, h)^ top

Add a rectangle shape to the collision detection system.

Parameters:
numbers x, y
The upper left corner of the rectangle.
numbers w, h
The width and height of the rectangle.
Returns:
Shape
The rectangle added to the scene.
Example:
rect = HC:addRectangle(100,120, 200,40)

function HC:addCircle(cx, cy, radius)^ top

Add a circle shape to the collision detection system.

Parameters:
numbers cx, cy
The circle center.
number radius
The circle radius.
Returns:
Shape
The circle added to the scene.
Example:
circle = HC:addCircle(400,300, 100)

function HC:addPoint(x, y)^ top

Add a point shape to the collision detection system.

Point shapes are most useful for bullets or suchs, because detecting collisions between a point and any other shape is a little faster than detecting collision between two non-point shapes. In case of a collision, the callback will not receive a valid minimum translation vector.

Parameters:
numbers x, y
The point's position.
Returns:
Shape
The point added to the scene.
Example:
bullets[#bulltes+1] = HC:addPoint(player.pos.x,player.pos.y)

function HC:remove(shape, ...)^ top

Remove a shape from the collision detection system. Note that if you remove a shape in the collide() callback, other shapes might still have collided with it, so the shape will be argument to the other calls of collide(). In any case, the stop() callback will be called in the next call to update for each shape which the removed shape collided with.

Parameters:
Shape(s) shape, ...
The shape(s) to be removed.
Returns:
Nothing
Example:
HC:remove(circle)
HC:remove(enemy1, enemy2)

function HC:addToGroup(group, shape, ...)^ top

function HC:removeFromGroup(group, shape, ...)^ top

Add or remove shapes to/from a group. Shapes in the same group will not emit collision callbacks when colliding with each other.

Parameters:
string group
The name of the group.
Shapes shape, ...
The shapes to be added or removed to the group.
Returns:
Nothing
Example:
HC:addToGroup("platforms", platform1, platform2, platform3)
HC:removeFromGroup("platforms", platform1)

function HC:setPassive(shape, ...)^ top

function HC:setActive(shape, ...)^ top

Flags a shape active or passive. Only active shapes will search for colliding shapes, i.e. there will be no collision reported when two passive shapes collide.

This enables you to significantly speed up the collision detection. Typical candidates for passive shapes are those which are numerous, but don't act in themselves, e.g. the level geometry.

Parameters:
Shapes shape, ...
The shapes to be flagged as passive/active.
Returns:
Nothing
Example:
HC:setPassive(platform1, platform2, platform3)

function setPassive(shape, ...)^ top

Sets shape to be passive. Passive shapes will be subject to collision detection, but will not actively search for collision candidates. This means that if two passive shapes collide, no collision callback will be invoked (in fact, the collision won't even be detected).

This function exists purely for performance optimisation. Use it wisely. Good candidates for passive shapes are traps and terrain.

Note: Shapes are active by default

Parameters:
Shapes shape, ...
The shapes to become passive.
Returns:
Nothing
Example:
hardoncollider.setPassive(ground, bridge, spikes)

function setActive(shape, ...)^ top

Makes shapes active again.

Note: Shapes are active by default

Parameters:
Shapes shape, ...
The shapes to become active.
Returns:
Nothing
Example:
hardoncollider.setActive(collapsing_bridge)

function HC:setGhost(shape, ...)^ top

function HC:setSolid(shape, ...)^ top

Makes a shape permeable or solid. Ghost shapes will not collide with any other shape.

Parameters:
Shapes shape, ...
The shapes to become permeable/solid.
Returns:
Nothing
Example:
-- make player invincible for 5 seconds
HC:setGhost(player)
Timer.add(5, function() HC:setSolid(player) end)

hardoncollider.shapes^ top

shapes = require "hardoncollider.shapes"

Shape classes with collision detection methods.

This defines methods to move, rotate and draw shapes created with hardoncollider.add*.

As each shape is at it's core a Lua table, you can attach values and add functions to it. Be careful though not to use keys that name a function or start with an underscore, e.g. move or _groups, since these are used internally. Everything else is fine.

If you don't want to use the full blown module, you can still use these classes to test for colliding shapes. They might also be useful for doing GUI stuff, e.g. when testing if the mouse hovers a button.

Some functions (getAxes and projectOn) are left undocumented, as they have little value outside the scope of collision detection.

Module overview

shapes.PolygonShape
A polygon shape.
shapes.CircleShape
A circle shape.
shapes.PointShape
A point shape.
shape:contains()
Test if shape contains a point.
shape:intersectsRay()
Test if shape intersects a ray.
shape:move()
Move the shape.
shape:moveTo()
Set the shape's position.
shape:rotate()
Rotate the shape.
shape:setRotation()
Set the shape's rotation.
shape:center()
Get the shape's center.
shape:rotation()
Get the shape's rotation.
shape:outcircle()
Get circumscribing circle.
shape:draw()
Draw the shape.
shape:collidesWith()
Test for collision.

class PolygonShape(x1,y1, ..., xn,yn)^ top

class PolygonShape(polygon)

Construct a shape using a non-intersecting ploygon.

You can either specify the coordinates as with hardoncollider.addPolygon() or use an instance of the Polygon class.

Parameters:
numbers x1,y1, ..., xn,yn
The corners of the polygon. At least three corners (that do not lie on a line) are needed.
Polygon polygon
Construct the shape from this polygon.
Returns:
Shape
The constructed shape.
Example:
shape = shapes.PolygonShape(100,100, 200,200, 300,100)

class CircleShape(cx,cy, radius)^ top

Construct a circular shape.

Parameters:
numbers cx, cy
The circle center.
number radius
The circle radius.
Returns:
Shape
The constructed circle shape.
Example:
shape = shapes.CircleShape(400,300, 100)

class PointShape(x,y)^ top

Construct a point shape.

Parameters:
numbers x, y
The point's position.
Returns:
Shape
The constructed point shape.
Example:
shape = shapes.PointShape(400,300)

function shape:contains(x, y)^ top

Test if the shape contains a given point.

Parameters:
numbers x, y
Point to test.
Returns:
boolean
true if x,y lies in the interior of the shape.
Example:
if unit.shape:contains(love.mouse.getPosition) then
    unit:setHovered(true)
end

function shape:intersectsRay(x, y, dx, dy)^ top

Test if the shape intersects a ray.

Parameters:
numbers x, y
Starting point of the ray.
numbers dx, dy
Direction of the ray.
Returns:
boolean intersection
true if the given ray intersects the shape.
number t (only if intersecting)
Ray parameter of the intersection.
Example:
local intersecting, t = player:intersectsRay(x,y, dx,dy)
if intersecting then -- laser pointer hits player
    local intersectionPoint = vector(x,y) + t * vector(dx,dy)
    player:addMark(intersectionPoint)
end

function shape:move(x, y)^ top

Move the shape.

Parameters:
numbers x, y
The direction to move the shape in.
Returns:
Nothing
Example:
circle:move(10,15) -- move the circle 10 units right and 15 units down

function shape:moveTo(x, y)^ top

Set the shape's position.

Parameters:
numbers x, y
Point to place the shape.
Returns:
Nothing
Example:
circle:moveTo(400,300) -- move circle to screen center

function shape:rotate(angle, cx,cy)^ top

Rotate the shape. A rotation center can be specified. If no center is given, the shape's center is used.

Parameters:
number angle
Amount to rotate the shape (in radians).
numbers cx, cy (optional)
Rotation center. Defaults to the shape's center if omitted.
Returns:
Nothing
Example:
rectangle:rotate(math.pi/4)

function shape:setRotation(angle, cx,cy)^ top

Set the rotation of a shape. A rotation center can be specified. If no center is given, the shape's center is used.

Equivalent to:

shape:rotate(angle - shape.rotation, cx,cy)
Parameters:
number angle
Rotation angle (in radians).
numbers cx, cy (optional)
Rotation center. Defaults to the shape's center if omitted.
Returns:
Nothing
Example:
rectangle:setRotation(math.pi, 100,100)

function shape:center()^ top

Get the center of the shape.

If the shape is a CircleShape, returns the circle center. In case of a point shape, returns the position. Else returns the polygon's centroid.

Parameters:
None
Returns:
numbers x, y
The center of the shape.
Example:
print("Circle at:", circle:center())

function shape:rotation()^ top

Get the shape's rotation angle in radians.

Parameters:
None
Returns:
number angle
The rotation angle in radians.
Example:
print("Box rotation:", box:rotation())

function shape:outcircle()^ top

Get circumscribing circle.

Parameters:
None
Returns:
numbers x, y
Center of the circle.
number r
Radius of the circle.
Example:
if player:hasShield() then
    -- draw shield
    love.graphics.circle('line', player:outcircle())
end

function shape:draw(mode)^ top

Draw the shape either filled or as outline.

Parameters:
DrawMode mode
How to draw the shape. Either 'line' or 'fill'.
Returns:
Nothing
Example:
circle:draw('fill')

function shape:collidesWith(other)^ top

Test if two shapes collide.

Parameters:
Shape other
Test for collision with this shape.
Returns:
boolean collide
true if the two shapes collide, false otherwise.
vector mtv
The minimum translation vector, or nil if the two shapes don't collide.
Example:
if circle:collidesWith(rectangle) then
    print("collision detected!")
end

hardoncollider.polygon^ top

polygon = require "hardoncollider.polygon"

Definition of a Polygon class and implementation of some handy algorithms.

On it's own, this class does not offer any collision detection. If you want that, use a PolygonShape instead.

Module overview

Polygon
The polygon class.
polygon:unpack()
Get coordinates.
polygon:clone()
Copy polygon.
polygon:getBBox()
Get bounding box.
polygon:isConvex()
Test if polygon is convex.
polygon:move()
Move polygon.
polygon:rotate()
Rotate polygon.
polygon:triangulate()
Split polygon in triangles.
polygon:splitConvex()
Split polygon into convex polygons.
polygon:mergedWith()
Merge polygon with other polygon.
polygon:contains()
Test if polygon contains a point.
polygon:intersectsRay()
Test if polygon intersects a ray.

class Polygon(x1,y1, ..., xn,yn)^ top

Construct a polygon.

At least three points that are not collinear (being on a straight line) are needed to construct the polygon. If there are collinear points, these points will be removed so that the overall shape of the polygon is not changed.

Parameters:
numbers x1,y1, ..., xn,yn
The corners of the polygon. At least three corners are needed.
Returns:
Polygon
The polygon object.
Example:
poly = polygon.Polygon(10,10, 40,50, 70,10, 40,30)

polygon.Polygon looks rather verbose - that is why you can actually call the module like a function to create an instance of the Polygon class:

poly = polygon(10,10, 40,50, 70,10, 40,30)

function polygon:unpack()^ top

Get the polygon's vertices. Useful for drawing with love.graphics.polygon().

Parameters:
None
Returns:
numbers x1,y1, ..., xn,yn
The vertices of the polygon.
Example:
love.graphics.draw('line', poly:unpack())

function polygon:clone()^ top

Get a copy of the polygon.

Since Lua uses references when simply assigning an existing polygon to a variable, unexpected things can happen when operating on the variable. Consider this code:

p1 = Polygon(10,10, 40,50, 70,10, 40,30)
p2 = p1
p3 = p1:clone()
p2:rotate(math.pi) -- p1 will be rotated, too!
p3:rotate(-math.pi) -- only p3 will be rotated
Parameters:
None
Returns:
Polygon polygon
A copy of the polygon.
Example:
copy = poly:clone()
copy:move(10,20)

function polygon:getBBox()^ top

Get axis aligned bounding box.

Parameters:
None
Returns:
numbers x1, y1
Upper left corner of the bounding box.
numbers x2, y2
Lower right corner of the bounding box.
Example:
x1,y1,x2,y2 = poly:getBBox()
-- draw bounding box
love.graphics.rectangle('line', x1,y2, x2-x1, y2-y1)

function polygon:isConvex()^ top

Test if a polygon is convex, i.e. a line line between any two points inside the polygon will lie in the interior of the polygon.

Parameters:
None
Returns:
boolean convex
true if the polygon is convex, false otherwise.
Example:
-- split into convex sub polygons
if not poly:isConvex() then
    list = poly:splitConvex()
else
    list = {poly:clone()}
end

function polygon:move(x,y)^ top

function polygon:move(direction)

Move a polygon in a direction. You can either use coordinates x,y or a hump vector.

Parameters:
numbers x, y
Coordinates of the direction to move.
vector direction
Direction to move.
Returns:
Nothing
Example:
poly:move(10,-5) -- move 10 units right and 5 units up

function polygon:rotate(angle)^ top

function polygon:rotate(angle, cx, cy)

function polygon:rotate(angle, center)

Rotate the polygon. You can define a rotation center. If it is omitted, the polygon will be rotated around it's centroid.

For defining a rotation center, you can either use coordinate form cx,cy or a hump vector.

Parameters:
number angle
The angle to rotate in radians.
numbers cx, cy
The rotation center.
vector center
The rotation center.
Returns:
Nothing
Example:
p1:rotate(math.pi/2)          -- rotate p1 by 90° around it's center
p2:rotate(math.pi/4, 100,100) -- rotate p2 by 45° around the point 100,100

function polygon:triangulate()^ top

Split the polygon into triangles.

Parameters:
None
Returns:
Array of Polygon triangles
Triangles that the polygon is composed of.
Example:
triangles = poly:triangulate()
for i,triangle in ipairs(triangles) do
    triangles.move(math.random(5,10), math.random(5,10))
end	

function polygon:splitConvex()^ top

Split the polygon into convex sub polygons.

Parameters:
None
Returns:
Array of Polygon convex_polygons
Convex polygons that form the original polygon.
Example:
convex = concave_polygon:splitConvex()
function love.draw()
    for i,poly in ipairs(convex) do
        love.graphics.polygon('fill', poly:unpack())
    end
end

function polygon:mergedWith(other)^ top

Create a merged polygon of two polygons if, and only if the two polygons share one edge. If the polygons share more than one edge, the result may be erroneous.

This function does not change either polygon, but rather create a new one.

Parameters:
Polygon other
The polygon to merge with.
Returns:
Polygon merged
The merged polygon, or nil if the two polygons don't share an edge.
Example:
merged = p1:mergedWith(p2)

function polygon:contains(x, y)^ top

Test if the polygon contains a given point.

Parameters:
numbers x, y
Point to test.
Returns:
boolean
true if x,y lies in the interior of the polygon.
Example:
if button:contains(love.mouse.getPosition) then
    button:setHovered(true)
end

function polygon:intersectsRay(x, y, dx, dy)^ top

Test if the polygon intersects a ray.

Parameters:
numbers x, y
Starting point of the ray.
numbers dx, dy
Direction of the ray.
Returns:
boolean intersection
true if the given ray intersects the shape.
number t (only if intersecting)
Ray parameter of the intersection.
Example:
if poly:intersectsRay(400,300, dx,dy) then
    love.graphics.setLine(2) -- highlight polygon
end

hardoncollider.spatialhash^ top

spatialhash = require "hardoncollider.spatialhash"

A spatial hash implementation that supports scenes of arbitrary size. The hash is sparse, which means that cells will only be created when needed.

Module overview

Spatialhash
Spatial hash class.
hash:cellCoords()
Get cell coordinates of a given vector.
hash:cell()
Get cell for a given vector.
hash:insert()
Insert object.
hash:remove()
Remove object.
hash:update()
Update object's position.
hash:getNeighbors()
Query neighbors of an object.

Spatialhash(cell_size)^ top

Create a new spatial hash given a cell size.

Choosing a good cell size depends on your application. To get a decent speedup, the average cell should not contain too many objects, nor should a single object occupy too many cells. A good rule of thumb is to choose the cell size so that the average object will occupy one cell only.

Parameters:
number cell_size (100)
Width and height of a cell.
Returns:
Spatialhash
A fresh object instance.
Example:
hash = spatialhash.Spatialhash(150)

As with Polygon(), you can call the module as a shortcut to the above:

hash = spatialhash(150)

function hash:cellCoords(v)^ top

Get coordinates of a given value, i.e. the cell index in which a given vector would be placed.

Parameters:
vector v
The position to query.
Returns:
vector
Coordinates of the cell which would contain v.
Example:
coords = hash:cellCoords(vector(love.mouse.getPosition()))

function hash:cell(v)^ top

Get the cell a given vector would be placed in. This is an actual cell, not the index.

A cell is a table which's keys and value are the objects stored in the cell, i.e.:

cell = {
    [obj1] = obj1,
    [obj2] = obj2,
    ...
}
You can iterate over the objects in a cell using pairs():
for object,_ in pairs(cell) do stuff(object) end

Parameters:
vector v
The position to query
Returns:
table
Set of objects contained in the cell.
Example:
cell = hash:cell(vector(love.mouse.getPosition()))

function hash:insert(obj, ul, lr)^ top

Insert an object into the hash using a given bounding box.

Parameters:
mixed obj
Object to place in the hash. It can be of any type except nil.
vector ul
Upper left corner of the bounding box.
vector lr
Lower right corner of the bounding box.
Returns:
Nothing
Example:
hash:insert(shape, vector(-100,-100), vector(0,-30))

function hash:remove(obj, ul, lr)^ top

function hash:remove(obj)

Remove an object from the hash using a bounding box.

If no bounding box is given, search the whole hash to delete the object.

Parameters:
mixed obj
The object to delete
vector ul
Upper left corner of the bounding box.
vector lr
Lower right corner of the bounding box.
Returns:
Nothing
Example:
hash:remove(shape, vector(-100,-100), vector(0,-30))
hash:remove(object_with_unknown_position)

function hash:update(obj, ul_old, lr_old, ul_new, lr_new)^ top

Update an objects position given the old bounding box and the new bounding box.

Parameters:
mixed obj
The object to be updated.
vector ul_old
Upper left corner of the bounding box before the object was moved.
vector lr_old
Lower right corner of the bounding box before the object was moved.
vector ul_new
Upper left corner of the bounding box after the object was moved.
vector lr_new
Lower right corner of the bounding box after the object was moved.
Returns:
Nothing
Example:
hash:update(shape, vector(-100,-100), vector(0,-30),
                   vector(-100,-70), vector(0,0))

function hash:getNeighbors(obj, ul, lr)^ top

Query for neighbors of an object given it's bounding box.

Parameters:
mixed obj
The object to query neighbors.
vector ul
Upper left corner of the object's bounding box.
vector lr
Lower right corner of the object's bounding box.
Returns:
Set
A set (i.e. table of t[other] = other) of neighboring objects.
Example:
local others = hash:getNeighbors(obj, vector(-100,-70), vector(0,0))
for _,other in pairs(others) do
    obj:pushAway(other)
end

hardoncollider.vector^ top

require "hardoncollider.vector"

See hump.vector

hardoncollider.class^ top

require "hardoncollider.class"

See hump.class