local V = Vector3.new
local Formulas = {}
Formulas.Basic = {
new = function(x, y, z) return V(x, y, z) end,
zero = function() return V(0, 0, 0) end,
one = function() return V(1, 1, 1) end,
getX = function(a) return a.X end,
getY = function(a) return a.Y end,
getZ = function(a) return a.Z end,
min = function(a, b) return V(math.min(a.X,b.X), math.min(a.Y,b.Y), math.min(a.Z,b.Z)) end,
max = function(a, b) return V(math.max(a.X,b.X), math.max(a.Y,b.Y), math.max(a.Z,b.Z)) end,
clamp = function(a, lo, hi) return V(
math.clamp(a.X, lo.X, hi.X),
math.clamp(a.Y, lo.Y, hi.Y),
math.clamp(a.Z, lo.Z, hi.Z)
) end,
abs = function(a) return V(math.abs(a.X), math.abs(a.Y), math.abs(a.Z)) end,
floor = function(a) return V(math.floor(a.X), math.floor(a.Y), math.floor(a.Z)) end,
ceil = function(a) return V(math.ceil(a.X), math.ceil(a.Y), math.ceil(a.Z)) end,
round = function(a) return V(math.round(a.X), math.round(a.Y), math.round(a.Z)) end,
sign = function(a) return V(math.sign(a.X), math.sign(a.Y), math.sign(a.Z)) end,
snap = function(a, grid) return V(
math.floor(a.X / grid + 0.5) * grid,
math.floor(a.Y / grid + 0.5) * grid,
math.floor(a.Z / grid + 0.5) * grid
) end,
sizelock = function(a: number) return V(a,a,a) end
}
Formulas.Length = {
magnitude = function(a) return a.Magnitude end,
magnitudeSq = function(a) return a.X*a.X + a.Y*a.Y + a.Z*a.Z end,
distance = function(a, b) return (a - b).Magnitude end,
distanceSq = function(a, b) local d = a - b; return d.X*d.X + d.Y*d.Y + d.Z*d.Z end,
manhattan = function(a, b) local d = a - b; return math.abs(d.X) + math.abs(d.Y) + math.abs(d.Z) end,
horizontal = function(a, b) return (V(a.X, 0, a.Z) - V(b.X, 0, b.Z)).Magnitude end,
vertical = function(a, b) return math.abs(a.Y - b.Y) end,
inRange = function(a, b, range) return Formulas.Length.distanceSq(a, b) <= range * range end,
}
Formulas.Direction = {
fromTo = function(a, b) return (b - a).Unit end,
normalize = function(a) return a.Unit end,
safeUnit = function(a)
if a.Magnitude < 1e-6 then return V(0, 0, 0) end
return a.Unit
end,
fromYaw = function(yaw) return V(-math.sin(yaw), 0, -math.cos(yaw)) end,
fromAngles = function(yaw, pitch) return V(
-math.sin(yaw) * math.cos(pitch),
math.sin(pitch),
-math.cos(yaw) * math.cos(pitch)
) end,
toYaw = function(dir) return math.atan2(-dir.X, -dir.Z) end,
toPitch = function(dir) return math.asin(dir.Unit.Y) end,
reflect = function(dir, normal) return dir - 2 * dir:Dot(normal) * normal end,
project = function(a, b) return b * (a:Dot(b) / b:Dot(b)) end,
reject = function(a, b) return a - Formulas.Direction.project(a, b) end,
slide = function(dir, normal) return dir - normal * dir:Dot(normal) end,
rotateAround = function(vec, axis, angle)
axis = axis.Unit
local cos = math.cos(angle)
local sin = math.sin(angle)
return vec * cos + axis:Cross(vec) * sin + axis * axis:Dot(vec) * (1 - cos)
end,
rotateY = function(vec, angle)
local cos = math.cos(angle)
local sin = math.sin(angle)
return V(vec.X * cos + vec.Z * sin, vec.Y, -vec.X * sin + vec.Z * cos)
end,
}
Formulas.Products = {
dot = function(a, b) return a:Dot(b) end,
cross = function(a, b) return a:Cross(b) end,
angle = function(a, b) return math.acos(math.clamp(a.Unit:Dot(b.Unit), -1, 1)) end,
angleDeg = function(a, b) return math.deg(Formulas.Products.angle(a, b)) end,
signedAngle = function(a, b, axis)
local cross = a:Cross(b)
local dot = a:Dot(b)
local sign = math.sign(cross:Dot(axis))
return math.atan2(cross.Magnitude * sign, dot)
end,
isPerp = function(a, b) return math.abs(a:Dot(b)) < 1e-6 end,
isParallel = function(a, b) return a:Cross(b).Magnitude < 1e-6 end,
triple = function(a, b, c) return a:Dot(b:Cross(c)) end,
}
Formulas.Interpolation = {
lerp = function(a, b, t) return a:Lerp(b, t) end,
lerpClamped = function(a, b, t) return a:Lerp(b, math.clamp(t, 0, 1)) end,
smoothstep = function(a, b, t)
t = math.clamp(t, 0, 1)
t = t * t * (3 - 2 * t)
return a:Lerp(b, t)
end,
smootherstep = function(a, b, t)
t = math.clamp(t, 0, 1)
t = t * t * t * (t * (t * 6 - 15) + 10)
return a:Lerp(b, t)
end,
easeIn = function(a, b, t) return a:Lerp(b, t * t) end,
easeOut = function(a, b, t) return a:Lerp(b, 1 - (1 - t) * (1 - t)) end,
expSmooth = function(current, target, speed, dt)
local alpha = 1 - math.exp(-speed * dt)
return current:Lerp(target, alpha)
end,
spring = function(current, target, velocity, stiffness, damping, dt)
local force = (target - current) * stiffness
local damp = velocity * damping
velocity = velocity + (force - damp) * dt
return current + velocity * dt, velocity
end,
moveTowards = function(current, target, maxDelta)
local diff = target - current
local dist = diff.Magnitude
if dist <= maxDelta or dist < 1e-6 then return target end
return current + diff.Unit * maxDelta
end,
quadBezier = function(p0, p1, p2, t)
local u = 1 - t
return p0 * (u * u) + p1 * (2 * u * t) + p2 * (t * t)
end,
cubicBezier = function(p0, p1, p2, p3, t)
local u = 1 - t
return p0 * (u*u*u) + p1 * (3*u*u*t) + p2 * (3*u*t*t) + p3 * (t*t*t)
end,
catmullRom = function(p0, p1, p2, p3, t)
local t2 = t * t
local t3 = t2 * t
return (p1 * 2 + (p2 - p0) * t + (p0 * 2 - p1 * 5 + p2 * 4 - p3) * t2 + (p1 * 3 - p0 - p2 * 3 + p3) * t3) * 0.5
end,
}
Formulas.Spatial = {
midpoint = function(a, b) return (a + b) * 0.5 end,
centroid = function(points)
local sum = V(0, 0, 0)
for _, p in ipairs(points) do sum += p end
return sum / #points
end,
barycenter = function(points, weights)
local sum = V(0, 0, 0)
local total = 0
for i, p in ipairs(points) do
sum += p * weights[i]
total += weights[i]
end
return sum / total
end,
offset = function(pos, dir, dist) return pos + dir.Unit * dist end,
alongLine = function(a, b, t) return a + (b - a) * t end,
closestOnSegment = function(p, a, b)
local ab = b - a
local t = math.clamp((p - a):Dot(ab) / ab:Dot(ab), 0, 1)
return a + ab * t
end,
closestOnLine = function(p, a, b)
local ab = b - a
local t = (p - a):Dot(ab) / ab:Dot(ab)
return a + ab * t
end,
perpXZ = function(dir) return V(-dir.Z, 0, dir.X) end,
perp3D = function(dir)
local up = math.abs(dir.Y) < 0.99 and V(0, 1, 0) or V(1, 0, 0)
return dir:Cross(up).Unit
end,
randomInSphere = function(center, radius)
local dir = V(
math.random() * 2 - 1,
math.random() * 2 - 1,
math.random() * 2 - 1
)
if dir.Magnitude < 1e-6 then return center end
return center + dir.Unit * (math.random() ^ (1/3)) * radius
end,
randomOnSphere = function(center, radius)
local theta = math.random() * math.pi * 2
local phi = math.acos(math.random() * 2 - 1)
return center + V(
math.sin(phi) * math.cos(theta),
math.sin(phi) * math.sin(theta),
math.cos(phi)
) * radius
end,
randomInCircle = function(center, radius)
local angle = math.random() * math.pi * 2
local r = math.sqrt(math.random()) * radius
return center + V(math.cos(angle) * r, 0, math.sin(angle) * r)
end,
randomInBox = function(center, size)
return center + V(
(math.random() - 0.5) * size.X,
(math.random() - 0.5) * size.Y,
(math.random() - 0.5) * size.Z
)
end,
}
Formulas.Camera = {
forward = function(cf) return cf.LookVector end,
right = function(cf) return cf.RightVector end,
up = function(cf) return cf.UpVector end,
toLocal = function(cf, worldVec) return cf:VectorToObjectSpace(worldVec) end,
toWorld = function(cf, localVec) return cf:VectorToWorldSpace(localVec) end,
posToLocal = function(cf, worldPos) return cf:PointToObjectSpace(worldPos) end,
posToWorld = function(cf, localPos) return cf:PointToWorldSpace(localPos) end,
inputToWorld = function(cf, inputX, inputZ)
return (cf.LookVector * -inputZ + cf.RightVector * inputX).Unit
end,
isInFront = function(cf, point, maxDist)
local local_ = cf:PointToObjectSpace(point)
return local_.Z < 0 and (not maxDist or local_.Magnitude <= maxDist)
end,
inFrustum = function(cf, point, fovDeg, aspect, near, far)
local local_ = cf:PointToObjectSpace(point)
if local_.Z > -near or local_.Z < -far then return false end
local halfV = math.tan(math.rad(fovDeg * 0.5)) * -local_.Z
local halfH = halfV * aspect
return math.abs(local_.X) <= halfH and math.abs(local_.Y) <= halfV
end,
}
Formulas.Movement = {
velocityTo = function(from, to, time)
return (to - from) / time
end,
displacement = function(velocity, time) return velocity * time end,
projectile = function(from, to, gravity, angleDeg)
local diff = to - from
local xz = V(diff.X, 0, diff.Z).Magnitude
local y = diff.Y
local angle = math.rad(angleDeg)
local v2 = (gravity * xz * xz) / (2 * math.cos(angle)^2 * (xz * math.tan(angle) - y))
if v2 < 0 then return nil, nil end
local v = math.sqrt(v2)
local dir = V(diff.X, 0, diff.Z).Unit
local vel = dir * (v * math.cos(angle)) + V(0, v * math.sin(angle), 0)
local t = xz / (v * math.cos(angle))
return vel, t
end,
optimalAngle = function() return math.rad(45) end,
jumpVelocity = function(height, gravity)
return math.sqrt(2 * gravity * height)
end,
jumpHeight = function(velocity, gravity)
return (velocity * velocity) / (2 * gravity)
end,
airTime = function(velocity, gravity)
return 2 * velocity / gravity
end,
terminalVelocity = function(gravity, dragCoeff)
return math.sqrt(gravity / dragCoeff)
end,
applyDrag = function(velocity, drag, dt)
return velocity * math.exp(-drag * dt)
end,
airResist = function(velocity, coeff, dt)
return velocity * (1 - coeff * dt)
end,
gravityStep = function(velocity, gravity, dt)
return velocity + V(0, -gravity * dt, 0)
end,
bounce = function(velocity, normal, restitution)
restitution = restitution or 1
local normalComponent = normal * velocity:Dot(normal)
local tangent = velocity - normalComponent
return tangent - normalComponent * restitution
end,
friction = function(velocity, normal, coeff, dt)
local tangent = Formulas.Direction.slide(velocity, normal)
if tangent.Magnitude < 1e-6 then return V(0, 0, 0) end
local decel = tangent.Unit * coeff * dt
if decel.Magnitude >= tangent.Magnitude then return V(0, 0, 0) end
return tangent - decel
end,
centripetal = function(velocity, radius)
return velocity.Magnitude^2 / radius
end,
circleMotion = function(center, radius, angle)
return center + V(math.cos(angle) * radius, 0, math.sin(angle) * radius)
end,
orbit = function(center, radius, angle, tilt)
local x = math.cos(angle) * radius
local z = math.sin(angle) * radius
local y = math.sin(angle) * (tilt or 0) * radius
return center + V(x, y, z)
end,
}
Formulas.AI = {
seek = function(position, target, maxSpeed)
return (target - position).Unit * maxSpeed
end,
flee = function(position, target, maxSpeed)
return (position - target).Unit * maxSpeed
end,
arrive = function(position, target, maxSpeed, slowRadius)
local toTarget = target - position
local dist = toTarget.Magnitude
if dist < 1e-6 then return V(0, 0, 0) end
local speed = maxSpeed
if dist < slowRadius then
speed = maxSpeed * (dist / slowRadius)
end
return toTarget.Unit * speed
end,
pursue = function(position, targetPos, targetVel, maxSpeed)
local toTarget = targetPos - position
local lookAhead = toTarget.Magnitude / maxSpeed
local futurePos = targetPos + targetVel * lookAhead
return Formulas.AI.seek(position, futurePos, maxSpeed)
end,
evade = function(position, targetPos, targetVel, maxSpeed)
local toTarget = position - targetPos
local lookAhead = toTarget.Magnitude / maxSpeed
local futurePos = targetPos + targetVel * lookAhead
return Formulas.AI.flee(position, futurePos, maxSpeed)
end,
wander = function(position, forward, wanderAngle, wanderRadius, wanderDist)
local circleCenter = position + forward * wanderDist
local offset = V(math.cos(wanderAngle) * wanderRadius, 0, math.sin(wanderAngle) * wanderRadius)
return circleCenter + offset - position
end,
separation = function(position, neighbors, desiredDist)
local steer = V(0, 0, 0)
for _, n in ipairs(neighbors) do
local toMe = position - n
local dist = toMe.Magnitude
if dist < desiredDist and dist > 1e-6 then
steer += toMe.Unit * (desiredDist - dist) / desiredDist
end
end
return steer
end,
alignment = function(neighbors)
if #neighbors == 0 then return V(0, 0, 0) end
local sum = V(0, 0, 0)
for _, v in ipairs(neighbors) do sum += v end
return (sum / #neighbors).Unit
end,
cohesion = function(position, neighbors)
if #neighbors == 0 then return V(0, 0, 0) end
local sum = V(0, 0, 0)
for _, p in ipairs(neighbors) do sum += p end
local center = sum / #neighbors
return (center - position).Unit
end,
flock = function(position, velocity, neighbors, weights)
weights = weights or { sep = 1.5, ali = 1.0, coh = 1.0 }
local sep = Formulas.AI.separation(position, neighbors, 10) * weights.sep
local ali = Formulas.AI.alignment(neighbors) * weights.ali
local coh = Formulas.AI.cohesion(position, neighbors) * weights.coh
return sep + ali + coh
end,
inFOV = function(eye, forward, target, fovDeg, maxDist)
local toTarget = target - eye
local dist = toTarget.Magnitude
if dist > maxDist then return false end
local angle = math.deg(math.acos(math.clamp(forward.Unit:Dot(toTarget.Unit), -1, 1)))
return angle <= fovDeg * 0.5
end,
lineOfSight = function(from, to) return (to - from).Unit end,
}
Formulas.Procedural = {
hash = function(v)
local n = v.X * 374761393 + v.Y * 668265263 + v.Z * 2147483647
n = bit32.bxor(n, bit32.rshift(n, 13))
n = n * 1274126177
return (bit32.band(n, 0xFFFFFF) / 0xFFFFFF)
end,
valueNoise = function(v)
local i = V(math.floor(v.X), math.floor(v.Y), math.floor(v.Z))
local f = v - i
f = V(f.X*f.X*(3-2*f.X), f.Y*f.Y*(3-2*f.Y), f.Z*f.Z*(3-2*f.Z))
local function corner(dx, dy, dz)
return Formulas.Procedural.hash(i + V(dx, dy, dz))
end
local c000 = corner(0,0,0); local c100 = corner(1,0,0)
local c010 = corner(0,1,0); local c110 = corner(1,1,0)
local c001 = corner(0,0,1); local c101 = corner(1,0,1)
local c011 = corner(0,1,1); local c111 = corner(1,1,1)
local x00 = c000 + (c100 - c000) * f.X
local x10 = c010 + (c110 - c010) * f.X
local x01 = c001 + (c101 - c001) * f.X
local x11 = c011 + (c111 - c011) * f.X
local y0 = x00 + (x10 - x00) * f.Y
local y1 = x01 + (x11 - x01) * f.Y
return y0 + (y1 - y0) * f.Z
end,
fbm = function(v, octaves, lacunarity, gain)
octaves = octaves or 4
lacunarity = lacunarity or 2.0
gain = gain or 0.5
local sum = 0
local amp = 1
local freq = 1
local max = 0
for _ = 1, octaves do
sum += Formulas.Procedural.valueNoise(v * freq) * amp
max += amp
amp *= gain
freq *= lacunarity
end
return sum / max
end,
warp = function(v, strength)
return v + V(
Formulas.Procedural.valueNoise(v),
Formulas.Procedural.valueNoise(v + V(5.2, 1.3, 7.1)),
Formulas.Procedural.valueNoise(v + V(9.7, 3.4, 2.8))
) * strength
end,
fibonacciSphere = function(n, radius)
local points = {}
local golden = math.pi * (3 - math.sqrt(5))
for i = 0, n - 1 do
local y = 1 - (i / (n - 1)) * 2
local r = math.sqrt(1 - y * y)
local theta = golden * i
table.insert(points, V(math.cos(theta) * r, y, math.sin(theta) * r) * radius)
end
return points
end,
scatter = function(count, minDist, bounds)
local points = {}
local attempts = 0
while #points < count and attempts < count * 30 do
attempts += 1
local p = Formulas.Spatial.randomInBox(V(0,0,0), bounds)
local ok = true
for _, q in ipairs(points) do
if (p - q).Magnitude < minDist then ok = false; break end
end
if ok then table.insert(points, p) end
end
return points
end,
}
Formulas.Combat = {
lead = function(shooter, targetPos, targetVel, projSpeed)
local toTarget = targetPos - shooter
local a = targetVel:Dot(targetVel) - projSpeed * projSpeed
local b = 2 * toTarget:Dot(targetVel)
local c = toTarget:Dot(toTarget)
local disc = b * b - 4 * a * c
if disc < 0 then return targetPos end
local t = (-b - math.sqrt(disc)) / (2 * a)
if t < 0 then t = (-b + math.sqrt(disc)) / (2 * a) end
return targetPos + targetVel * t
end,
spread = function(dir, angleDeg)
local angle = math.rad(angleDeg)
local random = V(
math.random() * 2 - 1,
math.random() * 2 - 1,
math.random() * 2 - 1
)
if random.Magnitude < 1e-6 then return dir end
local perp = dir:Cross(random).Unit
return Formulas.Direction.rotateAround(dir, perp, angle * math.random())
end,
knockback = function(explosionPos, targetPos, force)
return (targetPos - explosionPos).Unit * force
end,
inCone = function(origin, forward, target, angleDeg, range)
local toTarget = target - origin
local dist = toTarget.Magnitude
if dist > range then return false end
local angle = math.deg(math.acos(math.clamp(forward.Unit:Dot(toTarget.Unit), -1, 1)))
return angle <= angleDeg * 0.5
end,
falloff = function(distance, minDist, maxDist, maxDmg, minDmg)
if distance <= minDist then return maxDmg end
if distance >= maxDist then return minDmg end
local t = (distance - minDist) / (maxDist - minDist)
return maxDmg + (minDmg - maxDmg) * t
end,
aimAt = function(turretPos, targetPos)
local diff = targetPos - turretPos
local yaw = math.atan2(-diff.X, -diff.Z)
local pitch = math.asin(diff.Unit.Y)
return yaw, pitch
end,
}
Formulas.Utility = {
toCFrame = function(pos, lookDir, upDir)
return CFrame.lookAt(pos, pos + lookDir, upDir or V(0, 1, 0))
end,
fromCFrame = function(cf) return cf.Position end,
toTable = function(v) return { X = v.X, Y = v.Y, Z = v.Z } end,
fromTable = function(t) return V(t.X, t.Y, t.Z) end,
format = function(v) return string.format("(%.1f, %.1f, %.1f)", v.X, v.Y, v.Z) end,
approx = function(a, b, epsilon)
epsilon = epsilon or 1e-6
return (a - b).Magnitude < epsilon
end,
toColor = function(v)
return Color3.new(
math.clamp(math.abs(v.X) / 255, 0, 1),
math.clamp(math.abs(v.Y) / 255, 0, 1),
math.clamp(math.abs(v.Z) / 255, 0, 1)
)
end,
toCompass = function(dir)
local deg = math.deg(math.atan2(dir.X, -dir.Z))
return (deg + 360) % 360
end,
fromCompass = function(deg)
local rad = math.rad(deg)
return V(math.sin(rad), 0, -math.cos(rad))
end,
}
return Formulas