Luaのvectorとquaternionを使いやすくする
#LuaScriptingMod #LuaModに関してのメモ
テストがまだ十分ではないと思うので使用には注意。このファイルをdofileすればVector3とQuaternionが使えるようになる。通常のvectorとquaternionとは別物。各関数で引数の型チェックをしているが、デバッグには有利なものの完成したプログラムでも実行していると(微々たるものだと思うが)処理が遅くなるかもしれない。
機能
以下vはVector3のインスタンス、qはQuaternionのインスタンスとする。
ベクトル同士の足し算、引き算と、ベクトルとスカラーの掛け算、割り算と、クォータニオン同士の掛け算と、クォータニオンとベクトルの掛け算を普通の数のように演算子で計算できる。(Luaのメタテーブルを利用)
Vector3.new()とQuaternion.new()で生成できる。引数にはx,y,z(,w)を直接指定のほか、通常のvector, quaternionも投げ込むことができる。
通常のvector, quaternionにあるメソッドは一通り使用可能。追加でいくつかメソッドを用意してある。
v:raw()、q:raw()で通常のvector, quaternionを取得できる。
v:normalize()、v:magnitude()、v:sqr_magnitude()、q:inverse()はインスタンスメソッドとして実行できる。
Vector3.forward()、Vector3.back()、Vector3.up()、Vector3.down()、Vector3.right()、Vector3.left()、Vector3.zero()、Vector3.one()、Vector3.positive_huge()、Vector3.negative_huge()、Quaternion.identity()はスタティックメソッドとして新しいVector3、Quaternionのインスタンスを生成する。
メタテーブルの__tostringを実装しているので、printにそのまま投げ込める。
code:qualiternion_of_life.lua
-- aをd桁で丸める
local function round(a, d)
local p = math.pow(10, d)
return math.round(a * p) / p
end
-- aが指定通りのクラスのインスタンスかget_classnameを用いて確認
local function instance_check(a, classname, funcname)
if type(a) ~= 'table' then
print('Error: ' .. funcname .. ' got wrong type of value')
return
end
if type(a.get_classname) == 'function' and a.get_classname() ~= classname then
print('Error: ' .. funcname .. ' got wrong instance')
return
end
return a
end
local function instance_check_2(a, b, classname, funcname)
return instance_check(a, classname, funcname), instance_check(b, classname, funcname)
end
-- aが指定通りの型の値か確認
local function type_check(a, typename, funcname)
if type(a) ~= typename then
print('Error: ' .. funcname .. ' got wrong type of value')
return
end
return a
end
-- aとbが指定通りの型の値とクラスのインスタンスかget_classnameを用いて確認
local function type_instance_check(a, b, typename, classname, funcname)
local type_a = type(a)
local type_b = type(b)
if type_a == typename and type_b == 'table' then
return type_check(a, typename, funcname), instance_check(b, classname, funcname)
elseif type_a == 'table' and type_b == typename then
return type_check(b, typename, funcname), instance_check(a, classname, funcname)
else
print('Error: ' .. funcname ..' got wrong type of value')
return
end
end
-- オレオレvectorクラス
Vector3 = {
prototype = {}
}
-- コンストラクタ
-- x, y, z を個別に指定するか、通常のvectorテーブルを投げ込めばOK
function Vector3.new(x, y, z)
if type(x) == 'table' then
local v = x
x = v.x
y = v.y
z = v.z
end
x = type_check(x, 'number', 'Vector3.new')
y = type_check(y, 'number', 'Vector3.new')
z = type_check(z, 'number', 'Vector3.new')
local self = {
x = x,
y = y,
z = z
}
return setmetatable(self, {
__add = Vector3.add,
__sub = Vector3.subtract,
__mul = Vector3.multiply,
__div = Vector3.div,
__unm = Vector3.negative,
__tostring = function(v)
return '(' .. round(v.x, 2) .. ', ' .. round(v.y, 2) .. ', ' .. round(v.z, 2) .. ')'
end,
__index = Vector3.prototype
})
end
-- スタティックメソッド
function Vector3.forward()
return Vector3.new(0, 0, 1)
end
function Vector3.back()
return Vector3.new(0, 0, -1)
end
function Vector3.up()
return Vector3.new(0, 1, 0)
end
function Vector3.down()
return Vector3.new(0, -1, 0)
end
function Vector3.right()
return Vector3.new(1, 0, 0)
end
function Vector3.left()
return Vector3.new(-1, 0, 0)
end
function Vector3.zero()
return Vector3.new(0, 0, 0)
end
function Vector3.one()
return Vector3.new(1, 1, 1)
end
function Vector3.positive_huge()
return Vector3.new(math.huge, math.huge, math.huge)
end
function Vector3.negative_huge()
return Vector3.new(-math.huge, -math.huge, -math.huge)
end
function Vector3.distance(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.distance')
return vector.distance(a:raw(), b:raw())
end
function Vector3.dot(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.dot')
return vector.dot(a:raw(), b:raw())
end
function Vector3.lerp(a, b, t)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.lerp')
t = type_check(t, 'number', 'Vector3.lerp')
return Vector3.new(vector.lerp(a:raw(), b:raw(), t))
end
function Vector3.lerp_unclamped(a, b, t)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.lerp_unclamped')
t = type_check(t, 'number', 'Vector3.lerp_unclamped')
return Vector3.new(vector.lerp_unclamped(a:raw(), b:raw(), t))
end
function Vector3.max(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.max')
return Vector3.new(vector.max(a:raw(), b:raw()))
end
function Vector3.min(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.min')
return Vector3.new(vector.min(a:raw(), b:raw()))
end
function Vector3.move_towards(current, target, max_distance_delta)
current, target = instance_check_2(move_towards, target, 'Vector3', 'Vector3.move_towards')
max_distance_delta = type_check(max_distance_delta, 'number', 'Vector3.max_distance_delta')
return Vector3.new(vector.move_towards(current:raw(), target:raw(), max_distance_delta))
end
function Vector3.project(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.project')
return Vector3.new(vector.project(a:raw(), b:raw()))
end
function Vector3.scale(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.scale')
return Vector3.new(vector.scale(a:raw(), b:raw()))
end
function Vector3.add(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.add')
return Vector3.new(vector.add(a:raw(), b:raw()))
end
function Vector3.subtract(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.subtract')
return Vector3.new(vector.subtract(a:raw(), b:raw()))
end
function Vector3.negative(a)
a = instance_check(a, 'Vector3', 'Vector3.negative')
return Vector3.new(vector.negative(a:raw()))
end
function Vector3.multiply(a, b)
a, b = type_instance_check(a, b, 'number', 'Vector3', 'Vector3.multiply')
return Vector3.new(vector.multiply(b:raw(), a))
end
function Vector3.div(a, b)
a = instance_check(a, 'Vector3', 'Vector3.div')
b = type_check(b, 'number', 'Vector3.div')
return Vector3.new(vector.multiply(a:raw(), 1 / b))
end
function Vector3.equals(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.equals')
return vector.equals(a:raw(), b:raw())
end
function Vector3.angle(from, to)
from, to = instance_check_2(from, to, 'Vector3', 'Vector3.angle')
return vector.angle(from:raw(), to:raw())
end
function Vector3.clamp_magnitude(a, max_length)
a = instance_check(a, 'Vector3', 'Vector3.clamp_magnitude')
max_length = type_check(max_length, 'Vector3', 'Vector3.clamp_magnitude')
return Vector3.new(vector.clamp_magnitude(a:raw(), max_length))
end
function Vector3.cross(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Vector3.cross')
return Vector3.new(vector.cross(a:raw(), b:raw()))
end
function Vector3.project_on_plane(point, normal)
point, normal = instance_check_2(point, normal, 'Vector3', 'Vector3.project_on_plane')
return Vector3.new(vector.project_on_plane(point:raw(), normal:raw()))
end
function Vector3.reflect(in_direction, in_normal)
in_direction, in_normal = instance_check_2(in_direction, in_normal, 'Vector3', 'Vector3.reflect')
return Vector3.new(vector.reflect(in_direction:raw(), in_normal:raw()))
end
function Vector3.magnitude(a)
a = instance_check(a, 'Vector3', 'Vector3.magnitude')
return vector.magnitude(a:raw())
end
function Vector3.sqr_magnitude(a)
a = instance_check(a, 'Vector3', 'Vector3.sqr_magnitude')
return a.x * a.x + a.y * a.y + a.z * a.z
end
function Vector3.normalize(a)
a = instance_check(a, 'Vector3', 'Vector3.normalize')
return Vector3.new(vector.normalize(a:raw()))
end
-- インスタンスメソッド
function Vector3.prototype:magnitude()
return Vector3.magnitude(self)
end
function Vector3.prototype:sqr_magnitude()
return Vector3.sqr_magnitude(self)
end
function Vector3.prototype:normalize()
return Vector3.normalize(self)
end
function Vector3.prototype:raw()
return vector.new(self.x, self.y, self.z)
end
function Vector3.prototype:get_classname()
return 'Vector3'
end
-- オレオレquaternionクラス
Quaternion = {
prototype = {}
}
-- コンストラクタ
-- x, y, z, w を個別に指定するか、通常のquaternionテーブルを投げ込めばOK
function Quaternion.new(x, y, z, w)
if type(x) == 'table' then
local q = x
x = q.x
y = q.y
z = q.z
w = q.w
end
x = type_check(x, 'number', 'Quaternion.new')
y = type_check(y, 'number', 'Quaternion.new')
z = type_check(z, 'number', 'Quaternion.new')
w = type_check(w, 'number', 'Quaternion.new')
local self = {
x = x,
y = y,
z = z,
w = w
}
return setmetatable(self, {
__mul = function(a, b)
if type(b) == 'table' and type(b.get_classname) == 'function' and b.get_classname() == 'Vector3' then
return Quaternion.multiply_on_vector(a, b)
else
return Quaternion.multiply(a, b)
end
end,
__tostring = function(q)
return '(' .. round(q.x, 2) .. ', ' .. round(q.y, 2) .. ', ' .. round(q.z, 2) .. ', ' .. round(q.w, 2) .. ')'
end,
__index = Quaternion.prototype
})
end
-- スタティックメソッド
function Quaternion.identity()
return Quaternion.new(0, 0, 0, 1)
end
function Quaternion.euler(x, y, z)
if type(x) == 'table' then
local v = x
x = v.x
y = v.y
z = v.z
end
x = type_check(x, 'number', 'Quaternion.euler')
y = type_check(y, 'number', 'Quaternion.euler')
z = type_check(z, 'number', 'Quaternion.euler')
return Quaternion.new(quaternion.euler(x, y, z))
end
function Quaternion.angle(a, b)
a, b = instance_check_2(a, b, 'Quaternion', 'Quaternion.angle')
return quaternion.angle(a:raw(), b:raw())
end
function Quaternion.angle_axis(angle, axis)
angle = type_check(angle, 'number', 'Quaternion.angle_axis')
axis = instance_check(axis, 'Vector3', 'Quaternion.angle_axis')
return Quaternion.new(quaternion.angle_axis(angle, axis:raw()))
end
function Quaternion.dot(a, b)
a, b = instance_check_2(a, b, 'Quaternion', 'Quaternion.dot')
return quaternion.dot(a:raw(), b:raw())
end
function Quaternion.from_to_rotation(a, b)
a, b = instance_check_2(a, b, 'Vector3', 'Quaternion.from_to_rotation')
return Quaternion.new(quaternion.from_to_rotation(a:raw(), b:raw()))
end
function Quaternion.inverse(a)
a = instance_check(a, 'Quaternion', 'Quaternion.inverse')
return Quaternion.new(quaternion.inverse(a:raw()))
end
function Quaternion.lerp(a, b, t)
a, b = instance_check_2(a, b, 'Quaternion', 'Quaternion.lerp')
t = type_check(t, 'number', 'Quaternion.lerp')
return Quaternion.new(quaternion.lerp(a:raw(), b:raw(), t))
end
function Quaternion.lerp_unclamped(a, b, t)
a, b = instance_check_2(a, b, 'Quaternion', 'Quaternion.lerp_unclamped')
t = type_check(t, 'number', 'Quaternion.lerp_unclamped')
return Quaternion.new(quaternion.lerp_unclamped(a:raw(), b:raw(), t))
end
function Quaternion.slerp(a, b, t)
a, b = instance_check_2(a, b, 'Quaternion', 'Quaternion.slerp')
t = type_check(t, 'number', 'Quaternion.slerp')
return Quaternion.new(quaternion.slerp(a:raw(), b:raw(), t))
end
function Quaternion.slerp_unclamped(a, b, t)
a, b = instance_check_2(a, b, 'Quaternion', 'Quaternion.slerp_unclamped')
t = type_check(t, 'number', 'Quaternion.slerp_unclamped')
return Quaternion.new(quaternion.slerp_unclamped(a:raw(), b:raw(), t))
end
function Quaternion.look_rotation(forward, upwards)
forward, upwards = instance_check_2(forward, upwards, 'Vector3', 'Quaternion.look_rotation')
return Quaternion.new(quaternion.look_rotation(forward:raw(), upwards:raw()))
end
function Quaternion.rotate_towards(a, b, max_degrees_delta)
a, b = instance_check_2(a, b, 'Quaternion', 'Quaternion.rotate_towards')
max_degrees_delta = type_check(max_degrees_delta, 'number', 'Quaternion.rotate_towards')
return Quaternion.new(quaternion.rotate_towards(a:raw(), b:raw(), max_degrees_delta))
end
function Quaternion.multiply(a, b)
a, b = instance_check_2(a, b, 'Quaternion', 'Quaternion.multiply')
return Quaternion.new(quaternion.multiply(a:raw(), b:raw()))
end
function Quaternion.multiply_on_vector(a, b)
a = instance_check(a, 'Quaternion', 'Quaternion.multiply_on_vector')
b = instance_check(b, 'Vector3', 'Quaternion.multiply_on_vector')
return Vector3.new(quaternion.multiply_on_vector(a:raw(), b:raw()))
end
function Quaternion.equals(a, b)
a, b = instance_check_2(a, b, 'Quaternion', 'Quaternion.equals')
return quaternion.equals(a:raw(), b:raw())
end
-- インスタンスメソッド
function Quaternion.prototype:inverse()
return Quaternion.inverse(self)
end
function Quaternion.prototype:raw()
return { x = self.x, y = self.y, z = self.z, w = self.w }
end
function Quaternion.prototype:get_classname()
return 'Quaternion'
end