Math#
Moud has three value types for maths, and three tables of functions that work on them. vec3 builds
a Vector3, which is a point or a direction in the world. cframe builds a frame, a position and a
rotation held together, which is what a part's cframe property takes. quat builds a rotation on
its own, with no position in it. The math, angle and random tables hold functions that take
plain numbers and vectors.
Everything is metres and seconds, and every angle is in radians.
typeof names these values the way it names a number or a string: typeof(vec3(1, 2, 3)) is
"Vector3", and a frame, a rotation, a colour and a udim2 give "CFrame", "Quat", "Color" and
"UDim2". Every instance gives "Instance", so ask part:isA("Part") or read className to tell
one kind from another. Signals and connections answer with their own names, such as "Connection".
Vectors#
A Vector3 is three numbers: x and z run along the ground, y is height. The same type carries a
position, a size and a direction, so vec3(0, 66, 0) is a spot 66 metres up, vec3(40, 1, 40) is a
wide flat slab, and vec3(0, 1, 0) points at the sky.
local v = vec3(3, 4, 0)
v.x v.y v.z v.magnitude v.unit
v + w v - w v * 2 -v
v:distance(w) v:distanceSq(w) v:dot(w) v:cross(w)
v:lerp(w, 0.5) v:angleTo(w) -- radians
v:flat() -- y set to 0
v:clampMagnitude(10) v:abs() v:floor()magnitudeis how long the vector is, in metres. On the difference between two positions, that is the distance between them.unitis the same direction with a length of 1. Use it when you want a direction and the length is in your way, such as pushing something at a speed you choose.- Adding and subtracting work component by component, and multiplying by a number scales all three.
-vpoints the other way. distanceanddistanceSqmeasure between two positions.distanceSqskips the square root, so compare it against a squared range when you are checking many pairs each tick.dottells you how much two directions agree: 1 when they point the same way, 0 when they are at right angles, -1 when they are opposite.crossgives a vector at right angles to both.lerpwalks from one vector to another,0giving the first and1the second, sov:lerp(w, 0.5)is the midpoint.angleTois the angle between two directions, in radians.flatcopies the vector with y set to 0, which is how you measure a distance along the ground and ignore how far apart two things are vertically.clampMagnitudehands back a vector no longer than the length you give. Shorter vectors come back unchanged, so it caps a speed without changing its direction.absmakes each of the three numbers positive, andfloorrounds each one down.
Frames#
A frame holds a position and a rotation in one value. Parts, cameras and models all take one, so
writing an instance's cframe moves it and turns it in a single write.
local f = cframe(0, 65, 0)
cframe(position) -- at a point, unrotated
cframe(from, lookAtPoint) -- at a point, turned to face another point
cframe.angles(pitch, yaw, roll) -- a rotation at the origin, in radians
cframe.lookAt(from, to) -- at `from`, facing `to`
cframe.identity -- the origin, unrotatedf.position f.rotation f.lookVector f.rightVector f.upVector
f * g
f:inverse() f:lerp(g, t)
f:toObjectSpace(g) f:toWorldSpace(g)
f:pointToObjectSpace(p) f:pointToWorldSpace(p)
f:vectorToObjectSpace(v) f:vectorToWorldSpace(v)
f:lookAt(target) -- same position, turned to face a pointpositionis where the frame is androtationis how it is turned, with the position taken out.lookVector,rightVectorandupVectorare the three directions the frame faces: forward, to its right and out of its top. They are what you use to move something the way it is pointing rather than the way the world is.f * gappliesginsidef, so the second frame is read in the first one's axes. That is whypart.cframe = part.cframe * cframe.angles(0, dt, 0)spins a part around its own up axis wherever it stands, whilecframe.angles(0, dt, 0) * part.cframewould swing it around the world's.inversegives the frame that undoes this one.lerpblends between two frames, moving and turning at once.toObjectSpaceandtoWorldSpaceconvert a whole frame in and out of this one's axes.pointToObjectSpaceandpointToWorldSpacedo the same for a position, andvectorToObjectSpaceandvectorToWorldSpacefor a direction. A direction carries no position, so the vector pair only turns what you give it and never shifts it.f:lookAt(target)keeps the frame where it is and turns it to face a point. Use it to aim a turret or a camera without working out the angles yourself.
Rotations#
A quaternion is a rotation with no position attached. Reach for one when you want to blend between two rotations, or to store which way something is facing without saying where it is.
quat.identity
quat.axisAngle(vec3(0, 1, 0), math.pi / 2)
quat.euler(pitch, yaw, roll)
quat.lookAt(forward, up)
quat.fromTo(vec3(1, 0, 0), vec3(0, 0, -1)) -- the shortest turn between two directions
q:slerp(r, t) q:inverse() q:rotate(v) q:mul(r)quat.identityis no rotation at all.quat.axisAngleturns by an angle around an axis you give, so the example above is a quarter turn around the up axis.quat.eulerbuilds one from pitch, yaw and roll.quat.lookAtbuilds one from a forward direction and an up direction.quat.fromTogives the shortest turn that takes one direction onto another.slerpblends two rotations,0giving the first and1the second, and keeps the speed of the turn even along the way.inverseundoes the rotation,rotateturns a vector by it, andmulputs two rotations one after the other.
Angles are radians everywhere in Moud, in cframe.angles, in quat.euler and in the angle table
below. math.pi / 2 is a quarter turn and math.pi * 2 is a full one.
Numbers#
These sit alongside Luau's own math functions, in the same table.
math.remap(5, 0, 10, 0, 100) -- 50
math.lerp(a, b, t)
math.inverseLerp(a, b, value)
math.approach(value, target, step)
local velocity
x, velocity = math.smoothDamp(x, target, velocity, 0.3, dt)
math.noise(x, y, z) -- luau's perlin noise
math.fbm(x, y, z, 4) -- layered noise: octaves, lacunarity, gain
math.bezier({ a, b, c }, t) -- numbers or vectors
math.catmullRom({ a, b, c, d }, t) -- passes through every pointmath.remapmoves a number from one range into another. The example reads 5 out of 0 to 10 and writes it back into 0 to 100, which gives 50. It is how you turn health into the width of a bar.math.lerpblendsaandbbyt.math.inverseLerpruns the other way and tells you whichta value sits at.math.approachmoves a value towards a target by at moststepand stops there, so it never overshoots. Give it a step ofspeed * deltainside a step handler and it moves at that speed a second.math.smoothDampeases a value towards a target and remembers how fast it is going. It takes the current value, the target, the velocity, roughly how long it should take, and the seconds since the last step, and returns the new value and the new velocity. Keep the velocity in a variable and hand it back in each time, as the example does.math.noiseis Luau's Perlin noise.math.fbmlayers it: the fourth argument is how many octaves to add up, and lacunarity and gain follow it.math.bezierreads a list of control points as a curve, andmath.catmullRomreads a list of points the curve passes through. Both take numbers or vectors, so the same call smooths a value and a path.
Angles#
An angle that keeps growing eventually reads as a large number rather than a direction, and subtracting one angle from another can tell you to turn the long way round. These three functions handle that.
angle.wrap(a) -- into -pi to pi
angle.delta(a, b) -- the shortest turn from a to b
angle.lerp(a, b, t)angle.wrapbrings an angle back into the range -pi to pi.angle.deltagives the shortest turn from one angle to another, which can be negative. Use it to decide which way a body should turn to face something.angle.lerpblends two angles the short way round, so turning from just under pi to just over -pi crosses the gap instead of sweeping back through zero.
Random#
random.range(1, 5) random.int(1, 6) random.chance(0.25)
random.pick(list) random.shuffle(list)
random.unit() random.onSphere(3) random.inSphere(3)
random.inCircle(5) -- flat on the ground
random.inBox(vec3(4, 1, 4))random.rangegives a decimal number between the two you pass, andrandom.intgives a whole one, so the example rolls a die.random.chanceanswers true that fraction of the time:0.25is true one call in four.random.picktakes one entry out of a list, andrandom.shufflereorders the list.random.unitgives a direction of length 1.random.onSpheregives a point on the surface of a sphere of that radius andrandom.inSpherea point anywhere inside it.random.inCirclegives a point inside a circle flat on the ground, with y left at 0, which is what you want for scattering things across a floor.random.inBoxgives a point inside a box of that size.
