#!/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 #------------------------------------------------- #-- currently something wrong with vectors and the direction the bird is pointing in. #- Load libraries #------------------------------------------------- require 'opengl' require 'glut' #- Load modules #------------------------------------------------- include Math #- Define globals and constants #------------------------------------------------- TITLE = "alive" $scale = 0.4 #$dimx = 312 ; $dimy = 213 $dimx = 600 ; $dimy = 600 $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 def normalize! factor = 1.0 / sqrt(inject(0.0) {|a,b| a + b**2}) self.map! {|a| factor * a} end def normalize factor = 1.0 / sqrt(inject(0.0) {|a,b| a + b**2}) self.map {|a| factor * a} end end class Float def to_vector return cos(self* PI / 180),sin(self * PI / 180) end end class Fixnum 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 :color, :shape attr_reader :xpos, :ypos, :vector, :accel def initialize @passedsincelast = 0 @shape = "$bird" # Somewhere in the field @xpos = rand($dimx) ; @ypos = rand($dimy) # With a motion (speed is contained in the size of the vector) # and an acceleration @vector = rand(360).to_vector ; @vector.map! {|i| rand * i} @accel = rand(360).to_vector ; @accel.map! {|i| rand * i} end # move, change direction at the end of the field def move! @passedsincelast += 1 # 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 3: Acceleration! if @passedsincelast > 700 begin @accel = rand(360).to_vector end until @accel[0].abs > 0.5 and @accel[1].abs > 0.5 @passedsincelast = 0 end # moving @vector[0] = (1000 * @vector[0] + @accel[0]) / 1001.0 # I'm not sure if it is still acceleration @vector[1] = (1000 * @vector[1] + @accel[1]) / 1001.0 # when I describe it like this. it's more like a new vector #if (@vector[0] + @accel[0])**2 + (@vector[1] + @accel[1])**2 > 1 # @vector.normalize! #end @xpos += @vector[0] ; @ypos += @vector[1] # 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.vector.to_angle, 0.0, 0.0, 1.0) GL.CallList(eval(object.shape)) GL.PopMatrix() =begin # 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() =end } # 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