Hey I didn’t forget about this, I apologize for the super late response, I got busy the last few days and was feeling under the weather so wasn’t able to write anything up at that time.
Anyway, depending on what your requirements are, there are multiple different ways to approach this.
Roblox has a few instances relating to curves, which is notably used for the curve animation editor, however you can use it to create a spline that actually passes through the control points. I give a very basic example here, however suphi has a very in depth tutorial on how to use them here
If you don’t want to use float curves, there are many resources here on the forum that can give you what you’re looking for, for example here,
Another one is to create a bezier spline with each point, however the path will not pass through each of the vertices (ie the spline will be contained within the convex hull from which the vertices are composed), and because it uses factorialization, it can get expensive with many vertices:
local function binomialCoefficient(n: number, k: number) -- what the binomial coefficient is used for in the context of bezier curves is to determine the influence of each vertex to the final curve
-- "binomial coefficient = n! / (k! (n-k)!)"
-- ! denotes factorialization, which is the product of all positive integers up to that number, inclusively
local result = 1
for i = 1, k do
result *= n - (k - i) -- 1 * 2 * 3 ... k * (n - k + 1) * ... n
result /= i -- divide by 1, then 2, then 3 and so on up to `k` (k!)
end
return result
end
local function bernsteinPolynomial(n: number, k: number, t: number) -- this is the polynomial that determines the influence of each vertex to the final curve at a given `t`
-- n = vertex count
-- k = index of current vertex
-- t = 0 to 1, the position on the curve
-- B_k,n (u) = C(n, k) * u ^ k * (1 - u) ^ (n - k)
-- from the article, "C(n, k) is a binomial coefficient"
return binomialCoefficient(n, k) * t ^ k * (1 - t) ^ (n - k)
end
local function bezierSpline(vertices: { Vector3 }, t: number)
local vertexCount = #vertices - 1
local pointOnCurve = Vector3.zero
for i, vertex in vertices do -- sum up all vertices influence on the final curve at `t`
pointOnCurve += vertex * bernsteinPolynomial(vertexCount, i - 1, t)
end
return pointOnCurve
end
Derived from:
https://people.eecs.berkeley.edu/~jfc/cs184f98/lec21/lec21.html
To use it you would just hook up whatever event you’d like to use (ie heartbeat or use a loop) and add it to the elapsed time, then divide it by the total time to obtain a number between 0 and 1 which can be passed to the bezierSpline function.
local splineVertices = workspace:WaitForChild('SplineVertices')
local splineVisuals = workspace:WaitForChild('SplineVisuals')
local vertices: { Vector3 } = {}
local function updateCurve()
splineVisuals:ClearAllChildren()
for i = 1, 100 do -- roughly the path the object will follow
-- `i / 25` is the position on the curve, from 0 to 1
local pointOnCurve = bezierSpline(vertices, i / 100)
local newPart = Instance.new('Part')
newPart.Anchored = true
newPart.CanCollide = false
newPart.Material = Enum.Material.SmoothPlastic
newPart.Size = Vector3.new(0.2, 0.2, 0.2)
newPart.Color = Color3.new(1, 1, 1)
newPart.Parent = splineVisuals
local prevToThis = bezierSpline(vertices, (i + 1) / 100) - pointOnCurve -- dir = goal - origin
local tangent = prevToThis.Unit
local magnitude = prevToThis.Magnitude
newPart.CFrame = CFrame.lookAt(pointOnCurve, pointOnCurve + tangent, Vector3.yAxis)
newPart.Size = Vector3.new(0.2, 0.2, magnitude)
newPart.Parent = splineVisuals
end
end
local curvePart = Instance.new('Part')
curvePart.Anchored = true
curvePart.CanCollide = false
curvePart.Material = Enum.Material.SmoothPlastic
curvePart.Size = Vector3.new(0.5, 0.5, 0.5)
curvePart.Color = Color3.new(1)
curvePart.Parent = workspace
task.spawn(function()
local elapsed = 0
local traverseTime = 10
while true do
local pointOnCurve = bezierSpline(vertices, elapsed / traverseTime)
local tangent = (bezierSpline(vertices, (elapsed + 0.1) / traverseTime) - pointOnCurve).Unit
-- `+ 0.1` is to get a point slightly ahead of the current one so we can get the orientation
curvePart.CFrame = CFrame.lookAt(pointOnCurve, pointOnCurve + tangent, Vector3.yAxis)
elapsed += task.wait()
if elapsed > traverseTime then
elapsed = 0
end
end
end)
for _, vertex in splineVertices:GetChildren() do
vertices[tonumber(vertex.Name) :: number] = vertex.Position
vertex:GetPropertyChangedSignal('CFrame'):Connect(function()
vertices[tonumber(vertex.Name) :: number] = vertex.Position
updateCurve()
end)
end
updateCurve()
splineTester.rbxl (52.3 KB)
There is a more refined version of this available here which allows you to move an object at a consistent speed regardless of vertex distance/density and is probably more optimized than mine so I think that it may also be worth checking out.