Bolt - Broad & Narrow Phase Collision

Bolt

Custom Collision Detection featuring methods for both Broad and Narrow phase

GitHub Discord


Use Cases

Due to the library being completely decoupled from physical parts, you can run collision logic directly on object data, which is helpful for all kinds of applications where you don’t want a physical part to exist just to run a collision query. One example for this would be lag compensation where you would otherwise have to rewind all the physical player hitboxes just to use the native roblox methods.

Additionally, raycasts and shapecasts in this library still report hits when the cast started inside of the shape its checking against, which is desirable for hit detection in general.

This also covers capsules, as capsules are very useful for collision detection in general.

Features

  • Broad Phase
    • Dynamic AABB Trees for efficient candidate selection
  • Narrow Phase
    • special-cased functions for common primitives
    • GJK for all other primitives
    • raycast and shapecast for all primitives
    • MPR for collision information

Installation

Either get the .rbxm from the latest release or through wally:

bolt = "unityjaeger/bolt@0.3.3"

Performance

For Intersection tests you can expect the same performance as equivalent roblox methods, in some cases Bolt will be even faster.
image
Here is an example of GetPartsInPart vs. Bolt’s Broad Phase + GJK implementation, as you can see Bolt beats PartsInPart by roughly 15%, it would be even faster if special-cased intersection methods were used.

Known Limitations

GJK casts will not have usable contact information for cases where the cast starts inside of the other shape, and normals will not always be what you expect around vertices/edges due to the way GJK performs shapecasts. If you need penetration depth + a normal in this case, use MPR.

Quick Example

-- setup
local tree = bolt.aabb_tree.new({ aabb_padding = 1 })

-- register objects (once, or when they are created)
for id, obj in objects do
    tree:insert(id, obj.cf, obj.shape)
end

-- every frame, update moved objects
for id, obj in objects do
    tree:move(id, obj.cf)
end

-- broad + narrow phase query
local candidates = tree:query_shape(query_cf, query_shape)
for _, id in candidates do
    local obj = objects[id]
    --use the appropriate collision function, or gjk for the narrow phase check
    if bolt.collision.box_box(query_cf, query_shape, obj.cf, obj.shape) then
        -- confirmed hit
    end
end

Documentation can be found here

28 Likes

heres also a fun little demonstration


all these projectiles used bolt gjk raycasts for the hit detection (and a dynamic aabb tree for the walls)
image
native raycasts on the other hand take 3x longer on average for the same simulation
image

8 Likes

Bolt v0.2.0

Additions

  • partial and full aabb tree rebuilds using binned SAH for maintained tree quality
  • cost heuristic for deciding when to rebuild
  • static aabb trees for geometry that doesn’t move

Documentation for the new stuff is once again on the github

1 Like

another little demonstration


this is using the gjk shapecast for swept melee hitboxes

naturally since the hitbox is swept, fast animations arent an issue either
additionally due to the way shapecasts work in Bolt, hits where the shapecast and the rig already started out as intersecting will still count

2 Likes

although i dont think this suggestion should belong to the library but i wish there was sets of functions where i could get 3 of these datas in return, im working on a solution for one of my framework that needs a new character controller but i find shapecast not suitable for it cuz they dont react dynamically to objects like spinning walls (this was when im using FMShapecast), here’s what i think it should return

  • ContactNormal (in Vector3)
  • ContactPoint (also in Vector3)
  • Penetration (number)

otherwise this seems promising, i already had these added to my physics engine but haven’t tried my approach on it today cuz there was smth missing

1 Like

yea intersection functions with contact information would be a natural extension of GJK in the form of EPA but currently a good EPA implementation would be complex and pretty slow, i might add EPA once native classes are actually available but for now the focus of this library is just purely on intersection tests without contact information (except for the ray/shapecasting)

1 Like

instead of EPA, i might add MPR since its simpler, ill have to implement it and see how it goes

1 Like

yea minkowski portal refinement and sutherland-hodgman clipping will be added too, as a step u can do after confirming an intersection has occured through gjk or any of the intersection functions so u could theoretically write a physics engine with Bolt or implement collide and slide with a depenetration step so shapecasts cant make u slip through walls/the ground

1 Like

MPR will be added tomorrow (and then sutherland-hodgman clipping at a later date)

4 Likes

Bolt v.0.3.0

  • Added Minkowski Portal Refinement (MPR) so u can now get contact information from intersections
2 Likes

sutherland-hodgman clipping will come some time soon too for stuff like resting box-box contacts, which is needed for physics engines

1 Like

Bolt v0.3.3

0.3.1, 0.3.2 and this one were just fixes for bolt.mpr.is_seperated so it works properly :broken_heart:

1 Like

just a heads up: next up ill be reworking the api a bit so it isnt as annoying to do anything with the gjk/mpr functions, for example a shapecast currently involves:

local start = cf_b:ToObjectSpace(cf_a)
local direction = cf_b:VectorToObjectSpace(direction)
local hit, distance, normal = bolt.gjk.shapecast(
  start, direction, 1e-3,
  .05, shape_a, .05, shape_b
)
local world_hit = cf_b:PointToWorldSpace(hit)
local world_normal = cf_b:VectorToWorldSpace(normal)

and after the rework its gonna be just

local hit, distance, normal = bolt.gjk.shapecast(
  start, direction, 1e-3, shape_a, shape_b
)
1 Like

Bolt v0.4.0

improvements:

  • gjk/mpr no longer require u to do world/object space transformations urself

fixes:

  • mpr now handles edge cases properly

also made an actual docs site

thanks, i was about to say having 2 contact points felt very annoying cuz my physics engine only uses one, also there was this bug where if i moved a sphere with another sphere, it flickers like hell but only when it gets far enough (iirc or when it gets close to the point)

the flickering is probably due to the tolerance u use on mpr, for physics simulation u want a small tolerance, 1e-6 produced a stable point and normal for me
also didnt mention it in the log but mpr also only returns 1 point now since the 2 points are identical

thanks that fixed but another problem rises up again, this time it’s mainly the intersect part
popping the bolt.mpr.intersects() inside run service causes the whole game to freeze once both shapes intersect (it’s running on server “Run” without player in this pic)

ngl this is just an issue with floating point precision with 32 bit floats, ill cap the mpr in_tolerance to 1e-4 which still works without crashing

Bolt v0.4.2

changes:

  • flipped sign of returned normal in MPR so it points from B → A for consistency
  • capped in_tolerance parameter to 1e-4 due to numerical precision issues leading to potential crashes

it feels more like the mpr impl is fundamentally broken the more i experiment, guess ill have to rewrite it or something