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.hypot(v.x, math.hypot(v.y, 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.hypot(x, math.hypot(y, 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