├── .gitignore ├── ball-1D.rb └── ball-gravity.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /ball-1D.rb: -------------------------------------------------------------------------------- 1 | class Ball 2 | attr_accessor :x, :y, :radius 3 | 4 | def initialize(app, options = {}) 5 | @mass = options[:mass] or 0.0 6 | @velocity = options[:velocity] or [0.0, 0.0] 7 | @x = options[:position][0] 8 | @y = options[:position][1] 9 | @radius = 10 10 | @eta = 0.85 11 | 12 | @app = app 13 | @app.fill options[:fill] 14 | @circle = @app.oval :top => @x, :left => @y, :radius => @radius 15 | end 16 | 17 | # Momentum = mass * velocity 18 | def move 19 | @x += @velocity[0] * @mass 20 | @y += @velocity[1] * @mass 21 | 22 | @circle.move @x, @y 23 | end 24 | 25 | def check_collisions(balls) 26 | collided_ball = balls.find do |ball| 27 | ball != self and hit?(ball) 28 | end 29 | if collided_ball 30 | collide 31 | collided_ball.collide 32 | elsif collided_with_wall? 33 | collide 34 | end 35 | end 36 | 37 | def collided_with_wall? 38 | @x <= 0 or @x >= @app.width - (@radius * 2) 39 | end 40 | 41 | def collide 42 | @velocity[0] *= @eta 43 | @velocity[0] *= -1 44 | move 45 | end 46 | 47 | def hit?(ball) 48 | # If the length of the hypotenuse between two balls is 49 | # equal to or less than their combined widths then 50 | # they've collided 51 | origin_x = (ball.x - @x - @velocity[0]).abs 52 | origin_y = (ball.y - @y - @velocity[1]).abs 53 | hypotenuse = Math.sqrt((origin_x ** 2).to_f + (origin_y ** 2).to_f) 54 | hypotenuse <= ball.radius + @radius 55 | end 56 | end 57 | 58 | Shoes.app :width => 200, :height => 200 do 59 | background '#fff' 60 | balls = [] 61 | radius = 20 62 | center_x = (width - radius) / 2 63 | center_y = (height - radius) / 2 64 | balls << Ball.new(self, :mass => 0.5, 65 | :fill => '#ff0000', 66 | :radius => radius, 67 | :velocity => [4, 0.0], 68 | :position => [center_x - 50, center_y]) 69 | balls << Ball.new(self, :mass => 0.9, 70 | :fill => '#0000ff', 71 | :radius => radius, 72 | :velocity => [-2, 0.0], 73 | :position => [center_x + 50, center_y]) 74 | 75 | @anim = animate 30 do 76 | balls.each do |ball| 77 | ball.check_collisions balls 78 | end 79 | 80 | balls.each do |ball| 81 | ball.move 82 | end 83 | end 84 | end 85 | 86 | -------------------------------------------------------------------------------- /ball-gravity.rb: -------------------------------------------------------------------------------- 1 | require 'matrix' 2 | 3 | class Ball 4 | attr_accessor :radius, :mass, :position, :velocity, :inverse_mass 5 | 6 | def initialize(app, options = {}) 7 | @mass = options[:mass] or 0.0 8 | @velocity = Vector.[](options[:velocity][0], options[:velocity][1]) 9 | @position = Vector.[](options[:position][0], options[:position][1]) 10 | @radius = options[:radius] || 10 11 | @fixed = options[:fixed] || false 12 | @fill = options[:fill] || '#ff0000' 13 | @fill = '#000000' if @fixed 14 | @eta = 0.6 15 | @gravity = 1.0 16 | 17 | @inverse_mass = 1.0 / @mass 18 | @app = app 19 | @app.fill @fill 20 | draw 21 | end 22 | 23 | def draw 24 | @circle = @app.oval :top => @position[0], :left => @position[1], :radius => @radius 25 | end 26 | 27 | def remove 28 | @circle.hide 29 | @circle.remove 30 | end 31 | 32 | def fixed? 33 | @fixed 34 | end 35 | 36 | # Momentum = mass * velocity 37 | def update_momentum 38 | return if fixed? 39 | apply_gravity 40 | @position += @velocity * @mass 41 | end 42 | 43 | def update_sprite 44 | @circle.move @position[0], @position[1] 45 | end 46 | 47 | def move 48 | update_sprite 49 | update_momentum 50 | end 51 | 52 | def apply_gravity 53 | @velocity += Vector.[](0, @gravity) 54 | end 55 | 56 | def check_collisions 57 | wall_collisions 58 | end 59 | 60 | def bounce_off_wall 61 | @velocity = ((@velocity * -1) * @eta) 62 | end 63 | 64 | def wall_collisions 65 | if @position[0] <= 0 66 | @position = Vector.[](0, @position[1]) 67 | bounce_off_wall 68 | end 69 | 70 | if @position[0] >= @app.width - (@radius * 2) 71 | @position = Vector.[](@app.width - (@radius * 2), @position[1]) 72 | bounce_off_wall 73 | end 74 | 75 | if @position[1] <= 0 76 | @position = Vector.[](@position[0], 0) 77 | bounce_off_wall 78 | end 79 | 80 | if @position[1] >= @app.height - (@radius * 2) 81 | @position = Vector.[](@position[0], @app.height - (@radius * 2)) 82 | bounce_off_wall 83 | end 84 | end 85 | end 86 | 87 | Shoes.app :width => 400, :height => 400 do 88 | background '#fff' 89 | balls = [] 90 | 91 | def random_ball 92 | Ball.new(self, :mass => rand, 93 | :fill => rgb(rand(255), rand(255), rand(255)), 94 | :radius => 10, 95 | :velocity => [rand - 1.0, rand - 1.0], 96 | :fixed => false, 97 | :position => [rand(width), rand(50)]) 98 | 99 | end 100 | 101 | def load_balls 102 | balls = [] 103 | (1..10).each do 104 | balls << random_ball 105 | end 106 | 107 | balls 108 | end 109 | 110 | def reload(balls) 111 | balls.each { |ball| ball.remove } 112 | balls = load_balls 113 | end 114 | 115 | balls = reload(balls) 116 | 117 | @anim = animate 30 do 118 | balls.each do |ball| 119 | ball.check_collisions 120 | ball.move 121 | end 122 | 123 | keypress do |k| 124 | case k 125 | when 'a': 126 | balls << random_ball 127 | when 'r' 128 | balls = reload(balls) 129 | end 130 | end 131 | end 132 | end 133 | 134 | --------------------------------------------------------------------------------