#!/usr/bin/env ruby -rubygems # OVERLY SIMPLE INFECTION SIMULATOR WITH OPENGL FOR RUBY # by udo.schroeter@gmail.com # license: public domain # # if you haven't already, you need to install the "ruby-opengl" gem # for this example to work. on a unix system, you can use the # following command: # sudo gem install ruby-opengl require "gl" require "glu" require "glut" require "mathn" include Gl include Glu include Glut $window = "" # viewport movement variables $fXDiff = 0 $fYDiff = -45 $fZDiff = 0 $xLastIncr = 0 $yLastIncr = 0 $fXInertia = -0.0000 $fYInertia = -0.000 $fZInertia = -0.0035 $fScale = 0.15 $ftime = 0 $xLast = -1 $yLast = -1 $bmModifiers = 0 $rotate = 1 # playing field configuration $tick = 0; $polySize = 0.5 $polySizeW = 0.4 $entities = Array.new() $entityIdx = 0 $fastPi = 3.141 $deg2rad = $fastPi/180 $xMax = 25 TIMER_FREQUENCY_MILLIS = 20 # create all entities def makeEntities() (-7..7).each do |x| (-7..7).each do |y| entity = {} entity[:x] = x*2 entity[:y] = y*2 entity[:type] = 'human' entity[:direction] = 0 $entities.push(entity) end end end # make the OpenGL window def init_gl_window(width = 640, height = 480) glClearColor(0.0, 0.0, 0, 0) glClearDepth(1.0) glDepthFunc(GL_LEQUAL) glEnable(GL_DEPTH_TEST) glShadeModel(GL_SMOOTH) glMatrixMode(GL_PROJECTION) glLoadIdentity gluPerspective(45.0, width / height, 0.1, 100.0) glMatrixMode(GL_MODELVIEW) draw_gl_scene end # on window resize def reshape(width, height) height = 1 if height == 0 glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) glLoadIdentity gluPerspective(45.0, width / height, 0.1, 100.0) end # turn entities around at the border (just like at the Rio Grande but more effective) def doBoundary(entity) distFromZero = Math.sqrt(entity[:x]**2 + entity[:y]**2) if distFromZero > $xMax entity[:direction] = 180-180/$fastPi*Math.atan2(entity[:x], entity[:y])+20-rand(40) entity[:newdirection] = 180-180/$fastPi*Math.atan2(entity[:x], entity[:y])+20-rand(40) entity[:speed] = 0.01 end end # display a single entity def drawEntity(zpos, entity) if entity[:type] == 'human' dirmod = 0 r = 0 g = 0 b = 0.7 elsif entity[:type] == 'zombie' zombPhase = Math.sin($tick/10) dirmod = zombPhase*20+rand(4)-2 r = 0.6+zombPhase*0.2 g = 0 b = 0 end glPushMatrix() glTranslatef(+entity[:x], +entity[:y], +zpos) glRotatef(entity[:direction]+dirmod, 0,0,1) if entity[:anistate] entity[:anistate] -= 0.3 glRotatef(entity[:anistate], 0,1,0) r = entity[:anistate]/50+0.4 g = r b = r if entity[:anistate] < 0 entity.delete(:anistate) end end glTranslatef(-entity[:x], -entity[:y], -zpos) glBegin(GL_POLYGON) glColor3f(0.8,0.8,0.8) glVertex3f( 0.0+entity[:x], $polySize+entity[:y], 0.0+zpos) glColor3f(r,g,b) glVertex3f( $polySizeW+entity[:x], -$polySize+entity[:y], 0.0+zpos) glColor3f(r,g,b) glVertex3f(-$polySizeW+entity[:x], -$polySize+entity[:y], 0.0+zpos) glEnd glPopMatrix() end # get the nearest entity of a certain type def getNearest(type, mx, my) zombDist = 100000 zombIndex = nil $entities.each do |other| if (other[:type] == type) ex = other[:x]-mx ey = other[:y]-my dist = Math.sqrt(ex*ex + ey*ey) if dist < zombDist zombIndex = other zombDist = dist end end end if zombIndex zombIndex[:pdist] = zombDist end return(zombIndex) end # here you can plug in whatever decision making routine you like def DoEntityAI(entity) case(entity[:type]) when 'human' other = getNearest('zombie', entity[:x], entity[:y]) if other ex = other[:x]-entity[:x] ey = other[:y]-entity[:y] entity[:newdirection] = 180-180/$fastPi*Math.atan2(ex, ey) if (entity[:newdirection]-entity[:direction]).abs>180 entity[:newdirection] -= 360 end entity[:speed] = 0.01*(1/(0.8+other[:pdist])) end when 'zombie' if entity[:anistate] == nil other = getNearest('human', entity[:x], entity[:y]) if other ex = other[:x]-entity[:x] ey = other[:y]-entity[:y] entity[:newdirection] = 360-180/$fastPi*Math.atan2(ex, ey) if (entity[:newdirection]-entity[:direction]).abs>180 entity[:newdirection] -= 360 end entity[:speed] = 0.0025+0.005*(1/(1+other[:pdist])) if other[:pdist] < 1 other[:type] = 'zombie' other[:anistate] = 180 entity[:speed] = 0 other[:speed] = 0 end end end end end # update the scene def draw_gl_scene glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glMatrixMode(GL_MODELVIEW) glLoadIdentity glTranslatef(0.0, 0.0, -6.0) glScalef($fScale, $fScale, $fScale) $fXDiff += $fXInertia $fYDiff += $fYInertia $fZDiff += $fZInertia glRotatef($fYDiff, 1,0,0) glRotatef($fXDiff, 0,1,0) glRotatef($fZDiff, 0,0,1) $tick += 0.1 $entities.each do |entity| drawEntity(0, entity) end glutSwapBuffers end # draw the viewport during idle cycles of the CPU def idle glutPostRedisplay end # handle keyboard input keyboard = lambda do |key, x, y| case(key) when ?\e glutDestroyWindow($window) exit(0) when ?h entity = $entities[rand($entities.size)] entity[:anistate] = 180; entity[:type] = 'human' when ?z entity = $entities[rand($entities.size)] entity[:anistate] = 180; entity[:type] = 'zombie' when ?r $entities = Array.new() makeEntities() end glutPostRedisplay end # timer event that updates the position of the entities and calls decision making routine timer = lambda do |value| $entities.each do |entity| if entity[:speed] if entity[:speed] > 0.2 entity[:speed] = 0.2 end entity[:x] += 10*entity[:speed]*Math.sin(-(entity[:direction])*$deg2rad) entity[:y] += 10*entity[:speed]*Math.cos(-(entity[:direction])*$deg2rad) if entity[:speed] < 0.001 entity.delete(:speed) end end if entity[:newdirection] if entity[:newdirection] > entity[:direction] entity[:direction] += 2.5 else entity[:direction] -= 2.5 end if (entity[:newdirection] - entity[:direction]).abs < 1 entity.delete(:newdirection) end end doBoundary(entity) if rand(10) == 1 DoEntityAI(entity) end end glutTimerFunc(TIMER_FREQUENCY_MILLIS , timer, 0) end # handle mouse input (not implemented) mouse = lambda do |button,state,x,y| $bmModifiers = glutGetModifiers() if (button == GLUT_LEFT_BUTTON) if (state == GLUT_UP) end end end # display console help puts('press "z" to make a zombie') puts('press "h" to make a human') puts('press "r" to reload') puts('press ESC key to quit') # init entities makeEntities() # init OpenGL glutInit glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH); glutInitWindowSize(800, 500); glutInitWindowPosition(0, 0); $window = glutCreateWindow('RubyZombies!0.1'); glutDisplayFunc(method(:draw_gl_scene).to_proc); glutReshapeFunc(method(:reshape).to_proc); glutIdleFunc(method(:idle).to_proc); glutKeyboardFunc(keyboard); glutMouseFunc(mouse) init_gl_window(800, 500) glutTimerFunc(TIMER_FREQUENCY_MILLIS , timer, 0) glutMainLoop();