📐

動く地面から受ける速度ベクトルの求め方

2023/10/26に公開


横に動く地面が、止まっている球を掠める。球は横方向の影響をほとんど受けない。

考え方

球に対して垂直な成分のみが影響するので、速度ベクトル bd から bdx[1] を取り出す。

計算方法

各座標

b = V[711.0, 369.0]
c = V[106.0, 500.0]
d = V[415.0, 388.0]

地面 bc の法線の正規化を n とする。

bc = c - b
n = bc.normalize.perp  # => (-0.21162476017560725, -0.9773509916507052)

bd は地面の速度ベクトルで、

bd = d - b  # => (-296.0, 19.0)

速度ベクトルを三角形の斜辺とし法線を底としたとき内積で正射影 bdx が求まる。

bdx = n * bd.dot(n)  # => (-9.326569864243474, -43.07308983104811)

それが球に与える力になる。簡単に書くと、

bdx = bd.project_onto_normalized(n)  # => (-9.326569864243474, -43.07308983104811)

でよい。

見方を変えれば外積でも求まる。

bdx = n * bc.normalize.cross(bd)  # => (-9.326569864243474, -43.07308983104811)

が、内積を使った方がわかりやすい。

確認

地面の速度が

bd.magnitude  # => 296.60917045836595

なのに対して実際の球にかかる力

bdx.magnitude  # => 44.07126017061634

は、小さいのがわかる。

最適化

法線を正規化せずに n とし、

n = bc.perp

同様に計算した結果を n.length_squared[2] で割ると正規化を省ける。

bdx = n * bd.dot(n) / n.length_squared  # => (-9.326569864243474, -43.07308983104811)

上のは、

bdx = bd.project_onto(n)  # => (-9.326569864243474, -43.07308983104811)

とも書ける。

まとめ

地面の直線
bc = c - b  # => (-605.0, 131.0)
地面の速度ベクトル
velocity = d - b  # => (-296.0, 19.0)
地面の法線
n = bc.perp  # => (-131.0, -605.0)
地面が球に与える力
velocity.project_onto(n)  # => (-9.326569864243474, -43.07308983104811)
シミュレーション
class Wall
  attr_accessor :velocity

  attr_accessor :p0
  attr_accessor :p1

  def initialize
    @p0 = V.zero
    @p1 = V.zero
    @velocity = V.zero
  end

  def update
    if $app.pause
      return
    end

    @p0.replace(@p0 + @velocity)
    @p1.replace(@p1 + @velocity)
  end

  def draw
    $app.line_draw(p0, p1, infinity: true)
    $app.vector_draw(p0, p1)
  end
end

class Ball
  attr_accessor :location
  attr_accessor :velocity
  attr_accessor :restitution

  def initialize
    @location = $app.window_wh * V[0.5, 0.5]
    @velocity = V.zero
    @radius = 150
    @restitution = 1.0
  end

  def update
    if $app.pause
      return
    end

    a = @location
    b = $app.wall.p0
    c = $app.wall.p1
    bc = c - b
    n = bc.normalize.perp
    ba = @location - b

    distance = ba.dot(n)
    t = distance / @radius
    if t < 1.0
      if false
        a.replace(a + n * (1 - t) * @radius)                              # 位置補正(方法1)
        # a.replace(b + ba.project_onto(bc) + n * @radius)                # 位置補正(方法2)
        @velocity = @velocity - @velocity.project_onto_normalized(n) * (1 + @restitution) # 反射
      end
      @velocity += $app.wall.velocity.project_onto_normalized(n)
    end

    a.replace(a + @velocity)
  end

  def draw
    $app.vputs "ball velocity: #{@velocity.round(2)} (#{@velocity.length.round(2)})"
    $app.point_draw(@location)
    $app.circle_draw(@location, @radius)
    $app.vector_draw(@location, @location + @velocity.clamp_length_min(@radius))
  end
end

class App < Base
  attr_accessor :ball
  attr_accessor :wall
  attr_accessor :pause

  def initialize
    super

    @pause = true

    preset1
  end

  def preset1
    @ball = Ball.new
    @ball.location = window_wh * V[0.5, 0.5]
    @ball.velocity = V.zero

    @wall = Wall.new
    @wall.p0 = window_wh * V[0.95, 0.72]
    @wall.p1 = window_wh * V[0.40, 0.78]
    @wall.velocity = V[-2.0, 0.0]

    @points = [@ball.location, @wall.p0, @wall.p1]
  end

  def button_down(id)
    super

    case id
    when Gosu::KB_R
      preset1
    when Gosu::KB_P
      @pause ^= true
    end
  end

  def draw
    super

    @wall.update
    @ball.update

    @wall.draw
    @ball.draw

    vputs "[r] reset"
    vputs "[p] pause: #{@pause}"
  end

  show
end
ベクトル確認
class App < Base
  def initialize
    super

    preset1
  end

  def preset1
    a = V[238.0, 318.0]
    b = V[711.0, 369.0]
    c = V[106.0, 500.0]
    d = V[415.0, 388.0]
    @points = [a, b, c, d]
  end

  def button_down(id)
    super

    case id
    when Gosu::KB_1
      preset1
    end
  end

  def draw
    super

    a, b, c, d = @points

    bd = d - b
    bc = c - b
    n = bc.normalize.perp

    bdx = bd.project_onto_normalized(n)

    radius = 150

    # 線
    line_draw(b, c)
    vector_draw(b, c, "b", "c")
    vector_draw(b, d, "", "d", name: "bd", line_width: nil)

    # 法線と本当のベクトル
    line_draw(b, b + n, infinity: true)
    vector_draw(b, b + bdx, "", "", name: "bdx", color: :blue)

    # 球
    point_draw(a)
    arrow_head(a + V.from_angle(270.deg_to_rad), a, "a")
    circle_draw(a, radius, color: :grey)
    vector_draw(a, a + bdx, "", "", color: :blue)

    vputs "a: #{a.round}"
    vputs "b: #{b.round}"
    vputs "c: #{c.round}"
    vputs "d: #{d.round}"
    vputs "bdx: #{bdx.round(2)}"
  end

  show
end
脚注
  1. 変数名としてはいまいち。もっと良い名前を考えたい。 ↩︎

  2. n.dot(n) ↩︎

Discussion