Getting Started with PyBioSim : Using Simulator.py to create a world

From Multiagent Robots and Systems Group
Jump to: navigation, search

Working with Simulator.py

When you first open Simulator.py, you will see something like this.

'''
Created on Jan 16, 2015

@author: Arindam
'''

import numpy as np
import matplotlib.pyplot as plt
from numpy import *
from World import *
from Agent import Agent
from Obstacle import *
from pylab import *
import os
from Ball import Ball
from matplotlib import animation
from LinearAlegebraUtils import distBetween
from RunAtBallBrain import RunAtBallBrain
from Team import Team
from copy import deepcopy
import time
from FlockingBrain import FlockingBrain


#Called once for initialization
'''
Usage guidelines:
1. Define globals required for the simulation in the __init__ constructor, here we define a bunch of waypoints for the ball
2. Initialize the globals in the setup() method. 
'''

class Simulator(object):
    def __init__(self, world, simTime, fps, imageDirName):
        self.world = world
        self.simTime = simTime
        self.fps = fps
        self.imageDirName = imageDirName

    def setup(self):    
        
        
#called at a fixed 30fps always
    def fixedLoop(self):

    
#Called at specifed fps
    def loop(self, ax):       
        self.world.draw(ax)
       
                
    def run(self):
        #Run setup once
        self.setup()
        
        #Setup loop
        timeStep = 1/double(30)
        frameProb = double(self.fps) / 30
        currTime = double(0)
        drawIndex = 0
        physicsIndex = 0
        while(currTime < self.simTime):
            self.fixedLoop()
            currProb = double(drawIndex)/double(physicsIndex+1)
            if currProb < frameProb:
                self.drawFrame(drawIndex)  
                drawIndex+=1
            physicsIndex+=1
            currTime+=double(timeStep)
     
        print "Physics ran for "+str(physicsIndex)+" steps"
        print "Drawing ran for "+str(drawIndex)+" steps"
            
    def drawFrame(self, loopIndex):
        fig = plt.figure(figsize=(16,12))
        ax = fig.add_subplot(111, projection='3d') 
        ax.view_init(elev = 30)
        ax.set_xlabel("X")
        ax.set_ylabel("Y")
        ax.set_zlabel("Z")    
        fname = self.imageDirName + '/' + str(int(100000000+loopIndex)) + '.jpg' # name the file 
        self.loop(ax)
        plt.gca().set_ylim(ax.get_ylim()[::-1])
        savefig(fname, format='jpg', bbox_inches='tight')
        print 'Written Frame No.'+ str(loopIndex)+' to '+ fname
        plt.close()


#Simulation runs here
#set the size of the world
world = World(100, 100)
#specify which world to simulate, total simulation time, and frammerate for video
sim = Simulator(world, 60, 30, "images")
#run the simulation
sim.run()

'''
To create a video using the image sequence, execute the following command in command line.
>ffmpeg -f image2 -i "1%08d.jpg" -r 30 outPut.mp4
Make sure to set your current working directory to /images and have ffmpeg in your path.
'''

    

The import methods to note here are the setup(), loop() and fixedLoop() methods.

setup() : This method is called once before the simulation, we'll use this to initialize the world and setup various parameters.

loop() : This method is called repeatedly at some specified frame rate(we'll see how to change this later, right now its set to 30), you can think of this as a draw loop, by default it calls world.draw() which draws the world.

fixedLoop() : This method is called at 30 times a second fixed, always. This method should be used for moving objects in the world. This allows your movement to be asynchronous to the speed of the video.

Below is an example of how to use these methods to create a simulation of two teams chasing a moving ball.

Using setup() to initialize the world: First Lets setup two teams in our setup() method.

teamA = Team("A", '#ff99ff')
teamB = Team("B", '#ffcc99')

Syntax: Team("String Name of team", "Hex color code for team color")

Now lets create the agents that will be part of these teams. Here's a snippet on how to create an agent.

ag1Pos = array([80, 50, -20])
ag1Rot = array([30, 0, 0])
ag1Brain = RunAtBallBrain()
agent1 = Agent(teamA, ag1Pos, ag1Rot, ag1Brain, 5, 5, 5)

First we define the position where we want the agent to start. ag1Pos is a numpy array of the (x, y, x) co-ordinates.

Second we define the rotation we want the agent to start with. ag1Rot is a numpy array of (yaw, pitch , roll) in degrees.

We then initialize an instance of a "Brain" which defines the behavior of the agent. We'll see how to create a custom one later, right now you can use the provided RunAtBallBrain() which defines a behaviour in which the agent chases the ball.

Lastly, we initialize an agent as an Object of the Agent class. Syntax : Agent( "team of Agent", "Starting Position", "Starting Rotation", "Brain", "Turn Rate", "Radius of Collision", "Draw Radius") To add this agent into the world we need to call:

self.world.agents.append(agent1)


Now lets define the ball which the agents will chase.

#define a ball
ball = Ball(array([0, 0, 0]))
          
#add the ball to the world
self.world.balls.append(ball)

Similar to our agents we provide a numpy array as a position of our ball and we then add it to the world. Syntax: Ball( "position" )

Now for some decorative obstacles.

#define a bunch of obstacles
ob1Pos = array([-50,-50,-50])
ob1 = Obstacle(ob1Pos, 30)
         
ob2Pos = array([80,-50,-50])
ob2 = Obstacle(ob2Pos, 20)
         
#add obstacles to the world
self.world.obstacles.append(ob1);
self.world.obstacles.append(ob2)

Just like the ball, we define two obstacles with a numpy array as a position, additionally we provide a radius for the obstacle.

Syntax: Obstacle("position", "radius")

That's it, the world is setup, no we'll see how to move the agents and the ball around using fixedLoop().

Using fixedLoop() to move objects: Lets make sure we tell all our agents to move to do this we call,

for agent in self.world.agents:
    agent.moveAgent(self.world)

This uses the behavior defined in the agents brain to move them around.

Now, to move the ball.

The ball has a method call moveBall("position", "speed") which moves the ball to a certain position at a certain speed. We'll provide a series of points to this method to move it around.

Heres the logic for it:

1. Maintain a list of waypoints

2. Move the ball with a certain speed to waypoint[0].

3. If I'm really close to waypoint[0] then remove waypoint[0] from my lest of waypoints.(Python will conveniently reorder the list so waypoint[1] is now waypoint[0])

4. Continue till waypoints.size > 0

Important: We need to maintain list of "waypoints" for the ball, we cannot do this inside fixedLoop() because it would be re-created at the start of every fixedLoop() call, thus we need to define it as a global. Instead of using python's global keyword, we define it as a member of our Simulator class inside the Simulator class's __init()__ constructor.

Here's the above logic in code form,

for ball in self.world.balls:  
  if len(self.ballWPs) > 0:  
     ball.moveBall(self.ballWPs[0], 1)
         if distBetween(ball.position, self.ballWPs[0]) < 0.5:
             if len(self.ballWPs) > 0:
                 self.ballWPs.remove(self.ballWPs[0])

That,s it!

Your Simulator.py should look like this:

'''
Created on Jan 16, 2015

@author: Arindam
'''

import numpy as np
import matplotlib.pyplot as plt
from numpy import *
from World import *
from Agent import Agent
from Obstacle import *
from pylab import *
import os
from Ball import Ball
from matplotlib import animation
from LinearAlegebraUtils import distBetween
from RunAtBallBrain import RunAtBallBrain
from Team import Team
from copy import deepcopy
import time
from FlockingBrain import FlockingBrain


#Called once for initialization
'''
Usage guidelines:
1. Define globals required for the simulation in the __init__ constructor, here we define a bunch of waypoints for the ball
2. Initialize the globals in the setup() method. 
'''

class Simulator(object):
    def __init__(self, world, simTime, fps, imageDirName):
        self.world = world
        self.simTime = simTime
        self.fps = fps
        self.imageDirName = imageDirName
        self.currWP = 0
        self.ballWPs = [array([50.0, -100.0, 0.0]), array([0.0, 100.0, -70.0]), array([50.0, 20.0, 100.0]),array([-30.0, 50.0, -100.0]), array([80.0, -50.0, 50.0]), array([80.0, -50.0, -50.0]), array([-65.0, 20.0, 50.0]), array([-50.0, 20.0, -60.0])]

    def setup(self):    
        #setup directory to save the images
        self.imageDirName = 'images'
        try:
            os.mkdir(self.imageDirName)
        except:
            print self.imageDirName + " subdirectory already exists. OK."

  
         #define teams which the agents can be a part of
        teamA = Team("A", '#ff99ff')
        teamB = Team("B", '#ffcc99')
        #Defining a couple of agents 
        ag1Pos = array([80, 50, -20])
        ag1Rot = array([30, 0, 0])
        ag1Brain = RunAtBallBrain()
        agent1 = Agent(teamA, ag1Pos, ag1Rot, ag1Brain, 5, 5)
         
         
        ag2Pos = array([-80, 0, 0])
        ag2Rot = array([0, 0, 0])
        ag2Brain = RunAtBallBrain()
        agent2 = Agent(teamA, ag2Pos, ag2Rot, ag2Brain, 5, 5)
         
        ag3Pos = array([70, 30, 50])
        ag3Rot = array([0, 0, 0])
        ag3Brain = RunAtBallBrain()
        agent3 = Agent(teamB, ag3Pos, ag3Rot, ag3Brain, 5, 5)
         
        ag4Pos = array([-80, 20, 60])
        ag4Rot = array([0, 0, 0])
        ag4Brain = RunAtBallBrain()
        agent4 = Agent(teamB, ag4Pos, ag4Rot, ag4Brain, 5, 5)
         
        #Add the agent to the world
        self.world.agents.append(agent1)
        self.world.agents.append(agent2)
        self.world.agents.append(agent3)
        self.world.agents.append(agent4)

#         
        #define a bunch of obstacles
        ob1Pos = array([-50,-50,-50])
        ob1 = Obstacle(ob1Pos, 30)
         
        ob2Pos = array([80,-50,-50])
        ob2 = Obstacle(ob2Pos, 20)
         
        #add obstacles to the world
        self.world.obstacles.append(ob1);
        self.world.obstacles.append(ob2)
        
        #define a ball
        ball = Ball(array([0, 0, 0]))
        
        
        #add the ball to the world
        self.world.balls.append(ball)
        
#called at a fixed 30fps always
    def fixedLoop(self):
        for agent in self.world.agents:
            agent.moveAgent(self.world)
         
        for ball in self.world.balls:  
            if len(self.ballWPs) > 0:  
                ball.moveBall(self.ballWPs[0], 1)
                if distBetween(ball.position, self.ballWPs[0]) < 0.5:
                    if len(self.ballWPs) > 0:
                        self.ballWPs.remove(self.ballWPs[0])

    
#Called at specifed fps
    def loop(self, ax):       
        self.world.draw(ax)
       
                
    def run(self):
        #Run setup once
        self.setup()
        
        #Setup loop
        timeStep = 1/double(30)
        frameProb = double(self.fps) / 30
        currTime = double(0)
        drawIndex = 0
        physicsIndex = 0
        while(currTime < self.simTime):
            self.fixedLoop()
            currProb = double(drawIndex)/double(physicsIndex+1)
            if currProb < frameProb:
                self.drawFrame(drawIndex)  
                drawIndex+=1
            physicsIndex+=1
            currTime+=double(timeStep)
     
        print "Physics ran for "+str(physicsIndex)+" steps"
        print "Drawing ran for "+str(drawIndex)+" steps"
            
    def drawFrame(self, loopIndex):
        fig = plt.figure(figsize=(16,12))
        ax = fig.add_subplot(111, projection='3d') 
        ax.view_init(elev = 30)
        ax.set_xlabel("X")
        ax.set_ylabel("Y")
        ax.set_zlabel("Z")    
        fname = self.imageDirName + '/' + str(int(100000000+loopIndex)) + '.jpg' # name the file 
        self.loop(ax)
        plt.gca().set_ylim(ax.get_ylim()[::-1])
        savefig(fname, format='jpg', bbox_inches='tight')
        print 'Written Frame No.'+ str(loopIndex)+' to '+ fname
        plt.close()


#Simulation runs here
#set the size of the world
world = World(100, 100)
#specify which world to simulate, total simulation time, and frammerate for video
sim = Simulator(world, 60, 30, "images")
#run the simulation
sim.run()

'''
To create a video using the image sequence, execute the following command in command line.
>ffmpeg -f image2 -i "1%08d.jpg" -r 30 outPut.mp4
Make sure to set your current working directory to /images and have ffmpeg in your path.
'''