Skip to content

Instantly share code, notes, and snippets.

@KiraStack
Created February 1, 2026 19:14
Show Gist options
  • Select an option

  • Save KiraStack/6acd4ca5abf7e37f4f56232ac68543be to your computer and use it in GitHub Desktop.

Select an option

Save KiraStack/6acd4ca5abf7e37f4f56232ac68543be to your computer and use it in GitHub Desktop.
Shared module for generating and caching chunks for collision.
--[=[
@class TerrainCollision
@deprecated
Shared module for generating and caching chunks for collision.
]=]
-- Could use convex hulls (bloat)
-- but it is more complex and memory intensive
-- Constants
local GRIDSIZE = 4
local FATGRIDSIZE = 16
local SKIN_THICKNESS = vector.create(0.1, 0.1, 0.1)
-- Instances
local module = {
grid = {},
fatGrid = {},
}
-- Types
type self = typeof(module)
--[=[
@within TerrainCollision
@function FindAABB
@param part BasePart
@return number, number, number, number, number, number -- Handles AABB computation
]=]
local function FindAABB(part: BasePart): (number, number, number, number, number, number)
local pos = part.Position
local scale = part.Size / 2
local minx, miny, minz = math.huge, math.huge, math.huge
local maxx, maxy, maxz = -math.huge, -math.huge, -math.huge
-- Define min bounds
minx = math.min(minx, pos.X - scale.X)
miny = math.min(miny, pos.Y - scale.Y)
minz = math.min(minz, pos.Z - scale.Z)
-- Define max bounds
maxx = math.max(maxx, pos.X + scale.X)
maxy = math.max(maxy, pos.Y + scale.Y)
maxz = math.max(maxz, pos.Z + scale.Z)
-- Return
return minx, miny, minz, maxx, maxy, maxz
end
--[=[
@within TerrainCollision
@method Sweep
@param a vector
@param b vector
@return table [Distance, Fraction, Instance, Normal, Position] -- sweep result
]=]
function module:Sweep(
a: vector,
b: vector
): { Distance: number, Fraction: number, Instance: BasePart | nil, Normal: vector, Position: vector }
local data = {
Distance = 0,
Fraction = 1, -- refers to distance between `a` and collision point
Instance = nil,
Normal = vector.create(0, 1, 0), -- default to up
Position = b, -- default to given end position
}
-- Cache common variables
local checks = 0
local direction = (b - a)
local distance = vector.magnitude(direction)
local steps = math.ceil(distance / GRIDSIZE)
local delta = direction / steps
-- Loop through each step
for i = 1, steps do
-- Cache
local pos = a + (delta * i)
local min = pos - SKIN_THICKNESS
local max = pos + SKIN_THICKNESS
local candidates = self:QueryBox(min, max)
checks += #candidates
-- Loop through each candidate
for _, candidate: BasePart in ipairs(candidates) do
-- Cache
local cminx, cminy, cminz, cmaxx, cmaxy, cmaxz = FindAABB(candidate)
-- Compute intersection
if
max.x >= cminx
and min.x <= cmaxx
and max.y >= cminy
and min.y <= cmaxy
and max.z >= cminz
and min.z <= cmaxz
then
-- Found collision
data.Distance = distance * data.Fraction
data.Fraction = (i - 1) / steps -- (i - 1) because `i` is 1-indexed, but `Fraction` (>= 0) is 0-indexed
data.Position = a + direction * data.Fraction
data.Instance = candidate
print(candidate)
-- Return
return data
end
end
end
-- No collision found
data.Distance = math.huge -- distance is (basically) inf. at this point
return data
end
--[=[
@within TerrainCollision
@method QueryBox
@param min vector
@param max vector
@return () -- Handles AABB query
]=]
function module.QueryBox(self: self, min: vector, max: vector): { BasePart }
local results = {}
local size = vector.magnitude(max - min)
-- Handle fat grid
for x = (min.x // FATGRIDSIZE), (max.x // FATGRIDSIZE) do
for y = (min.y // FATGRIDSIZE), (max.y // FATGRIDSIZE) do
for z = (min.z // FATGRIDSIZE), (max.z // FATGRIDSIZE) do
local key = vector.create(x, y, z)
local cell = self.fatGrid[key]
if cell then
for i = 1, #cell do
results[#results + 1] = cell[i]
end
end
end
end
end
-- Handle normal grid
for x = (min.x // GRIDSIZE), (max.x // GRIDSIZE) do
for y = (min.y // GRIDSIZE), (max.y // GRIDSIZE) do
for z = (min.z // GRIDSIZE), (max.z // GRIDSIZE) do
local key = vector.create(x, y, z)
local cell = self.grid[key]
if cell then
for i = 1, #cell do
results[#results + 1] = cell[i]
end
end
end
end
end
-- Return
return results
end
--[=[
@within TerrainCollision
@method Visualize
@deprecated
@param state boolean
@return () -- Describe world grid
]=]
function module.SpawnDebugGridBox(self: self, state: boolean) end
--[=[
@within TerrainCollision
@method ComputeGrid
@return () -- Handles grid computation
]=]
function module.ComputeGrid(self: self)
self.grid = {}
self.fatGrid = {}
-- Loop through each part
for _, part in pairs(workspace:GetDescendants()) do
-- Handle ignored objects
if not part:IsA("BasePart") or part:IsA("Terrain") or not part.CanCollide then
continue
end
local minx, miny, minz, maxx, maxy, maxz = FindAABB(part)
local size = vector.magnitude(vector.create(maxx - minx, maxy - miny, maxz - minz))
if size > FATGRIDSIZE then
-- Handle fat grid
for x = (minx // FATGRIDSIZE), (maxx // FATGRIDSIZE) do
for y = (miny // FATGRIDSIZE), (maxy // FATGRIDSIZE) do
for z = (minz // FATGRIDSIZE), (maxz // FATGRIDSIZE) do
local key = vector.create(x, y, z)
local cell = self.fatGrid[key]
if not cell then
cell = {}
self.fatGrid[key] = cell
end
table.insert(cell, part)
end
end
end
else
-- Handle normal grid
for x = (minx // GRIDSIZE), (maxx // GRIDSIZE) do
for y = (miny // GRIDSIZE), (maxy // GRIDSIZE) do
for z = (minz // GRIDSIZE), (maxz // GRIDSIZE) do
local key = vector.create(x, y, z)
local cell = self.grid[key]
if not cell then
cell = {}
self.grid[key] = cell
end
table.insert(cell, part)
end
end
end
end
end
end
return module
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment