#!/usr/local/bin/ruby #------------------------------------------------- #- #- A template ruby program to combine agent-based #- models with opengl / glut #- #- Build by meddling with the examples of ruby/glut #- on the internet and in the ruby installation dir #- most notable: Gears.rb by Arto Bendiken and the #- Freeze-Simulator.rb by Ruben Medellin #------------------------------------------------- #- Load libraries #------------------------------------------------- require 'opengl' require 'glut' #- Load modules #------------------------------------------------- include Math #- Define globals and constants #------------------------------------------------- TITLE = "alive" $scale = 0.4 $dimx = 312 $dimy = 213 $objects = [] #- Define / Alter global functions #------------------------------------------------- class Array def to_angle if self[1] >= 0 return (180 * acos(self[0]) / PI) % 360 else return (-180 * acos(self[0]) / PI) % 360 end end def random return self[rand(self.size)] end end class Float def to_vector return cos(self* PI / 180),sin(self * PI / 180) end end def angle_diff(a,b) oneway = [a,b].max - [a,b].min otherway = [a,b].min + 360 - [a,b].max # 3 + 360 - 350 = 13.. determine sign if oneway < otherway if a <= b return oneway else return -1 * oneway end else if a <= b return -1 * otherway else return otherway end end end #-- Define classes #------------------------------------------------- class Flock # Flocks stay together attr_reader :xpos, :ypos, :color, :angle, :aim, :shape def initialize @shape = "$bird" # Somewhere in the field @xpos = rand($dimx) ; @ypos = rand($dimy) # With a direction @angle = rand(360).to_f @aim = @angle end # move, change direction at the end of the field def move! # Find local boids - self locality = $flock.find_all {|bird| distance(bird,self) < 40 and bird != self} too_close = locality.find_all {|bird| distance(bird,self) < 20} # re-align aim angle, based on local boids and too_close boids if locality.size > 0 locality_angle = locality.inject(0) {|sum,i| sum + i.angle} / locality.size.to_f @aim = locality_angle end if too_close.size > 0 too_close_angle = too_close.inject(0) {|sum,i| sum + i.angle} / too_close.size.to_f if (@aim - too_close_angle).abs < 20 then @aim += 25 * (rand(3) - 1) end end # Wander algorithm 1: If aim is too different from angle, adjust. # aim randomly walks between 0 and 360 degrees # #diff = angle_diff(@angle,@aim).abs #if diff > 16 then # @angle = ((50 * @angle + angle_diff(@angle,@aim)) / 50.0) % 360 #end # random component #@aim += 3 * (rand(3) - 1) #33% chance of not changing #@aim = @aim % 360 # Wander algorithm 2: random chance on changing direction # @aim = rand(360) if rand < 0.001 @angle = ((250 * @angle + angle_diff(@angle,@aim)) / 250.0) % 360 # moving vector = self.angle.to_vector @xpos += 0.2 * vector[0] ; @ypos += 0.2 * vector[1] # also determines speed and bumpiness # Wrap the world @xpos = $dimx / $scale if @xpos < 0 @ypos = $dimy / $scale if @ypos < 0 @xpos = 0 if @xpos > $dimx / $scale @ypos = 0 if @ypos > $dimy / $scale end def distance(one,two) return sqrt((one.xpos - two.xpos)**2 + (one.ypos - two.ypos)**2) end end # Main class class GameSys #Standard initializer. It prepares the windows to be called. def initialize #- Initialize the neccesary agents #------------------------------------------------- $flock = [] $path = [] $cycle = 0 1.times {$flock << Flock.new} #Standard GL methods GLUT.Init GLUT.InitDisplayMode(GLUT::DOUBLE | GLUT::RGB) GLUT.InitWindowSize($dimx, $dimy) GLUT.InitWindowPosition(100, 100) GLUT.CreateWindow(TITLE) GL.ShadeModel(GL::FLAT) #- Here we define the polygons shapes of our #- agents #------------------------------------------------- $bird = GL.GenLists(1) GL.NewList($bird, GL::COMPILE) GL.Begin(GL::LINE_LOOP) GL.Color([0.0, 0.6, 0.2, 1.0]) GL.Vertex2f( -6, 3.5) GL.Vertex2f( -2.5, 1.8) GL.Vertex2f(1.8, 2.6) GL.Vertex2f(3.5, 8) GL.Vertex2f( -4, 20) GL.Vertex2f( 8, 8) GL.Vertex2f( 4, 1.6) GL.Vertex2f( 6, 0) GL.Vertex2f( 4, -1.6) GL.Vertex2f( 8, -8) GL.Vertex2f( -4, -20) GL.Vertex2f(3.5, -8) GL.Vertex2f(1.8, -2.6) GL.Vertex2f( -2.5, -1.8) GL.Vertex2f( -6, -3.5) GL.End GL.EndList() #------------------------------------------------- GLUT.DisplayFunc(method(:display).to_proc) GLUT.KeyboardFunc(Proc.new{|k, x, y| exit if k == 27}) GLUT.ReshapeFunc(method(:reshape).to_proc) # IdleFunc takes a proc object and calls it continously whenever it can. GLUT.IdleFunc(method(:tick).to_proc) end #------------------------------------------------- #- Main loop of the Agent-based model #- This is basically where the model resides. As #- you can see, in this template it is extremely #- simple. All it does is make everyone move. #================================================= def tick $cycle += 1 $flock.each {|bird| bird.move! } $path << [$flock.first.xpos,$flock.first.ypos] if $cycle % 100 == 0 $objects = $flock GLUT.PostRedisplay ; GLUT.SwapBuffers end #================================================= # Resizes the window, in this case, also resizes the field def reshape(width, height) GL.Viewport(0, 0, width, height) GL.MatrixMode(GL::PROJECTION) GL.LoadIdentity GL.Ortho(0, width, 0, height, -1, 1) $dimx = width ; $dimy = height display end # Draws the $object array def display GL.Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT) $objects.each {|object| # bird GL.PushMatrix() GL.Scale( $scale , $scale , $scale ) GL.Translate(object.xpos, object.ypos, 0.0) GL.Rotate(object.angle, 0.0, 0.0, 1.0) GL.CallList(eval(object.shape)) GL.PopMatrix() # direction GL.PushMatrix() GL.Scale( $scale , $scale , $scale ) GL.Translate(object.xpos, object.ypos, 0.0) GL.Rotate(object.angle, 0.0, 0.0, 1.0) GL::Color 1 , 1 , 1 GL.Begin(GL::LINES) GL::Vertex2f 0.0 , 0.0 GL::Vertex2f 40.0 , 0.0 GL.End GL.PopMatrix() # aim GL.PushMatrix() GL.Scale( $scale , $scale , $scale ) GL.Translate(object.xpos, object.ypos, 0.0) GL.Rotate(object.aim, 0.0, 0.0, 1.0) GL::Color 1 , 0 , 0 GL.Begin(GL::LINES) GL::Vertex2f 0.0 , 0.0 GL::Vertex2f 40.0 , 0.0 GL.End GL.PopMatrix() } # draw trajectory GL.PushMatrix() GL.Scale( $scale , $scale , $scale ) GL.Translate(0.0 , 0.0 , 0.0) GL::Color 1 , 0 , 1 GL.PointSize(3.0) GL.Begin(GL::POINTS) $path.each {|point| GL::Vertex2f point[0],point[1] } GL.End GL.PopMatrix() GLUT.SwapBuffers() end # Starts the main loop def start GLUT.MainLoop end end #Let the fun begin GameSys.new.start