📐

# 固定カプセルに衝突した円の補正と反射

2023/10/05に公開

の続きとして

• 線分がカプセル状
• 裏面にも当たり判定がある

の条件で考える。

## 固定値

``````a = V[480.0, 200.0]
b = V[320.0, 200.0]
``````
``````c = V[400.0, 108.0]             # 円心
d = V[416.0, 148.0]             # 円心 + 速度ベクトル
``````
カプセル半径(筒の幅の半分)
``````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
``````

## 始点の半円との判定

``````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
``````

## 終点の半円との判定

``````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
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.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)

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
``````