3 Note: The vector.*-functions must be able to accept old vectors that had no metatables
7 local setmetatable = setmetatable
12 vector.metatable = metatable
14 local xyz = {"x", "y", "z"}
16 -- only called when rawget(v, key) returns nil
17 function metatable.__index(v, key)
18 return rawget(v, xyz[key]) or vector[key]
21 -- only called when rawget(v, key) returns nil
22 function metatable.__newindex(v, key, value)
23 rawset(v, xyz[key] or key, value)
28 local function fast_new(x, y, z)
29 return setmetatable({x = x, y = y, z = z}, metatable)
32 function vector.new(a, b, c)
33 if type(a) == "table" then
34 assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()")
35 return fast_new(a.x, a.y, a.z)
37 assert(b and c, "Invalid arguments for vector.new()")
38 return fast_new(a, b, c)
40 return fast_new(0, 0, 0)
43 function vector.from_string(s, init)
44 local x, y, z, np = string.match(s, "^%s*%(%s*([^%s,]+)%s*[,%s]%s*([^%s,]+)%s*[,%s]" ..
45 "%s*([^%s,]+)%s*[,%s]?%s*%)()", init)
49 if not (x and y and z) then
52 return {x = x, y = y, z = z}, np
55 function vector.to_string(v)
56 return string.format("(%g, %g, %g)", v.x, v.y, v.z)
58 metatable.__tostring = vector.to_string
60 function vector.equals(a, b)
65 metatable.__eq = vector.equals
69 function vector.length(v)
70 return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
72 -- Note: we can not use __len because it is already used for primitive table length
74 function vector.normalize(v)
75 local len = vector.length(v)
77 return fast_new(0, 0, 0)
79 return vector.divide(v, len)
83 function vector.floor(v)
84 return vector.apply(v, math.floor)
87 function vector.round(v)
95 function vector.apply(v, func)
103 function vector.distance(a, b)
107 return math.sqrt(x * x + y * y + z * z)
110 function vector.direction(pos1, pos2)
111 return vector.subtract(pos2, pos1):normalize()
114 function vector.angle(a, b)
115 local dotp = vector.dot(a, b)
116 local cp = vector.cross(a, b)
117 local crossplen = vector.length(cp)
118 return math.atan2(crossplen, dotp)
121 function vector.dot(a, b)
122 return a.x * b.x + a.y * b.y + a.z * b.z
125 function vector.cross(a, b)
127 a.y * b.z - a.z * b.y,
128 a.z * b.x - a.x * b.z,
129 a.x * b.y - a.y * b.x
133 function metatable.__unm(v)
134 return fast_new(-v.x, -v.y, -v.z)
137 -- add, sub, mul, div operations
139 function vector.add(a, b)
140 if type(b) == "table" then
154 function metatable.__add(a, b)
162 function vector.subtract(a, b)
163 if type(b) == "table" then
177 function metatable.__sub(a, b)
185 function vector.multiply(a, b)
186 if type(b) == "table" then
200 function metatable.__mul(a, b)
201 if type(a) == "table" then
216 function vector.divide(a, b)
217 if type(b) == "table" then
231 function metatable.__div(a, b)
232 -- scalar/vector makes no sense
242 function vector.offset(v, x, y, z)
250 function vector.sort(a, b)
251 return fast_new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z)),
252 fast_new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))
255 function vector.check(v)
256 return getmetatable(v) == metatable
259 local function sin(x)
260 if x % math.pi == 0 then
267 local function cos(x)
268 if x % math.pi == math.pi / 2 then
275 function vector.rotate_around_axis(v, axis, angle)
276 local cosangle = cos(angle)
277 local sinangle = sin(angle)
278 axis = vector.normalize(axis)
279 -- https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
280 local dot_axis = vector.multiply(axis, vector.dot(axis, v))
281 local cross = vector.cross(v, axis)
283 cross.x * sinangle + (v.x - dot_axis.x) * cosangle + dot_axis.x,
284 cross.y * sinangle + (v.y - dot_axis.y) * cosangle + dot_axis.y,
285 cross.z * sinangle + (v.z - dot_axis.z) * cosangle + dot_axis.z
289 function vector.rotate(v, rot)
290 local sinpitch = sin(-rot.x)
291 local sinyaw = sin(-rot.y)
292 local sinroll = sin(-rot.z)
293 local cospitch = cos(rot.x)
294 local cosyaw = cos(rot.y)
295 local cosroll = math.cos(rot.z)
296 -- Rotation matrix that applies yaw, pitch and roll
299 sinyaw * sinpitch * sinroll + cosyaw * cosroll,
300 sinyaw * sinpitch * cosroll - cosyaw * sinroll,
309 cosyaw * sinpitch * sinroll - sinyaw * cosroll,
310 cosyaw * sinpitch * cosroll + sinyaw * sinroll,
314 -- Compute matrix multiplication: `matrix` * `v`
316 matrix[1][1] * v.x + matrix[1][2] * v.y + matrix[1][3] * v.z,
317 matrix[2][1] * v.x + matrix[2][2] * v.y + matrix[2][3] * v.z,
318 matrix[3][1] * v.x + matrix[3][2] * v.y + matrix[3][3] * v.z
322 function vector.dir_to_rotation(forward, up)
323 forward = vector.normalize(forward)
324 local rot = vector.new(math.asin(forward.y), -math.atan2(forward.x, forward.z), 0)
328 assert(vector.dot(forward, up) < 0.000001,
329 "Invalid vectors passed to vector.dir_to_rotation().")
330 up = vector.normalize(up)
331 -- Calculate vector pointing up with roll = 0, just based on forward vector.
332 local forwup = vector.rotate(vector.new(0, 1, 0), rot)
333 -- 'forwup' and 'up' are now in a plane with 'forward' as normal.
334 -- The angle between them is the absolute of the roll value we're looking for.
335 rot.z = vector.angle(forwup, up)
337 -- Since vector.angle never returns a negative value or a value greater
338 -- than math.pi, rot.z has to be inverted sometimes.
339 -- To determine wether this is the case, we rotate the up vector back around
340 -- the forward vector and check if it worked out.
341 local back = vector.rotate_around_axis(up, forward, -rot.z)
343 -- We don't use vector.equals for this because of floating point imprecision.
344 if (back.x - forwup.x) * (back.x - forwup.x) +
345 (back.y - forwup.y) * (back.y - forwup.y) +
346 (back.z - forwup.z) * (back.z - forwup.z) > 0.0000001 then