Ejemplo n.º 1
0
    def __init__(self, file, callback, sky=True):
        self.tracker = []
        self.x = None
        self.y = None
        self.z = None
        self.tick_length = 0
        self.tick = 0
        self.asteroids = AsteroidManager(self)
        self.cam = Camera()

        self._sky = sky

        self._program = None
        self.callback = callback
        self.programs = self._load_programs()
        self._parse(file)

        del self.callback  # So it can't be used after loading finishes

        self._time_accumulate = 0
        self._projection_matrix = self.cam.projection_matrix()

        for entity in self.tracker:
            entity.update()

        for name in ('planet', 'model', 'belt'):
            shader = self.activate_shader(name)
            shader.uniform_vec3('u_sun.ambient', 0.1, 0.1, 0.1)
            shader.uniform_vec3('u_sun.diffuse', 1, 1, 1)
            shader.uniform_vec3('u_sun.specular', 0.5, 0.5, 0.5)
            shader.uniform_vec3('u_sun.position', 0, 0, 0)
            shader.uniform_float('u_sun.intensity', 1)

        shader = self.activate_shader('clouds')
        shader.uniform_vec3('u_sun', 0, 0, 0)
        self.activate_shader(None)
Ejemplo n.º 2
0
    def load(self, *args, **kwargs):
        if self.loaded or self.__load_started:
            return

        self.__load_started = True

        def callback(phase, message, progress):
            self.draw_loading(phase, message, progress)
            self.flip()
            self.dispatch_events()

        start = clock()
        self.fps = 0
        self.world = World('world.json', callback, self.world_options)
        phase = 'Initializing game...'
        print phase
        callback(phase, '', 0)
        self.speed = INITIAL_SPEED
        self.keys = set()
        self.info = True
        self.debug = False
        self.orbit = True
        self.running = True
        self.moving = True
        self.info_precise = False
        self.atmosphere = True
        self.cloud = not texture.badcard

        self.tick = self.world.tick_length
        self.ticks = [1, 2, 5, 10, 20, 40, 60,                # Second range
                      120, 300, 600, 1200, 1800, 2700, 3600,  # Minute range
                      7200, 14400, 21600, 43200, 86400,       # Hour range
                      172800, 432000, 604800,                 # 2, 5, 7 days
                      1209600, 2592000,                       # 2 week, 1 month
                      5270400, 7884000, 15768000, 31536000,   # 2, 3, 6, 12 months
                      63072000, 157680000, 315360000,         # 2, 5, 10 years
                      630720000, 1576800000, 3153600000,      # 20, 50, 100 years
        ]
        self.__time_per_second_cache = None
        self.__time_per_second_value = None
        self.__time_accumulate = 0

        def speed_incrementer(object, increment):
            def incrementer():
                object.speed += increment

            return incrementer

        def attribute_toggler(object, attribute):
            getter = attrgetter(attribute)

            def toggler():
                setattr(object, attribute, not getter(object))

            return toggler

        def increment_tick():
            index = self.ticks.index(self.tick) + 1
            if index < len(self.ticks):
                self.tick = self.ticks[index]

        def decrement_tick():
            index = self.ticks.index(self.tick) - 1
            if index >= 0:
                self.tick = self.ticks[index]

        self.key_handler = {
            key.ESCAPE: pyglet.app.exit,
            key.NUM_ADD: speed_incrementer(self, 1),
            key.NUM_SUBTRACT: speed_incrementer(self, -1),
            key.NUM_MULTIPLY: speed_incrementer(self, 10),
            key.NUM_DIVIDE: speed_incrementer(self, -10),
            key.PAGEUP: speed_incrementer(self, 100),
            key.PAGEDOWN: speed_incrementer(self, -100),
            key.HOME: speed_incrementer(self, 1000),
            key.END: speed_incrementer(self, -1000),
            key.R: lambda: setattr(self.cam, 'roll', 0),
            key.I: attribute_toggler(self, 'info'),
            key.D: attribute_toggler(self, 'debug'),
            key.O: attribute_toggler(self, 'orbit'),
            key.P: attribute_toggler(self, 'info_precise'),
            key.C: attribute_toggler(self, 'cloud'),
            key.X: attribute_toggler(self, 'atmosphere'),
            key.ENTER: attribute_toggler(self, 'running'),
            #key.Q: self.screenshot,
            key.INSERT: increment_tick,
            key.DELETE: decrement_tick,
            key.SPACE: self.launch_meteor,
            key.E: lambda: self.set_exclusive_mouse(False),
            key.F: lambda: self.set_fullscreen(not self.fullscreen),
        }

        self.mouse_press_handler = {
            mouse.LEFT: self.launch_meteor,
            mouse.RIGHT: attribute_toggler(self, 'moving'),
        }

        self.label = pyglet.text.Label('', font_name='Consolas', font_size=12, x=10, y=self.height - 20,
                                       color=(255,) * 4, multiline=True, width=600)
        self.cam = Camera()

        self.exclusive = False

        glClearColor(0, 0, 0, 1)
        glClearDepth(1.0)

        glAlphaFunc(GL_GEQUAL, 0.2)
        glDepthFunc(GL_LEQUAL)
        glEnable(GL_DEPTH_TEST)
        glShadeModel(GL_SMOOTH)

        glMatrixMode(GL_MODELVIEW)

        glEnable(GL_LIGHTING)
        glEnable(GL_LIGHT0)
        glEnable(GL_LIGHT1)

        glEnable(GL_POLYGON_OFFSET_FILL)

        fv4 = GLfloat * 4

        glLightfv(GL_LIGHT0, GL_POSITION, fv4(.5, .5, 1, 0))
        glLightfv(GL_LIGHT0, GL_SPECULAR, fv4(.5, .5, 1, 1))
        glLightfv(GL_LIGHT0, GL_DIFFUSE,  fv4(1, 1, 1, 1))
        glLightfv(GL_LIGHT1, GL_POSITION, fv4(1, 0, .5, 0))
        glLightfv(GL_LIGHT1, GL_DIFFUSE,  fv4(.5, .5, .5, 1))
        glLightfv(GL_LIGHT1, GL_SPECULAR, fv4(1, 1, 1, 1))

        phase = 'Loading asteroids...'
        print phase

        def load_asteroids(files):
            for id, file in enumerate(files):
                callback(phase, 'Loading %s...' % file, float(id) / len(files))
                yield model_list(load_model(file), 5, 5, 5, (0, 0, 0))

        self.asteroid_ids = list(load_asteroids([r'asteroids/01.obj', r'asteroids/02.obj', r'asteroids/03.obj']))

        c = self.cam
        c.x, c.y, c.z = self.world.start
        c.pitch, c.yaw, c.roll = self.world.direction

        phase = 'Updating entities...'
        print phase
        callback(phase, '', 0)
        for entity in self.world.tracker:
            entity.update()

        print 'Loaded in %s seconds.' % (clock() - start)
        self.loaded = True
        pyglet.clock.schedule(self.update)
        self.on_resize(self.width, self.height)  # On resize handler does nothing unless it's loaded
Ejemplo n.º 3
0
class Applet(pyglet.window.Window):
    def __init__(self, *args, **kwargs):
        self.world_options = kwargs.pop('world_options', {})

        super(Applet, self).__init__(*args, **kwargs)
        texture.init()

        if hasattr(self.config, '_attribute_names'):
            info = ['  %-17s %s' % (key + ':', getattr(self.config, key))
                    for key in self.config._attribute_names]
            info = ['%-25s %-25s' % group for group in
                    izip_longest(info[::2], info[1::2], fillvalue='')]
            info = 'OpenGL configuration:\n' + '\n'.join(info)
        else:
            info = 'Unknown OpenGL configuration'

        self.loaded = False
        self.__load_started = False
        self._loading_phase = pyglet.text.Label(font_name='Consolas', font_size=20, x=10, y=self.height - 100,
                                                color=(255, 255, 255, 255), width=self.width - 20, halign='center',
                                                multiline=True, text='Punyverse is starting...')
        self._loading_label = pyglet.text.Label(font_name='Consolas', font_size=16, x=10, y=self.height - 200,
                                                color=(255, 255, 255, 255), width=self.width - 20, halign='center',
                                                multiline=True)
        self._info_label = pyglet.text.Label(font_name='Consolas', font_size=13, x=10, y=self.height - 320,
                                             color=(255, 255, 255, 255), width=self.width - 20,
                                             multiline=True, text=info)
        pyglet.clock.schedule_once(self.load, 0)

    def load(self, *args, **kwargs):
        if self.loaded or self.__load_started:
            return

        self.__load_started = True

        def callback(phase, message, progress):
            self.draw_loading(phase, message, progress)
            self.flip()
            self.dispatch_events()

        start = clock()
        self.fps = 0
        self.world = World('world.json', callback, self.world_options)
        phase = 'Initializing game...'
        print phase
        callback(phase, '', 0)
        self.speed = INITIAL_SPEED
        self.keys = set()
        self.info = True
        self.debug = False
        self.orbit = True
        self.running = True
        self.moving = True
        self.info_precise = False
        self.atmosphere = True
        self.cloud = not texture.badcard

        self.tick = self.world.tick_length
        self.ticks = [1, 2, 5, 10, 20, 40, 60,                # Second range
                      120, 300, 600, 1200, 1800, 2700, 3600,  # Minute range
                      7200, 14400, 21600, 43200, 86400,       # Hour range
                      172800, 432000, 604800,                 # 2, 5, 7 days
                      1209600, 2592000,                       # 2 week, 1 month
                      5270400, 7884000, 15768000, 31536000,   # 2, 3, 6, 12 months
                      63072000, 157680000, 315360000,         # 2, 5, 10 years
                      630720000, 1576800000, 3153600000,      # 20, 50, 100 years
        ]
        self.__time_per_second_cache = None
        self.__time_per_second_value = None
        self.__time_accumulate = 0

        def speed_incrementer(object, increment):
            def incrementer():
                object.speed += increment

            return incrementer

        def attribute_toggler(object, attribute):
            getter = attrgetter(attribute)

            def toggler():
                setattr(object, attribute, not getter(object))

            return toggler

        def increment_tick():
            index = self.ticks.index(self.tick) + 1
            if index < len(self.ticks):
                self.tick = self.ticks[index]

        def decrement_tick():
            index = self.ticks.index(self.tick) - 1
            if index >= 0:
                self.tick = self.ticks[index]

        self.key_handler = {
            key.ESCAPE: pyglet.app.exit,
            key.NUM_ADD: speed_incrementer(self, 1),
            key.NUM_SUBTRACT: speed_incrementer(self, -1),
            key.NUM_MULTIPLY: speed_incrementer(self, 10),
            key.NUM_DIVIDE: speed_incrementer(self, -10),
            key.PAGEUP: speed_incrementer(self, 100),
            key.PAGEDOWN: speed_incrementer(self, -100),
            key.HOME: speed_incrementer(self, 1000),
            key.END: speed_incrementer(self, -1000),
            key.R: lambda: setattr(self.cam, 'roll', 0),
            key.I: attribute_toggler(self, 'info'),
            key.D: attribute_toggler(self, 'debug'),
            key.O: attribute_toggler(self, 'orbit'),
            key.P: attribute_toggler(self, 'info_precise'),
            key.C: attribute_toggler(self, 'cloud'),
            key.X: attribute_toggler(self, 'atmosphere'),
            key.ENTER: attribute_toggler(self, 'running'),
            #key.Q: self.screenshot,
            key.INSERT: increment_tick,
            key.DELETE: decrement_tick,
            key.SPACE: self.launch_meteor,
            key.E: lambda: self.set_exclusive_mouse(False),
            key.F: lambda: self.set_fullscreen(not self.fullscreen),
        }

        self.mouse_press_handler = {
            mouse.LEFT: self.launch_meteor,
            mouse.RIGHT: attribute_toggler(self, 'moving'),
        }

        self.label = pyglet.text.Label('', font_name='Consolas', font_size=12, x=10, y=self.height - 20,
                                       color=(255,) * 4, multiline=True, width=600)
        self.cam = Camera()

        self.exclusive = False

        glClearColor(0, 0, 0, 1)
        glClearDepth(1.0)

        glAlphaFunc(GL_GEQUAL, 0.2)
        glDepthFunc(GL_LEQUAL)
        glEnable(GL_DEPTH_TEST)
        glShadeModel(GL_SMOOTH)

        glMatrixMode(GL_MODELVIEW)

        glEnable(GL_LIGHTING)
        glEnable(GL_LIGHT0)
        glEnable(GL_LIGHT1)

        glEnable(GL_POLYGON_OFFSET_FILL)

        fv4 = GLfloat * 4

        glLightfv(GL_LIGHT0, GL_POSITION, fv4(.5, .5, 1, 0))
        glLightfv(GL_LIGHT0, GL_SPECULAR, fv4(.5, .5, 1, 1))
        glLightfv(GL_LIGHT0, GL_DIFFUSE,  fv4(1, 1, 1, 1))
        glLightfv(GL_LIGHT1, GL_POSITION, fv4(1, 0, .5, 0))
        glLightfv(GL_LIGHT1, GL_DIFFUSE,  fv4(.5, .5, .5, 1))
        glLightfv(GL_LIGHT1, GL_SPECULAR, fv4(1, 1, 1, 1))

        phase = 'Loading asteroids...'
        print phase

        def load_asteroids(files):
            for id, file in enumerate(files):
                callback(phase, 'Loading %s...' % file, float(id) / len(files))
                yield model_list(load_model(file), 5, 5, 5, (0, 0, 0))

        self.asteroid_ids = list(load_asteroids([r'asteroids/01.obj', r'asteroids/02.obj', r'asteroids/03.obj']))

        c = self.cam
        c.x, c.y, c.z = self.world.start
        c.pitch, c.yaw, c.roll = self.world.direction

        phase = 'Updating entities...'
        print phase
        callback(phase, '', 0)
        for entity in self.world.tracker:
            entity.update()

        print 'Loaded in %s seconds.' % (clock() - start)
        self.loaded = True
        pyglet.clock.schedule(self.update)
        self.on_resize(self.width, self.height)  # On resize handler does nothing unless it's loaded

    def screenshot(self):
        image = pyglet.image.get_buffer_manager().get_color_buffer()
        if hasattr(self, '_hwnd') and not self.modifiers & key.MOD_CTRL:
            from ctypes import windll, cdll
            from PIL import Image
            import tempfile
            CF_BITMAP = 2
            
            image = Image.fromstring(image.format, (image.width, image.height), image.get_image_data().data)
            image = image.convert('RGB').transpose(Image.FLIP_TOP_BOTTOM)
            fd, filename = tempfile.mkstemp('.bmp')
            try:
                with os.fdopen(fd, 'w') as file:
                    image.save(file, 'BMP')
                if not isinstance(filename, unicode):
                    filename = filename.decode('mbcs')
                image = windll.user32.LoadImageW(None, filename, 0, 0, 0, 0x10)
                windll.user32.OpenClipboard(self._hwnd)
                windll.user32.EmptyClipboard()
                windll.user32.SetClipboardData(CF_BITMAP, image)
                windll.user32.CloseClipboard()
            finally:
                os.remove(filename)
        else:
            image.save(os.path.expanduser('~/punyverse.png'))

    def set_exclusive_mouse(self, exclusive):
        super(Applet, self).set_exclusive_mouse(exclusive)
        self.exclusive = exclusive

    def launch_meteor(self):
        c = self.cam
        dx, dy, dz = c.direction()
        speed = abs(self.speed) * 1.1 + 5
        dx *= speed
        dy *= speed
        dz *= speed
        self.world.tracker.append(Asteroid(random.choice(self.asteroid_ids), (c.x, c.y - 3, c.z + 5),
                                           direction=(dx, dy, dz)))

    def on_mouse_press(self, x, y, button, modifiers):
        self.modifiers = modifiers
        if not self.loaded:
            return

        if not self.exclusive:
            self.set_exclusive_mouse(True)
        else:
            if button in self.mouse_press_handler:
                self.mouse_press_handler[button]()

    def on_mouse_motion(self, x, y, dx, dy):
        if not self.loaded:
            return

        if self.exclusive:  # Only handle camera movement if mouse is grabbed
            self.cam.mouse_move(dx * MOUSE_SENSITIVITY, dy * MOUSE_SENSITIVITY)

    def on_key_press(self, symbol, modifiers):
        self.modifiers = modifiers
        if symbol == key.Q:
            self.screenshot()
        if not self.loaded:
            return
        if self.exclusive:  # Only handle keyboard input if mouse is grabbed
            if symbol in self.key_handler:
                self.key_handler[symbol]()
            else:
                self.keys.add(symbol)

    def on_key_release(self, symbol, modifiers):
        if not self.loaded:
            return

        if symbol in self.keys:
            self.keys.remove(symbol)

    def on_resize(self, width, height):
        if not self.loaded:
            return super(Applet, self).on_resize(width, height)

        height = max(height, 1) # Prevent / by 0
        self.label.y = height - 20
        glViewport(0, 0, width, height)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        # A field of view of 45
        gluPerspective(45.0, width / float(height), 1, 50000000.0)
        glMatrixMode(GL_MODELVIEW)

    def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
        if not self.loaded:
            return

        self.speed += scroll_y * 50 + scroll_x * 500

    def get_time_per_second(self):
        if self.__time_per_second_cache == self.tick:
            return self.__time_per_second_value
        time = self.tick + .0
        unit = 'seconds'
        for size, name in ((60, 'minutes'), (60, 'hours'), (24, 'days'), (365, 'years')):
            if time < size:
                break
            time /= size
            unit = name
        result = '%s %s' % (round(time, 1), unit)
        self.__time_per_second_cache = self.tick
        self.__time_per_second_value = result
        return result

    def update(self, dt):
        c = self.cam

        if self.exclusive:
            if key.A in self.keys:
                c.roll += 4 * dt * 10
            if key.S in self.keys:
                c.roll -= 4 * dt * 10
            if self.moving:
                c.move(self.speed * 10 * dt)

        if self.running:
            delta = self.tick * dt
            update = int(delta + self.__time_accumulate + 0.5)
            if update:
                self.__time_accumulate = 0
                self.world.tick += update
                for entity in self.world.tracker:
                    entity.update()
                    collision = entity.collides(c.x, c.y, c.z)
                    if collision:
                        self.speed *= -1
                        c.move(self.speed * 12 * dt)
            else:
                self.__time_accumulate += delta

    def draw_loading(self, phase=None, message=None, progress=None):
        glClear(GL_COLOR_BUFFER_BIT)
        glLoadIdentity()
        if phase is not None:
            self._loading_phase.text = phase
        if message is not None:
            self._loading_label.text = message
        self._loading_phase.draw()
        self._loading_label.draw()
        if progress is not None:
            progress_bar(10, self.height - 240, self.width - 20, 50, progress)
        self._info_label.draw()

    def on_draw(self, glMatrixBuffer=GLfloat * 16):
        if not self.loaded:
            return self.draw_loading()

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()

        c = self.cam

        x, y, z = c.x, c.y, c.z
        glRotatef(c.pitch, 1, 0, 0)
        glRotatef(c.yaw, 0, 1, 0)
        glRotatef(c.roll, 0, 0, 1)
        glTranslatef(-x, -y, -z)

        glEnable(GL_LIGHTING)
        world = self.world
        get_distance = entity_distance(x, y, z)
        if x != world.x or y != world.y or z != world.z:
            world.tracker.sort(key=get_distance, reverse=True)
            world.tracker.sort(key=attrgetter('background'), reverse=True)
            world.x, world.y, world.z = x, y, z

        for entity in world.tracker:
            x, y, z = entity.location
            pitch, yaw, roll = entity.rotation

            with glMatrix(), glRestore(GL_CURRENT_BIT):
                glTranslatef(x, y, z)
                glRotatef(pitch, 1, 0, 0)
                glRotatef(yaw, 0, 1, 0)
                glRotatef(roll, 0, 0, 1)
                glCallList(entity.id)
                if self.debug:
                    with glMatrix(), glRestore(GL_ENABLE_BIT | GL_POLYGON_BIT | GL_LINE_BIT):
                        glLineWidth(0.25)
                        glPolygonOffset(1, 1)
                        glDisable(GL_LIGHTING)
                        glDisable(GL_TEXTURE_2D)
                        glColor3f(0, 1, 0)
                        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
                        glCallList(entity.id)

            has_corona = hasattr(entity, 'corona') and entity.corona
            has_atmosphere = hasattr(entity, 'atmosphere') and entity.atmosphere
            if self.atmosphere and (has_corona or has_atmosphere):
                with glMatrix(), glRestore(GL_ENABLE_BIT):
                    x0, y0, z0 = entity.location
                    glTranslatef(x0, y0, z0)
                    matrix = glMatrixBuffer()
                    glGetFloatv(GL_MODELVIEW_MATRIX, matrix)
                    matrix[0: 3] = [1, 0, 0]
                    matrix[4: 7] = [0, 1, 0]
                    matrix[8:11] = [0, 0, 1]
                    glLoadMatrixf(matrix)
                    glEnable(GL_BLEND)
                    if has_atmosphere:
                        glCallList(entity.atmosphere)
                    if has_corona:
                        x, y, z = c.direction()
                        glTranslatef(-x, -y, -z)
                        glCallList(entity.corona)

            if self.cloud and hasattr(entity, 'cloudmap') and entity.cloudmap:
                with glMatrix(), glRestore(GL_ENABLE_BIT):
                    glEnable(GL_BLEND)
                    glEnable(GL_ALPHA_TEST)
                    glTranslatef(*entity.location)
                    pitch, yaw, roll = entity.rotation
                    glRotatef(pitch, 1, 0, 0)
                    glRotatef(yaw, 0, 1, 0)
                    glRotatef(roll, 0, 0, 1)
                    glCallList(entity.cloudmap)

            if self.orbit and hasattr(entity, 'get_orbit') and hasattr(entity, 'parent'):
                parent = entity.parent
                distance = get_distance(parent)
                if distance < parent.orbit_show:
                    with glMatrix(), glRestore(GL_ENABLE_BIT | GL_LINE_BIT | GL_CURRENT_BIT):
                        glTranslatef(*entity.parent.location)
                        glDisable(GL_LIGHTING)
                        solid = distance < parent.orbit_opaque
                        glColor4f(1, 1, 1, 1 if solid else
                                  (1 - (distance - parent.orbit_opaque) / parent.orbit_blend))
                        if not solid:
                            glEnable(GL_BLEND)
                        glLineWidth(1)
                        glCallList(entity.get_orbit())

        glColor4f(1, 1, 1, 1)
        glDisable(GL_TEXTURE_2D)

        width, height = self.get_size()

        if self.info:
            ortho(width, height)
            if self.info_precise:
                info = ('%d FPS @ (x=%.2f, y=%.2f, z=%.2f) @ %s, %s/s\n'
                        'Direction(pitch=%.2f, yaw=%.2f, roll=%.2f)\nTick: %d' %
                        (pyglet.clock.get_fps(), c.x, c.y, c.z, self.speed, self.get_time_per_second(),
                         c.pitch, c.yaw, c.roll, self.world.tick))
            else:
                info = ('%d FPS @ (x=%.2f, y=%.2f, z=%.2f) @ %s, %s/s\n' %
                        (pyglet.clock.get_fps(), c.x, c.y, c.z, self.speed, self.get_time_per_second()))
            self.label.text = info
            self.label.draw()
            with glRestore(GL_CURRENT_BIT | GL_LINE_BIT):
                glLineWidth(2)
                cx, cy = width / 2, height / 2
                glColor4f(0, 1, 0, 1)
                circle(10, 20, (cx, cy))
                glPopAttrib()
            frustrum()
Ejemplo n.º 4
0
class World(object):
    PROGRAMS = {
        'sky': ('sky.vertex.glsl', 'sky.fragment.glsl'),
        'planet': ('planet.vertex.glsl', 'planet.fragment.glsl'),
        'clouds': ('clouds.vertex.glsl', 'clouds.fragment.glsl'),
        'star': ('star.vertex.glsl', 'star.fragment.glsl'),
        'ring': ('ring.vertex.glsl', 'ring.fragment.glsl'),
        'atmosphere': ('atmosphere.vertex.glsl', 'atmosphere.fragment.glsl'),
        'text': ('text.vertex.glsl', 'text.fragment.glsl'),
        'line': ('line.vertex.glsl', 'line.fragment.glsl'),
        'model': ('model.vertex.glsl', 'model.fragment.glsl'),
        'belt': ('belt.vertex.glsl', 'model.fragment.glsl'),
    }

    def __init__(self, file, callback, sky=True):
        self.tracker = []
        self.x = None
        self.y = None
        self.z = None
        self.tick_length = 0
        self.tick = 0
        self.asteroids = AsteroidManager(self)
        self.cam = Camera()

        self._sky = sky

        self._program = None
        self.callback = callback
        self.programs = self._load_programs()
        self._parse(file)

        del self.callback  # So it can't be used after loading finishes

        self._time_accumulate = 0
        self._projection_matrix = self.cam.projection_matrix()

        for entity in self.tracker:
            entity.update()

        for name in ('planet', 'model', 'belt'):
            shader = self.activate_shader(name)
            shader.uniform_vec3('u_sun.ambient', 0.1, 0.1, 0.1)
            shader.uniform_vec3('u_sun.diffuse', 1, 1, 1)
            shader.uniform_vec3('u_sun.specular', 0.5, 0.5, 0.5)
            shader.uniform_vec3('u_sun.position', 0, 0, 0)
            shader.uniform_float('u_sun.intensity', 1)

        shader = self.activate_shader('clouds')
        shader.uniform_vec3('u_sun', 0, 0, 0)
        self.activate_shader(None)

    def _load_programs(self):
        programs = {}
        count = len(self.PROGRAMS)
        for i, (name, (vertex, fragment)) in enumerate(six.iteritems(self.PROGRAMS)):
            self.callback('Loading shaders (%d of %d)...' % (i, count),
                          'Loading shader "%s" (%s, %s).' % (name, vertex, fragment), i / count)
            programs[name] = Program(vertex, fragment)
        return programs

    def evaluate(self, value):
        return eval(str(value), {'__builtins__': None}, self._context)

    @property
    def length(self):
        return self._length

    @property
    def au(self):
        return self._au

    def _parse(self, file):
        self.callback('Parsing configuration...', 'Loading configuration file...', 0)
        with open(os.path.join(os.path.dirname(__file__), file)) as f:
            root = json.load(f, object_pairs_hook=OrderedDict)
        self._au = root.get('au', 2000)
        self._length = root.get('length', 4320)
        self._context = {'AU': self._au, 'TEXTURE': texture.max_texture_size(), 'KM': 1.0 / self._length}

        self.tick_length = root.get('tick', 4320)  # How many second is a tick?

        # Need to know how many objects are being loaded
        self._objects = 0
        self._current_object = 0

        def count_objects(bodies):
            for body in six.itervalues(bodies):
                self._objects += 1
                count_objects(body.get('satellites', {}))
        count_objects(root['bodies'])

        if 'start' in root:
            info = root['start']
            self.cam.x = self.evaluate(info.get('x', 0))
            self.cam.y = self.evaluate(info.get('y', 0))
            self.cam.z = self.evaluate(info.get('z', 0))
            self.cam.pitch = self.evaluate(info.get('pitch', 0))
            self.cam.yaw = self.evaluate(info.get('yaw', 0))
            self.cam.roll = self.evaluate(info.get('roll', 0))

        for planet, info in six.iteritems(root['bodies']):
            self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects),
                          'Loading %s.' % planet, self._current_object / self._objects)
            self._body(planet, info)
            self._current_object += 1

        if 'belts' in root:
            belt_count = len(root['belts'])
            for i, (name, info) in enumerate(six.iteritems(root['belts']), 1):
                self.callback('Loading belts (%d of %d)...' % (i, belt_count),
                              'Loading %s.' % name, i / belt_count)
                self.tracker.append(Belt(name, self, info))

        if 'sky' in root and self._sky:
            def callback(index, file):
                self.callback('Loading sky...', 'Loading %s.' % file, index / 6)
            self.tracker.append(Sky(self, root['sky'], callback))

        if 'asteroids' in root:
            asteroids = root['asteroids']
            for i, file in enumerate(asteroids):
                self.callback('Loading asteroids...', 'Loading %s...' % file, i / len(asteroids))
                self.asteroids.load(file)

        self.font_tex = load_alpha_mask(root['font'], clamp=True)

    def _body(self, name, info, parent=None):
        if 'texture' in info:
            body = SphericalBody(name, self, info, parent)
        elif 'model' in info:
            body = ModelBody(name, self, info, parent)
        else:
            raise ValueError('Nothing to load for %s.' % name)

        if parent:
            parent.satellites.append(body)
        else:
            self.tracker.append(body)

        for satellite, info in six.iteritems(info.get('satellites', {})):
            self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects),
                          'Loading %s, satellite of %s.' % (satellite, name), self._current_object / self._objects)
            self._body(satellite, info, body)
            self._current_object += 1

    def spawn_asteroid(self):
        if self.asteroids:
            c = self.cam
            dx, dy, dz = c.direction()
            speed = abs(self.cam.speed) * 1.1 + 5
            self.tracker.append(self.asteroids.new((c.x, c.y - 3, c.z + 5), (dx * speed, dy * speed, dz * speed)))

    def update(self, dt, move, tick):
        c = self.cam
        c.update(dt, move)
        self.vp_matrix = None

        if tick:
            delta = self.tick_length * dt
            update = int(delta + self._time_accumulate + 0.5)
            if update:
                self._time_accumulate = 0
                self.tick += update

                for entity in self.tracker:
                    entity.update()
                    collision = entity.collides(c.x, c.y, c.z)
                    if collision:
                        c.speed *= -1
                        c.move(c.speed * 12 * dt)
            else:
                self._time_accumulate += delta

    def view_matrix(self):
        return self.cam.view_matrix

    def projection_matrix(self):
        return self._projection_matrix

    @cached_property
    def vp_matrix(self):
        return self._projection_matrix * self.cam.view_matrix

    def resize(self, width, height):
        self.cam.aspect = width / max(height, 1)
        self._projection_matrix = self.cam.projection_matrix()
        self.vp_matrix = None

    def activate_shader(self, name):
        program = None
        if self._program != name:
            if name is None:
                glUseProgram(0)
            else:
                program = self.programs[name]
                glUseProgram(program.program)
            self._program = name
        elif self._program is not None:
            program = self.programs[self._program]
        return program