Last active
September 16, 2018 18:38
-
-
Save DarkWiiPlayer/0a64fe8488f488971de75700d5eddf40 to your computer and use it in GitHub Desktop.
LuaJIT vectors
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- 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