📐

点に衝突した円の補正と反射

2023/10/01に公開

わかっていること

a = V[400.0, 300.0]

c = V[440.0, 220.0]
radius = 120

速度ベクトル

d = V[280.0, 220.0]
speed = d - c  # => (-160.0, 0.0)

反射係数

e = 1.0

点は電柱のようにびくともしないものとする。

円に点がめり込んでいるか?

点から円までのベクトルと単位ベクトルを用意する。

ac = c - a          # => (40.0, -80.0)
acn = ac.normalize  # => (0.4472135954999579, -0.8944271909999159)

その長さが距離になる。

distance = ac.length         # => 89.44271909999159
distance = c.distance_to(a)  # => 89.44271909999159

距離から半径を引いた値が負であればめり込んでいる。

gap = distance - radius  # => -30.557280900008408
gap.round.negative?      # => true

円の位置を補正する

ずれた分だけ押し返す場合

c2 = c + acn * -gap  # => (453.6656314599949, 192.6687370800101)

点から半径分の位置に再配置する場合

c2 = a + acn * radius  # => (453.6656314599949, 192.6687370800101)

どちらの方法でも結果は同じだが、もし点が円だと仮定すると前者の方がシンプルになりそう。

速度ベクトルを反射させる

speed2 = (speed - acn * speed.dot(acn) * 2) * e                # => (-96.0, -128.0)
speed2 = (speed - speed.project_onto_normalized(acn) * 2) * e  # => (-96.0, -128.0)
speed2 = speed.bounce(acn) * e                                 # => (-96.0, -128.0)
コード
class App < Base
  def initialize
    super

    a = window_wh * V[0.50, 0.75]  # => (400.0, 300.0)
    c = window_wh * V[0.55, 0.55]  # => (440.00000000000006, 220.00000000000003)
    d = window_wh * V[0.35, 0.55]  # => (280.0, 220.00000000000003)
    @points = [a, c, d]
  end

  def draw
    super

    a, c, d = @points
    radius = 120
    speed = d - c

    ac = c - a

    c2 = nil
    e = 1.0

    distance = c.distance_to(a)
    gap = distance - radius
    if gap.round.negative?
      c2 = c + ac.normalize * -gap
      speed2 = speed.bounce(ac.normalize) * e
      line_draw(a, a + ac.perp, color: :blue_light, infinity: true)
    end

    vputs "a: #{a.round}"
    vputs "c: #{c.round}"
    vputs "d: #{d.round}"
    vputs "ac: #{ac.round}"
    vputs "speed: #{speed.round}"

    vector_draw(c, d, "c", "d")

    line_draw(a, c)
    point_draw(a)
    arrow_head(a + V.from_angle(270.deg_to_rad), a, "a")

    circle_draw(c, radius)
    if c2
      vputs "c2: #{c2.round}" if c2
      vputs "speed2: #{speed2.round}" if speed2
      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