class RhomdosRender:
    def __init__(self,
                 game,
                 duration=None,
                 storage_path="storage/3d/frame",
                 fps=30,
                 as_gif=False,
                 gif_name=None,
                 size=(480, 480)):
        self.game = game
        self.duration = duration
        self.storage_path = storage_path
        self.fps = fps
        self.as_gif = as_gif
        self.gif_name = gif_name
        self.base = ShowBase(windowType='none')
        self.base.openWindow(type=('offscreen' if as_gif else 'onscreen'),
                             size=size)

        depth, height, width = game.shape
        self.rhomdos = []

        for z in range(depth):
            self.rhomdos.append([])
            for y in range(height):
                self.rhomdos[-1].append([])
                for x in range(width):
                    rr = RhomdoRender((z, y, x), game.shape)
                    render.attachNewNode(rr.geomnode)
                    self.rhomdos[-1][-1].append(rr)

        plight = PointLight('plight')
        plight.setColor(VBase4(1, 1, 1, 1))
        plight.setAttenuation((0, 0, .0005))
        self.plnp = render.attachNewNode(plight)
        self.plnp.setPos(0, 0, (game.shape[0] * SR2) + 40)
        render.setLight(self.plnp)

        if self.as_gif:
            self.base.movie(self.storage_path,
                            duration=self.duration,
                            fps=self.fps)

        self.base.taskMgr.add(self.spinCameraTask, "SpinCameraTask")
        self.base.taskMgr.add(self.updateClock, "UpdateClock")

        self.frame = 0
        self.game_step = 0

    def spinCameraTask(self, task):
        angleDegrees = task.time * 40.0
        angleRadians = angleDegrees * (pi / 180.0)
        self.base.camera.setPos(60 * sin(angleRadians),
                                -60 * cos(angleRadians), 32)
        self.base.camera.setHpr(angleDegrees, -30, 0)
        return Task.cont

    def run(self):
        if self.as_gif:
            for i in tqdm(list(range(self.duration * self.fps))):
                self.base.taskMgr.step()
            self.generate_gif()
        else:
            while True:
                self.base.taskMgr.step()

    def updateClock(self, task):
        self.frame = task.time

        adjusted_time = task.time * 8
        if ceil(adjusted_time) > self.game_step:
            self.updateRhomdos()
            self.game_step = ceil(adjusted_time)

        return Task.cont

    def generate_gif(self):
        with imageio.get_writer(self.gif_name, mode='I') as writer:
            for i in tqdm(list(range(3, self.duration * self.fps))):
                filename = f"{self.storage_path}_{str(i).rjust(4, '0')}.png"
                writer.append_data(imageio.imread(filename))
                os.remove(filename)

    def updateRhomdos(self):
        self.game.advance()
        depth, height, width = self.game.shape
        for z in range(depth):
            for y in range(height):
                for x in range(width):
                    rr = self.rhomdos[z][y][x]
                    if self.game.grid[-1, z, y, x, 0] == 1:
                        rr.show()
                    else:
                        rr.hide()
Beispiel #2
0
class BananaWorld(DirectObject):
    def __init__(self, movie_data_file, record, use_eye_data=False, use_lfp_data=False):
        DirectObject.__init__(self)
        self.record = record
        # make sure directory exists
        movie_name = '../movies/frames/game/game'
        environ = 'original'
        # environ = 'circle'

        data = MovieData(movie_data_file, use_eye_data)

        # Things that can affect camera:
        # options resolution resW resH
        self.base = ShowBase()
        lens = PerspectiveLens()
        # Fov is set in config for 60
        lens.setFov(60)
        # aspect ratio should be same as window size
        # this was for 800x600
        # field of view 60 46.8264...
        # aspect ratio 1.3333
        movie_res = [800, 600]
        # set aspect ratio to original game
        #print resolution
        lens.setAspectRatio(data.resolution[0] / data.resolution[1])
        #print lens.getAspectRatio()
        #lens.setAspectRatio(800.0 / 600.0)
        self.base.cam.node().setLens(lens)
        # print('Fov', lens.getFov())
        # print('Aspect Ratio', lens.getAspectRatio())
        # set near to be same as avatar's radius
        # affects how close you get to the bananas
        lens.setNear(0.1)
        #print('near camera', lens.getNear())
        #base.cam.setPos(0, 0, 1)
        #print('x', base.win.getXSize())
        #print('y', base.win.getYSize())
        # when doing the calibration task I used the orthographic lens with normal render,
        # so the origin was in the center, but when using pixel2d the origin is in the top
        # left corner, so we must move the coordinate system to the right and down by half
        # the screen
        # covert resolution
        eye_factor = [movie_res[0]/data.resolution[0], movie_res[1]/data.resolution[1]]
        # print('move_res', movie_res)
        # print('actual movie res', self.base.win.getXSize(), self.base.win.getYSize())
        # print('data_res', data.resolution)
        # print('eye factor', eye_factor)
        # calibration not very good...
        #fudge_factor_x = 50
        #fudge_factor_y = 80
        fudge_factor_x = 0
        fudge_factor_y = 60
        self.eye_data = deque()
        self.last_eye_ts = None
        # since for eye_data we are looping and adding to the end of the list,
        # but then we will be pulling from the front of the list, we have a queue,
        # so let's use deque. (Other data was reversed in movie_data).
        if use_eye_data:
            for i in data.raw_eye_data:
                x = (float(i[0]) * eye_factor[0]) + (self.base.win.getXSize() / 2) + fudge_factor_x
                y = (float(i[1]) * eye_factor[1]) - (self.base.win.getYSize() / 2) + fudge_factor_y
                self.eye_data.append((x, y))
            #print self.eye_data
            # container for eye trace
            self.eyes = []
            #print(len(self.eye_data))
            # make generators for eye data
            self.last_eye = self.eye_data.popleft()
            #print(len(self.eye_data))
            # time stamps are all reversed, so can use normal pop and then assign directly,
            # like other time variables
            self.last_eye_ts = data.eye_ts.pop()
            self.eye_ts = data.eye_ts

        # need to adjust y position for lfp
        self.lfp_gain = 0.05
        # lfp_offset determines where each trace is on the y axis
        lfp_offset = -500  # bottom
        self.lfp_offset = []
        #self.lfp_offset = -100  # top of screen
        self.last_lfp = []
        self.gen_lfp = []
        # make a generator for lfp data
        # this code is a little silliness, and I'm popping a giant list from the wrong end
        # when I start plotting lfp again, fix this!
        if use_lfp_data:
            for data in data.lfp_data:
                self.lfp_offset.append(lfp_offset)
                self.last_lfp.append([(data.pop(0) * self.lfp_gain) + lfp_offset])
                lfp_offset += 100
                # self.gen_lfp.append(get_data(data))

        # last_lfp_x determines where on the x axis we start the lfp trace
        self.start_x_trace = 50

        # bring in data we care about.
        self.avatar_pos = data.avatar_pos
        self.avatar_pt = data.avatar_pt
        self.avatar_h = data.avatar_h
        self.avatar_ht = data.avatar_ht
        self.fruit_status = data.fruit_status
        self.fruit_status_ts = data.fruit_status_ts
        self.fruit_pos = data.fruit_pos
        self.fruit_pos_ts = data.fruit_pos_ts
        self.trial_mark = data.trial_mark

        # print 'fruit pos timestamps', self.fruit_pos_ts
        # initialize other variables
        self.eye_spot = None

        # set starting point for avatar
        points = self.avatar_pos.pop()
        self.base.cam.setPos(Point3(points[0], points[1], points[2]))
        self.base.cam.setH(self.avatar_h.pop())
        self.avatar_ht.pop()
        self.avatar_pt.pop()

        # get last time stamp (first of list) for avatar to calculate length of movie
        # add half a second buffer.
        movie_length = self.avatar_ht[0] + 0.8
        print('movie length', movie_length)
        self.set_environment(environ)

        #load bananas
        # if we are not starting at the beginning of the trial, some of the bananas may
        # already be gone. Create them, and then stash them, so the index still refers to
        # the correct banana

        self.fruitModel = {}
        # print('fruit', self.fruit_pos)

        for k, v in self.fruit_pos.iteritems():
            #print('i', i)
            # print('k', k)
            #print('v', v)
            if 'banana' in k:
                self.fruitModel[k] = self.base.loader.loadModel('../goBananas/models/bananas/banana.bam')
                self.fruitModel[k].setScale(0.5)
            elif 'cherry' in k:
                self.fruitModel[k] = self.base.loader.loadModel('../goBananas/models/fruit/cherries.egg')
                self.fruitModel[k].setScale(0.08)
            # position = self.fruit_pos[k]['position'].pop(0)
            # print position
            heading = v['head']
            #print heading
            # self.fruitModel[k].setPos(
            #     Point3(float(position[0]), float(position[1]), float(position[2])))

            self.fruitModel[k].setH(float(heading))
            self.fruitModel[k].reparentTo(self.base.render)
            # assume all fruit stashed to start
            self.fruitModel[k].stash()
            if k in data.alpha:
                print 'set alpha', data.alpha
                self.alpha_node_path = self.fruitModel[k]
                self.alpha_node_path.setTransparency(TransparencyAttrib.MAlpha)

        if self.record:
            print('make movie', movie_name)
            self.movie_task = self.base.movie(movie_name, movie_length, 30, 'png', 4)

        self.gameTask = taskMgr.add(self.frame_loop, "frame_loop")

        self.gameTask.last = 0         # Task time of the last frame

        #print('trialmarks', self.trial_mark)
        #print('start', self.gameTask.game_time)
        #print('head start', self.avatar_ht[-1])
        #print('increment', (1 / 60) * 1000000)

    def set_environment(self, environ):

        if environ == 'original':
            terrainModel = self.base.loader.loadModel('../goBananas/models/play_space/field.bam')
            skyModel = self.base.loader.loadModel('../goBananas/models/sky/sky.bam')
            skyModel.setPos(Point3(0, 0, 0))
            skyModel.setScale(1.6)
            treeModel = self.base.loader.loadModel('../goBananas/models/trees/palmTree.bam')
            treeModel.setPos(Point3(13, 13, 0))
            treeModel.setScale(0.0175)
            treeModel.reparentTo(self.base.render)
            skyscraper = self.base.loader.loadModel('../goBananas/models/skyscraper/skyscraper.bam')
            skyscraper.setPos(Point3(-13, -13, 0))
            skyscraper.setScale(0.3)
            skyscraper.reparentTo(self.base.render)
            stLightModel = self.base.loader.loadModel('../goBananas/models/streetlight/streetlight.bam')
            stLightModel.setPos(Point3(-13, 13, 0))
            stLightModel.setScale(0.75)
            stLightModel.reparentTo(self.base.render)
        elif environ == 'circle':
            terrainModel = self.base.loader.loadModel('../goBananas/models/play_space/round_courtyard.bam')
            skyModel = self.base.loader.loadModel('../goBananas/models/sky/sky_kahana2.bam')
            skyModel.setPos(Point3(0, 0, -0.5))
            skyModel.setScale(Point3(2, 2, 4))

        terrainModel.setPos(Point3(0, 0, 0))
        terrainModel.reparentTo(self.base.render)
        #print 'terrain', terrainModel.getPos()
        skyModel.reparentTo(self.base.render)
        #print 'sky', skyModel.getPos()

        self.eye_spot = self.base.loader.loadModel("models/ball")
        #eye_texture = base.loader.loadTexture('textures/spotlight.png')
        #self.eye_spot.setTexture(eye_texture, 1)
        self.eye_spot.setScale(50)
        self.eye_spot.setTransparency(TransparencyAttrib.MAlpha)
        self.eye_spot.setColor(1, 1, 1, 0.3)

    def frame_loop(self, task):
        dt = task.time - task.last
        task.last = task.time
        #print('time', task.time)
        #print('trial marker', self.trial_mark[-1])
        # check to see if anything has happened.
        # there is a position and heading for every time stamp for the avatar.
        if self.avatar_pt:
            self.update_avt_p(task.time)
        else:
            # if we aren't moving the avatar anymore, assume done
            print 'done'
            return task.done
        if self.avatar_ht:
            self.update_avt_h(task.time)
        if self.fruit_pos_ts:
            self.move_fruit(task.time)
        if self.fruit_status_ts:
            self.update_fruit(task.time)
        # if len(self.banana_ts) > 0 and self.banana_ts[0] < task.time - 0.5:
        #    self.update_banana()
        if self.last_eye_ts:
            self.update_eye(task.time)
        #if self.trial_mark and self.trial_mark[-1] < task.time:
        #    self.move_fruit()
        for ind, last_lfps in enumerate(self.last_lfp):
            self.update_LFP(dt, last_lfps, self.lfp[ind], self.lfp_offset[ind], self.gen_lfp[ind])
        return task.cont

    def update_avt_h(self, t_time):
        while self.avatar_ht[-1] < t_time:
            self.base.cam.setH(self.avatar_h.pop())
            self.avatar_ht.pop()
            if not self.avatar_ht:
                break

    def update_avt_p(self, t_time):
        # print('avatar', self.avatar_pt[-1], 'time', t_time)
        while self.avatar_pt[-1] < t_time:
            points = self.avatar_pos.pop()
            # print points
            self.base.cam.setPos(Point3(points[0], points[1], points[2]))
            self.avatar_pt.pop()
            if not self.avatar_pt:
                break

    def update_fruit(self, t_time):
        # print self.avatar_pos[-1]
        while self.fruit_status_ts[-1] < t_time:
            current_list = self.fruit_status.pop()
            # print 'current list', current_list
            # list goes: fruit name, what happens, how much
            if current_list[1] == 'alpha':
                # sometimes alpha is one...
                if float(current_list[2]) <= 1:
                    self.alpha_node_path.setAlphaScale(float(current_list[2]))
            if current_list[1] == 'stash':
                if current_list[2] == 'True':
                    self.fruitModel[current_list[0]].stash()
                else:
                    # print 'unstash', current_list[0]
                    self.fruitModel[current_list[0]].unstash()
            #         print self.fruitModel[current_list[0]].isStashed()
            self.fruit_status_ts.pop()
            if not self.fruit_status_ts:
                break

    def move_fruit(self, t_time):
        # did not reverse, since pain in the ass, and likely not many
        # print 'position', self.fruit_pos_ts[0][0]
        # print 'delete' self.fruit_pos_ts
        # print 'time', t_time
        while self.fruit_pos_ts[0][0] < t_time:
            ts, fruit = self.fruit_pos_ts.pop(0)
            # print('current time stamp', ts)
            position = self.fruit_pos[fruit]['position'].pop(0)
            # print('move fruit', fruit, position)
            self.fruitModel[fruit].setPos(
                Point3(float(position[0]), float(position[1]), float(position[2])))
            # print('next timestamp', self.fruit_pos_ts[0])
            if not self.fruit_pos_ts:
                break

    def update_LFP(self, dt, last_lfp, lfp_trace, offset, gen_lfp):
        # lfp data is taken at 1000Hz, and dt is the number of seconds since
        # the last frame was flipped, so plot number of points = dt * 1000
        lfp = LineSegs()
        lfp.setThickness(1.0)
        #print('points to plot', int(dt * 1000))
        #self.lfp_test += int(dt * 1000)
        #print('points so far', self.lfp_test)

        for i in range(int(dt * 1000)):
            try:
                last_lfp.append((next(gen_lfp) * self.lfp_gain) + offset)
                #last_lfp_x += 0.05
                # only plotting 200 data points at a time
                while len(last_lfp) > 3500:
                    last_lfp.pop(0)
            except StopIteration:
                #print('done with lfp')
                break

        if lfp_trace:
            lfp_trace[0].removeNode()
            lfp_trace.pop(0)
        lfp.moveTo(self.start_x_trace, 55, last_lfp[0])
        x = self.start_x_trace
        for i in last_lfp:
            x += .1
            lfp.drawTo(x, 55, i)
        node = self.base.pixel2d.attachNewNode(lfp.create())
        lfp_trace.append(node)

        # get rid of lfp trace from a while ago..
        #while len(self.lfp) > 50:
        #    self.lfp[0].removeNode()
        #    self.lfp.pop(0)

    def update_eye(self, t_time):
        #eye = LineSegs()
        #eye.setThickness(10.0)
        #print('last_eye', self.last_eye)

        group_eye = []
        # get eye movements since the last frame
        while self.last_eye_ts < t_time:
            try:
                group_eye.append(self.eye_data.popleft())
                self.last_eye_ts = self.eye_ts.pop()
            except StopIteration:
                #make the next eye movement something crazy in the future
                self.last_eye_ts = t_time + 10000
                #print('break')
                taskMgr.remove('self.movie_task')
                break

        if group_eye:
            # plotting the average
            # have to sum in a loop, because tuples in a list
            #eye.moveTo(self.last_eye[0], 55, self.last_eye[1])
            sum_x = 0
            sum_y = 0
            for i in group_eye:
                sum_x += i[0]
                sum_y += i[1]
            self.last_eye = [sum_x / len(group_eye), sum_y / len(group_eye)]
            self.eye_spot.setPos(self.last_eye[0], 55, self.last_eye[1])
            self.eye_spot.reparentTo(self.base.pixel2d)
Beispiel #3
0
    def __init__(self, agent=None, env=None, show_learning=True):
        ShowBase.__init__(self)
        ShowBase.movie(self,
                       namePrefix='images/camera1/jmage',
                       duration=60,
                       fps=60,
                       format='jpg',
                       sd=4,
                       source=None)

        self.agent = agent
        self.env = env
        self.bicycle = self.env.env
        current_state = self.env.reset()
        current_state = current_state[np.newaxis]
        self.action = self.agent.action(current_state)
        self.action = self.action.flatten()

        self.show_learning = show_learning
        if self.show_learning:
            self.theta_counter = 0
            self.theta_index = 0
        self.elapsed_time = 0

        self.omegaText = self.genLabelText("", 1)
        self.thetaText = self.genLabelText("", 2)
        self.timeText = self.genLabelText("", 3)

        self.wheel_roll = 0
        self.torque = 0
        self.butt_displacement = 0

        # Load the environment model.
        self.environ = self.loader.loadModel("maps/Ground2.egg")
        ## Reparent the model to render.
        self.environ.reparentTo(self.render)

        # Disable the use of the mouse to control the camera.
        self.disableMouse()

        # "out-of-body experience"; toggles camera control.
        self.accept('o', self.oobe)

        # Add the spinCameraTask procedure to the task manager.
        self.taskMgr.add(self.followBikeTask, "FollowBikeTask")
        self.taskMgr.add(self.simulateBicycleTask, "SimulateBicycleTask")

        self.rear_wheel = self.loader.loadModel("maps/wheel3.egg")
        self.rear_wheel.reparentTo(self.render)
        self.rear_wheel.setPos(0, 0, self.bicycle.r)

        self.frame = self.loader.loadModel("maps/frame.egg")
        self.frame.reparentTo(self.rear_wheel)
        self.frame.setColor(1, 0, 0)

        self.butt = self.loader.loadModel("maps/frame.egg")
        self.butt.reparentTo(self.frame)
        self.butt.setColor(1, 0, 0)
        self.butt.setScale(1, 0.1, 1)
        self.butt.setZ(1.5 * self.bicycle.r)
        self.butt.setY(0.3 * self.bicycle.L)

        # place goal
        self.goalPost = self.loader.loadModel("maps/fork.egg")
        self.goalPost.reparentTo(self.render)

        self.fork = self.loader.loadModel("maps/fork.egg")
        self.fork.reparentTo(self.frame)
        self.fork.setColor(0, 0, 1)
        self.fork.setPos(0, self.bicycle.L, self.bicycle.r)

        self.front_wheel = self.loader.loadModel("maps/wheel3.egg")
        self.front_wheel.reparentTo(self.fork)
        self.front_wheel.setColor(1, 1, 1)
        self.front_wheel.setPos(0, 0, -self.bicycle.r)

        self.handlebar = self.loader.loadModel("maps/fork.egg")
        self.handlebar.reparentTo(self.fork)
        self.handlebar.setColor(0, 0, 1)
        self.handlebar.setPos(0, 0, self.bicycle.r)
        self.handlebar.setHpr(0, 0, 90)

        self.torqueLeftIndicator = self.loader.loadModel("maps/fork.egg")
        self.torqueLeftIndicator.reparentTo(self.fork)
        self.torqueLeftIndicator.setColor(0, 0, 1)
        self.torqueLeftIndicator.setPos(-self.bicycle.r, 0, self.bicycle.r)
        self.torqueLeftIndicator.hide()

        self.torqueRightIndicator = self.loader.loadModel("maps/fork.egg")
        self.torqueRightIndicator.reparentTo(self.fork)
        self.torqueRightIndicator.setColor(0, 0, 1)
        self.torqueRightIndicator.setPos(self.bicycle.r, 0, self.bicycle.r)
        self.torqueRightIndicator.hide()

        self.camera.setPos(5, -5, 10)
Beispiel #4
0
class AvatarWorld(DirectObject):
    def __init__(self, datafile, record=True, distance_goal=None):
        DirectObject.__init__(self)
        # environ = 'circle'
        environ = 'original'
        movie_name = '../movies/frames/avatar/avatar'

        data = MovieData(datafile)
        # bring in data that we will use
        self.avatar_pos = [[j/2 for j in i] for i in data.avatar_pos]
        self.avatar_pt = data.avatar_pt
        self.fruit_status = data.fruit_status
        self.fruit_status_ts = data.fruit_status_ts
        self.fruit_pos = data.fruit_pos
        self.fruit_pos_ts = data.fruit_pos_ts
        self.trial_mark = data.trial_mark
        self.alpha = data.alpha

        self.scale_factor = 1

        # print 'alpha', self.alpha
        # toggle to detect when a block of trials is finished
        self.block_done = False
        # print 'fruit status', self.fruit_status
        self.use_alpha = False
        # convoluted way to see if we are really using invisible fruit
        for i in self.fruit_status:
            if i[1] == 'alpha' and float(i[2]) < 1:
                self.use_alpha = True
                # print 'true', float(i[2])
        # print 'use alpha', self.use_alpha
        # really should pull this from the config file (distance_goal)
        # everything is at 1/2 size
        if distance_goal:
            self.goal_radius = [i/2 for i in distance_goal]
        else:
            # total arbitrary guess
            self.goal_radius = [3/2, 3/2]
        # Things that can affect camera:
        # options resolution resW resH
        self.base = ShowBase()
        props = WindowProperties()
        # props.setSize(600, 600)
        self.base.win.requestProperties(props)

        self.drawing_layer = 25    
        #corner = 600/100 * 5/6
        if environ == 'original':
            border = LineSegs()
            border.setThickness(2.0)
            corner = 5.5    
            # print corner
            # red
            border.setColor(1, 0, 0)
            border.moveTo(corner, 25, corner)
            border.drawTo(corner, 25, -corner)

            # purple
            border.setColor(1, 0, 1)
            border.moveTo(corner, 25, -corner)
            border.drawTo(-corner, 25, -corner)

            # white
            border.setColor(1, 1, 1)
            border.moveTo(-corner, 25, -corner)
            border.drawTo(-corner, 25, corner)

            # green
            border.setColor(0, 1, 0)
            border.moveTo(-corner, 25, corner)
            border.drawTo(corner, 25, corner)
            self.base.render.attachNewNode(border.create(True))

            imageObject = OnscreenImage(image='textures/lightpost.png',
                                        pos=(-0.9, 25, 0.9), scale=(0.06, 1, 0.08), color=(0.9, 0.9, 0.9, 0.8))
            imageObject.setTransparency(TransparencyAttrib.MAlpha)
            imageObject1 = OnscreenImage(image='textures/palm_tree.png',
                                        pos=(0.85, 25, 0.9), scale=0.09, color=(0.9, 0.9, 0.9, 0.8))
            imageObject1.setTransparency(TransparencyAttrib.MAlpha)
            imageObject2 = OnscreenImage(image='textures/transamerica_thumb.png',
                                        pos=(-0.9, 25, -0.9), scale=0.2, color=(0.9, 0.9, 0.9, 0.8))
            imageObject2.setTransparency(TransparencyAttrib.MAlpha)
            # background color doesn't show up anyway
            #base.setBackgroundColor(115 / 255, 115 / 255, 115 / 255)
        else:
            # circle
            # needs to be smaller
            self.scale_factor = 0.7
            color = Point3(0.9, 0.9, 0.9)
            self.make_circle(8 * self.scale_factor, (0, 0, 0), color)
        last_avt = self.avatar_pos.pop()
        self.last_avt = [i * self.scale_factor for i in last_avt]
        #base.cam.setPos(Point3(points[0], points[1], points[2]))
        #base.cam.setH(self.avatar_h.pop(0))
        #self.avatar_ht.pop(0)
        self.avatar_pt.pop()
        # get last time stamp (first of list) for avatar to calculate length of movie
        # add half a second buffer.
        movie_length = self.avatar_pt[0] + 0.5
        print('movie length', movie_length)
        self.avatar_node = []
        self.avatar_color = [1, 1, 1]
        self.alpha_circle_node = []

        self.fruitModel = {}
        # print('fruit', self.fruit_pos)

        for k, v in self.fruit_pos.iteritems():
            # print('i', i)
            # print('k', k)
            # print('v', v)
            if 'banana' in k:
                self.fruitModel[k] = self.base.loader.loadModel('models/ball')
                self.fruitModel[k].setScale(0.5)
                self.fruitModel[k].setColor(1, 1, 0, 1)
            elif 'cherry' in k:
                self.fruitModel[k] = self.base.loader.loadModel('models/ball')
                self.fruitModel[k].setScale(0.5)
                self.fruitModel[k].setColor(1, 0, 0, 1)
            # position = self.fruit_pos[k]['position'].pop(0)
            # print position
            heading = v['head']
            #print heading
            # self.fruitModel[k].setPos(
            #     Point3(float(position[0]), float(position[1]), float(position[2])))

            self.fruitModel[k].setH(float(heading))
            self.fruitModel[k].reparentTo(self.base.render)
            # assume all fruit stashed to start
            self.fruitModel[k].stash()
            if k in data.alpha:
                # print 'set alpha', data.alpha
                self.alpha_node_path = self.fruitModel[k]
                self.alpha_node_path.setTransparency(TransparencyAttrib.MAlpha)

        if record:
            self.movie_task = self.base.movie(movie_name, movie_length, 30, 'png', 4)

        #self.accept("space", base.taskMgr.add, [self.frame_loop, "frame_loop"])
        self.gameTask = taskMgr.add(self.frame_loop, "frame_loop")

        self.gameTask.last = 0         # Task time of the last frame

        # start the clock at 1 second before the official start so has time to load
        #self.gameTask.game_time = start_time - 100

    def frame_loop(self, task):
        #dt = task.time - task.last
        #task.last = task.time
        if self.avatar_pt:
            self.update_avt_p(task.time)
        else:
            # if we aren't moving the avatar anymore, assume done
            print 'done'
            return task.done
        if self.fruit_pos_ts:
            self.move_fruit(task.time)
        if self.fruit_status_ts:
            self.update_fruit(task.time)
        #if self.trial_mark and self.trial_mark[-1] <= task.time:
        #    self.move_fruit()
        return task.cont

    def update_avt_p(self, t_time):
        avt = LineSegs()
        avt.setThickness(5)
        avt.setColor(self.avatar_color[0], self.avatar_color[1], self.avatar_color[2])
        group_avatar = []
        while self.avatar_pt[-1] < t_time:
            group_avatar.append(self.avatar_pos.pop())
            # print points
            self.avatar_pt.pop()
            if not self.avatar_pt:
                break
        # print('positions', group_avatar)
        if group_avatar:
            avt.moveTo(self.last_avt[0], self.drawing_layer, self.last_avt[1])
            self.last_avt = [i * self.scale_factor for i in group_avatar[0]]
            for i in group_avatar:
                # print(i[0], i[1], i[2])
                pos = [j * self.scale_factor for j in i]
                avt.drawTo(pos[0], self.drawing_layer, pos[1])
            self.avatar_node.append(self.base.render.attachNewNode(avt.create()))

    def update_fruit(self, t_time):
        # print self.avatar_pos[-1]
        while self.fruit_status_ts[-1] < t_time:
            current_list = self.fruit_status.pop()
            # print current_list
            # print 'alpha', self.alpha
            # list goes: fruit name, what happens, how much
            if current_list[1] == 'alpha':
                self.alpha_node_path.setAlphaScale(float(current_list[2]))
                # if we are changing banana alpha to something less than 1, turn on circle
                # print 'alpha', float(current_list[2])
                if float(current_list[2]) == 0:
                    # print 'make circle for invisible'
                    self.block_done = True
                    self.make_circle(self.goal_radius[1], self.alpha_node_path.getPos())
                    self.avatar_color = [0, 1, 1]
                elif float(current_list[2]) < 1:
                    # print 'make circle for alpha'
                    self.make_circle(self.goal_radius[0], self.alpha_node_path.getPos())
            if current_list[1] == 'stash':
                if current_list[2] == 'True':
                    self.fruitModel[current_list[0]].stash()
                    # want if recall fruit turns off, circle goes away
                    # if another fruit turns off, and the next fruit to
                    # have an event is the alpha fruit, then
                    # circle appears
                    # this is almost certainly problematic for gobananas data
                    # with alpha fruit, maybe
                    # see what is next in line, if next is alpha and current is
                    # recall stashing,
                    # print self.fruit_status[-1]
                    # if stashing the recall fruit, erase the circle,
                    # return avatar line color to normal
                    if current_list[0] in self.alpha:
                        # print 'erase circle'
                        self.erase_circle()
                        self.avatar_color = [1, 1, 1]
                    # this is not true for newer data, but leaving it here for old data:
                    # no good way to tell the difference between an invisible recall fruit
                    # and a solid recall fruit, other than looking at timing. ugh. Brute force.
                    # can't use timing, self.fruit_status_ts[-1]
                    # if we are stashing a cherry, and the next thing to happen is alpha 1 and
                    # the next time stamp is not immediately, than must be invisible.
                    if self.use_alpha:
                        if current_list[0] not in self.alpha:
                            if self.fruit_status[-1][2] == '1':
                                # print 'next alpha?'
                                # print self.fruit_status_ts[-2] - self.fruit_status_ts[-1]
                                if self.fruit_status_ts[-2] - self.fruit_status_ts[-1] > 0.2:
                                    print 'should only reach here with old data'
                                    self.make_circle(self.goal_radius[1], self.alpha_node_path.getPos())
                                    self.avatar_color = [0, 1, 1]
                else:
                    # print 'unstash'
                    self.fruitModel[current_list[0]].unstash()
                    # print self.fruitModel[current_list[0]].isStashed()
            self.fruit_status_ts.pop()
            if not self.fruit_status_ts:
                break

    def move_fruit(self, t_time):
        # did not reverse, since pain in the ass, and likely not many
        while self.fruit_pos_ts[0][0] < t_time:
            # whenever we draw fruit, make the drawing layer closer to the camera,
            # since otherwise which lines are on top is a bit random
            self.drawing_layer -= 0.01
            #print 'layer', self.drawing_layer
            ts, fruit = self.fruit_pos_ts.pop(0)
            # print('current time stamp', ts)
            # 
            position = [float(i) * self.scale_factor * 0.5 for i in self.fruit_pos[fruit]['position'].pop(0)]
            # print('move fruit', fruit, position)
            self.fruitModel[fruit].setPos(
                Point3(position[0], 25, position[1]))
            # print 'fruit pos ts', self.fruit_pos_ts
            # print 'time', t_time
            if not self.fruit_pos_ts:
                break

    def make_circle(self, radius, center, color=None):
        circle = LineSegs()
        circle.setThickness(2.0)
        if not color:
            circle.setColor(1, 1, 0, 1)
        else:
            circle.setColor(color)
        angle_radians = radians(360)
        # print alpha_pos
        for i in range(50):
            a = angle_radians * i / 49
            y = radius * sin(a)
            x = radius * cos(a)
            circle.drawTo((x + center[0], self.drawing_layer, y + center[2]))
        if not color:
            self.alpha_circle_node.append(self.base.render.attachNewNode(circle.create()))
        else:
            self.base.render.attachNewNode(circle.create())
    def erase_circle(self):
        for i in self.alpha_circle_node:
            i.detachNode()
        if self.block_done:
            for i in self.avatar_node:
                i.detachNode()
            self.block_done = False