Math and Space
Spline
Smooth 3D paths through control points. Sample positions and orientations, move at constant speed, find the nearest point and build tubes.
Getting it
local Spline = Engine:GetPackage("Spline")Overview
A spline is a smooth curve defined by a few control points. Use it for camera paths, rails, roller coasters, rivers, NPC patrol routes or anything that should move along a curve.
Every spline is parameterised by t from 0 (start) to 1 (end); values outside that range are clamped. Because t does not grow evenly with distance on a curve, use GetPointByDistance or GetLength when you need constant speed.
local Spline = Engine:GetPackage("Spline")
local path = Spline.Catmull({
Vector3.new(0, 10, 0),
Vector3.new(40, 20, 10),
Vector3.new(80, 15, -20),
Vector3.new(120, 30, 0),
})
local length = path:GetLength()
local speed = 25 -- studs per second
local travelled = 0
RunService.Heartbeat:Connect(function(dt)
travelled = (travelled + speed * dt) % length
local t = path:Project(path:GetPointByDistance(travelled))
part.CFrame = path:GetCFrame(t)
end)Kinds of spline
| Constructor | Passes through points? | Notes |
|---|---|---|
Spline.Catmull(points, { loop?, tension? }) | Yes | The usual choice. tension 0 is the classic smooth curve; higher values tighten corners. Needs 2 points (3 for a loop). |
Spline.Linear(points, { loop? }) | Yes | Straight segments. |
Spline.Hermite(points, tangents, { loop? }) | Yes | You give the direction and strength at each point. |
Spline.BSpline(points, { loop? }) | No | Very smooth; points act as magnets. |
Spline.Bezier(points) | Only the ends | One Bézier curve through all control points. |
With loop = true the last point connects back to the first.
Sampling
| Method | Description |
|---|---|
:GetPoint(t): Vector3 | Position. |
:GetTangent(t): Vector3 | Direction of travel (not normalised). |
:GetAcceleration(t): Vector3 | Second derivative. |
:GetCurvature(t): number | How sharply the path bends (1 / radius). |
:GetNormal(t): Vector3 | Direction the path bends towards. |
:GetFrenetFrame(t): (tangent, normal, binormal) | Orientation frame from the curve itself. |
:GetCFrame(t): CFrame | Position looking along the path, upright by the configured up vector. |
:Sample(n): { CFrame } | n CFrames spread evenly along the length. |
:ToPoints(n): { Vector3 } | n positions. |
Distance
| Method | Description |
|---|---|
:GetLength(t0?, t1?): number | Length of the whole spline, or between two t values. |
:GetPointByDistance(distance): Vector3 | Position a given number of studs from the start. |
:Project(position): number | The t of the point on the spline closest to position. Great for "how far along the track is this player". |
Lengths come from a lookup table built the first time you ask; its accuracy is the DefaultLengthSamples setting.
Shape
| Method | Description |
|---|---|
:GetBounds(): (min, max) | Bounding box corners. |
:GetCenter(): Vector3 | Centre of the bounds. |
:Reverse(): Spline | The same path backwards. |
:Offset(distance): Spline | A parallel path distance studs to the side, e.g. for the second rail of a track. |
:SubSpline(t0, t1): Spline | Part of the path as its own spline. |
:GetCrossSection(t, radius, sides?): { Vector3 } | A ring of points around the path at t. |
:GetCrossSections(n, radius, sides?): { { Vector3 } } | n rings along the path, for building your own tube mesh. To sweep any 2D shape along a spline, use GeometryService:ExtrudeAsync. |
Inspection
:GetKind(), :GetControlPoints(), :GetTangents() (Hermite only), :GetTension() (Catmull only), :GetSegmentCount() and :IsLoop().
Configuration
DefaultConfiguration/Packages/Spline6 settings| Setting | Default | What it does |
|---|---|---|
| DefaultTension | 0 | Tension Spline.Catmull() uses when config.tension is omitted. |
| DefaultLoop | false | Whether a spline closes back on itself when config.loop is omitted. |
| DefaultUp | Vector3.yAxis | Up-vector hint :GetCFrame() uses when none is passed. |
| DefaultLengthSamples | 64 | Samples in the lazily built arc-length table; higher is more accurate but costs more. |
| DefaultProjectRefinementIterations | 24 | Golden-section steps :Project() refines past its nearest table sample. |
| DefaultCrossSectionSides | 8 | Sides :GetCrossSection(s)() generate when none is passed. |
Types
export type Spline
export type SplineCatmullConfig = { loop: boolean?, tension: number? }
export type SplineLoopConfig = { loop: boolean? }