ベクトルと行列の案
昔っから何度も出ては消えてを繰り返しているネタである。作ってみていまいちなので却下してという話で、動かすだけならすぐにでもできるが、それが扱いやすいかというと微妙になってしまう。
これがなぜ扱いにくいかというと、もともとが数学の道具であって、そのままではゲームプログラミングと地味なところで噛み合わないからなのではないかと考えている。
そこで、数学的な動きはそのままに、数学的にあり得ない部分をゲーム的に補間するという荒技で機能追加してみた。こんな感じに動く。
assert(Vector.new(1,2) + Vector.new(1,2) == Vector.new(2,4)) assert(Vector.new(1,2) + Vector.new(1,2,3) == Vector.new(2,4)) # assert(Vector.new(1,2,3) + Vector.new(1,2) == Vector.new(2,4,3)) # assert(Vector.new(1,2,3) + 5 == Vector.new(6,7,8)) assert(Vector.new(1,1) * Matrix.transration(1,2) == Vector.new(2,3)) # assert(Vector.new(1,1,1) * Matrix.transration(1,2) == Vector.new(2,3,1)) assert(Vector.new(2,2,2) * Matrix.transration(1,2,3) == Vector.new(3,4,5)) # assert(Vector.new(2,2,2,1) * Matrix.transration(1,2,3) == Vector.new(3,4,5,1)) assert(Vector.new(5,5).rotate(45) == Vector.new(5,5) * Matrix.rotation(45)) #
右端に#がついている行が数学的にはエラーにならなければならない部分である。でもこのように動いてくれるなら、ゲーム用としては扱いやすくなるのではないか。
もともと、たとえば2要素のベクトルは2Dの座標を表すのに使うが、行列との演算で平行移動させることができない。3要素目に1をいれたベクトルじゃないといけないからだ。しかし3要素目に1をいれたベクトルはそれ同士を足すと3要素目が2になってしまうのでなにかと面倒だ。
要素数が足りない部分にたいして、自動的に0なり1なりを補間して、平行移動とかができるようにしてやれば面倒さが無くなる。まあ、むちゃくちゃだ。怒られそうだ。
ついでにSpriteクラスにメソッドを追加して座標をベクトル指定できるようにもしてみた。こんな感じに動く。
s = Sprite.new s.xy = Vector.new(1,2) assert(s.x == 1 && s.y == 2) assert(s.xy == Vector.new(1,2)) s.x = 5 assert(s.xy == Vector.new(5,2)) s.xyz = Vector.new(10,20,30) assert(s.x == 10 && s.y == 20 && s.z == 30) assert(s.xyz == Vector.new(10,20,30)) s.x = 50 assert(s.xyz == Vector.new(50,20,30))
上と組み合わせればわりと自由に座標を操作することができそうである。とりあえずRubyで書いたコードを置いておくが、機能は足りてないし遅いしエラーチェックしてないしでこれを使うのはおすすめしない。DXRuby1.5devにでも作って入れて試してみようと思っているところ。
コードは長いので続きに。
require 'dxruby' class Vector @@to_deg = Math::PI / 180.0 def initialize(*v) @vec = v end def rotate(angle, center_x = 0, center_y = 0) rad = @@to_deg * angle sin = Math.sin(rad) cos = Math.cos(rad) tempx = @vec[0] - center_x tempy = @vec[1] - center_y x = tempx * cos - tempy * sin + center_x y = tempx * sin + tempy * cos + center_y Vector.new(x, y) end def +(v) case v when Vector Vector.new(*@vec.map.with_index{|s,i|s+(v[i]?v[i]:0)}) when Array Vector.new(*@vec.map.with_index{|s,i|s+(v[i]?v[i]:0)}) when Numeric Vector.new(*@vec.map{|s|s+v}) else nil end end def *(matrix) if matrix.size > @vec.size temp = @vec + [1] else temp = @vec end result = [] for i in 0...(matrix.size) data = 0 for j in 0...(temp.size) data += temp[j] * matrix[j][i] end result.push(data) end Vector.new(*result[0, @vec.size]) end def [](i) @vec[i] end def size @vec.size end def to_a @vec end def x @vec[0] end def y @vec[1] end def z @vec[2] end def w @vec[3] end def xy Vector.new(@vec[0..1]) end def xyz Vector.new(@vec[0..2]) end def ==(t) @vec == t.to_a end end class Matrix @@to_deg = Math::PI / 180.0 def initialize(*arr) @arr = Array.new(arr.size) {|i| Vector.new(*arr[i])} end def *(a) result = [] for i in 0...(a.size) result.push(@arr[i] * a) end Matrix.new(*result) end def [](i) @arr[i] end def size @arr.size end def self.rotation(angle) rad = @@to_deg * angle cos = Math.cos(rad) sin = Math.sin(rad) Matrix.new( [ cos, sin, 0], [-sin, cos, 0], [ 0, 0, 1] ) end def self.rotation_z(angle) rad = @@to_deg * angle cos = Math.cos(rad) sin = Math.sin(rad) Matrix.new( [ cos, sin, 0, 0], [-sin, cos, 0, 0], [ 0, 0, 1, 0], [ 0, 0, 0, 1] ) end def self.rotation_x(angle) rad = @@to_deg * angle cos = Math.cos(rad) sin = Math.sin(rad) Matrix.new( [ 1, 0, 0, 0], [ 0, cos, sin, 0], [ 0,-sin, cos, 0], [ 0, 0, 0, 1] ) end def self.rotation_y(angle) rad = @@to_deg * angle cos = Math.cos(rad) sin = Math.sin(rad) Matrix.new( [ cos, 0,-sin, 0], [ 0, 1, 0, 0], [ sin, 0, cos, 0], [ 0, 0, 0, 1] ) end def self.transration(x, y, z = nil) if z Matrix.new( [ 1, 0, 0, 0], [ 0, 1, 0, 0], [ 0, 0, 1, 0], [ x, y, z, 1] ) else Matrix.new( [ 1, 0, 0], [ 0, 1, 0], [ x, y, 1] ) end end def to_a @arr.map {|v|v.to_a}.flatten end end class Sprite def xy=(v) self.x = v.x self.y = v.y end def xy Vector.new(self.x, self.y) end def xyz=(v) self.x = v.x self.y = v.y self.z = v.z end def xyz Vector.new(self.x, self.y, self.z) end end def assert(t) raise "error" unless t end assert(Vector.new(1,2) + Vector.new(1,2) == Vector.new(2,4)) assert(Vector.new(1,2) + Vector.new(1,2,3) == Vector.new(2,4)) # assert(Vector.new(1,2,3) + Vector.new(1,2) == Vector.new(2,4,3)) # assert(Vector.new(1,2,3) + 5 == Vector.new(6,7,8)) assert(Vector.new(1,1) * Matrix.transration(1,2) == Vector.new(2,3)) # assert(Vector.new(1,1,1) * Matrix.transration(1,2) == Vector.new(2,3,1)) assert(Vector.new(2,2,2) * Matrix.transration(1,2,3) == Vector.new(3,4,5)) # assert(Vector.new(2,2,2,1) * Matrix.transration(1,2,3) == Vector.new(3,4,5,1)) assert(Vector.new(5,5).rotate(45) == Vector.new(5,5) * Matrix.rotation(45)) # s = Sprite.new s.xy = Vector.new(1,2) assert(s.x == 1 && s.y == 2) assert(s.xy == Vector.new(1,2)) s.x = 5 assert(s.xy == Vector.new(5,2)) s.xyz = Vector.new(10,20,30) assert(s.x == 10 && s.y == 20 && s.z == 30) assert(s.xyz == Vector.new(10,20,30)) s.x = 50 assert(s.xyz == Vector.new(50,20,30))