Skip to content

Instantly share code, notes, and snippets.

@DarkWiiPlayer
Last active September 16, 2018 18:38
Show Gist options
  • Save DarkWiiPlayer/0a64fe8488f488971de75700d5eddf40 to your computer and use it in GitHub Desktop.
Save DarkWiiPlayer/0a64fe8488f488971de75700d5eddf40 to your computer and use it in GitHub Desktop.
LuaJIT vectors
--- Vector objects with several mathematical operations
-- @author DarkWiiPlayer
-- @license MIT
-- luacheck: ignore 4.1
local transformation, array4 = require "classes.transformation"
local ffi = require "ffi"
local sin, cos = math.sin, math.cos
local sqrt = math.sqrt
local angle = math.atan2
local vector_2d
local near_equal = require"lib.compare".near_equal_unit
--- Helpers
-- @section helpers
--- Check if the object is a number.
-- @param object The object to be tested
-- @treturn boolean
-- @usage is_num(20) -- true
-- @usage is_num('string') -- false
local function is_num(obj) return type(obj)=="number" end
--- Check if the object is a 2D vector.
-- @param object The object to be tested
-- @treturn boolean
-- @usage is_2d(vector_2d(1, 1)) -- true
-- @usage is_2d(20) -- false
local function is_2d(obj) return ffi.istype(vector_2d, obj) end
--- Creates a metafunction for an operator from a method.
-- @tparam string method The name of the method to map the operation to
-- @usage __equal = metaop("equal")
local function metaop(name)
return function(a, b)
if is_2d(a) then
return a[name](a, b)
else
return b[name](b, a)
end
end
end
--- vector_2d
-- @type vector_2d
vector_2d = ffi.metatype([[
struct {
const unsigned double x, y;
}
]], {
__index = {
--- Multiply the vector with a scalar or another vector (dot product)
-- @param other The scalar or vector to multiply with
-- @treturn vector
-- @usage vector_2d(1, 1):multiply(2) == vector_2d(2, 2)
multiply = function(self, other)
if is_num(other) then
return vector_2d(self.x*other, self.y*other)
elseif is_2d(other) then
return self.x*other.x + self.y*other.y
end
end;
--- Add to another vector or extend it by a scalar.
-- @param other A vector or a scalar to add
-- @treturn vector
-- @usage vector_2d(1, 1):add(vector_2d(1, 2)) == vector_2d(2, 3)
-- @usage vector_2d(1, 1):add(math.sqrt(2)) == vector_2d(2, 2)
add = function(self, other)
if is_num(other) then
local angle = angle(self.x, self.y) -- self:angle()
return vector_2d(self.x + cos(angle) * other, self.y + sin(angle) * other)
elseif is_2d(other) then
return vector_2d(self.x+other.x, self.y+other.y)
end
end;
--- Returns the angle of the vector from the X-Axis in radians
-- @treturn number
-- @usage vector_2d(1, 0):angle() == math.pi / 2
angle = function(self)
return angle(self.y, self.x)
end;
--- Rotates the vector by 180 degrees / negates both coordinates
-- @treturn vector
flip = function(self)
return vector_2d(-self.x, -self.y)
end;
--- Returns the length of the vector
-- @treturn number
-- @usage vector_2d(1, 1):length() == math.sqrt(2)
length = function(self)
return sqrt(self.x*self.x + self.y*self.y)
end;
--- Returns the squared length of the vector (faster than real length).
-- This can be used for comparing the lengths of two vectors,
-- or comparing the length to a fixed value.
-- @usage vector_2d(1, 1):length2() < 3
-- @usage vector_2d(1, 1):length2() < vector_2d(2, 2):length2()
length2 = function(self)
return self.x * self.x + self.y * self.y
end;
--- Subtracts a value from the vector, see add
-- @param other The value to subtract
-- @see add
subtract = function(self, other)
if is_num(other) then
return self + (-other)
elseif is_2d(other) then
return vector_2d(self.x-other.x, self.y-other.y)
else
error("Invalid operation "..type(self).." - "..type(other), 2)
end
end;
exactly_equal = function(self, other)
if is_2d(other) then
return self.x==other.x and self.y==other.y
else
error("Attempting to compare vector(2d) with "..type(other), 2)
end
end;
equal = function(self, other)
if is_2d(other) then
--return abs(self.x-other.x)<EPSILON and abs(self.y-other.y)<EPSILON
return near_equal(self.x, other.x) and near_equal(self.y, other.y)
else
error("Attempting to compare vector(2d) with "..type(other), 2)
end
end;
--- Returns a vector of same direction and length `number`
-- @treturn vector
-- @tparam[opt=1] number length
-- @usage vector_2d(2, 2):normalize(4):length() == 4
-- @usage vector_2d(3, 0):normalize():angle() == math.pi/2
normalize = function(self, length)
length = length or 1
local len = #self
if len == length then
return self
else
local fact = length/len
return vector_2d(self.x*fact, self.y*fact)
end
end;
linear_combination = function(self, basis_x, basis_y)
if basis_y then
error("Not Implemented!", 2)
else -- Assumes basis_y is basis_x + 90 degrees
local angle = self:angle() - basis_x:angle()
local f_len = self:length() / basis_x:length()
return vector_2d(
f_len * cos(angle),
f_len * sin(angle)
)
end
end;
transformation = function(self)
return transformation:rotate(self:angle()):scale(1/self:length())
end;
--- Transforms a vector with a given transformation matrix
-- @tparam matrix A transformation matrix
-- @usage vector_2d(2, 2):transform(vector_2d(0, 2):transformation()) == vector_2d(1, -1)
transform = function(self, matrix)
local m = matrix.M
return vector_2d(
m[0][0] * self.x + m[0][1] * self.y,
m[1][0] * self.x + m[1][1] * self.y
)
end;
--- Given two vectors, returns a transformation to the base they represent.
-- If the two vectors don't represent a base, nil is returned.
-- @tparam vector first The first vector
-- @tparam vector second The second vector
basis = function(self, other)
return not near_equal(self.x/other.x, self.y/other.y)
and transformation()
end;
};
__sub = function(a, b)
if is_2d(a) then
return a:subtract(b)
else
return a:subtract_from(b)
end
end;
__eq = metaop "equal";
__mul = metaop "multiply";
__add = metaop "add";
__len = metaop "length";
__unm = metaop "flip";
__tostring = function(self)
return "[vector] ("..self.x..", "..self.y..")"
end;
})
return vector_2d, array4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment