Strike
2D Collision Detection for Lua using the Separating-Axis Theorem
Install / Use
/learn @Aweptimum/StrikeREADME
Strike
Strike (Separating-Axis Theorem Routines for Ill-tempered, Kaleidascopic Entities) is a 2D SAT Collision Detection library.
Made primarily for the LÖVE community, but should be compatible with any Lua version (at least at time of writing)
Installation
Download or recurisvely clone Strike into your project's directory
If using git's command line to clone Strike, run either of the following commands to clone it:
git clone --recurse-submodules https://github.com/Aweptimum/Strike.git (git >= 2.13)
git clone --recursive https://github.com/Aweptimum/Strike.git (git < 2.13)
If using github desktop, it automatically resolves submodules, so no command-line needed
Then require it:
local S = require 'Strike'
Shapes
Accessed via S.hapes, Shapes are objects representing convex geometry, mostly polygons and circles. They are not used for collision detection by themselves, but are at your disposal. The available ones are in the /shapes directory, but you can add your own shape definitions as well. With the exception of Circle, every shape is extended from ConvexPolygon and overrides its constructor. There are a few more things to note, but it's important to know that both object definitions and instantiation are handled by rxi's classic. classic's distinction is that the constructor definition,:new, does not return the object itself. The object's __call method handles that, which is in turn handled by classic.
An example of a shape structure
Rect = {
vertices = {}, -- list of {x,y} coords
convex = true, -- boolean
centroid = {x = 0, y = 0}, -- {x, y} coordinate pair
radius = 0, -- radius of circumscribed circle
area = 0 -- absolute/unsigned area of polygon
}
To actually define this, the ConvexPolygon definition can be extended and overriden with a new constructor. Example in Defining Your Own Shapes
See Basic Colliders down below for available Shapes.
Now for some Shape methods:
Transforming Shapes
All of the below methods return self if you need to do some transformation chaining
shape:translate(dx, dy) -- adds dx and dy to each shapes' points
shape:translateTo(x,y) -- translates centroid to position (and everything with it)
shape:rotate(angle, refx, refy) -- rotates by `angle` (radians) about a reference point (defaults to centroid)
shape:rotateTo(angle, refx, refy) -- rotates the shape *to* an `angle` (radians) about a reference point (defaults to centroid)
shape:scale(sf, refx, refy) -- scales by factor `sf` with respect to a reference point (defaults to centroid)
Querying Shapes
Under the hood, Strike's Shape class uses a Transform object, so directly accessing coordinates won't give expected values. Relevant getters return transformed coordinates, such as getVertex, so definitely use them.
shape:getArea() -- Returns the area of the shape
shape:getCentroid() -- Returns the centroid of the shape as a table {x = x, y = y}
shape:getAreaCentroid() -- Returns the area *and* the centroid of the shape
shape:getRadius() -- Returns the radies of the shape's circumscribed circle
shape:getBbox() -- Returns the minimum AABB dimensions of the shape as 4 numbers: minimum-x, minimum-y, width, height
shape:unpack() -- Returns the args the shape was constructed with
More specific query methods:
shape:getEdge(i)
Given an index, returns the corresponding numbered edge. Returns nil if OOB
shape:getVertex(i)
Given an index, returns the corresponding numbered edge. Returns nil if OOB
shape:project(nx, ny)
Given two normalized vector components, returns the minimum and maximum values of the shape's projection onto the vector
shape:containsPoint(point)
Given a point ({x=x,y=y}), returns true if it is within bounds of the shape, else false
shape:rayIntersects(x,y, nx,ny)
Given a ray-origin x,y and a normalized vector, returns true if it intersects the shape, else false
shape:rayInteresections(x,y, nx,ny, ts) -- Returns a table of numbers that are
Same args as rayIntersects w/ optional table to insert into. Returns a list of numbers representing lengths along normal nx, ny
Misc Shape Methods
There are a few more methods, specifically:
shape:copy(x,y, angle_rads) -- (returns a copy, go figure) w/ centroid located at `x, y` and the specified angle.
shape:ipairs( ?shape2 ) -- Edge iterator. shape2 only necessary for Circles, which need another shape to get a useful edge
shape:vecs( ?shape2 ) -- Vector iterator. shape2 only necessary for Circles, which need another shape to get a useful edge
Colliders
Accessed via S.trikers, Colliders are grab-bags of geometry that are used for collision detection. They can be composed of Shapes, but may also contain other Colliders (and their shapes). The only requirement is that every shape in the Collider is convex. As with Shapes, available Colliders are defined in the /colliders directory and auto-loaded in. You can define custom collider definitions for particular collections of geometry that you're fond of. Just look at the included Capsule definition for a simple example. The included collider objects are listed below.
Basic Colliders
These correspond to the available S.hapes, but are not Shapes themselves. Their constructors are identical to the referenced shapes, and are automatically defined as Collider(shape_name(shape_args)). They are a Collider that contains a single shape of the same name.
Circle
c = S.trikers.Circle(x_pos, y_pos, radius, angle_rads)
Creates a circle centered on {x_pos, y_pos} with the given radius. angle_rads not really useful at the moment, might delete it.
ConvexPolygon
cp = S.trikers.ConvexPolygon(x,y, ...)
Takes a vardiadic list of x,y pairs that describe a convex polygon.
Should be in counter-clockwise winding order, but the constructor will automatically sort non-ccw input when it fails a convexity check.
Edge
e = S.trikers.Edge(x1,y1, x2,y2)
Takes the two endpoints of an edge and... creates an edge
Ellipse
el = S.trikers.Ellipse(x_pos, y_pos, a, b, segments, angle_rads)
Creates a discretized Ellipse centered at {x_pos, y_pos}, a and b are lengths of major/minor axes, segments is the number of edges to use to approximate the Ellipse, and angle_rads is the angled offset in radians.
Rectangle
S.trikers.Rectangle(x_pos, y_pos, dx, dy, angle_rads)
Creates a rectangle centered at {x_pos, y_pos} with width dx and height dy, offset by angle_rads (radians).
RegularPolygon
r = S.trikers.RegularPolygon(x_pos, y_pos, n, radius, angle_rads)
Creates a regular polygon centered at {x_pos, y_pos} with n sides. radius is the radius of the circumscribed circle, angle_rads is the angled offset in radians.
Composite Colliders
Collider
coll = S.trikers.Collider( S.hapes.Rectangle(...), S.hapes.Circle(...), S.trikers.Circle(...), ... )
Creates a collider object given a variadic amount of S.hapes or S.trikers that contains the specified geometry, though S.hapes make for a flatter object
Capsule
cap = S.trikers.Capsule(x_pos, y_pos, dx, dy, angle_rads)
Creates a capsule centered at {x_pos, y_pos} with width dx and height dy, offset by angle_rads (radians). Circles are along vertical axis.
Concave
concave = S.trikers.Concave(x,y, ...)
Takes a vardiadic list of x,y pairs that describe a concave polygon.
Should be in pseudo-counter-clockwise winding order.
Transforming Colliders
The base Collider class (and all colliders that extend it) have the below methods
All of them return self if you need to do some transformation chaining
collider:translate(dx, dy) -- adds dx and dy to each shapes' points
collider:translateTo(x,y) -- translates centroid to position (and everything with it)
collider:rotate(angle, refx, refy) -- rotates by `angle` (radians) about a reference point (defaults to centroid)
collider:rotateTo(angle, refx, refy) -- rotates the shape *to* an `angle` (radians) about a reference point (defaults to centroid)
collider:scale(sf, refx, refy) -- scales by factor `sf` with respect to a reference point (defaults to centroid)
Querying Colliders
collider:getArea() -- Returns the area of the collider
collider:getCentroid() -- Returns the centroid of the collider as a table {x = x, y = y}
collider:getAreaCentroid() -- Returns the area *and* the centroid of the collider
collider:getRadius() -- Returns the radies of the collider's circumscribed circle
collider:getBbox() -- Returns the minimum AABB dimensions of the collider as 4 numbers: minimum-x, minimum-y, width, height
collider:unpack() -- Returns the the shapes a collider contains
More specific query methods:
collider:project(nx, ny)
Given two normalized vector components, returns the minimum and maximum values of the collider's projection onto the vector. Not super useful for concave colliders, but it's there.
Of course, there's also ray-methods, but they're expounded upon in Ray Intersection
Manipulating Colliders
Other useful methods include:
collider:copy() -- returns a copy of the collider
collider:remove(index, ...) -- removes a shape at the specified index, can handle multiple indexes
**uses table.remove internally, so as long as you don't have tens of thousands of shapes in a collider, you'll be fine!
collider:consolidate() -- will merge incident convex polygons together, makes for less iterations if applicable
Collider Iterating
There's the expensive :ipairs() method which use
