3 Note: The vector.*-functions must be able to accept old vectors that had no metatables
7 local setmetatable = setmetatable
9 -- vector.metatable is set by C++.
10 local metatable = vector.metatable
12 local xyz = {"x", "y", "z"}
14 -- only called when rawget(v, key) returns nil
15 function metatable.__index(v, key)
16 return rawget(v, xyz[key]) or vector[key]
19 -- only called when rawget(v, key) returns nil
20 function metatable.__newindex(v, key, value)
21 rawset(v, xyz[key] or key, value)
26 local function fast_new(x, y, z)
27 return setmetatable({x = x, y = y, z = z}, metatable)
30 function vector.new(a, b, c)
32 return fast_new(a, b, c)
35 -- deprecated, use vector.copy and vector.zero directly
36 if type(a) == "table" then
39 assert(not a, "Invalid arguments for vector.new()")
44 function vector.zero()
45 return fast_new(0, 0, 0)
48 function vector.copy(v)
49 assert(v.x and v.y and v.z, "Invalid vector passed to vector.copy()")
50 return fast_new(v.x, v.y, v.z)
53 function vector.from_string(s, init)
54 local x, y, z, np = string.match(s, "^%s*%(%s*([^%s,]+)%s*[,%s]%s*([^%s,]+)%s*[,%s]" ..
55 "%s*([^%s,]+)%s*[,%s]?%s*%)()", init)
59 if not (x and y and z) then
62 return fast_new(x, y, z), np
65 function vector.to_string(v)
66 return string.format("(%g, %g, %g)", v.x, v.y, v.z)
68 metatable.__tostring = vector.to_string
70 function vector.equals(a, b)
75 metatable.__eq = vector.equals
79 function vector.length(v)
80 return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
82 -- Note: we can not use __len because it is already used for primitive table length
84 function vector.normalize(v)
85 local len = vector.length(v)
87 return fast_new(0, 0, 0)
89 return vector.divide(v, len)
93 function vector.floor(v)
94 return vector.apply(v, math.floor)
97 function vector.round(v)
105 function vector.apply(v, func)
113 function vector.combine(a, b, func)
121 function vector.distance(a, b)
125 return math.sqrt(x * x + y * y + z * z)
128 function vector.direction(pos1, pos2)
129 return vector.subtract(pos2, pos1):normalize()
132 function vector.angle(a, b)
133 local dotp = vector.dot(a, b)
134 local cp = vector.cross(a, b)
135 local crossplen = vector.length(cp)
136 return math.atan2(crossplen, dotp)
139 function vector.dot(a, b)
140 return a.x * b.x + a.y * b.y + a.z * b.z
143 function vector.cross(a, b)
145 a.y * b.z - a.z * b.y,
146 a.z * b.x - a.x * b.z,
147 a.x * b.y - a.y * b.x
151 function metatable.__unm(v)
152 return fast_new(-v.x, -v.y, -v.z)
155 -- add, sub, mul, div operations
157 function vector.add(a, b)
158 if type(b) == "table" then
172 function metatable.__add(a, b)
180 function vector.subtract(a, b)
181 if type(b) == "table" then
195 function metatable.__sub(a, b)
203 function vector.multiply(a, b)
204 if type(b) == "table" then
218 function metatable.__mul(a, b)
219 if type(a) == "table" then
234 function vector.divide(a, b)
235 if type(b) == "table" then
249 function metatable.__div(a, b)
250 -- scalar/vector makes no sense
260 function vector.offset(v, x, y, z)
268 function vector.sort(a, b)
269 return fast_new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z)),
270 fast_new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))
273 function vector.check(v)
274 return getmetatable(v) == metatable
277 local function sin(x)
278 if x % math.pi == 0 then
285 local function cos(x)
286 if x % math.pi == math.pi / 2 then
293 function vector.rotate_around_axis(v, axis, angle)
294 local cosangle = cos(angle)
295 local sinangle = sin(angle)
296 axis = vector.normalize(axis)
297 -- https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
298 local dot_axis = vector.multiply(axis, vector.dot(axis, v))
299 local cross = vector.cross(v, axis)
301 cross.x * sinangle + (v.x - dot_axis.x) * cosangle + dot_axis.x,
302 cross.y * sinangle + (v.y - dot_axis.y) * cosangle + dot_axis.y,
303 cross.z * sinangle + (v.z - dot_axis.z) * cosangle + dot_axis.z
307 function vector.rotate(v, rot)
308 local sinpitch = sin(-rot.x)
309 local sinyaw = sin(-rot.y)
310 local sinroll = sin(-rot.z)
311 local cospitch = cos(rot.x)
312 local cosyaw = cos(rot.y)
313 local cosroll = math.cos(rot.z)
314 -- Rotation matrix that applies yaw, pitch and roll
317 sinyaw * sinpitch * sinroll + cosyaw * cosroll,
318 sinyaw * sinpitch * cosroll - cosyaw * sinroll,
327 cosyaw * sinpitch * sinroll - sinyaw * cosroll,
328 cosyaw * sinpitch * cosroll + sinyaw * sinroll,
332 -- Compute matrix multiplication: `matrix` * `v`
334 matrix[1][1] * v.x + matrix[1][2] * v.y + matrix[1][3] * v.z,
335 matrix[2][1] * v.x + matrix[2][2] * v.y + matrix[2][3] * v.z,
336 matrix[3][1] * v.x + matrix[3][2] * v.y + matrix[3][3] * v.z
340 function vector.dir_to_rotation(forward, up)
341 forward = vector.normalize(forward)
342 local rot = vector.new(math.asin(forward.y), -math.atan2(forward.x, forward.z), 0)
346 assert(vector.dot(forward, up) < 0.000001,
347 "Invalid vectors passed to vector.dir_to_rotation().")
348 up = vector.normalize(up)
349 -- Calculate vector pointing up with roll = 0, just based on forward vector.
350 local forwup = vector.rotate(vector.new(0, 1, 0), rot)
351 -- 'forwup' and 'up' are now in a plane with 'forward' as normal.
352 -- The angle between them is the absolute of the roll value we're looking for.
353 rot.z = vector.angle(forwup, up)
355 -- Since vector.angle never returns a negative value or a value greater
356 -- than math.pi, rot.z has to be inverted sometimes.
357 -- To determine wether this is the case, we rotate the up vector back around
358 -- the forward vector and check if it worked out.
359 local back = vector.rotate_around_axis(up, forward, -rot.z)
361 -- We don't use vector.equals for this because of floating point imprecision.
362 if (back.x - forwup.x) * (back.x - forwup.x) +
363 (back.y - forwup.y) * (back.y - forwup.y) +
364 (back.z - forwup.z) * (back.z - forwup.z) > 0.0000001 then