🎡
新井式回転抽選器シミュレーション
球 vs 壁
a = wall.p0
b = wall.p1
ab = b - a
ac = ball.location - a
n = ab.normalize.perp
distance = ac.dot(n)
t = distance / ball.radius
if t < 1.0
ball.location = ball.location + n * (1.0 - t) * ball.radius
ball.velocity = ball.velocity - ball.velocity.project_onto_normalized(n) * (1 + ball.restitution)
end
球 vs 球
ab = b.location - a.location
distance = ab.length
gap = distance - a.radius - b.radius
if gap.round.negative?
len = -gap
n = ab.normalize
am = a.mass
bm = b.mass
a.location = a.location + n * len * -bm / (am + bm)
b.location = b.location + n * len * am / (am + bm)
as = a.velocity.dot(n)
bs = b.velocity.dot(n)
e = 1.0
as2 = (am*as + bm*bs - (e * bm * (as - bs))) / (am + bm)
bs2 = (am*as + bm*bs + (e * am * (as - bs))) / (am + bm)
a.velocity = a.velocity.reject_from_normalized(n) + n * as2
b.velocity = b.velocity.reject_from_normalized(n) + n * bs2
end
コード
class Ball
attr_accessor :location
attr_accessor :velocity
attr_accessor :radius
attr_accessor :mass
def initialize
@location = $app.window_wh.center + V.rand_norm
@velocity = V.rand_norm
@acceleration = V.zero
@gravity = V[0.0, 0.12]
@radius = rand(12..24)
@mass = @radius**2
@restitution = 0.999
end
def update
$app.rebound(self, $app.pivot)
$app.walls.each do |wall|
a = wall.p0
b = wall.p1
ab = b - a
ac = @location - a
n = ab.normalize.perp
distance = ac.dot(n)
t = distance / @radius
if t < 1.0
@location = @location + n * (1.0 - t) * @radius
@velocity = @velocity - @velocity.project_onto_normalized(n) * (1 + @restitution)
end
end
acceleration_add(@gravity * @mass)
@velocity += @acceleration
@location += @velocity
@acceleration = V.zero
end
def draw
$app.circle_draw(@location, @radius, sides: 16)
$app.line_draw(@location, @location + @velocity.clamp_length_min(@radius))
end
def acceleration_add(force)
@acceleration += force / @mass
end
end
class Wall
attr_accessor :p0
attr_accessor :p1
def initialize(index)
@index = index
end
def update
@p0 = position(0)
@p1 = position(1)
end
def draw
$app.line_draw(p0, p1, line_width: 2)
end
def position(i)
center = $app.window_wh.center
speed = 60 * 12
angle = (1.0 / $app.walls.size * (@index + i)) + (1.0 / speed) * $app.frame_count
center + V.from_angle(angle.turn_to_rad) * center * 0.95
end
end
class Pivot
attr_accessor :location
attr_accessor :velocity
attr_accessor :radius
attr_accessor :mass
def initialize
@location = $app.window_wh.center
@velocity = V.zero
@mass = 1000000000.0
@radius = 30.0
end
def update
@location += velocity
end
def draw
$app.circle_draw(location, radius, sides: 16, line_width: 3)
end
end
class App < Base
attr_accessor :pivot
attr_accessor :balls
attr_accessor :walls
def initialize
super
reset
end
def reset
@pivot = Pivot.new
@walls = 8.times.collect.with_index { |i| Wall.new(i) }
@balls = 50.times.collect { Ball.new }
end
def button_down(id)
super
case id
when Gosu::KB_R
reset
end
end
def update
super
@pivot.update
@walls.each(&:update)
@balls.combination(2) { |a, b| rebound(a, b) }
@balls.each(&:update)
end
def draw
super
@pivot.draw
@walls.each(&:draw)
@balls.each(&:draw)
end
def rebound(a, b)
ab = b.location - a.location
distance = ab.length
gap = distance - a.radius - b.radius
if gap.round.negative?
len = -gap
n = ab.normalize
am = a.mass
bm = b.mass
a.location = a.location + n * len * -bm / (am + bm)
b.location = b.location + n * len * am / (am + bm)
as = a.velocity.dot(n)
bs = b.velocity.dot(n)
e = 1.0
as2 = (am*as + bm*bs - (e * bm * (as - bs))) / (am + bm)
bs2 = (am*as + bm*bs + (e * am * (as - bs))) / (am + bm)
a.velocity = a.velocity.reject_from_normalized(n) + n * as2
b.velocity = b.velocity.reject_from_normalized(n) + n * bs2
end
end
def window_size_default
V[800, 800]
end
show
end
Discussion