from collections import OrderedDict import os.path try: import json except ImportError: try: import simplejson as json except ImportError: raise SystemExit('No JSON module found') try: from punyverse._model import model_list, load_model except ImportError: from punyverse.model import model_list, load_model from punyverse.glgeom import * from punyverse.entity import * from punyverse.texture import * from punyverse import texture from math import pi, sqrt G = 6.67384e-11 # Gravitation Constant def get_best_texture(info, optional=False, loader=load_texture): cheap = False skip = False texture = None if isinstance(info, list): for item in info: if isinstance(item, list): if len(item) == 4: cheap = True texture = item break continue try: texture = loader(item) except ValueError: pass else: break else: cheap = True texture = [1, 1, 1, 1] else: try: texture = loader(info) except ValueError: if optional: skip = True else: cheap = True texture = [1, 1, 1, 1] return cheap, skip, texture def load_world(file, callback=lambda message, completion: None): return World(file, callback) class World(object): def __init__(self, file, callback): self.tracker = [] self.start = (0, 0, 0) self.direction = (0, 0, 0) self.x = None self.y = None self.z = None self.tick_length = 1 self.tick = 0 self.callback = callback self._phase = 'Parsing configuration...' self._parse(file) del self.callback # So it can't be used after loading finishes def _eval(self, value): return eval(str(value), {'__builtins__': None}, self._context) def _parse(self, file): self.callback(self._phase, '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, 'KM': 1.0 / self._length} tick = root.get('tick', 4320) # How many second is a tick? self.tick_length = tick # Need to know how many objects are being loaded self._objects = 0 self._current_object = 0 def count_objects(bodies): for body in bodies.itervalues(): self._objects += 1 count_objects(body.get('satellites', {})) count_objects(root['bodies']) print self._objects, 'objects to be loaded...' if 'start' in root: info = root['start'] x = self._eval(info.get('x', 0)) y = self._eval(info.get('y', 0)) z = self._eval(info.get('z', 0)) pitch = self._eval(info.get('pitch', 0)) yaw = self._eval(info.get('yaw', 0)) roll = self._eval(info.get('roll', 0)) self.start = (x, y, z) self.direction = (pitch, yaw, roll) for planet, info in root['bodies'].iteritems(): message = 'Loading %s.' % planet print message self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects), message, float(self._current_object) / self._objects) self._body(planet, info) self._current_object += 1 if 'belts' in root: self._phase = 'Loading belts...' self._current_object = 0 for name, info in root['belts'].iteritems(): message = 'Loading %s.' % name print message self.callback(self._phase, message, float(self._current_object) / len(root['belts'])) self._belt(name, info) def _belt(self, name, info): x = self._eval(info.get('x', 0)) y = self._eval(info.get('y', 0)) z = self._eval(info.get('z', 0)) radius = self._eval(info.get('radius', 0)) cross = self._eval(info.get('cross', 0)) count = int(self._eval(info.get('count', 0))) scale = info.get('scale', 1) longitude = info.get('longitude', 0) inclination = info.get('inclination', 0) argument = info.get('argument', 0) rotation = info.get('period', 31536000) theta = 360 / (rotation + .0) if rotation else 0 models = info['model'] if not isinstance(models, list): models = [models] objects = [] for model in models: objects.append(model_list(load_model(model), info.get('sx', scale), info.get('sy', scale), info.get('sz', scale), (0, 0, 0))) self.tracker.append(Belt(compile(belt, radius, cross, objects, count), (x, y, z), (inclination, longitude, argument), rotation_angle=theta, world=self)) def _body(self, name, info, parent=None): lighting = info.get('lighting', True) x = self._eval(info.get('x', 0)) y = self._eval(info.get('y', 0)) z = self._eval(info.get('z', 0)) pitch = self._eval(info.get('pitch', 0)) yaw = self._eval(info.get('yaw', 0)) roll = self._eval(info.get('roll', 0)) rotation = self._eval(info.get('rotation', 86400)) radius = self._eval(info.get('radius', self._length)) / self._length background = info.get('background', False) orbit_distance = self._eval(info.get('orbit_distance', self._au)) division = info.get('division', max(min(int(radius / 8), 60), 10)) if 'texture' in info: cheap, skip, texture = get_best_texture(info['texture'], optional=info.get('optional', False)) if skip: return if cheap: object_id = compile(colourball, radius, division, division, texture) else: if 'normal' in info: object_id = compile(normal_sphere, radius, division, texture, info['normal'], lighting=lighting) else: object_id = compile(sphere, radius, division, division, texture, lighting=lighting) elif 'model' in info: scale = info.get('scale', 1) object_id = model_list(load_model(info['model']), info.get('sx', scale), info.get('sy', scale), info.get('sz', scale), (0, 0, 0)) else: print 'Nothing to load for %s.' % name return params = {'world': self, 'orbit_distance': orbit_distance} if parent is None: type = Body else: x, y, z = parent.location distance = self._eval(info.get('distance', 100)) # Semi-major axis when actually displayed in virtual space sma = self._eval(info.get('sma', distance)) # Semi-major axis used to calculate orbital speed if hasattr(parent, 'mass') and parent.mass is not None: period = 2 * pi * sqrt((sma * 1000) ** 3 / (G * parent.mass)) speed = 360 / (period + .0) if not rotation: # Rotation = 0 assumes tidal lock rotation = period else: speed = info.get('orbit_speed', 1) type = Satellite params.update(parent=parent, orbit_speed=speed, distance=distance / self._length, eccentricity=info.get('eccentricity', 0), inclination=info.get('inclination', 0), longitude=info.get('longitude', 0), argument=info.get('argument', 0)) if 'mass' in info: params['mass'] = info['mass'] atmosphere_id = 0 cloudmap_id = 0 corona_id = 0 if 'atmosphere' in info: atmosphere_data = info['atmosphere'] atm_size = self._eval(atmosphere_data.get('diffuse_size', None)) atm_texture = atmosphere_data.get('diffuse_texture', None) cloud_texture = atmosphere_data.get('cloud_texture', None) corona_texture = atmosphere_data.get('corona_texture', None) if cloud_texture is not None: cheap, _, cloud_texture = get_best_texture(cloud_texture, loader=load_clouds) if not cheap: cloudmap_id = compile(sphere, radius + 2, division, division, cloud_texture, lighting=False) if corona_texture is not None: cheap, _, corona = get_best_texture(corona_texture) if not cheap: corona_size = atmosphere_data.get('corona_size', radius / 2) corona_division = atmosphere_data.get('corona_division', 100) corona_ratio = atmosphere_data.get('corona_ratio', 0.5) corona_id = compile(flare, radius, radius + corona_size, corona_division, corona_ratio, corona) if atm_texture is not None: cheap, _, atm_texture = get_best_texture(atm_texture) if not cheap: atmosphere_id = compile(disk, radius, radius + atm_size, 30, atm_texture) theta = 360 / (rotation + .0) if rotation else 0 object = type(object_id, (x, y, z), (pitch, yaw, roll), rotation_angle=theta, atmosphere=atmosphere_id, cloudmap=cloudmap_id, background=background, corona=corona_id, **params) self.tracker.append(object) if 'ring' in info: ring_data = info['ring'] texture = ring_data.get('texture', None) distance = self._eval(ring_data.get('distance', radius * 1.2)) size = self._eval(ring_data.get('size', radius / 2)) pitch = self._eval(ring_data.get('pitch', pitch)) yaw = self._eval(ring_data.get('yaw', yaw)) roll = self._eval(ring_data.get('roll', roll)) cheap, _, texture = get_best_texture(texture) if not cheap: self.tracker.append( type(compile(disk, distance, distance + size, 30, texture), (x, y, z), (pitch, yaw, roll), **params)) for satellite, info in info.get('satellites', {}).iteritems(): message = 'Loading %s, satellite of %s.' % (satellite, name) print message self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects), message, float(self._current_object) / self._objects) self._body(satellite, info, object) self._current_object += 1