They also would create unwanted behaviour in your application: if two points are close enough to collide, but are right on the border between two octants and thus in different octants, they will not collide.
For example, if the collission tolerance is the distance between these two points, then these points should collide. But they won’t, because they’re in different octants.
Anyways, I wrote a script for you. It’ll generate a bunch of points, then check if any points are in the same octant. If they are, they’ll be made red.
local function isPointWithinBounds(point, minBound, maxBound)
return point.X >= minBound.X and point.X <= maxBound.X
and point.Y >= minBound.Y and point.Y <= maxBound.Y
and point.Z >= minBound.Z and point.Z <= maxBound.Z
end
local function divideVolumeInEights(minBound, maxBound)
local subdivisions = {}
local halfSize = (maxBound - minBound) / 2
local center = minBound + halfSize
for x = 0, 1 do
for y = 0, 1 do
for z = 0, 1 do
local offset = Vector3.new(x, y, z) * halfSize
local newMinBound = minBound + offset
local newMaxBound = newMinBound + halfSize
table.insert(subdivisions, {newMinBound, newMaxBound})
end
end
end
return subdivisions
end
local function visualizeDivision(minBound, maxBound)
local part = Instance.new("Part")
part.Size = maxBound - minBound
part.Position = minBound + part.Size / 2
part.Anchored = true
part.CanCollide = false
part.Color = Color3.new(1, 0, 0)
part.Transparency = 0.9
part.Parent = workspace
return part
end
local function octree(points, minBound, maxBound, visualize, depth)
depth = depth or 1
if #points <= 1 or depth > 3 then
-- Terminal condition: either only one point or max depth reached.
if visualize and #points > 1 then
for _, entry in ipairs(points) do
if entry[2] then
entry[2].Color = Color3.new(1, 0, 0)
end
end
end
return
end
local subdivisions = divideVolumeInEights(minBound, maxBound)
for _, bounds in ipairs(subdivisions) do
local subMin, subMax = bounds[1], bounds[2]
local pointsInDivision = {}
if visualize then
visualizeDivision(subMin, subMax)
end
for _, entry in ipairs(points) do
local point, part = entry[1], entry[2]
if isPointWithinBounds(point, subMin, subMax) then
table.insert(pointsInDivision, {point, part})
end
end
if #pointsInDivision > 0 then
octree(pointsInDivision, subMin, subMax, visualize, depth + 1)
end
end
end
local function generateRandomPointsInVolume(minBound, maxBound, quantity, visualize)
local points = {}
for i = 1, quantity or 100 do
local x = math.random(minBound.X, maxBound.X)
local y = math.random(minBound.Y, maxBound.Y)
local z = math.random(minBound.Z, maxBound.Z)
local point = Vector3.new(x, y, z)
local part
if visualize then
part = Instance.new("Part")
part.Size = Vector3.new(0.4, 0.4, 0.4)
part.Position = point
part.Anchored = true
part.CanCollide = false
part.Color = Color3.new(0, 1, 0)
part.Transparency = 0
part.Parent = workspace
end
table.insert(points, {point, part})
end
return points
end
-- Test setup
local minBound, maxBound = Vector3.zero, Vector3.new(50, 50, 50)
local points = generateRandomPointsInVolume(minBound, maxBound, 100, true)
octree(points, minBound, maxBound, true)