123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- --- A quaternion and associated utilities.
- -- @module quat
- local modules = (...):gsub('%.[^%.]+$', '') .. "."
- local constants = require(modules .. "constants")
- local vec3 = require(modules .. "vec3")
- local DOT_THRESHOLD = constants.DOT_THRESHOLD
- local DBL_EPSILON = constants.DBL_EPSILON
- local acos = math.acos
- local cos = math.cos
- local sin = math.sin
- local min = math.min
- local max = math.max
- local sqrt = math.sqrt
- local quat = {}
- local quat_mt = {}
- -- Private constructor.
- local function new(x, y, z, w)
- return setmetatable({
- x = x or 0,
- y = y or 0,
- z = z or 0,
- w = w or 1
- }, quat_mt)
- end
- -- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
- local status, ffi
- if type(jit) == "table" and jit.status() then
- status, ffi = pcall(require, "ffi")
- if status then
- ffi.cdef "typedef struct { double x, y, z, w;} cpml_quat;"
- new = ffi.typeof("cpml_quat")
- end
- end
- -- Statically allocate a temporary variable used in some of our functions.
- local tmp = new()
- local qv, uv, uuv = vec3(), vec3(), vec3()
- --- Constants
- -- @table quat
- -- @field unit Unit quaternion
- -- @field zero Empty quaternion
- quat.unit = new(0, 0, 0, 1)
- quat.zero = new(0, 0, 0, 0)
- --- The public constructor.
- -- @param x Can be of two types: </br>
- -- number x X component
- -- table {x, y, z, w} or {x=x, y=y, z=z, w=w}
- -- @tparam number y Y component
- -- @tparam number z Z component
- -- @tparam number w W component
- -- @treturn quat out
- function quat.new(x, y, z, w)
- -- number, number, number, number
- if x and y and z and w then
- assert(type(x) == "number", "new: Wrong argument type for x (<number> expected)")
- assert(type(y) == "number", "new: Wrong argument type for y (<number> expected)")
- assert(type(z) == "number", "new: Wrong argument type for z (<number> expected)")
- assert(type(w) == "number", "new: Wrong argument type for w (<number> expected)")
- return new(x, y, z, w)
- -- {x, y, z, w} or {x=x, y=y, z=z, w=w}
- elseif type(x) == "table" then
- local xx, yy, zz, ww = x.x or x[1], x.y or x[2], x.z or x[3], x.w or x[4]
- assert(type(xx) == "number", "new: Wrong argument type for x (<number> expected)")
- assert(type(yy) == "number", "new: Wrong argument type for y (<number> expected)")
- assert(type(zz) == "number", "new: Wrong argument type for z (<number> expected)")
- assert(type(ww) == "number", "new: Wrong argument type for w (<number> expected)")
- return new(xx, yy, zz, ww)
- end
- return new(0, 0, 0, 1)
- end
- --- Create a quaternion from an angle/axis pair.
- -- @tparam number angle Angle (in radians)
- -- @tparam vec3 axis
- -- @treturn quat out
- function quat.from_angle_axis(angle, axis)
- local s = sin(angle * 0.5)
- local c = cos(angle * 0.5)
- return new(axis.x * s, axis.y * s, axis.z * s, c)
- end
- --- Create a quaternion from a normal/up vector pair.
- -- @tparam vec3 normal
- -- @tparam vec3 up (optional)
- -- @treturn quat out
- function quat.from_direction(normal, up)
- local u = up or vec3.unit_z
- local n = normal:normalize()
- local a = u:cross(n)
- local d = u:dot(n)
- return new(a.x, a.y, a.z, d + 1)
- end
- --- Clone a quaternion.
- -- @tparam quat a Quaternion to clone
- -- @treturn quat out
- function quat.clone(a)
- return new(a.x, a.y, a.z, a.w)
- end
- --- Add two quaternions.
- -- @tparam quat a Left hand operant
- -- @tparam quat b Right hand operant
- -- @treturn quat out
- function quat.add(a, b)
- return new(
- a.x + b.x,
- a.y + b.y,
- a.z + b.z,
- a.w + b.w
- )
- end
- --- Subtract a quaternion from another.
- -- @tparam quat a Left hand operant
- -- @tparam quat b Right hand operant
- -- @treturn quat out
- function quat.sub(a, b)
- return new(
- a.x - b.x,
- a.y - b.y,
- a.z - b.z,
- a.w - b.w
- )
- end
- --- Multiply two quaternions.
- -- @tparam quat a Left hand operant
- -- @tparam quat b Right hand operant
- -- @treturn quat out
- function quat.mul(a, b)
- return new(
- a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y,
- a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z,
- a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x,
- a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
- )
- end
- --- Multiply a quaternion and a vec3.
- -- @tparam quat a Left hand operant
- -- @tparam vec3 b Right hand operant
- -- @treturn quat out
- function quat.mul_vec3(a, b)
- qv.x = a.x
- qv.y = a.y
- qv.z = a.z
- uv = qv:cross(b)
- uuv = qv:cross(uv)
- return b + ((uv * a.w) + uuv) * 2
- end
- --- Multiply a quaternion by an exponent.
- -- @tparam quat a Left hand operant
- -- @tparam number n Right hand operant
- -- @treturn quat out
- function quat.pow(a, n)
- if n == 0 then
- return new(0, 0, 0, 1)
- end
- if n > 0 then
- return a * a^(n-1)
- end
- if n < 0 then
- return a:reciprocal()^(-n)
- end
- end
- --- Normalize a quaternion.
- -- @tparam quat a Quaternion to normalize
- -- @treturn quat out
- function quat.normalize(a)
- if a:is_zero() then
- return new(0, 0, 0, 0)
- end
- return a:scale(1 / a:len())
- end
- --- Get the dot product of two quaternions.
- -- @tparam quat a Left hand operant
- -- @tparam quat b Right hand operant
- -- @treturn number dot
- function quat.dot(a, b)
- return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
- end
- --- Return the length of a quaternion.
- -- @tparam quat a Quaternion to get length of
- -- @treturn number len
- function quat.len(a)
- return sqrt(a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w)
- end
- --- Return the squared length of a quaternion.
- -- @tparam quat a Quaternion to get length of
- -- @treturn number len
- function quat.len2(a)
- return a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w
- end
- --- Multiply a quaternion by a scalar.
- -- @tparam quat a Left hand operant
- -- @tparam number s Right hand operant
- -- @treturn quat out
- function quat.scale(a, s)
- return new(
- a.x * s,
- a.y * s,
- a.z * s,
- a.w * s
- )
- end
- --- Alias of from_angle_axis.
- -- @tparam number angle Angle (in radians)
- -- @tparam vec3 axis
- -- @treturn quat out
- function quat.rotate(angle, axis)
- return quat.from_angle_axis(angle, axis)
- end
- --- Return the conjugate of a quaternion.
- -- @tparam quat a Quaternion to conjugate
- -- @treturn quat out
- function quat.conjugate(a)
- return new(-a.x, -a.y, -a.z, a.w)
- end
- --- Return the inverse of a quaternion.
- -- @tparam quat a Quaternion to invert
- -- @treturn quat out
- function quat.inverse(a)
- tmp.x = -a.x
- tmp.y = -a.y
- tmp.z = -a.z
- tmp.w = a.w
- return tmp:normalize()
- end
- --- Return the reciprocal of a quaternion.
- -- @tparam quat a Quaternion to reciprocate
- -- @treturn quat out
- function quat.reciprocal(a)
- if a:is_zero() then
- error("Cannot reciprocate a zero quaternion")
- return false
- end
- tmp.x = -a.x
- tmp.y = -a.y
- tmp.z = -a.z
- tmp.w = a.w
- return tmp:scale(1 / a:len2())
- end
- --- Lerp between two quaternions.
- -- @tparam quat a Left hand operant
- -- @tparam quat b Right hand operant
- -- @tparam number s Step value
- -- @treturn quat out
- function quat.lerp(a, b, s)
- return (a + (b - a) * s):normalize()
- end
- --- Slerp between two quaternions.
- -- @tparam quat a Left hand operant
- -- @tparam quat b Right hand operant
- -- @tparam number s Step value
- -- @treturn quat out
- function quat.slerp(a, b, s)
- local dot = a:dot(b)
- if dot < 0 then
- a = -a
- dot = -dot
- end
- if dot > DOT_THRESHOLD then
- return a:lerp(b, s)
- end
- dot = min(max(dot, -1), 1)
- local theta = acos(dot) * s
- local c = (b - a * dot):normalize()
- return a * cos(theta) + c * sin(theta)
- end
- --- Unpack a quaternion into individual components.
- -- @tparam quat a Quaternion to unpack
- -- @treturn number x
- -- @treturn number y
- -- @treturn number z
- -- @treturn number w
- function quat.unpack(a)
- return a.x, a.y, a.z, a.w
- end
- --- Return a boolean showing if a table is or is not a quat.
- -- @tparam quat a Quaternion to be tested
- -- @treturn boolean is_quat
- function quat.is_quat(a)
- if type(a) == "cdata" then
- return ffi.istype("cpml_quat", a)
- end
- return
- type(a) == "table" and
- type(a.x) == "number" and
- type(a.y) == "number" and
- type(a.z) == "number" and
- type(a.w) == "number"
- end
- --- Return a boolean showing if a table is or is not a zero quat.
- -- @tparam quat a Quaternion to be tested
- -- @treturn boolean is_zero
- function quat.is_zero(a)
- return
- a.x == 0 and
- a.y == 0 and
- a.z == 0 and
- a.w == 0
- end
- --- Return a boolean showing if a table is or is not a real quat.
- -- @tparam quat a Quaternion to be tested
- -- @treturn boolean is_real
- function quat.is_real(a)
- return
- a.x == 0 and
- a.y == 0 and
- a.z == 0
- end
- --- Return a boolean showing if a table is or is not an imaginary quat.
- -- @tparam quat a Quaternion to be tested
- -- @treturn boolean is_imaginary
- function quat.is_imaginary(a)
- return a.w == 0
- end
- --- Convert a quaternion into an angle/axis pair.
- -- @tparam quat a Quaternion to convert
- -- @treturn number angle
- -- @treturn vec3 axis
- function quat.to_angle_axis(a)
- if a.w > 1 or a.w < -1 then
- a = a:normalize()
- end
- local x, y, z
- local angle = 2 * acos(a.w)
- local s = sqrt(1 - a.w * a.w)
- if s < DBL_EPSILON then
- x = a.x
- y = a.y
- z = a.z
- else
- x = a.x / s
- y = a.y / s
- z = a.z / s
- end
- return angle, vec3(x, y, z)
- end
- --- Convert a quaternion into a vec3.
- -- @tparam quat a Quaternion to convert
- -- @treturn vec3 out
- function quat.to_vec3(a)
- return vec3(a.x, a.y, a.z)
- end
- --- Return a formatted string.
- -- @tparam quat a Quaternion to be turned into a string
- -- @treturn string formatted
- function quat.to_string(a)
- return string.format("(%+0.3f,%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z, a.w)
- end
- quat_mt.__index = quat
- quat_mt.__tostring = quat.to_string
- function quat_mt.__call(_, x, y, z, w)
- return quat.new(x, y, z, w)
- end
- function quat_mt.__unm(a)
- return a:scale(-1)
- end
- function quat_mt.__eq(a,b)
- if not quat.is_quat(a) or not quat.is_quat(b) then
- return false
- end
- return a.x == b.x and a.y == b.y and a.z == b.z and a.w == b.w
- end
- function quat_mt.__add(a, b)
- assert(quat.is_quat(a), "__add: Wrong argument type for left hand operant. (<cpml.quat> expected)")
- assert(quat.is_quat(b), "__add: Wrong argument type for right hand operant. (<cpml.quat> expected)")
- return a:add(b)
- end
- function quat_mt.__sub(a, b)
- assert(quat.is_quat(a), "__sub: Wrong argument type for left hand operant. (<cpml.quat> expected)")
- assert(quat.is_quat(b), "__sub: Wrong argument type for right hand operant. (<cpml.quat> expected)")
- return a:sub(b)
- end
- function quat_mt.__mul(a, b)
- assert(quat.is_quat(a), "__mul: Wrong argument type for left hand operant. (<cpml.quat> expected)")
- assert(quat.is_quat(b) or vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operant. (<cpml.quat> or <cpml.vec3> or <number> expected)")
- if quat.is_quat(b) then
- return a:mul(b)
- end
- if type(b) == "number" then
- return a:scale(b)
- end
- return a:mul_vec3(b)
- end
- function quat_mt.__pow(a, n)
- assert(quat.is_quat(a), "__pow: Wrong argument type for left hand operant. (<cpml.quat> expected)")
- assert(type(n) == "number", "__pow: Wrong argument type for right hand operant. (<number> expected)")
- return a:pow(n)
- end
- if status then
- ffi.metatype(new, quat_mt)
- end
- return setmetatable({}, quat_mt)
|