Getting Started with PyBioSim : Using Simulator.py to create a world
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. '''