📐
固定カプセルに衝突した円の補正と反射
の続きとして
- 線分がカプセル状
- 裏面にも当たり判定がある
の条件で考える。
固定値
線分
a = V[480.0, 200.0]
b = V[320.0, 200.0]
円
c = V[400.0, 108.0] # 円心
d = V[416.0, 148.0] # 円心 + 速度ベクトル
radius = 80
カプセル半径(筒の幅の半分)
radius2 = 30
事前に計算しておくもの
円の速度ベクトル
cd = d - c # => (16.0, 40.0)
線分の法線の正規化
ab = b - a # => (-160.0, 0.0)
abn = ab.normalize # => (-1.0, 0.0)
線分の両端から円心に向けたベクトル
ac = c - a # => (-80.0, -92.0)
bc = c - b # => (80.0, -92.0)
筒との判定
hit = false
unless hit
inside = (ac.dot(abn) * bc.dot(abn)).negative? # => true
if inside
unless hit
distance = abn.cross(ac)
sign = distance.positive? ? 1 : -1 # => 1
t = distance / (radius + radius2) # => 0.8363636363636363
if t.abs < 1.0
hit = true
c2 = c + abn.perp * (1 - t.abs) * (radius + radius2) * sign # => (400.0, 90.0)
speed2 = cd.bounce(abn.perp) # => (16.0, -40.0)
end
end
end
end
線分との違いは radius2 を条件に追加するところ。また裏面にも対応できるように「線分にめり込んだ長さを半径の長さまで」とする。それには t.abs < 1.0
の条件を入れる。
始点の半円との判定
unless hit
distance = c.distance_to(a)
gap = distance - radius - radius2
hit = gap.round.negative?
if hit
c2 = c + ac.normalize * -gap
speed2 = cd.bounce(ac.normalize)
end
end
こちらも radius2 を追加するのみ。
終点の半円との判定
unless hit
distance = c.distance_to(b)
gap = distance - radius - radius2
hit = gap.round.negative?
if hit
c2 = c + bc.normalize * -gap
speed2 = cd.bounce(bc.normalize)
end
end
始点の半円と同様に行う。
コード
class App < Base
def initialize
super
layout0
@mode = 0
@perp = 0
end
def layout0
a = V[480.0, 200.0]
b = V[320.0, 200.0]
c = V[400.0, 108.0]
d = V[416.0, 148.0]
@points = [a, b, c, d]
end
def layout1
a = V[480.0, 200.0]
b = V[320.0, 200.0]
c = V[400.0, 292.0]
d = V[384.0, 238.0]
@points = [a, b, c, d]
end
def layout2
a = V[480.0, 200.0]
b = V[320.0, 200.0]
c = V[559.0, 142.0]
d = V[609.0, 309.0]
@points = [a, b, c, d]
end
def layout3
a = V[480.0, 200.0]
b = V[320.0, 200.0]
c = V[237.0, 230.0]
d = V[323.0, 310.0]
@points = [a, b, c, d]
end
def button_down(id)
super
if id == Gosu::KB_Z
@mode = @mode.next.modulo(4)
if @mode == 0
layout0
end
if @mode == 1
layout1
end
if @mode == 2
layout2
end
if @mode == 3
layout3
end
end
end
def draw
super
a, b, c, d = @points
radius = 80
radius2 = 30
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
if false
unless hit
distance = abn.cross(ac)
gap = distance - radius - radius2
hit = gap.round.negative?
if hit
c2 = c + abn.perp * -gap
speed2 = cd.bounce(abn.perp) * e
end
end
end
if false
unless hit
distance = abn.cross(ac)
sign = distance.positive? ? 1 : -1
gap = distance * sign - radius - radius2
if -gap < radius + radius2
if gap.round.negative?
hit = true
c2 = c + abn.perp * -gap * sign
speed2 = cd.bounce(abn.perp) * e
end
end
end
end
if true
unless hit
distance = abn.cross(ac)
sign = distance.positive? ? 1 : -1
t = distance / (radius + radius2)
if t.abs < 1.0
hit = true
c2 = c + abn.perp * (1 - t.abs) * (radius + radius2) * sign
speed2 = cd.bounce(abn.perp) * e
end
end
end
end
end
unless hit
distance = c.distance_to(a)
gap = distance - radius - radius2
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 - radius2
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", color: :grey_lighter)
vector_draw(c, d, "c", "d")
if true
v = abn.perp * radius2
line_draw(a + v, b + v, line_width: 1)
line_draw(a - v, b - v, line_width: 1)
line_draw(a + v, b + v, line_width: 1)
line_draw(a - v, b - v, line_width: 1)
circle_draw(a, radius2, start: abn.angle.rad_to_deg + 180, half: true, line_width: 2, sides: 32)
circle_draw(b, radius2, start: abn.angle.rad_to_deg + 0, half: true, line_width: 2, sides: 32)
end
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