# ScatterPack
# Scatters like a school of fish

$dimensions = 600

class ScatterPack
  attr_accessor :fear

  def initialize
    @head_count = 4
    @grid_size = 400
    @margin = 85
    startval = rand
    colorval = rand
    @discs = []
    $app.grid(@head_count, @head_count, @grid_size, @grid_size) do |coord|
      x, y = *coord
      color = self.generate_color(x, y, startval, colorval)
      s = rand * (@grid_size / @head_count)
      self.add_disc(x + @margin, y + @margin, s, color)
      color = self.generate_color(x, y, startval, colorval)
      delta_x = (rand - 0.5)*10
      delta_y = (rand - 0.5)*10
      delta_s = (rand - 0.5)*200
      self.add_disc(x + delta_x + @margin, y + delta_y + @margin, delta_s, color)
      colorval += 0.01
    end
    @fear=0.005
  end
  
  def draw
    @discs.each do |disc| 
      disc.update
      disc.draw
      mouse_x, mouse_y = $app.mouse[1], $app.mouse[2]
      get_discs_within_range(mouse_x, mouse_y, 100).each do |disc|
        center_x, center_y = *disc.center
        angle = Math.atan2(mouse_y-center_y,mouse_x-center_x)
        x_vector = Math.cos(angle) * @fear #0.05
        y_vector = Math.sin(angle) * @fear #0.05
        disc.x_vel = disc.x_vel - x_vector
        disc.y_vel = disc.y_vel - y_vector
      end
    end
  end
  
  def generate_color(x, y, startval, colorval)
    $app.rgb(Math.sin(startval + y*x/100.0), Math.cos(colorval), Math.cos(rand * 0.5 + 0.5), rand)
  end
  
  def add_disc(x, y, radius, color)
    @discs << Disc.new(x, y, radius, color)
  end
  
  def get_discs_within_range(x, y, distance)
    in_range = []
    @discs.each do |disc|
      center_x, center_y = *disc.center
      if Math.sqrt( (center_x - x)**2 + (center_y - y)**2 ) < distance
        in_range << disc
      end
    end
    return in_range
  end
end

class Disc
  attr_accessor :x, :y, :radius, :color, :x_vel, :y_vel
  
  def initialize(x, y, radius, color)
    @x, @y, @radius, @color = x, y, radius, color
    @x_vel, @y_vel = (rand - 0.5), (rand - 0.5)
  end
  
  def update
    [[@x_vel, 'x'], [@y_vel, 'y']].each do |vel|
      if vel[0] > 0.5
        instance_variable_set("@#{vel[1]}_vel", vel[0] - 0.05)
      elsif vel[0] < -0.5
        instance_variable_set("@#{vel[1]}_vel", vel[0] + 0.05)
      end
    end
    @x, @y = @x % $dimensions, @y % $dimensions
    @x, @y = @x + @x_vel, @y + @y_vel
  end
  
  def center
    return [@x + (0.5 * @radius), @y + (0.5 * @radius)]
  end
  
  def draw
    $app.fill(@color)
    $app.oval(@x, @y, @radius, @radius)
  end
end

Shoes.app :width => $dimensions, :height => $dimensions, :title => "ScatterPack" do
  $app = self
  scatter = ScatterPack.new
  animate(30) do
    clear do
      scatter.draw
    end
  end

  click do |btn,x,y|
    scatter.fear= -0.005
  end

  release do |btn,x,y|
    scatter.fear=0.05
  end
end

# This sort of grid method could conceivably be added to Shoes.
Shoes.class_eval do
  def grid(num_wide, num_tall, px_width, px_height)
    coords = []
    x_spacing, y_spacing = px_width / (num_wide-1), px_height / (num_tall-1)
    num_wide.times do |x|
      num_tall.times do |y|
        coord = [x*x_spacing, y*y_spacing]
        if block_given?
          yield coord
        else
          coords << coord
        end
      end
    end
    return coords
  end
end
