diff --git a/quaternion.lua b/quaternion.lua index 98ee5fdb712f70171af8502e0018b107c436cfd8..4aed970626dbe0c7ff6e51435f7769ad223620fe 100644 --- a/quaternion.lua +++ b/quaternion.lua @@ -1,47 +1,45 @@ require("kara3d.vector") require("kara3d.matrix") +-- ********** DEFINITION AND GENERIC FUNCTIONS ********** + quaternion = {class = "quaternion"} quaternion.__index = quaternion +-- Creates the quaternion ai + bj + dk + d function quaternion.new(a, b, c, d) local q = {x = a or 0, y = b or 0, z = c or 0, w = d or 1} setmetatable(q, quaternion) return q end -function quaternion:inv() - return quaternion.new(-self.x, -self.y, -self.z, -self.w) +-- Clones this quaternion +function quaternion:clone() + return quaternion.new(self.x, self.y, self.z, self.w) end +-- Returns this quaternion's coordinates, separated by commas function quaternion:tostring() return self.x .. ", " .. self.y .. ", " .. self.z .. ", " .. self.w end -function quaternion:set(x, y, z, w) - if (type(x) == "table" and x.class == "quaternion") then - self.x = x.x - self.y = x.y - self.z = x.z - self.w = x.w - else - self.x = x or 0 - self.y = y or 0 - self.z = z or 0 - self.w = w or 1 - end - return self -end +-- ********** STATIC FUNCTIONS ********** + +-- Returns the quaternion 0i + 0j + 0k + 1, which is no rotation +function quaternion.identity() return quaternion.new() end +-- Returns the product of two (normalized) quaternions function quaternion.mul_quaternion(q1, q2) - local b, c, d, a = q1:unpack() - local f, g, h, e = q2:unpack() + local b, c, d, a = q1:normalized():unpack() + local f, g, h, e = q2:normalized():unpack() return quaternion.new(a*e - b*f - c*g - d*h, a*f + b*e + c*h - d*g, a*g + c*e + d*f - b*h, a*h + d*e + b*g - c*f) end +-- Returns a vector equal to the vector v (size >= 3) +-- rotated by the quaternion q function quaternion.mul_vector(q, v) local v3 = vector.new(3, v.values) local u1 = vector.new(3, {q.x, q.y, q.z}) @@ -51,6 +49,7 @@ function quaternion.mul_vector(q, v) return 2*d * u1 + (q.w*q.w - u1:length2()) * v3 + 2*q.w * c end +-- Returns a quaternion representing the rotation from -> to (size = 3) function quaternion.from_to_rotation(from, to) local v0 = from:normalized() local v1 = to:normalized() @@ -61,6 +60,8 @@ function quaternion.from_to_rotation(from, to) return quaternion.axis_angle(vector.cross(v0, v1):normalize(), math.acos(d)) end +-- Returns a quaternion representing the rotation of angle around axis +-- If angle is nil, then the norm of axis will be used as the angle function quaternion.axis_angle(axis, angle) angle = angle or axis:length() @@ -71,10 +72,36 @@ function quaternion.axis_angle(axis, angle) return quaternion.new(naxis:x() * hsin, naxis:y() * hsin, naxis:z() * hsin, hcos) end +-- ********** INSTANCE FUNCTIONS ********** + +-- Sets this quaternion's coordinates to these values +-- Arguments may be 4 numbers or 1 table with indexes x, y, z and w +function quaternion:set(x, y, z, w) + if (type(x) == "table" and x.class == "quaternion") then + self.x = x.x + self.y = x.y + self.z = x.z + self.w = x.w + else + self.x = x or 0 + self.y = y or 0 + self.z = z or 0 + self.w = w or 1 + end + return self +end + +-- Returns the inverse of this quaternion +function quaternion:inv() + return quaternion.new(-self.x, -self.y, -self.z, -self.w) +end + +-- Returns the 4 coordinates of this quaternion function quaternion:unpack() return self.x, self.y, self.z, self.w end +-- Normalizes this quaternion, so that its norm is 1 function quaternion:normalize() local len = self:length() if len ~= 0 and len ~= 1 then @@ -86,26 +113,24 @@ function quaternion:normalize() return self end +-- Returns a quaternion which is equal to this quaternion, normalized function quaternion:normalized() local q = self:clone() q:normalize() return q end -function quaternion:clone() - return quaternion.new(self.x, self.y, self.z, self.w) -end - +-- Returns the square of the norm of this quaternion function quaternion:length2() return self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w end +-- Returns the norm of this quaternion function quaternion:length() return math.sqrt(self:length2()) end -function quaternion.identity() return quaternion.new() end - +-- Returns the rotation matrix (4x4) corresponding to this quaternion function quaternion:to_matrix() local x, y, z, w = self:unpack() local m = matrix.new(4, 4, diff --git a/transform.lua b/transform.lua index 4abe68413737d6b88f4a53bd32c81c1165042329..e755162374a7b43daf28013abba6f4e56b0f3329 100644 --- a/transform.lua +++ b/transform.lua @@ -2,9 +2,13 @@ require("kara3d.vector") require("kara3d.matrix") require("kara3d.quaternion") +-- ********** DEFINITION AND GENERIC FUNCTIONS ********** + transform = {class = "transform"} transform.__index = transform +-- Creates a new transform with position p, rotation r, scale s and parent par +-- p and s should be of size 3, r is a quaternion, and par may be nil function transform.new(p, r, s, par) p = p or vector.zero(3) r = r or quaternion.identity() @@ -14,6 +18,7 @@ function transform.new(p, r, s, par) return t end +-- Returns a string that represents this transform function transform:tostring() return "(pos = (" .. self.pos:tostring() .. "), " .. "rot = (" .. self.rot:tostring() .. "), " @@ -21,11 +26,15 @@ function transform:tostring() .. (self.parent and "has parent" or "no parent") .. ")" end +-- ********** INSTANCE FUNCTIONS ********** + +-- Rotates this transform by angle around axis function transform:rotate(axis, angle) self.rot = quaternion.axis_angle(axis, angle) * self.rot return self end +-- Returns the local or global position of this transform function transform:position(global) local p = self.pos:clone() if (p.size > 4) then p:set_size(4) end @@ -36,6 +45,7 @@ function transform:position(global) return p:set_size(3) end +-- Returns the local or global rotation of this transform function transform:rotation(global) local q = self.rot:normalized() if (global and self.parent) then @@ -44,6 +54,7 @@ function transform:rotation(global) return q end +-- Returns the local or global scale of this transform function transform:scale(global) local s = self.scl:clone() if (global and self.parent) then @@ -55,6 +66,7 @@ function transform:scale(global) return s end +-- Returns the transformation matrix corresponding to this transform function transform:matrix(global) local translation = matrix.new(4, 4, {1, 0, 0, self.pos:get(1), diff --git a/vector.lua b/vector.lua index e77d9e4ce7b109449e2a75214d0f5943da43aa95..60a4e6749c588be863a4b92549fce9d5a327257d 100644 --- a/vector.lua +++ b/vector.lua @@ -1,6 +1,10 @@ +-- ********** DEFINITION AND GENERIC FUNCTIONS ********** + vector = {class = "vector"} vector.__index = vector +-- Creates a vector of size n, initialized with values vals +-- Uninitialized values are set to 0 function vector.new(n, vals) if (type(n) == "table") then vals = n n = #n end vals = vals or {} @@ -12,53 +16,10 @@ function vector.new(n, vals) return v end --- operator overloading -function vector.__add(a, b) - local small = a.size < b.size and a or b - local big = a.size < b.size and b or a - local r = big:clone() - for i = 1, small.size do r:set(i, r:get(i) + small:get(i)) end - return r -end - -function vector.__unm(a) - local r = vector.new(a.size) - for i = 1, r.size do r:set(i, -(a:get(i))) end - return r -end - -function vector.__sub(a, b) - local oppb = -b - return a + oppb -end - -function vector.__mul(a, b) - if type(a) == "number" and type(b) ~= "number" then - local r = vector.new(b.size) - for i = 1, r.size do r:set(i, a * b:get(i)) end - return r - elseif type(a) ~= "number" and type(b) == "number" then - local r = vector.new(a.size) - for i = 1, r.size do r:set(i, a:get(i) * b) end - return r - elseif type(a) ~= "number" and type(b) ~= "number" then - local small = a.size < b.size and a or b - local big = a.size < b.size and b or a - local r = vector.new(big.size) - for i = 1, small.size do r:set(i, small:get(i) * big:get(i)) end - return r - else - return a * b - end -end - -function vector.__eq(a, b) - if (a.size ~= b.size) then return false end - for i = 1, a.size do if (a:get(i) ~= b:get(i)) then return false end end - return true -end +-- Clones this vector +function vector:clone() return vector.new(self.size, self.values) end --- actual functions +-- Returns this vector's coordinates, separated by commas function vector:tostring() local str = "" for i = 1, self.size - 1 do @@ -67,30 +28,26 @@ function vector:tostring() return str .. self:get(self.size) end -function vector:clone() return vector.new(self.size, self.values) end - -function vector:length2() - local sum = 0 - for i = 1, self.size do sum = sum + self:get(i) * self:get(i) end - return sum -end - -function vector:length() return math.sqrt(self:length2()) end +-- ********** STATIC FUNCTIONS ********** -function vector:is_unit() return self:length2() == 1 end +-- Returns a vector full of 0s, with size size +function vector.zero(size) return vector.new(size) end -function vector:normalize() - local len = self:length() - if len == 0 or len == 1 then return self end - for i = 1, self.size do self:set(i, self:get(i) / len) end - return self +-- Returns a vector full of 1s, with size size +function vector.one(size) + local vals = {} + for i = 1, size do vals[i] = 1 end + return vector.new(size, vals) end -function vector:normalized() return self:clone():normalize() end - -function vector.project_vector(from, to) return (vector.dot(from, to) / to:length2()) * to end -function vector.project_plane(from, normal) return from - vector.project_vector(from, normal) end +-- Returns a vector of size size, filled with 0s and a 1 at index +function vector.cardinal(size, index) + local vals = {} + for i = 1, size do vals[i] = i == index and 1 or 0 end + return vector.new(size, vals) +end +-- Returns the dot product of two vectors function vector.dot(a, b) local min_size = math.min(a.size, b.size) local sum = 0 @@ -98,20 +55,41 @@ function vector.dot(a, b) return sum end +-- Returns the cross product of two vectors of size 3 +function vector.cross(a, b) + return vector.new(3, {a.values[2] * b.values[3] - a.values[3] * b.values[2], + a.values[3] * b.values[1] - a.values[1] * b.values[3], + a.values[1] * b.values[2] - a.values[2] * b.values[1]}) +end + +-- Returns a vector which is the projection of the vector from to the vector to +function vector.project_vector(from, to) return (vector.dot(from, to) / to:length2()) * to end + +-- Returns a vector which is the projection of the vector from to the plane of normal normal +function vector.project_plane(from, normal) return from - vector.project_vector(from, normal) end + +-- Returns the square of the distance between two vectors function vector.distance2(a, b) return (a - b):length2() end + +-- Returns the distance between two vectors function vector.distance(a, b) return (a - b):length() end +-- Returns the angle between two vectors function vector.angle(from, to) return math.acos(vector.dot(from, to) / (from:length() * to:length())) end + +-- Returns a vector lerped between from and to, with parameter t function vector.lerp(from, to, t) return t * from + (1 - t) * to end -function vector:set_values(vals) - vals = vals or {} - for i = 1, #vals do self.values[i] = vals[i] end - for i = #vals + 1, self.size do self.values[i] = nil end - self.size = #vals - return self -end +-- ********** INSTANCE FUNCTIONS ********** +-- Sets the ith cell of this vector to value +function vector:set(i, value) self.values[i] = value return self end + +-- Returns the ith value of this vector +function vector:get(i) return self.values[i] end + +-- Sets the size of this vector to n +-- Uninitialized values are set to 0, and overflowing values are set to nil function vector:set_size(n) if (self.size == n) then return self end for i = n + 1, self.size do self:set(i, nil) end @@ -120,31 +98,101 @@ function vector:set_size(n) return self end -function vector.cross(a, b) - return vector.new(3, {a.values[2] * b.values[3] - a.values[3] * b.values[2], - a.values[3] * b.values[1] - a.values[1] * b.values[3], - a.values[1] * b.values[2] - a.values[2] * b.values[1]}) +-- Sets the values of this vector to vals +-- Uninitialized values are set to 0 +function vector:set_values(vals) + vals = vals or {} + for i = 1, #vals do self:set(i, vals[i]) end + for i = #vals + 1, self.size do self:set(i, 0) end + self.size = #vals + return self end -function vector.zero(size) return vector.new(size) end -function vector.one(size) - local vals = {} - for i = 1, size do vals[i] = 1 end - return vector.new(size, vals) -end -function vector.cardinal(size, index) - local vals = {} - for i = 1, size do vals[i] = i == index and 1 or 0 end - return vector.new(size, vals) +-- Returns the square of the norm of this vector +function vector:length2() + local sum = 0 + for i = 1, self.size do sum = sum + self:get(i) * self:get(i) end + return sum end -function vector:get(index) return self.values[index] end -function vector:set(index, value) - self.values[index] = value +-- Returns the norm of this vector +function vector:length() return math.sqrt(self:length2()) end + +-- Returns ||v|| == 1, v being this vector +function vector:is_unit() return self:length2() == 1 end + +-- Sets the coordinates of this vector so that it has the same direction, +-- but has a norm equal to 1 +function vector:normalize() + local len = self:length() + if len == 0 or len == 1 then return self end + for i = 1, self.size do self:set(i, self:get(i) / len) end return self end +-- Returns a vector with the same direction as this vector, but with norm 1 +function vector:normalized() return self:clone():normalize() end + +-- Convenience functions that return the first four coordinates function vector:x() return self.values[1] end function vector:y() return self.values[2] end function vector:z() return self.values[3] end function vector:w() return self.values[4] end + +-- ********** OPERATOR OVERLOADING ********** + +-- + binary operator overload +-- Returns a vector c such that for all i, c(i) = a(i) + b(i) +function vector.__add(a, b) + local small = a.size < b.size and a or b + local big = a.size < b.size and b or a + local r = big:clone() + for i = 1, small.size do r:set(i, r:get(i) + small:get(i)) end + return r +end + +-- - unary operator overload +-- Returns a vector c such that for all i, c(i) = -a(i) +function vector.__unm(a) + local r = vector.new(a.size) + for i = 1, r.size do r:set(i, -(a:get(i))) end + return r +end + +-- - binary operator overload +-- Returns a vector c such that for all i, c(i) = a(i) - b(i) +function vector.__sub(a, b) + local oppb = -b + return a + oppb +end + +-- * binary operator overload +-- If one of the arguments is a scalar, scales the other argument by it +-- Else, returns a vector c such that for all i, c(i) = a(i) * b(i) +function vector.__mul(a, b) + if type(a) == "number" and type(b) ~= "number" then + local r = vector.new(b.size) + for i = 1, r.size do r:set(i, a * b:get(i)) end + return r + elseif type(a) ~= "number" and type(b) == "number" then + local r = vector.new(a.size) + for i = 1, r.size do r:set(i, a:get(i) * b) end + return r + elseif type(a) ~= "number" and type(b) ~= "number" then + local small = a.size < b.size and a or b + local big = a.size < b.size and b or a + local r = vector.new(big.size) + for i = 1, small.size do r:set(i, small:get(i) * big:get(i)) end + return r + else + return a * b + end +end + +-- == binary operator overload +-- Returns "for all i, a(i) == b(i)"" +function vector.__eq(a, b) + if (a.size ~= b.size) then return false end + for i = 1, a.size do if (a:get(i) ~= b:get(i)) then return false end end + return true +end