diff --git a/punyverse/camera.py b/punyverse/camera.py index a1eb0db..29abf9e 100644 --- a/punyverse/camera.py +++ b/punyverse/camera.py @@ -1,4 +1,4 @@ -from math import sin, cos, radians +from math import sin, cos, radians, hypot class Camera(object): @@ -39,3 +39,6 @@ class Camera(object): dx = cos(radians(self.yaw - 90)) * m dz = sin(radians(self.yaw - 90)) * m return dx, dy, dz + + def distance(self, x, y, z): + return hypot(hypot(x - self.x, y - self.y), z - self.z) diff --git a/punyverse/entity.py b/punyverse/entity.py index 5bcdb70..f93be59 100644 --- a/punyverse/entity.py +++ b/punyverse/entity.py @@ -1,113 +1,189 @@ -from math import sqrt +import random +from math import sqrt, pi from pyglet.gl import * +# noinspection PyUnresolvedReferences +from six.moves import range +from punyverse.glgeom import compile, sphere, flare, disk, glMatrix, glRestore, belt from punyverse.orbit import KeplerOrbit +from punyverse.texture import get_best_texture, load_clouds + +try: + from punyverse._model import model_list, load_model +except ImportError: + from punyverse.model import model_list, load_model + +G = 6.67384e-11 # Gravitation Constant class Entity(object): - def __init__(self, id, location, rotation=(0, 0, 0), direction=(0, 0, 0), background=False): - self.id = id + def __init__(self, name, location, rotation=(0, 0, 0), direction=(0, 0, 0)): + self.name = name self.location = location self.rotation = rotation self.direction = direction - self.background = background def update(self): x, y, z = self.location dx, dy, dz = self.direction self.location = x + dx, y + dy, z + dz - + def collides(self, x, y, z): return False + def draw(self, cam, options): + raise NotImplementedError() + class Asteroid(Entity): - def __init__(self, *args, **kwargs): - super(Asteroid, self).__init__(*args, **kwargs) + asteroid_ids = [] + + @classmethod + def load_asteroid(cls, file): + cls.asteroid_ids.append(model_list(load_model(file), 5, 5, 5, (0, 0, 0))) + + def __init__(self, location, direction): + super(Asteroid, self).__init__('Asteroid', location, direction=direction) + self.asteroid_id = random.choice(self.asteroid_ids) def update(self): super(Asteroid, self).update() + rx, ry, rz = self.rotation # Increment all axis to 'spin' self.rotation = rx + 1, ry + 1, rz + 1 + def draw(self, cam, options): + with glMatrix(self.location, self.rotation), glRestore(GL_CURRENT_BIT): + glCallList(self.asteroid_id) + class Belt(Entity): - def __init__(self, *args, **kwargs): - self.rotation_angle = kwargs.pop('rotation_angle', 5) - self.world = kwargs.pop('world') - super(Belt, self).__init__(*args, **kwargs) + def __init__(self, name, world, info): + self.world = world + + x = world.evaluate(info.get('x', 0)) + y = world.evaluate(info.get('y', 0)) + z = world.evaluate(info.get('z', 0)) + radius = world.evaluate(info.get('radius', 0)) + cross = world.evaluate(info.get('cross', 0)) + count = int(world.evaluate(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) + models = info['model'] + if not isinstance(models, list): + models = [models] + + objects = [model_list(load_model(model), info.get('sx', scale), info.get('sy', scale), + info.get('sz', scale), (0, 0, 0)) for model in models] + + self.belt_id = compile(belt, radius, cross, objects, count) + self.rotation_angle = 360.0 / rotation if rotation else 0 + + super(Belt, self).__init__(name, (x, y, z), (inclination, longitude, argument)) def update(self): super(Belt, self).update() pitch, yaw, roll = self.rotation self.rotation = pitch, self.world.tick * self.rotation_angle % 360, roll + def draw(self, cam, options): + with glMatrix(self.location, self.rotation), glRestore(GL_CURRENT_BIT): + glCallList(self.belt_id) + + +class Sky(Entity): + def __init__(self, world, info): + pitch = world.evaluate(info.get('pitch', 0)) + yaw = world.evaluate(info.get('yaw', 0)) + roll = world.evaluate(info.get('roll', 0)) + + super(Sky, self).__init__('Sky', (0, 0, 0), (pitch, yaw, roll)) + self.world = world + + texture = get_best_texture(info['texture']) + division = info.get('division', 30) + self.sky_id = compile(sphere, info.get('radius', 1000000), division, division, texture, + inside=True, lighting=False) + + def draw(self, cam, options): + with glMatrix((-cam.x, -cam.y, -cam.z), self.rotation), glRestore(GL_CURRENT_BIT): + glCallList(self.sky_id) + class Body(Entity): - def __init__(self, *args, **kwargs): - self.rotation_angle = kwargs.pop('rotation_angle', 5) - self.atmosphere = kwargs.pop('atmosphere', 0) - self.cloudmap = kwargs.pop('cloudmap', 0) - self.corona = kwargs.pop('corona', 0) - self.last_tick = 0 - self.mass = kwargs.pop('mass', None) - self.radius = kwargs.pop('radius', None) - self.world = kwargs.pop('world') - orbit_distance = kwargs.pop('orbit_distance', 40000) + .0 + def __init__(self, name, world, info, parent=None): + self.world = world + self.parent = parent + self.satellites = [] + + x = world.evaluate(info.get('x', 0)) + y = world.evaluate(info.get('y', 0)) + z = world.evaluate(info.get('z', 0)) + pitch = world.evaluate(info.get('pitch', 0)) + yaw = world.evaluate(info.get('yaw', 0)) + roll = world.evaluate(info.get('roll', 0)) + rotation = world.evaluate(info.get('rotation', 86400)) + + self.mass = info.get('mass') + + orbit_distance = float(world.evaluate(info.get('orbit_distance', world.au))) self.orbit_show = orbit_distance * 1.25 self.orbit_blend = orbit_distance / 4 self.orbit_opaque = orbit_distance - super(Body, self).__init__(*args, **kwargs) - self.initial_roll = self.rotation[2] - def update(self): - super(Body, self).update() + super(Body, self).__init__(name, (x, y, z), (pitch, yaw, roll)) + self.initial_roll = roll - if self.last_tick != self.world.tick: - self.last_tick = self.world.tick - pitch, yaw, roll = self.rotation - roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360 - self.rotation = pitch, yaw, roll - - def collides(self, x, y, z): - if self.radius is None: - return False - ox, oy, oz = self.location - dx, dy, dz = x - ox, y - oy, z - oz - distance = sqrt(dx*dx + dy*dy + dz*dz) - if distance > self.radius: - return False - return (ox + dx * self.radius / distance, - oy + dy * self.radius / distance, - oz + dz * self.radius / distance) + self.orbit = None + self.orbit_speed = None + if parent: + # Semi-major axis when actually displayed in virtual space + distance = world.evaluate(info.get('distance', 100)) + # Semi-major axis used to calculate orbital speed + sma = world.evaluate(info.get('sma', distance)) -class Satellite(Body): - def __init__(self, *args, **kwargs): - self.parent = kwargs.pop('parent') - self.orbit_speed = kwargs.pop('orbit_speed', 1) + if hasattr(parent, 'mass') and parent.mass is not None: + period = 2 * pi * sqrt((sma * 1000) ** 3 / (G * parent.mass)) + self.orbit_speed = 360.0 / period + if not rotation: # Rotation = 0 assumes tidal lock + rotation = period + else: + self.orbit_speed = info.get('orbit_speed', 1) - # Semi-major axis and eccentricity defines orbit - distance = kwargs.pop('distance', 100) - eccentricity = kwargs.pop('eccentricity', 0) + self.orbit = KeplerOrbit(distance / world.length, info.get('eccentricity', 0), info.get('inclination', 0), + info.get('longitude', 0), info.get('argument', 0)) - # Inclination, longitude of ascending node, and argument of periapsis defines orbital plane - inclination = kwargs.pop('inclination', 0) - longitude = kwargs.pop('longitude', 0) - argument = kwargs.pop('argument', 0) + self.rotation_angle = 360.0 / rotation if rotation else 0 # Orbit calculation self.orbit_id = None self.orbit_cache = None - self.theta = 0 - # OpenGL's z-axis is reversed - self.orbit = KeplerOrbit(distance, eccentricity, inclination, longitude, argument) - super(Satellite, self).__init__(*args, **kwargs) + def update(self): + super(Body, self).update() + + pitch, yaw, roll = self.rotation + roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360 + self.rotation = pitch, yaw, roll + + if self.orbit: + px, py, pz = self.parent.location + x, z, y = self.orbit.orbit(self.world.tick * self.orbit_speed % 360) + self.location = (x + px, y + py, z + pz) + + for satellite in self.satellites: + satellite.update() def get_orbit(self): + if not self.orbit: + return + # Cache key is the three orbital plane parameters and eccentricity cache = (self.orbit.eccentricity, self.orbit.longitude, self.orbit.inclination, self.orbit.argument) if self.orbit_cache == cache: @@ -129,18 +205,146 @@ class Satellite(Body): self.orbit_cache = cache return id - def update(self): - super(Body, self).update() # Notice how the parent class is skipped + def _draw_orbits(self, distance): + with glMatrix(self.parent.location), glRestore(GL_ENABLE_BIT | GL_LINE_BIT | GL_CURRENT_BIT): + glDisable(GL_LIGHTING) + solid = distance < self.parent.orbit_opaque + glColor4f(1, 1, 1, 1 if solid else (1 - (distance - self.parent.orbit_opaque) / self.parent.orbit_blend)) + if not solid: + glEnable(GL_BLEND) + glLineWidth(1) + glCallList(self.get_orbit()) + + def draw(self, cam, options): + self._draw(cam, options) + + if options.orbit and self.orbit: + dist = cam.distance(*self.parent.location) + if dist < self.parent.orbit_show: + self._draw_orbits(dist) + + for satellite in self.satellites: + satellite.draw(cam, options) + + def _draw(self, cam, options): + raise NotImplementedError() + + def collides(self, x, y, z): + return self._collides(x, y, z) or any(satellite.collides(x, y, z) for satellite in self.satellites) + + def _collides(self, x, y, z): + return False + + +class SphericalBody(Body): + def __init__(self, name, world, info, parent=None): + super(SphericalBody, self).__init__(name, world, info, parent) + + self.radius = world.evaluate(info.get('radius', world.length)) / world.length + division = info.get('division', max(min(int(self.radius / 8), 60), 10)) + + texture = get_best_texture(info['texture']) + self.sphere_id = compile(sphere, self.radius, division, division, texture) + + self.atmosphere_id = 0 + self.cloudmap_id = 0 + self.corona_id = 0 + self.ring_id = 0 + + if 'atmosphere' in info: + atmosphere_data = info['atmosphere'] + atm_size = world.evaluate(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: + cloud_texture = get_best_texture(cloud_texture, loader=load_clouds) + self.cloudmap_id = compile(sphere, self.radius + 2, division, division, cloud_texture, lighting=False) + + if corona_texture is not None: + corona = get_best_texture(corona_texture) + corona_size = atmosphere_data.get('corona_size', self.radius / 2) + corona_division = atmosphere_data.get('corona_division', 100) + corona_ratio = atmosphere_data.get('corona_ratio', 0.5) + self.corona_id = compile(flare, self.radius, self.radius + corona_size, corona_division, + corona_ratio, corona) + + if atm_texture is not None: + atm_texture = get_best_texture(atm_texture) + self.atmosphere_id = compile(disk, self.radius, self.radius + atm_size, 30, atm_texture) + + if 'ring' in info: + distance = world.evaluate(info['ring'].get('distance', self.radius * 1.2)) + size = world.evaluate(info['ring'].get('size', self.radius / 2)) - if self.last_tick != self.world.tick: - self.last_tick = self.world.tick pitch, yaw, roll = self.rotation - roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360 - self.rotation = pitch, yaw, roll + pitch = world.evaluate(info['ring'].get('pitch', pitch)) + yaw = world.evaluate(info['ring'].get('yaw', yaw)) + roll = world.evaluate(info['ring'].get('roll', roll)) + self.ring_rotation = pitch, yaw, roll - self.parent.update() - px, py, pz = self.parent.location - self.theta = self.world.tick * self.orbit_speed % 360 - x, z, y = self.orbit.orbit(self.theta) - self.location = (x + px, y + py, z + pz) + self.ring_id = compile(disk, distance, distance + size, 30, + get_best_texture(info['ring'].get('texture', None))) + def _draw_sphere(self): + with glMatrix(self.location, self.rotation), glRestore(GL_CURRENT_BIT): + glCallList(self.sphere_id) + + def _draw_atmosphere(self, cam, glMatrixBuffer=GLfloat * 16): + with glMatrix(self.location), glRestore(GL_ENABLE_BIT): + 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 self.atmosphere_id: + glCallList(self.atmosphere_id) + + if self.corona_id: + x, y, z = cam.direction() + glTranslatef(-x, -y, -z) + glCallList(self.corona_id) + + def _draw_clouds(self): + with glMatrix(self.location, self.rotation), glRestore(GL_ENABLE_BIT): + glEnable(GL_BLEND) + glEnable(GL_ALPHA_TEST) + glCallList(self.cloudmap_id) + + def _draw_rings(self): + with glMatrix(self.location, self.ring_rotation), glRestore(GL_CURRENT_BIT): + glCallList(self.ring_id) + + def _draw(self, cam, options): + self._draw_sphere() + + if options.atmosphere and (self.atmosphere_id or self.corona_id): + self._draw_atmosphere(cam) + + if options.cloud and self.cloudmap_id: + self._draw_clouds() + + if self.ring_id: + self._draw_rings() + + def _collides(self, x, y, z): + ox, oy, oz = self.location + dx, dy, dz = x - ox, y - oy, z - oz + distance = sqrt(dx * dx + dy * dy + dz * dz) + return distance <= self.radius + + +class ModelBody(Body): + def __init__(self, name, world, info, parent=None): + super(ModelBody, self).__init__(name, world, info, parent) + + scale = info.get('scale', 1) + self.object_id = model_list(load_model(info['model']), info.get('sx', scale), info.get('sy', scale), + info.get('sz', scale), (0, 0, 0)) + + def _draw(self, cam, options): + with glMatrix(self.location, self.rotation), glRestore(GL_CURRENT_BIT): + glCallList(self.object_id) diff --git a/punyverse/game.py b/punyverse/game.py index 417e412..fdbe7c7 100644 --- a/punyverse/game.py +++ b/punyverse/game.py @@ -1,9 +1,8 @@ #!/usr/bin/python from operator import attrgetter -from math import hypot, sqrt, atan2, degrees +from math import hypot from time import clock import time -import random import os import six @@ -29,8 +28,7 @@ from pyglet.window import key, mouse import pyglet - -INITIAL_SPEED = 0 # The initial speed of the player +INITIAL_SPEED = 0 # The initial speed of the player MOUSE_SENSITIVITY = 0.3 # Mouse sensitivity, 0..1, none...hyperspeed MAX_DELTA = 5 @@ -109,15 +107,15 @@ class Applet(pyglet.window.Window): self.cloud = not texture.badcard self.tick = self.world.tick_length - self.ticks = [1, 2, 5, 10, 20, 40, 60, # Second range + 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 - ] + 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 @@ -202,9 +200,9 @@ class Applet(pyglet.window.Window): 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_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_DIFFUSE, fv4(.5, .5, .5, 1)) glLightfv(GL_LIGHT1, GL_SPECULAR, fv4(1, 1, 1, 1)) phase = 'Loading asteroids...' @@ -213,9 +211,9 @@ class Applet(pyglet.window.Window): 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)) + Asteroid.load_asteroid(file) - self.asteroid_ids = list(load_asteroids([r'asteroids/01.obj', r'asteroids/02.obj', r'asteroids/03.obj'])) + load_asteroids(['asteroids/01.obj', 'asteroids/02.obj', 'asteroids/03.obj']) c = self.cam c.x, c.y, c.z = self.world.start @@ -269,8 +267,7 @@ class Applet(pyglet.window.Window): 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))) + self.world.tracker.append(Asteroid((c.x, c.y - 3, c.z + 5), (dx, dy, dz))) def on_mouse_press(self, x, y, button, modifiers): self.modifiers = modifiers @@ -313,7 +310,7 @@ class Applet(pyglet.window.Window): if not self.loaded: return super(Applet, self).on_resize(width, height) - height = max(height, 1) # Prevent / by 0 + height = max(height, 1) # Prevent / by 0 self.label.y = height - 20 glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) @@ -402,77 +399,10 @@ class Applet(pyglet.window.Window): 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): - if entity.background: - glTranslatef(c.x, c.y, c.z) - else: - 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()) + entity.draw(c, self) glColor4f(1, 1, 1, 1) glDisable(GL_TEXTURE_2D) diff --git a/punyverse/glgeom.py b/punyverse/glgeom.py index aa7beeb..2d49beb 100644 --- a/punyverse/glgeom.py +++ b/punyverse/glgeom.py @@ -1,6 +1,7 @@ from math import * from random import random, gauss, choice +# noinspection PyUnresolvedReferences from six.moves import range from pyglet.gl import * from pyglet.gl.gl_info import have_extension @@ -34,9 +35,22 @@ class glRestore(object): class glMatrix(object): + def __init__(self, location=None, rotation=None): + self.location = location + self.rotation = rotation + def __enter__(self): glPushMatrix() + if self.location: + glTranslatef(*self.location) + + if self.rotation: + pitch, yaw, roll = self.rotation + glRotatef(pitch, 1, 0, 0) + glRotatef(yaw, 0, 1, 0) + glRotatef(roll, 0, 0, 1) + def __exit__(self, exc_type, exc_val, exc_tb): glPopMatrix() diff --git a/punyverse/texture.py b/punyverse/texture.py index cf2e706..28aacee 100644 --- a/punyverse/texture.py +++ b/punyverse/texture.py @@ -1,20 +1,21 @@ from __future__ import print_function -from pyglet import image -from pyglet.gl import * -from ctypes import c_int, byref, c_uint +import itertools import os.path import struct -import itertools +from ctypes import c_int, byref, c_uint from io import BytesIO import six +from pyglet import image +from pyglet.gl import * from six.moves import zip try: from punyverse._glgeom import bgr_to_rgb, flip_vertical except ImportError: import warnings + warnings.warn('Compile _glgeom.c, or double the start up time.') # Use magick when _glgeom is not compiled (is actually slower) @@ -27,6 +28,7 @@ except ImportError: from six.moves import range + def bgr_to_rgb(source, width, height, alpha=False): length = len(source) depth = length // (width * height) @@ -37,23 +39,24 @@ except ImportError: for x in range(width): offset = y * row + x * depth for i in range(depth2): - result[offset+i] = source[offset+depth2-i-1] + result[offset + i] = source[offset + depth2 - i - 1] if alpha: - result[offset+depth2] = source[offset+depth2] + result[offset + depth2] = source[offset + depth2] return six.binary_type(result) + def flip_vertical(source, width, height): length = len(source) row = length // height result = bytearray(length) for y1 in range(height): y2 = height - y1 - 1 - result[y1*row:y1*row+row] = source[y2*row:y2*row+row] + result[y1 * row:y1 * row + row] = source[y2 * row:y2 * row + row] return six.binary_type(result) else: magick = False -__all__ = ['load_texture', 'load_clouds', 'load_image', 'pil_load'] +__all__ = ['load_texture', 'load_clouds', 'load_image', 'get_best_texture'] id = 0 cache = {} @@ -76,11 +79,12 @@ def init(): import warnings warnings.warn('Please update your graphics drivers if possible') - #bgra = gl_info.have_extension('GL_EXT_bgra') + # bgra = gl_info.have_extension('GL_EXT_bgra') if power_of_two is None: power_of_two = gl_info.have_version(2) or gl_info.have_extension('GL_ARB_texture_non_power_of_two') + is_power2 = lambda num: num != 0 and ((num & (num - 1)) == 0) @@ -134,7 +138,7 @@ def image_info(data): h, w = struct.unpack(">HH", jpeg.read(4)) break else: - jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2) + jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0]) - 2) b = jpeg.read(1) width = int(w) height = int(h) @@ -233,17 +237,10 @@ def load_texture(file): glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) gluBuild2DMipmaps(GL_TEXTURE_2D, depth, width, height, mode, GL_UNSIGNED_BYTE, texture) - cache[path] = id - return id -def pil_load(file): - from PIL import Image - return Image.open(os.path.join(os.path.dirname(__file__), 'assets', 'textures', file)) - - def load_clouds(file): if os.path.isabs(file): path = file @@ -275,3 +272,15 @@ def load_clouds(file): cache[path] = id return id + + +def get_best_texture(info, loader=load_texture): + if isinstance(info, list): + for item in info: + try: + return loader(item) + except ValueError: + pass + else: + return loader(info) + raise ValueError('No texture found') diff --git a/punyverse/world.json b/punyverse/world.json index 36ecee7..0274c57 100644 --- a/punyverse/world.json +++ b/punyverse/world.json @@ -286,19 +286,6 @@ "rotation": 57996, "pitch": -90, "yaw": 28.32 - }, - "sky": { - "texture": "sky.jpg", - "rotation": 0, - "optional": true, - "lighting": false, - "radius": 305000000, - "division": 30, - "pitch": 90, - "yaw": 30, - "roll": 180, - "delta": 0, - "background": true } }, "belts": { @@ -311,6 +298,15 @@ "rotation": 114536500 } }, + "sky": { + "texture": "sky.jpg", + "rotation": 0, + "radius": 305000000, + "division": 30, + "pitch": 90, + "yaw": 30, + "roll": 180 + }, "start": { "z": "AU - 400", "yaw": 180 diff --git a/punyverse/world.py b/punyverse/world.py index ed085c0..5e99444 100644 --- a/punyverse/world.py +++ b/punyverse/world.py @@ -18,27 +18,9 @@ try: 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, loader=load_texture): - if isinstance(info, list): - for item in info: - try: - return loader(item) - except ValueError: - pass - else: - return loader(info) - raise ValueError('No texture found') - def load_world(file, callback=lambda message, completion: None): return World(file, callback) @@ -61,9 +43,17 @@ class World(object): self._parse(file) del self.callback # So it can't be used after loading finishes - def _eval(self, value): + 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(self._phase, 'Loading configuration file...', 0) with open(os.path.join(os.path.dirname(__file__), file)) as f: @@ -88,12 +78,12 @@ class World(object): 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)) + x = self.evaluate(info.get('x', 0)) + y = self.evaluate(info.get('y', 0)) + z = self.evaluate(info.get('z', 0)) + pitch = self.evaluate(info.get('pitch', 0)) + yaw = self.evaluate(info.get('yaw', 0)) + roll = self.evaluate(info.get('roll', 0)) self.start = (x, y, z) self.direction = (pitch, yaw, roll) @@ -112,134 +102,33 @@ class World(object): message = 'Loading %s.' % name print(message) self.callback(self._phase, message, float(self._current_object) / len(root['belts'])) - self._belt(name, info) + self.tracker.append(Belt(name, self, 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)) + if 'sky' in root: + self._phase = 'Loading sky...' + message = 'Loading sky.' + print(message) + self.callback(self._phase, message, 0) + self.tracker.append(Sky(self, root['sky'])) 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: - texture = get_best_texture(info['texture']) - if self.options.get('normal', False) and 'normal' in info: - object_id = compile(normal_sphere, radius, division, texture, - info['normal'], lighting=lighting, inside=background) - else: - object_id = compile(sphere, radius, division, division, texture, - lighting=lighting, inside=background) + body = SphericalBody(name, self, info, parent) 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)) + body = ModelBody(name, self, info, parent) else: print('Nothing to load for %s.' % name) return - params = {'world': self, 'orbit_distance': orbit_distance, 'radius': None if background else radius} - if parent is None: - type = Body + if parent: + parent.satellites.append(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: - cloud_texture = get_best_texture(cloud_texture, loader=load_clouds) - cloudmap_id = compile(sphere, radius + 2, division, division, cloud_texture, lighting=False) - - if corona_texture is not None: - corona = get_best_texture(corona_texture) - 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: - atm_texture = get_best_texture(atm_texture) - atmosphere_id = compile(disk, radius, radius + atm_size, 30, atm_texture) - - theta = 360.0 / rotation 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)) - - texture = get_best_texture(texture) - self.tracker.append(type(compile(disk, distance, distance + size, 30, texture), (x, y, z), - (pitch, yaw, roll), **params)) + self.tracker.append(body) for satellite, info in six.iteritems(info.get('satellites', {})): 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._body(satellite, info, body) self._current_object += 1