vec#
A point in space, or a direction. vec is the maths helper every position, rotation and direction in this API is made of.
Use it to
- add, subtract and scale positions
- measure a distance, or the direction from one thing to another
- find a point part-way between two
- turn, bounce or limit a direction
At a glance#
What you want done, and the verb that does it.
Make and combine
Measure
local vec = require("vec")Making one#
vec() -- (0, 0, 0)vec(5) -- (5, 5, 5), the same on every axisvec(1, 2, 3) -- per axisvec({ x = 1, y = 2, z = 3 }) -- a copy of any table with x, y, zvec(self:position()) -- a copy of another vecTip
You rarely need to build one. Every reading in this API already hands you a vec. self:position(), self:rotation(), hit.pos, hit.normal, shot.eye, shot.dir, player.pos, player.look. And every verb that takes a point also accepts a plain { x =, y =, z = } table. vec is what you reach for when you want to do maths on one.
Maths on points#
local a = vec(1, 0, 0)local b = vec(0, 2, 0) a + b -- vec(1, 2, 0)a - b -- vec(1, -2, 0)-a -- vec(-1, 0, 0)a * 3 -- vec(3, 0, 0) scaling3 * a -- the samea / 2 -- vec(0.5, 0, 0)a == b -- falsetostring(a) -- "vec(1, 0, 0)"Two you will use constantly:
-- a step of 2 metres in the direction somebody is lookinglocal ahead = player.pos + player.look * 2 -- just off a surface, so a decal does not flicker against itlocal safe = hit.pos + hit.normal * 0.006Methods#
len#
v:len() → number
How long the vector is. On a direction it is 1; on the difference between two points it is the distance between them.
dist#
v:dist(other) → number
Distance between two points. Clearer than (a - b):len() and does the same thing.
if self:position():dist(player.pos) > 20 then return -- too far away to careendnormalize#
v:normalize() → vec
The same direction, one metre long. This is what turns "the way from me to you" into something you can multiply by a speed.
local away = (target - here):normalize()self:set_velocity(away * 12)A zero-length vector normalizes to zero rather than to an error or a broken number, so you never have to guard against it.
dot#
v:dot(other) → number
How much two directions agree. 1 is the same way, 0 is a right angle, -1 is opposite.
The practical use is a field of view, meaning "is this in front of me?":
local to_player = (player.pos - here):normalize()if facing:dot(to_player) > 0.7 then -- roughly within a 45° cone in frontendcross#
v:cross(other) → vec
A direction at right angles to both. Mostly used to find "sideways" from "forward" and "up".
local right = forward:cross(vec(0, 1, 0)):normalize()lerp#
v:lerp(other, t) → vec
A point part-way between two. t = 0 is where you started, t = 1 is the target, 0.5 is halfway.
-- creep a tenth of the way toward the target each tickself:move_to(here:lerp(target, 0.1))unpack#
v:unpack() → three numbers
local x, y, z = here:unpack()angle#
v:angle(other) → number
The angle between two directions, in degrees, from 0 (same way) to 180 (opposite). A zero vector has no direction and answers 0.
if facing:angle(player.pos - here) < 30 then -- within a 60° cone in frontendreflect#
v:reflect(normal) → vec
The direction after bouncing off a surface. normal is the surface's normal, which is exactly what a raycast hands you.
local hit = world:raycast({ from = here, dir = dir, max = 50 })if hit then dir = dir:reflect(hit.normal) -- the ricochetendproject#
v:project(onto) → vec
The part of v that points along onto. Take it away and what is left runs across it, which is how you slide along a wall instead of stopping dead:
local along_wall = velocity - velocity:project(hit.normal)rotate_y#
v:rotate_y(degrees) → vec
Turned about the vertical axis, the same turn self:rotate_by(vec(0, degrees, 0)) gives an object. Handy for spreading shots or placing things in a ring:
for i = 0, 7 do local spot = here + vec(4, 0, 0):rotate_y(i * 45) world:spawn({ shape = "sphere", pos = spot, scale = 0.3 })endclamp_len#
v:clamp_len(max) → vec
The same direction, never longer than max. A speed limit:
self:set_velocity((self:velocity() + push):clamp_len(15))flat#
v:flat() → vec
The same vector with y set to zero: its horizontal part. Use it before normalize when something should turn toward a player without tilting up or down.
local toward = (player.pos - here):flat():normalize()Notes#
A vec never changes. Every operation returns a new one, so handing yours to something else can never come back modified.
It costs nothing. vec is pure arithmetic inside your script. It never touches the world, never counts against your effect budget, and never needs a permission. Misusing it (multiplying two vecs, say) is an ordinary script error, like any other bug.
See also#
- self is where readings come from
- world:raycast gives you
posandnormal
Updated 17 October 2026