Splash.lua is a Lua module for making collisions easier and managing objects in 2D space. It enables ray casting, spatial querying, and collision resolution of circles, rectangles, and line segments. It uses similar collision responses to bump.lua to resolve collisions. In fact, Splash.lua is very similar to bump.lua, but has more general collision shapes. Splash.lua is still a work in progress.
There are a few LÖVE demos in the demos
subdirectory that shows only some of
Splash's capabilities. Run a demo from the main project folder like so:
love demos/pick_a_demo
Splash works with the concept of the World, a manager that keeps track of all things in it.
local splash = require "splash"
local world = splash.new(cellSize) -- or splash(cellSize)
Creates a new Splash world with a given cellSize. Leave cellSize
as nil to get
the default of 128.
Splash associates Things in a World with Shapes. Currently, there are three different types of Shapes: Circles, Segs, and AABBs.
local aabb = splash.aabb(x, y, w, h)
Creates an AABB at (x, y)
with a width and height of w
and h
. AABBs are
just rectangles that cannot rotate. Both width and height must be positive.
local circle = splash.circle(x, y, r)
Creates a circle at (x, y)
with a radius of r
. The radius must be positive.
local seg = splash.seg(x1, y1, dx, dy)
Creates a line segment from (x1, y1)
to (x1 + dx, y1 + dy)
.
Shapes also have a few common methods.
local x, y, ... = shape:unpack() -- Unpacks all of the values used to construct the Shape
local x, y, ... - shape() -- Shortcut for unpacking the Shape
local newShape = shape:clone() -- Creates a copy of the Shape
local didIntersect = shape:intersect(otherShape) -- Checks if two Shapes intersect. For segments, returns the time of intersection between 0 and 1
local didCollide, t, nx, ny, cornerCollide = shape:sweep(other, xto, yto) -- Checks if a Shape would intersect another Shape when moving to point
-- (xto, yto). Returns information about the collision including when it happened, and the collision normal.
local x, y = shape:pos() -- Unpacks only the first two values of the Shape, which are x and y.
local shape = shape:update(newx, newy, [...]) -- Updates the values of the Shape without creating a new Shape. Returns the Shape for convenience
Things are keys that are associated with Shapes in a Splash World. They can be any Lua type, but should probably be tables.
local shape = splash.aabb(x, y, w, h)
local thing = world:add({}, shape)
Adds a Thing to the world with the Shape. Returns the Thing and the Shape for convenience.
thing, shape = world:remove(thing)
Removes a Thing from the world. Returns the Thing removed and its associated Shape for convenience.
thing, shape = world:setShape(thing, shape)
Changes the Shape of a Thing in the World. Returns the Thing for convenience, along with the new Shape.
shape:update(100, 100)
thing, shape = world:update(thing)
Updates the Shape of a Thing in the World without creating a new Shape. Use this
to move Things around instead of creating a new Shape every step with
setShape
. The above example sets the position of thing to (100, 100),
regardless of what kind of shape it is, and is equivalent to:
thing, shape = world:update(thing, 100, 100)
local shape = world:shape(thing)
Gets the Shape associated with a Thing.
local type, x, y, a, b = world:unpackShape(thing)
Gets the unpack data of a Shape associated with Thing. Equivalent to
world:shape(thing):unpack()
, but doesn't create a new Shape.
local x, y = world:pos(thing)
Gets the position of a Thing in the World. Equivalent to
world:shape(thing):pos()
.
Splash.lua can collide Things with other Things in a World in an efficient and
general manner. Splash.lua works on the basis of collision responses that are
used to resolve collisions. Be default, three are built in: 'slide'
,
'touch'
, and 'cross'
. These should work the same as they do in
bump.lua.
xto, yto = world:move(thing, xgoal, ygoal, [filter, callback])
Moves a Thing in the world to (xto, yto), colliding with the collision method
based on filter. The filter is a function of two arguments, thing1 and thing2,
and returns a collision type to be used to resolve the collision. If a logical
false is returned, then no collision occurs between thing1 and thing2. If no
filter is provided, then the default response of 'slide' is used. If callback is
provided, then it is called on every collision with arguments:
callback(thing, other, x, y, xgoal, ygoal, normalx, normaly)
.
xto, yto, collisions = world:moveExt(thing, xgoal, ygoal, [filter])
Similar to world:move
, but returns a sequence of collisions instead of using a
callback. Every element of the collision
sequence is a table containing the
following information about collisions.
- self: the Thing that was moved.
- other: the Thing that self collided with
- x, y: the position of self when the collision occurs.
- xgoal, ygoal: the position self is heading towards after the collision.
- nx, ny: the normal vector of the collision.
There are also two functions world:check
and world:checkExt
that do exactly
the same things as the move functions, but don't update the position of the
Thing in the world. They are useful for checking if collisions would have
occurred had a Thing been moved to a location.
Splash offers three different ways of checking the World. Mapping, Querying,
and Iterating. Mapping applies a function over every object found in the
searched area. Querying returns a sequence of all objects in the searched area.
Iterating returns an iterator over all objects in the searched area. All query
functions have a corresponding map
and iter
function of a similar name.
The map
and iter
functions, however, give extra information to the user in
some cases.
world:mapAll(f(thing))
world:mapShape(f(thing), shape)
world:mapCell(f(thing), cx, cy)
world:mapPoint(f(thing), x, y)
local things = world:queryAll([filter])
local things = world:queryShape(shape, [filter])
local things = world:queryCell(cx, cy, [filter])
local things = world:queryPoint(x, y, [filter])
for thing in world:iterAll() do ... end
for thing in world:iterShape(shape) do ... end
for thing in world:iterCell(cx, cy) do ... end
for thing in world:iterPoint(x, y) do ... end
Besides these general functions, Splash also has a fast, early exit ray-casting function that returns a thing, point of intersection, and time of intersection (parameter from 0 to 1).
local thing, endx, endy, t1 = world:castRay(x1, y1, x2, y2)
These functions should be used mainly for debugging and inspecting what Splash is doing. They deal mostly with how Splash puts objects into cells for fast lookup.
local cellx, celly = world:toCell(x, y)
Converts a world coordinate to a cell coordinate.
local aabb = world:fromCell(cellx, celly)
Returns the world coordinates of a cell's most negative corner. Again, useful mainly for debugging.
local count = world:cellThingCount(cx, cy)
Returns the number of things in a cell.
local cellCount = world:countCells()
Returns the number of populated cells in the world.
Use splash.lua like any other Lua module. It's a single file, so just copy splash.lua to your project source folder.
- Add tests (busted)
- More swept collisions - not all sweeps have been implemented.
- Whatever features seem useful
- Better, more fully featured demo(s)
- Fix typos and other issues in this README
- Cell coordinates are currently limited to 2^25. That should be big enough for anyone, but it's an unnecessary limitation.
- Bug squashing