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 simply 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 init()
function.
To get a less boring explanation on how to use this, see the tutorial (once it's there).
Initializes the library. Call this in love.load()
. All the parameters can be omitted.
cell_size
(100)callback_start
(empty function)callback_persist
(empty function)callback_stop
(empty function)function love.load()
hardoncollider.init(150)
end
Sets the different 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.
Each callback has the following prototype:
function callback(dt, shape_one, shape_two, mtv_x, mtv_y)
The two shape
parameters are the colliding shapes. The last two parameters, 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.
start
persist
stop
function start(dt, shape_one, shape_two, mtv_x, mtv_y)
print('started colliding:', shape_one, shape_two)
print('mtv:', mtv_x, mtv_y)
end
function persist_one(dt, shape_one, shape_two, mtv_x, mtv_y)
print('still colliding:', shape_one, shape_two)
-- 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 persist_two(dt, shape_one, shape_two, mtv_x, mtv_y)
print('still 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()
hardoncollider.init(100)
-- set initial callbacks
hardoncollider.setCallbacks(start, persist_one, stop)
-- change persist callback
hardoncollider.setCallbacks{persist = persist_two}
end
Checks for collisions and call callbacks. Use this in love.update(dt)
.
A maximum time delta can be specified. dt
will be chopped up in slices
not bigger than this maximum and the scene is updated for each time slice.
Note that the delta time has no effect on the collision detection itself, but will be passed to the callback functions.
dt
function love.update(dt)
hardoncollider.update(dt, .02)
end
Automatically call hardoncollider.update(dt)
after each love.update(dt)
.
Basically overwrites love.update(dt)
with the following function:
function love.update(dt)
_old_love_update(dt)
hardoncollider.update(dt)
end
You can define a maximum time step. If dt
is bigger than this step, the
new update
function will be called multiple times with a dt
smaller or equal to the maximum time step:
while dt > max_step do
update(max_step)
dt = dt - max_step
end
update(dt)
If max_step
is bigger than 1
, it is assumed to declare a
minimum frame rate.
Once set, you can disable the auto update with
setNoAutoUpdate()
, but beware that this
might break other libs that hook into love.update, e.g.
hump.gamestate. To prevent unexpected
side effects, it's best practice to call setAutoUpdate()
after any other
library hooked itself into love.update()
max_step
(optional)function love.load()
HC.init(100, collision_start, collision_persist, collision_stop)
game_init()
HC.setAutoUpdate(30) -- maintain at least a update framerate of 30 FPS
end
Resets love.update()
to the state before calling
setAutoUpdate()
.
if game_over then
hardoncollider.setNoAutoUpdate()
end
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.
x1,y1, ..., xn,yn
shape = hardoncollider.addPolygon(10,10, 40,50, 70,10, 40,30)
Add a rectangle shape to the collision detection system.
x, y
w, h
rect = hardoncollider.addRectangle(100,120, 200,40)
Add a circle shape to the collision detection system.
cx, cy
radius
circle = hardoncollider.addCircle(400,300, 100)
Remove a shape from the collision detection system. Note that if you remove a shape in
the start
or persist
callback, other shapes might still have
collided with it, so the shape will be argument to the other calls of start
or persist
. In any case, the stop
callback will be called
in the next call to update
for each shape which the removed shape collided with.
shape
hardoncollider.remove(circle)
Add shapes to a group. Shapes in the same group will not collide with each other.
group
shape, ...
hardoncollider.addToGroup("platforms", platform1, platform2, platform3)
Remove shapes from a group.
group
shape, ...
hardoncollider.removeFromGroup("platforms", not_a_platform)
Makes a shape permeable. Ghost shapes will not collide with any other shape.
shape, ...
if cheat.set_ghost then
hardoncollider.setGhost(player)
end
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.
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.
x1,y1, ..., xn,yn
polygon
shape = shapes.PolygonShape(100,100, 200,200, 300,100)
Construct a circular shape.
cx, cy
radius
shape = shapes.CircleShape(400,300, 100)
Move the shape.
x, y
circle:move(10,15) -- move the circle 10 units right and 15 units down
Rotate the shape. A rotation center can be specified. If no center is given, the shape's center is used.
angle
cx, cy
rectangle:rotate(math.pi/4)
Get the center of the shape.
If the shape is a CircleShape, this is the circle center, else it's the polygon's centroid.
x, y
print("Circle at:", circle:center())
Draw the shape either filled or as outline.
mode
'line'
or 'fill'
.circle:draw('fill')
Test if two shapes collide.
other
collide
true
if the two shapes collide, false
otherwise.mtv
nil
if the two shapes don't collide.if circle:collidesWith(rectangle) then
print("collision detected!")
end
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.
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.
x1,y1, ..., xn,yn
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)
Get the polygon's vertices. Useful for drawing with love.graphics.polygon()
.
x1,y1, ..., xn,yn
love.graphics.draw('line', poly:unpack())
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
polygon
copy = poly:clone()
copy:move(10,20)
Get axis aligned bounding box.
x1, y1
x2, y2
x1,y1,x2,y2 = poly:getBBox()
-- draw bounding box
love.graphics.rectangle('line', x1,y2, x2-x1, y2-y1)
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.
convex
true
if the polygon is convex, false
otherwise.-- split into convex sub polygons
if not poly:isConvex() then
list = poly:splitConvex()
else
list = {poly:clone()}
end
Move a polygon in a direction. You can either use coordinates x,y
or a hump vector.
x, y
direction
poly:move(10,-5) -- move 10 units right and 5 units up
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.
angle
cx, cy
center
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
Split the polygon into triangles.
triangles
triangles = poly:triangulate()
for i,triangle in ipairs(triangles) do
triangles.move(math.random(5,10), math.random(5,10))
end
Split the polygon into convex sub polygons.
convex_polygons
convex = concave_polygon:splitConvex()
function love.draw()
for i,poly in ipairs(convex) do
love.graphics.polygon('fill', poly:unpack())
end
end
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.
other
merged
nil
if the two polygons don't share an edge.merged = p1:mergedWith(p2)
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.
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.
cell_size
(100)hash = spatialhash.Spatialhash(150)
As with Polygon()
, you can call the module as
a shortcut to the above:
hash = spatialhash(150)
Get coordinates of a given value, i.e. the cell index in which a given vector would be placed.
v
v
.coords = hash:cellCoords(vector(love.mouse.getPosition()))
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
v
cell = hash:cell(vector(love.mouse.getPosition()))
Insert an object into the hash using a given bounding box.
hash:insert(shape, vector(-100,-100), vector(0,-30))
Remove an object from the hash using a bounding box.
If no bounding box is given, search the whole hash to delete the object.
hash:remove(shape, vector(-100,-100), vector(0,-30))
hash:remove(object_with_unknown_position)
Update an objects position given the old bounding box and the new bounding box.
obj
ul_old
lr_old
ul_new
lr_new
hash:update(shape, vector(-100,-100), vector(0,-30),
vector(-100,-70), vector(0,0))
Query for neighbors of an object given it's bounding box.
local others = hash:getNeighbors(obj, vector(-100,-70), vector(0,0))
for _,other in ipairs(others) do
obj:pushAway(other)
end
require "hardoncollider.vector"
See hump.vector
require "hardoncollider.class"
See hump.class