4 Vectors
dxtr edited this page 2026-10-18 16:33:29 +02:00

Vectors

vec2, vec3 and vec4 are vectors of 2, 3 and 4 single floats. Every function below exists for each size, named with its prefix, vec2-, vec3- or vec4-, except vec3-cross, and for doubles, as dvec3- and so on; see Doubles. Functions that return a vector take an optional last argument, result; see Results and Allocation.

Making vectors

(gm:make-vec3 :x 1 :y 2 :z 3)   ; => #(1.0 2.0 3.0)
(gm:make-vec3 :y 1)             ; => #(0.0 1.0 0.0)
(gm:make-vec3)                  ; => #(0.0 0.0 0.0)
(gm:make-vec4 :x 1/2)           ; => #(0.5 0.0 0.0 0.0)

make-vec3 takes the components by keyword, as any reals, and those not given are 0, W included. vec3-copy copies a vector, and vec3-set sets all of one's components and returns it:

(gm:vec3-set (gm:make-vec3) 0 0 1)   ; => #(0.0 0.0 1.0)

Components

A vector is also a typed structure over its array, as (defstruct (vec3 (:type (vector single-float))) x y z) makes one, so each component has an accessor named for the type: vec2-x and vec2-y, vec3-x to vec3-z, and vec4-x to vec4-w. setf writes a component:

(let ((v (gm:make-vec3 :x 1 :y 2 :z 3)))
  (setf (gm:vec3-x v) 10.0)
  v)
;; => #(10.0 2.0 3.0)

A component takes a float of its vector's precision and nothing else, as its array holds: (setf (gm:vec3-x v) 10) signals type-error, as (setf (aref v 0) 10) does, and SBCL warns of it when it compiles a constant. The constructors and vec3-set take any reals.

An accessor checks for its own type, so vec3-x refuses a vec4, and vec3-z a vec2, with type-error.

Arithmetic

Function Returns
vec3+, vec3- The sum and the difference.
vec3*, vec3/ The product and the quotient, component by component.
vec3-scale The vector times a real.
vec3-negate The vector pointing the other way.
vec3-min, vec3-max The lesser and the greater component of each pair.
vec3-lerp The point a fraction of the way from one vector to another: the first at 0, the second at 1.
vec3-cross The cross product, perpendicular to both, by the right-hand rule. vec3 only.
(gm:vec3-scale (gm:make-vec3 :x 1 :y 2 :z 3) 2)                             ; => #(2.0 4.0 6.0)
(gm:vec3* (gm:make-vec3 :x 1 :y 2 :z 3) (gm:make-vec3 :x 2 :y 2 :z 2))      ; => #(2.0 4.0 6.0)
(gm:vec3-min (gm:make-vec3 :x 1 :y 5 :z 3) (gm:make-vec3 :x 4 :y 2 :z 6))   ; => #(1.0 2.0 3.0)
(gm:vec3-lerp (gm:make-vec3) (gm:make-vec3 :x 10 :y 20 :z 30) 1/4)          ; => #(2.5 5.0 7.5)
(gm:vec3-cross (gm:make-vec3 :x 1) (gm:make-vec3 :y 1))                     ; => #(0.0 0.0 1.0)

Lengths, distances and angles

Function Returns
vec3-dot The dot product, a single float.
vec3-length The length.
vec3-length-squared The length squared: cheaper, and enough to compare lengths.
vec3-distance The distance between two points.
vec3-distance-squared The distance squared: cheaper, and enough to compare distances.
vec3-angle The angle between two vectors, in radians, from 0 to π; 0 if either is zero.
vec3-normalize The vector scaled to length 1. A zero vector stays zero.
(gm:vec3-dot (gm:make-vec3 :x 1 :y 2 :z 3) (gm:make-vec3 :x 4 :y 5 :z 6))   ; => 32.0
(gm:vec3-angle (gm:make-vec3 :x 1) (gm:make-vec3 :y 1))                     ; => 1.5707964
(gm:vec3-normalize (gm:make-vec3 :x 3 :z 4))                                ; => #(0.6 0.0 0.8)
(gm:vec3-normalize (gm:make-vec3))                                          ; => #(0.0 0.0 0.0)

(let ((a (gm:make-vec3 :x 1 :y 1 :z 1))
      (b (gm:make-vec3 :x 4 :y 5 :z 1)))
  (list (gm:vec3-distance a b) (gm:vec3-distance-squared a b)))
;; => (5.0 25.0)

vec3-angle uses Kahan's formula, which keeps its precision for vectors nearly parallel or nearly opposite, where the arc cosine of the normalised dot product loses it.

Normalising never divides by zero: a zero vector stays zero, as in Godot, so a direction that may vanish, such as a character's input, needs no check first.

Reflecting and projecting

Function Returns
(vec3-reflect incident normal) incident reflected off a surface whose unit normal is normal, as GLSL's reflect does it.
(vec3-project vector onto) The part of vector along onto, which need not be unit. Onto a zero vector, zero.
(vec3-project-on-plane vector normal) vector less its part along normal, which need not be unit: what is left in the plane, as a body sliding along a wall moves. A zero normal leaves vector as it is.
(gm:vec3-reflect (gm:make-vec3 :x 1 :y -1) (gm:make-vec3 :y 1))            ; => #(1.0 1.0 0.0)
(gm:vec3-project (gm:make-vec3 :x 3 :y 4) (gm:make-vec3 :x 2))             ; => #(3.0 0.0 0.0)
(gm:vec3-project-on-plane (gm:make-vec3 :x 3 :y -4) (gm:make-vec3 :y 1))   ; => #(3.0 0.0 0.0)

A ball moving down and to the right bounces up off a floor, and a body pushed down and to the right slides along it to the right.

Limiting and moving

Function Returns
(vec3-clamp-length vector max-length) vector shortened to max-length if it is longer, and otherwise as it is: a speed limit. max-length is a real of 0 or more.
(vec3-move-toward from to delta) The point delta further from from toward to, but no further than to, as Godot's move_toward: moving toward a target at a speed. A negative delta moves away.
(gm:vec3-clamp-length (gm:make-vec3 :x 3 :y 4) 2)                  ; => #(1.2 1.6 0.0)
(gm:vec3-move-toward (gm:make-vec3) (gm:make-vec3 :x 10) 3)        ; => #(3.0 0.0 0.0)
(gm:vec3-move-toward (gm:make-vec3 :x 9) (gm:make-vec3 :x 10) 3)   ; => #(10.0 0.0 0.0)

The same for single numbers is on Single Numbers.

Comparing

Floats that went through arithmetic rarely compare equal. vec3-approx= is true if no component differs by more than an epsilon, 1e-5 by default:

(gm:vec3-approx= (gm:make-vec3 :x 1 :y 2 :z 3) (gm:make-vec3 :x 1 :y 2 :z 3.000001))   ; => T