Introduction
I’ve created a module that allows you to create an nth degree Bezier Curve given Vector3s and BaseParts (to add points that can change as the BaseParts position changes). This module can return the position of a point at any interval t, where 0 ≤ t ≤ 1, and is able to return the derivative of the Bezier Curve at any interval t, where 0 ≤ t ≤ 1. This is especially useful when you want to create something like an arrow following a path. The arrow has to point in the direction of the tangent vector at a point, so this module allows that to happen.
I would also like to add that this module uses the explicit definition of a nth degree Bezier Curve to get a point at t. It creates a sum of the Bernstein basis polynomials of degree n, where n is the degree of the Bezier Curve. Getting the derivative of this explicit definition, you arrive with the formula that gives you the derivative of the Bezier Curve at point t.
Most modules here on the dev forum use the recursive definition of the Bezier Curve, which, although it is reliant and accurate, is slower and less versatile.
Usage
Usage
Usage of this module is very simple, first create a Bezier object with the constructor, and add points to the Bezier. Then, use the functions Bezier:CalculatePointAt(t) and Bezier:CalculateDerivativeAt(t). The constructor can take multiple Vector3 and BasePart inputs, so creating a Bezier with points is very easy.
Constructor:
local Bezier = require(script:WaitForChild("Bezier"))
local NewBezier = Bezier.new(workspace.P1, workspace.P2, workspace.P3, workspace.P4)
-- constructor: Bezier.new(...: Vector3 | BasePart): BezierObject
-- this creates a 4th degree Bezier (otherwise known as a Cubic Bezier Curve)
The constructor can take in multiple arguments, so you can create a nth degree Bezier right off the bat.
Properties:
print(NewBezier.Length) -- the length of the Bezier Curve created
print(NewBezier.LengthIterations) -- the number of times to iterate through the curve to get a length (higher the number, the higher the accuracy)
Methods:
NewBezier:AddBezierPoint(Point: Vector3 | BasePart, Index: number?)
-- adds a point to the Bezier object at the given index position
-- if an index is not given, it'll add the point at the end of the list of points
NewBezier:ChangeBezierPoint(Index: number, NewPoint: Vector3 | BasePart)
-- changes the point at the index to the given point
-- if the current point at the index is not found, then this will prompt an error
NewBezier:GetPoint(index: number): Vector3?
-- returns the point of the Bezier at the given index
-- if the point is not found, this will error
NewBezier:RemoveBezierPoint(index: number)
-- removes the BezierPoint at the given index
-- will not error if there isn't a BezierPoint at the index, but it really only works if there is
NewBezier:UpdateLength()
-- updates the bezier curve's length
-- function does not need to be called, the object does this automatically
NewBezier:CalculatePositionAt(t: number): Vector3
-- gets the position of the Bezier Curve at t
-- only works if there is at least 1 point in the Bezier
NewBezier:CalculatePositionRelativeToLength(t: number): Vector3
-- gets the position of the BezierCurve at t, relative to the Curve's length
-- only works if there is at least 1 point in the Bezier
-- t must be a percentage number between 0 and 1, referring to the percentage length of the curve
NewBezier:CalculateDerivativeAt(t: number): Vector3
-- gets the derivative of the Bezier Curve at t
-- only works if there are at least 2 points in the Bezier
NewBezier:CalculateDerivativeRelativeToLength(t: number): Vector3
-- gets the derivative of the Bezier Curve at t, relative to the Cuve's length
-- only works if there are at least 2 points in the Bezier
-- t must be a percentage number between 0 and 1, referring to the percentage length of the curve
NewBezier:CreateVector3Tween(object: Instance | {[any]: any}, propertyTable: {any}, TweenInfo, RelativeToLength: boolean?): Tween
-- tween an object's Vector3 property through the Bezier
-- the object does not have to be an Instance, it can also be a regular table
-- the object's properties will tween as long as you give the correct property indeces in the propertyTable
-- optional boolean at the end to make the tween relative to the length of the curve, or parameter t
NewBezier:CreateCFrameTween(object: Instance | {[any]: any}, propertyTable: {any}, TweenInfo, RelativeToLength: boolean?): Tween
-- tween an object's CFrame property through the Bezier
-- the object does not have to be an Instance, it can also be a regular table
-- the object's properties will tween as long as you give the correct property indeces in the propertyTable
-- optional boolean at the end to make the tween relative to the length of the curve or parameter t
Examples
Examples
Example 1
The following code produces a visual Bezier Curve in the workspace, controlled by the green parts (in the video). The parts are labeled P1, P2, P3, and P4 in order of the indeces of the points in the Bezier.
local Bezier = require(script:WaitForChild("Bezier"))
local NewBezier = Bezier.new(workspace.P1, workspace.P2, workspace.P3, workspace.P4)
local NumPoints = 20
local Points = {}
for i = 1, NumPoints do
local TargetPart = Instance.new("Part", workspace)
TargetPart.Size = Vector3.new(0.75, 0.75, 3)
TargetPart.BrickColor = BrickColor.new("Really red")
TargetPart.CanCollide = false
TargetPart.Anchored = true
table.insert(Points, TargetPart)
end
local Lines = {}
for i = 1, NumPoints - 1 do
local TargetPart = Instance.new("Part", workspace)
TargetPart.Size = Vector3.new(0.5, 0.5, 1)
TargetPart.BrickColor = BrickColor.new("Black")
TargetPart.CanCollide = false
TargetPart.Anchored = true
table.insert(Lines, TargetPart)
end
while wait() do
for i = 1, #Points do
local t = (i - 1) / (#Points - 1)
-- calculates the position and derivative of the Bezier Curve at t
local position = NewBezier:CalculatePositionAt(t)
local derivative = NewBezier:CalculateDerivativeAt(t)
-- sets the position and orientation of the point based on the
-- position and derivative of the Bezier Curve
Points[i].CFrame = CFrame.new(position, position + derivative)
end
for i = 1, #Lines do
local line = Lines[i]
local p1, p2 = Points[i].Position, Points[i + 1].Position
line.Size = Vector3.new(line.Size.X, line.Size.Y, (p2 - p1).Magnitude)
line.CFrame = CFrame.new(0.5 * (p1 + p2), p2)
end
end
Running this code in studio, we get the following:
Example 2
The previous example created a visual display for the Bezier that was made. Using this, we can now show how the Bezier Module can tween objects within the Bezier.
The following code creates two Tween objects using Bezier:CreateVector3Tween() and Bezier:CreateCFrameTween(), each acting on a part I named “GlowPart” parented to workspace. This works like a regular Tween.
local GlowPartTweenInfo = TweenInfo.new(15, Enum.EasingStyle.Sine, Enum.EasingDirection.Out, 0, false, 0)
local Tween = NewBezier:CreateVector3Tween(workspace.GlowPart, {"Position"}, GlowPartTweenInfo)
local Tween2 = NewBezier:CreateCFrameTween(workspace.GlowPart, {"CFrame"}, GlowPartTweenInfo)
Tween2:Play()
Running this in studio, we get the following:
Notice that as I move the control points in the Bezier, the path of the GlowPart also changes even though the Tween is playing. The path will constantly change as the control points are being changed, and the CFrame of the GlowPart changes accordingly.
Relative to Bezier Curve Length
Relative to Bezier Curve Length
You may be wondering what it means for the positions and derivatives of the Bezier Curve to be relative to the length of the Bezier Curve. This section provides an explanation of that.
Suppose you have a Bezier Curve made of 5 control points. The curve looks something like this:
As points are created within that curve, some points in the curve may lie very close to other points even if those points are spread at equal intervals. This means that, the point Bezier:CalculatePositionAt(0.25) is not always the same as the point at 25% of the Bezier Curve’s length. You can see this visually here:
I placed 100 points within this curve equally spread in the interval t. The variable t goes from 0 - 1 at an interval of 1/100. However, you can see that these points are not equally spread out. They become more compact in areas where the control points seem to go backwards in order.
Now this prompts the question, “how do we fix this?” Well, fixing this problem is easy now that we can easily calculate the derivative, we are able to calculate the arc length of the Bezier Curve using an approximation:
where l is the length of the Bezier Curve, r’(t) is the derivative of the Bezier Curve at t, and n is the number of iterations (for accuracy purposes).
Since we now have the arc length of the Bezier Curve, we can find values of t in the Bezier Curve where it returns a position where the length of the curve from the beginning of the curve to the position at t is equal to the given percentage of the length of the curve. Doing this, we end up with this result:
Now, all the points are equidistant from each other despite the fact that some control parts are closer and further away from each other. The points are being returned based on the percentage of the length of the curve. To give a visual for the effect that the RelativeToLength parameter gives in the functions Bezier:CreateVector3Tween() and Bezier:CreateCFrameTween(), I will tween two objects.
In the video above, the blue neon block is the tween relative to the length of the bezier, and the yellow neon block is the regular tween in the Bezier Curve. You can see that the yellow block slows down at the part where all the points are clamped up together, and speeds up at the points where they aren’t. The blue block moves at a constant speed throughout the entire curve.
Updates
October 25, 2021
- Organized module
- Removed global functions
- Fixed bugs that would error on “Back” and “Elastic” easing styles
- Added hidden
_connections
property to Bezier objects that contains connections made with BaseParts
Summary
The Bezier module is up for grabs, you can get it here:
Hopefully this helps you in any of your Bezier Curve needs!
(If you want more information on the math of Bezier Curves, you can see more here)