反射・壁ずり・鏡像ベクトルの考え方
目的
直線 ab に衝突した速度ベクトル cd を反射させたい。
ここまでにわかっていること
a = V[600.0, 340.0]
b = V[200.0, 60.0]
c = V[360.0, 52.0]
d = V[400.0, 200.0]
ab = b - a # => (-400.0, -280.0)
cd = d - c # => (40.0, 148.0)
dc = c - d # => (-40.0, -148.0)
最初にやること
ベクトル de を求める。
ここが求まれば後は足したりずらしたりすればあっちこっちが求まる。
内積で正射影 de を求める
- ベクトル cd を反転し斜辺と考える
- 直線 ab から法線を出す
「直線 ab の法線の正規化」を n とし、dc から n に対して内積を取るとベクトル de の長さが求まる。単位ベクトルの n にその長さを掛けると膨らんで de になる。
n = ab.perp.normalize # => (0.5734623443633283, -0.8192319205190405)
length = dc.dot(n) # => 98.30783046228487
de = n * length # => (56.3758389261745, -80.53691275167786)
de の長さは直線 ab を地面とすれば外積でも求まる。が、そこはあんまり重要じゃない。
cd.cross(ab.normalize) # => 98.30783046228487
高速化
de = dc.project_onto(ab.perp) # => (56.37583892617449, -80.53691275167785)
とすれば一発で de を求められる。project_onto の中身は、
de = ab.perp * dc.dot(ab.perp) / ab.perp.length_squared # => (56.375838926174495, -80.53691275167785)
となっていて平方根を使わないので速い。その上、小数の誤差が生じにくい。なので、長さからベクトルを求めるのではなく、直接ベクトルを求めたい場合は平方根を省ける。
反射ベクトルまでの道のり (考え方1)
e は d から de 進めたところなので
e = d + de # => (456.3758389261745, 119.46308724832215)
ce は e - c で
ce = e - c # => (96.37583892617448, 67.46308724832215)
h は c から ce を2つ分進めたところにある。
h = c + ce * 2 # => (552.751677852349, 186.9261744966443)
としたけどもう e はわかっているんだから e から ce だけ進めても同じ。
h = e + ce # => (552.751677852349, 186.9261744966443)
反射ベクトルと壁ずりベクトル
h まで求まれば dh が反射ベクトルになる。
dh = h - d # => (152.75167785234896, -13.073825503355692)
ついでに h から直線の方に de だけずらせば g がわかる。
g = h - de # => (496.3758389261745, 267.46308724832215)
g は d 経由で ce だけ進めたものとしてイメージしてもいい。
g = d + ce # => (496.3758389261745, 267.46308724832215)
g が求まれば dg が壁ずりベクトルになる。
dg = g - d # => (96.37583892617448, 67.46308724832215)
計算方法を整理する
de は重要なので置いといて
de = dc.project_onto(ab.perp) # => (56.37583892617449, -80.53691275167785)
次の計算を整理する。
e = d + de # => (456.3758389261745, 119.46308724832215)
ce = e - c # => (96.37583892617448, 67.46308724832215)
h = e + ce # => (552.751677852349, 186.9261744966443)
g = h - de # => (496.3758389261745, 267.46308724832215)
dg = g - d # => (96.37583892617448, 67.46308724832215)
dh = h - d # => (152.75167785234896, -13.073825503355692)
まず e を消す。
ce = (d + de) - c # => (96.37583892617448, 67.46308724832215)
h = d + de + (d + de) - c # => (552.751677852349, 186.9261744966443)
g = h - de # => (496.3758389261745, 267.46308724832215)
dg = g - d # => (96.37583892617448, 67.46308724832215)
dh = h - d # => (152.75167785234896, -13.073825503355692)
ce と h と g も消す。
dg = d + d + de + de - c - de - d # => (96.3758389261746, 67.46308724832215)
dh = d + d + de + de - c - d # => (152.75167785234908, -13.073825503355692)
さらに打ち消し当っているのを消す。
dg = d - c + de # => (96.37583892617448, 67.46308724832215)
dh = d - c + de + de # => (152.75167785234896, -13.073825503355692)
さらに d - c はベクトル cd のことなので最後は
dg = cd + de # => (96.37583892617448, 67.46308724832215)
dh = cd + de * 2 # => (152.75167785234896, -13.073825503355692)
となる。
反射ベクトルまでの道のり (考え方2)
壁を突き抜けたところから戻る考え方で、速度ベクトルをもうひとつ足したところを f とする。そこから de を足せば g が求まる。ついでにもう1つ足せば h が求まる。
f = d + cd # => (440.0, 348.0)
g = f + de # => (496.3758389261745, 267.46308724832215)
h = f + de + de # => (552.751677852349, 186.9261744966443)
dg = g - d # => (96.37583892617448, 67.46308724832215)
dh = h - d # => (152.75167785234896, -13.073825503355692)
ここで、また計算を整理する。f g h を消すと、
dg = d + cd + de - d # => (96.37583892617448, 67.46308724832215)
dh = d + cd + de + de - d # => (152.75167785234896, -13.073825503355692)
となり、打ち消し合う d を取る。
dg = cd + de # => (96.37583892617448, 67.46308724832215)
dh = cd + de * 2 # => (152.75167785234896, -13.073825503355692)
するとおもしろいことに壁を意識して辿ったときの式と同じになるのだった。
考え方の最適化
途中の位置ベクトルを求めようとしていたから無駄に複雑になっていた。上の図をよく見れば青と赤は同じ形になっている。つまり壁ずりベクトルは青が入射ベクトルの先端から生えているだけ。反射ベクトルは壁ずりベクトルの最後を伸ばしただけだとわかる。
鏡像ベクトル
直線を鏡として見たときに鏡の中にいる自分を表わす。ベクトル de を二倍したところから速度ベクトルを引くと求まる。反転すると反射ベクトルになる。
計算方法まとめ
a = V[600.0, 340.0]
b = V[200.0, 60.0]
c = V[360.0, 52.0]
d = V[400.0, 200.0]
n = (b - a).perp.normalize # => (0.5734623443633283, -0.8192319205190405)
speed = d - c # => (40.0, 148.0)
speed - speed.project_onto_normalized(n) # => (96.37583892617451, 67.46308724832214)
speed.slide(n) # => (96.37583892617451, 67.46308724832214)
speed - speed.project_onto_normalized(n) * 2 # => (152.75167785234902, -13.073825503355721)
speed.bounce(n) # => (152.75167785234902, -13.073825503355721)
speed.project_onto_normalized(n) * 2 - speed # => (-152.75167785234902, 13.073825503355721)
speed.reflect(n) # => (-152.75167785234902, 13.073825503355721)
コード
class App < Base
def initialize
super
a = window_wh * V[0.75, 0.85] # => (600.0, 340.0)
b = window_wh * V[0.25, 0.15] # => (200.0, 60.0)
c = window_wh * V[0.45, 0.13] # => (360.0, 52.0)
d = window_wh * V[0.50, 0.50] # => (400.0, 200.0)
@points.concat([a, b, c, d])
@mode = 0
end
def button_down(id)
super
if id == Gosu::KB_Z
@mode = @mode.next.modulo(8)
end
end
def draw
super
a, b, c, d = @points
ab = b - a
cd = d - c
dc = c - d
n = ab.perp
if @mode == 0
vputs "a: #{a.round}"
vputs "b: #{b.round}"
vputs "c: #{c.round}"
vputs "d: #{d.round}"
vector_draw(c, d, "c", "d")
end
n = ab.perp.normalize
de = dc.project_onto(ab.perp)
e = d + de
ce = e - c
h = c + ce
f = d + cd
g = f + de
h = f + de + de
dh = h - d
dg = g - d
point_draw(d + cd.slide(n), color: :yellow_light)
point_draw(d + cd.reflect(n), color: :green_light)
point_draw(d + cd.bounce(n), color: :orange_light)
if @mode == 1 || @mode == 3 || @mode == 4
vector_draw(d, c, "d", "c")
vector_draw(d, e, "", "e")
line_draw(c, e)
vputs "de: #{de.round}"
end
if @mode == 2
vputs "n: #{n.round(2)}"
vputs "de: #{de.round}"
vector_draw(d, c, "d", "c")
line_draw(d, d + ab.perp.clamp_length_max(1000), color: :blue_light, line_width: 2)
end
if @mode == 3 || @mode == 4
vector_draw(c, h, "", "h")
vputs "ce: #{ce.round}"
end
if @mode == 4
vector_draw(d, h, "", "")
vector_draw(d, g, "", "g")
vputs "dh: #{dh.round}"
vputs "dg: #{dg.round}"
line_draw(h, g)
end
if @mode == 5 || @mode == 6
vector_draw(d, e, "", "e")
vector_draw(c, d, "c", "d")
vector_draw(d, f, "", "f")
vector_draw(d, g, "", "g")
vector_draw(d, h, "", "h")
line_draw(f, g)
line_draw(g, h)
end
if @mode == 6
line_draw(c, d, color: :blue, line_width: 2)
line_draw(d, e, color: :blue, line_width: 2)
line_draw(d, f, color: :red, line_width: 2)
line_draw(f, g, color: :red, line_width: 2)
end
if @mode == 7
v = cd.project_onto_normalized(n)
vector_draw(c, d, "c", "d")
vector_draw(d, d + v, "", "", color: :green_light, line_width: 1)
vector_draw(d + v, d + v + v, "", "", color: :green_light, line_width: 1)
vector_draw(d + v + v, d + v + v - cd, "", "", color: :green_light, line_width: 1)
vector_draw(d, d + cd.reflect(n), "", "")
end
vector_draw(a, b, "a", "b")
end
def window_size_default
V[800, 400]
end
show
end
Discussion