high-performance, versatile Lua library for advanced vector math operations, including component-wise arithmetic, normalization, projections, rotations, and more.
--- Vector library supporting 2D, 3D, and 4D vectors with rich operations.
-- Supports vector arithmetic, normalization, dot/cross products, projections, rotations, and more.
-- Handles vectors of dimension 2, 3, and 4 transparently.
-- Includes error checks for invalid operations (e.g., 3D operations on 2D vectors).
local vec = require("vectors")
-- Example vectors for testing:
local v2a = vec.new(1, 2) -- 2D vector (x=1, y=2)
local v2b = vec.new(3, 4) -- another 2D vector
local v3a = vec.new(1, 2, 3) -- 3D vector
local v3b = vec.new(4, 5, 6) -- another 3D vector
local v4a = vec.new(1, 2, 3, 4) -- 4D vector
local v4b = vec.new(5, 6, 7, 8) -- another 4D vector
-- vec.new(x, y, [z, w])
-- Creates a new vector (2D, 3D, or 4D depending on args)
print("New 2D vector:", v2a)
print("New 3D vector:", v3a)
print("New 4D vector:", v4a)
-- Arithmetic: __add, __sub, __mul, __div
print("Addition 2D:", v2a + v2b) -- Component-wise addition
print("Subtraction 3D:", v3a - v3b) -- Component-wise subtraction
print("Multiply by scalar 4D:", v4a * 2)
print("Component-wise multiplication 2D:", v2a * v2b)
print("Divide by scalar 3D:", v3b / 2)
print("Component-wise division 4D:", v4b / v4a)
print("Scalar divided by vector 2D:", 10 / v2a)
-- Equality and tostring
print("Are v2a and clone equal?", v2a == v2a:clone())
print("Vector to string:", tostring(v3a))
-- vec.fromAngle(angle)
local angle = math.pi / 4
local dir = vec.fromAngle(angle)
print("Unit vector from angle 45 degrees:", dir)
-- vec:toTable()
print("v3a to table:", v3a:toTable())
-- vec:clone()
local v2a_clone = v2a:clone()
print("Clone equals original:", v2a == v2a_clone)
-- vec:xy() and vec:zw()
print("Extract XY of v4a:", v4a:xy())
print("Extract ZW of v4a:", v4a:zw())
-- Warning: zw() errors if vector is not 4D
-- vec:length() and vec:lengthSq()
print("Length of v3a:", v3a:length())
print("Squared length of v3a:", v3a:lengthSq())
-- vec:normalize()
local norm = v3a:normalize()
print("Normalized v3a:", norm)
-- Warning: normalizing zero vector throws error
-- vec:dot(other)
print("Dot product v3a·v3b:", v3a:dot(v3b))
-- vec:cross(other) - 3D only
local cross = v3a:cross(v3b)
print("Cross product v3a x v3b:", cross)
-- Warning: cross() errors if vectors are not 3D
-- vec:angleTo(other) and angleToDegrees(other)
print("Angle between v2a and v2b (radians):", v2a:angleTo(v2b))
print("Angle between v2a and v2b (degrees):", v2a:angleToDegrees(v2b))
-- Warning: errors if any vector length is zero
-- vec:dist(other) and distSq(other)
print("Distance between v2a and v2b:", v2a:dist(v2b))
print("Squared distance between v2a and v2b:", v2a:distSq(v2b))
-- vec:isZero(epsilon) and isZeroFast()
print("Is zero (near) v2a:", v2a:isZero())
print("Is zero (exact) zero vector:", vec.zero(2):isZeroFast())
-- vec:setLength(len)
local v2_scaled = v2a:setLength(10)
print("v2a scaled to length 10:", v2_scaled)
-- vec:clampLength(maxLen)
local v2_clamped = v2_scaled:clampLength(5)
print("v2_scaled clamped to length 5:", v2_clamped)
-- vec:reflect(normal)
local incident = vec.new(1, -1)
local normal = vec.new(0, 1) -- Reflect off horizontal surface
local reflected = incident:reflect(normal)
print("Reflected vector:", reflected)
-- vec:lerp(toVec, t) and safeLerp(toVec, t)
print("Lerp v2a->v2b at t=0.5:", v2a:lerp(v2b, 0.5))
print("Safe lerp with t=1.5 (clamped):", v2a:safeLerp(v2b, 1.5))
-- vec:projectOn(other)
print("Projection of v3a on v3b:", v3a:projectOn(v3b))
-- vec:projectOnPlane(normal) - 3D only
local planeNormal = vec.new(0, 0, 1)
print("Projection of v3a on plane:", v3a:projectOnPlane(planeNormal))
-- vec:almostEqual(other, epsilon)
print("v2a almost equal to clone:", v2a:almostEqual(v2a:clone()))
print("v2a almost equal to v2b (strict):", v2a:almostEqual(v2b, 1e-9))
-- vec:isNormalized(epsilon)
print("Is norm normalized?", norm:isNormalized())
-- vec:isFinite()
print("Is v2a finite?", v2a:isFinite())
-- vec:hadamard(other) (component-wise product)
print("Hadamard product v2a * v2b:", v2a:hadamard(v2b))
-- vec:angle() - 2D only
print("Angle of v2a:", v2a:angle())
-- Warning: errors if vector is not 2D
-- vec:rotate(angle) - 2D only
local rotated = v2a:rotate(math.pi / 2)
print("v2a rotated 90 degrees CCW:", rotated)
-- Warning: errors if vector is not 2D
-- vec:rotateAroundPoint(point, angle) - 2D only
local center = vec.new(1, 1)
print("v2a rotated 90° around (1,1):", v2a:rotateAroundPoint(center, math.pi / 2))
-- vec:perpendicular() - 2D only
print("Perpendicular to v2a:", v2a:perpendicular())
-- Warning: errors if vector is not 2D
-- vec:abs()
print("Absolute v4b:", v4b:abs())
-- vec:min(other) and vec:max(other)
print("Min components of v2a, v2b:", v2a:min(v2b))
print("Max components of v2a, v2b:", v2a:max(v2b))
-- vec:clamp(minVec, maxVec)
local clamped = v2a:clamp(vec.new(0, 1), vec.new(2, 3))
print("v2a clamped between (0,1) and (2,3):", clamped)
-- vec:snap(gridSize)
local snapped = vec.new(1.3, 2.7):snap(1)
print("Snapped vector to grid 1:", snapped)
-- vec:set(x, y, [z, w])
local v = v2a:clone()
v:set(10, 20)
print("Set components to 10, 20:", v)
-- vec:toScreen(fov) - 3D only (requires render.screen_size)
-- This function projects 3D point to 2D screen coordinates assuming camera at origin looking along +Z
-- Example usage would require a rendering environment; skipped here for brevity
-- vec.zero(dim)
print("Zero vector 2D:", vec.zero(2))
print("Zero vector 3D:", vec.zero(3))
print("Zero vector 4D:", vec.zero(4))
-- vec:is3D() and vec:is4D()
print("v3a is 3D?", v3a:is3D())
print("v4a is 4D?", v4a:is4D())
-- vec:cross2D(other) returns scalar z-component of 2D cross product
print("2D cross product of v2a and v2b (scalar):", v2a:cross2D(v2b))
-- vec:floor() and vec:ceil()
print("Floor v4b:", v4b:floor())
print("Ceil v4b:", v4b:ceil())