📐
動く地面から受ける速度ベクトルの求め方
横に動く地面が、止まっている球を掠める。球は横方向の影響をほとんど受けない。
考え方
球に対して垂直な成分のみが影響するので、速度ベクトル 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
Discussion