📐
固定の線分に衝突した円の補正と反射
手順
- 線分と判定を行う
- 始点と判定を行う
- 終点と判定を行う
必ず線分との判定を最初に行う。
固定値
線分 ab と法線の正規化:
a = V[520.0, 320.0]
b = V[280.0, 320.0]
ab = b - a # => (-240.0, 0.0)
abn = ab.normalize # => (-1.0, 0.0)
円の半径:
radius = 120
線分との判定
c = V[400.0, 240.0]
d = V[400.0, 280.0]
speed = d - c # => (0.0, 40.0)
円心の垂線が線分に含まれるかを判定する。
ac = c - a
bc = c - b
inside = (ac.dot(abn) * bc.dot(abn)).negative? # => true
true なら次へ。
円が線にめり込んでいるか?
distance = abn.cross(ac) # => 80.0
gap = distance - radius # => -40.0
gap.round.negative? # => true
true なら次へ。
押し戻す。
c2 = c + abn.perp * -gap # => (400.0, 200.0)
速度ベクトルを反射させる。
speed2 = speed.bounce(abn.perp) # => (0.0, -40.0)
始点との判定
線分に衝突していなかった場合はこちらの判定を行う。
c = V[568.0, 240.0]
d = V[400.0, 200.0]
speed = d - c # => (-168.0, -40.0)
ac = c - a # => (48.0, -80.0)
acn = ac.normalize # => (0.5144957554275265, -0.8574929257125441)
始点が円にめり込んでいるか?
distance = c.distance_to(a)
gap = distance - radius
gap.round.negative? # => true
true なら次へ。
めり込んでいる分だけ押し返す。
c2 = c + acn * -gap # => (581.7394906513032, 217.1008489144947)
速度ベクトルを反射させる。
speed2 = speed.bounce(acn) # => (-114.3529411764706, -129.41176470588235)
終点との判定
始点と同様に行う。
c = V[232.0, 240.0]
d = V[400.0, 200.0]
speed = d - c # => (168.0, -40.0)
bc = c - b # => (-48.0, -80.0)
bcn = bc.normalize # => (-0.5144957554275265, -0.8574929257125441)
distance = c.distance_to(b) # => 93.29523031752481
gap = distance - radius # => -26.704769682475188
gap.round.negative? # => true
c2 = c + bcn * -gap # => (218.26050934869681, 217.1008489144947)
speed2 = speed.bounce(bcn) # => (114.3529411764706, -129.41176470588235)
判定の順序に注意する
繰り返しになるが線分との判定を優先すること。そうしないと線分に円がめり込む。
裏面で反発させたいときは?
これまでのコードは一方通行タイプだったので、線分の裏(左側)から円が侵入した場合、表から来たと判定され、表に押し出されてしまう。それが困る場合にのみ裏面にも対応する。表から来たか裏から来たかは速度ベクトルの向きでわかりそうだが、他の物体によって押されて衝突した場合を考えると、必ずしも線分を向いているとは限らない。
この場合は「円心が線分のどちら側にあるか」で判定し「線分にめり込んだ長さを半径の長さまで」とすればよい。
この裏面に対応した場合、半径より移動量の方が多いと突き抜けるので、単に壁でいいのなら一方通行にした方がよい。
関連
コード
class App < Base
def initialize
super
layout0
@mode = 0
@perp = 0
end
def layout0
a = window_wh * V[0.65, 0.80] # => (520.0, 320.0)
b = window_wh * V[0.35, 0.80] # => (280.0, 320.0)
c = window_wh * V[0.50, 0.60] # => (400.0, 240.0)
d = window_wh * V[0.50, 0.70] # => (400.0, 280.0)
@points = [a, b, c, d]
end
def layout1
a = window_wh * V[0.65, 0.80] # => (520.0, 320.0)
b = window_wh * V[0.35, 0.80] # => (280.0, 320.0)
c = window_wh * V[0.72, 0.60] # => (576.0, 240.0)
d = window_wh * V[0.50, 0.50] # => (400.0, 200.0)
@points = [a, b, c, d]
end
def layout2
a = window_wh * V[0.65, 0.80] # => (520.0, 320.0)
b = window_wh * V[0.35, 0.80] # => (280.0, 320.0)
c = window_wh * V[0.28, 0.60] # => (224.00000000000003, 240.0)
d = window_wh * V[0.50, 0.50] # => (400.0, 200.0)
@points = [a, b, c, d]
end
def button_down(id)
super
if id == Gosu::KB_Z
@mode = @mode.next.modulo(3)
if @mode == 0
layout0
end
if @mode == 1
layout1
end
if @mode == 2
layout2
end
end
end
def draw
super
a, b, c, d = @points
radius = 120
cd = d - c
ab = b - a
abn = ab.normalize
ac = c - a
bc = c - b
hit = false
e = 1.0
unless hit
inside = (ac.dot(abn) * bc.dot(abn)).negative?
if inside
distance = abn.cross(ac)
gap = distance - radius
hit = gap.round.negative?
if hit
c2 = c + abn.perp * -gap
speed2 = cd.bounce(abn.perp) * e
end
end
end
unless hit
distance = c.distance_to(a)
gap = distance - radius
hit = gap.round.negative?
if hit
c2 = c + ac.normalize * -gap
speed2 = cd.bounce(ac.normalize) * e
line_draw(a, a + ac.perp, color: :blue_light, infinity: true)
end
end
unless hit
distance = c.distance_to(b)
gap = distance - radius
hit = gap.round.negative?
if hit
c2 = c + bc.normalize * -gap
speed2 = cd.bounce(bc.normalize) * e
line_draw(b, b + bc.perp, color: :grey, infinity: true)
end
end
vputs "a: #{a.round}"
vputs "b: #{b.round}"
vputs "c: #{c.round}"
vputs "d: #{d.round}"
vputs "ab: #{ab.round}"
vputs "abn: #{abn.round(2)}"
vputs "ac: #{ac.round}"
vputs "bc: #{bc.round}"
vputs "hit: #{hit}"
vputs "inside: #{inside}"
vputs "c2: #{c2.round}" if c2
vector_draw(a, b, "a", "b")
vector_draw(c, d, "c", "d")
vector_draw(a, c, "", "", color: :orange_light)
vector_draw(b, c, "", "", color: :orange_light)
circle_draw(c, radius)
if hit
circle_draw(c2, radius, color: :blue_light, line_width: 4)
vector_draw(c2, c2 + speed2, "", "", color: :blue_light)
arrow_head(c2 + V.from_angle(270.deg_to_rad), c2, "c2", color: :blue_light)
end
end
def window_size_default
V[800, 400]
end
show
end
Discussion