Moved drawing logic to the entities themselves.

This commit is contained in:
Quantum 2018-08-23 00:23:09 -04:00
parent 1b973b0082
commit 63d42bca55
7 changed files with 370 additions and 325 deletions

View file

@ -1,4 +1,4 @@
from math import sin, cos, radians from math import sin, cos, radians, hypot
class Camera(object): class Camera(object):
@ -39,3 +39,6 @@ class Camera(object):
dx = cos(radians(self.yaw - 90)) * m dx = cos(radians(self.yaw - 90)) * m
dz = sin(radians(self.yaw - 90)) * m dz = sin(radians(self.yaw - 90)) * m
return dx, dy, dz return dx, dy, dz
def distance(self, x, y, z):
return hypot(hypot(x - self.x, y - self.y), z - self.z)

View file

@ -1,113 +1,189 @@
from math import sqrt import random
from math import sqrt, pi
from pyglet.gl import * 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.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): class Entity(object):
def __init__(self, id, location, rotation=(0, 0, 0), direction=(0, 0, 0), background=False): def __init__(self, name, location, rotation=(0, 0, 0), direction=(0, 0, 0)):
self.id = id self.name = name
self.location = location self.location = location
self.rotation = rotation self.rotation = rotation
self.direction = direction self.direction = direction
self.background = background
def update(self): def update(self):
x, y, z = self.location x, y, z = self.location
dx, dy, dz = self.direction dx, dy, dz = self.direction
self.location = x + dx, y + dy, z + dz self.location = x + dx, y + dy, z + dz
def collides(self, x, y, z): def collides(self, x, y, z):
return False return False
def draw(self, cam, options):
raise NotImplementedError()
class Asteroid(Entity): class Asteroid(Entity):
def __init__(self, *args, **kwargs): asteroid_ids = []
super(Asteroid, self).__init__(*args, **kwargs)
@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): def update(self):
super(Asteroid, self).update() super(Asteroid, self).update()
rx, ry, rz = self.rotation rx, ry, rz = self.rotation
# Increment all axis to 'spin' # Increment all axis to 'spin'
self.rotation = rx + 1, ry + 1, rz + 1 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): class Belt(Entity):
def __init__(self, *args, **kwargs): def __init__(self, name, world, info):
self.rotation_angle = kwargs.pop('rotation_angle', 5) self.world = world
self.world = kwargs.pop('world')
super(Belt, self).__init__(*args, **kwargs) 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): def update(self):
super(Belt, self).update() super(Belt, self).update()
pitch, yaw, roll = self.rotation pitch, yaw, roll = self.rotation
self.rotation = pitch, self.world.tick * self.rotation_angle % 360, roll 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): class Body(Entity):
def __init__(self, *args, **kwargs): def __init__(self, name, world, info, parent=None):
self.rotation_angle = kwargs.pop('rotation_angle', 5) self.world = world
self.atmosphere = kwargs.pop('atmosphere', 0) self.parent = parent
self.cloudmap = kwargs.pop('cloudmap', 0) self.satellites = []
self.corona = kwargs.pop('corona', 0)
self.last_tick = 0 x = world.evaluate(info.get('x', 0))
self.mass = kwargs.pop('mass', None) y = world.evaluate(info.get('y', 0))
self.radius = kwargs.pop('radius', None) z = world.evaluate(info.get('z', 0))
self.world = kwargs.pop('world') pitch = world.evaluate(info.get('pitch', 0))
orbit_distance = kwargs.pop('orbit_distance', 40000) + .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_show = orbit_distance * 1.25
self.orbit_blend = orbit_distance / 4 self.orbit_blend = orbit_distance / 4
self.orbit_opaque = orbit_distance self.orbit_opaque = orbit_distance
super(Body, self).__init__(*args, **kwargs)
self.initial_roll = self.rotation[2]
def update(self): super(Body, self).__init__(name, (x, y, z), (pitch, yaw, roll))
super(Body, self).update() self.initial_roll = roll
if self.last_tick != self.world.tick: self.orbit = None
self.last_tick = self.world.tick self.orbit_speed = None
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)
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): if hasattr(parent, 'mass') and parent.mass is not None:
def __init__(self, *args, **kwargs): period = 2 * pi * sqrt((sma * 1000) ** 3 / (G * parent.mass))
self.parent = kwargs.pop('parent') self.orbit_speed = 360.0 / period
self.orbit_speed = kwargs.pop('orbit_speed', 1) 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 self.orbit = KeplerOrbit(distance / world.length, info.get('eccentricity', 0), info.get('inclination', 0),
distance = kwargs.pop('distance', 100) info.get('longitude', 0), info.get('argument', 0))
eccentricity = kwargs.pop('eccentricity', 0)
# Inclination, longitude of ascending node, and argument of periapsis defines orbital plane self.rotation_angle = 360.0 / rotation if rotation else 0
inclination = kwargs.pop('inclination', 0)
longitude = kwargs.pop('longitude', 0)
argument = kwargs.pop('argument', 0)
# Orbit calculation # Orbit calculation
self.orbit_id = None self.orbit_id = None
self.orbit_cache = None self.orbit_cache = None
self.theta = 0 def update(self):
# OpenGL's z-axis is reversed super(Body, self).update()
self.orbit = KeplerOrbit(distance, eccentricity, inclination, longitude, argument)
super(Satellite, self).__init__(*args, **kwargs) 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): def get_orbit(self):
if not self.orbit:
return
# Cache key is the three orbital plane parameters and eccentricity # Cache key is the three orbital plane parameters and eccentricity
cache = (self.orbit.eccentricity, self.orbit.longitude, self.orbit.inclination, self.orbit.argument) cache = (self.orbit.eccentricity, self.orbit.longitude, self.orbit.inclination, self.orbit.argument)
if self.orbit_cache == cache: if self.orbit_cache == cache:
@ -129,18 +205,146 @@ class Satellite(Body):
self.orbit_cache = cache self.orbit_cache = cache
return id return id
def update(self): def _draw_orbits(self, distance):
super(Body, self).update() # Notice how the parent class is skipped 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 pitch, yaw, roll = self.rotation
roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360 pitch = world.evaluate(info['ring'].get('pitch', pitch))
self.rotation = pitch, yaw, roll 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() self.ring_id = compile(disk, distance, distance + size, 30,
px, py, pz = self.parent.location get_best_texture(info['ring'].get('texture', None)))
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)
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)

View file

@ -1,9 +1,8 @@
#!/usr/bin/python #!/usr/bin/python
from operator import attrgetter from operator import attrgetter
from math import hypot, sqrt, atan2, degrees from math import hypot
from time import clock from time import clock
import time import time
import random
import os import os
import six import six
@ -29,8 +28,7 @@ from pyglet.window import key, mouse
import pyglet 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 MOUSE_SENSITIVITY = 0.3 # Mouse sensitivity, 0..1, none...hyperspeed
MAX_DELTA = 5 MAX_DELTA = 5
@ -109,15 +107,15 @@ class Applet(pyglet.window.Window):
self.cloud = not texture.badcard self.cloud = not texture.badcard
self.tick = self.world.tick_length 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 120, 300, 600, 1200, 1800, 2700, 3600, # Minute range
7200, 14400, 21600, 43200, 86400, # Hour range 7200, 14400, 21600, 43200, 86400, # Hour range
172800, 432000, 604800, # 2, 5, 7 days 172800, 432000, 604800, # 2, 5, 7 days
1209600, 2592000, # 2 week, 1 month 1209600, 2592000, # 2 week, 1 month
5270400, 7884000, 15768000, 31536000, # 2, 3, 6, 12 months 5270400, 7884000, 15768000, 31536000, # 2, 3, 6, 12 months
63072000, 157680000, 315360000, # 2, 5, 10 years 63072000, 157680000, 315360000, # 2, 5, 10 years
630720000, 1576800000, 3153600000, # 20, 50, 100 years 630720000, 1576800000, 3153600000, # 20, 50, 100 years
] ]
self.__time_per_second_cache = None self.__time_per_second_cache = None
self.__time_per_second_value = None self.__time_per_second_value = None
self.__time_accumulate = 0 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_POSITION, fv4(.5, .5, 1, 0))
glLightfv(GL_LIGHT0, GL_SPECULAR, fv4(.5, .5, 1, 1)) 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_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)) glLightfv(GL_LIGHT1, GL_SPECULAR, fv4(1, 1, 1, 1))
phase = 'Loading asteroids...' phase = 'Loading asteroids...'
@ -213,9 +211,9 @@ class Applet(pyglet.window.Window):
def load_asteroids(files): def load_asteroids(files):
for id, file in enumerate(files): for id, file in enumerate(files):
callback(phase, 'Loading %s...' % file, float(id) / len(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 = self.cam
c.x, c.y, c.z = self.world.start c.x, c.y, c.z = self.world.start
@ -269,8 +267,7 @@ class Applet(pyglet.window.Window):
dx *= speed dx *= speed
dy *= speed dy *= speed
dz *= speed dz *= speed
self.world.tracker.append(Asteroid(random.choice(self.asteroid_ids), (c.x, c.y - 3, c.z + 5), self.world.tracker.append(Asteroid((c.x, c.y - 3, c.z + 5), (dx, dy, dz)))
direction=(dx, dy, dz)))
def on_mouse_press(self, x, y, button, modifiers): def on_mouse_press(self, x, y, button, modifiers):
self.modifiers = modifiers self.modifiers = modifiers
@ -313,7 +310,7 @@ class Applet(pyglet.window.Window):
if not self.loaded: if not self.loaded:
return super(Applet, self).on_resize(width, height) 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 self.label.y = height - 20
glViewport(0, 0, width, height) glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION) glMatrixMode(GL_PROJECTION)
@ -402,77 +399,10 @@ class Applet(pyglet.window.Window):
get_distance = entity_distance(x, y, z) get_distance = entity_distance(x, y, z)
if x != world.x or y != world.y or z != world.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=get_distance, reverse=True)
world.tracker.sort(key=attrgetter('background'), reverse=True)
world.x, world.y, world.z = x, y, z world.x, world.y, world.z = x, y, z
for entity in world.tracker: for entity in world.tracker:
x, y, z = entity.location entity.draw(c, self)
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())
glColor4f(1, 1, 1, 1) glColor4f(1, 1, 1, 1)
glDisable(GL_TEXTURE_2D) glDisable(GL_TEXTURE_2D)

View file

@ -1,6 +1,7 @@
from math import * from math import *
from random import random, gauss, choice from random import random, gauss, choice
# noinspection PyUnresolvedReferences
from six.moves import range from six.moves import range
from pyglet.gl import * from pyglet.gl import *
from pyglet.gl.gl_info import have_extension from pyglet.gl.gl_info import have_extension
@ -34,9 +35,22 @@ class glRestore(object):
class glMatrix(object): class glMatrix(object):
def __init__(self, location=None, rotation=None):
self.location = location
self.rotation = rotation
def __enter__(self): def __enter__(self):
glPushMatrix() 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): def __exit__(self, exc_type, exc_val, exc_tb):
glPopMatrix() glPopMatrix()

View file

@ -1,20 +1,21 @@
from __future__ import print_function from __future__ import print_function
from pyglet import image import itertools
from pyglet.gl import *
from ctypes import c_int, byref, c_uint
import os.path import os.path
import struct import struct
import itertools from ctypes import c_int, byref, c_uint
from io import BytesIO from io import BytesIO
import six import six
from pyglet import image
from pyglet.gl import *
from six.moves import zip from six.moves import zip
try: try:
from punyverse._glgeom import bgr_to_rgb, flip_vertical from punyverse._glgeom import bgr_to_rgb, flip_vertical
except ImportError: except ImportError:
import warnings import warnings
warnings.warn('Compile _glgeom.c, or double the start up time.') warnings.warn('Compile _glgeom.c, or double the start up time.')
# Use magick when _glgeom is not compiled (is actually slower) # Use magick when _glgeom is not compiled (is actually slower)
@ -27,6 +28,7 @@ except ImportError:
from six.moves import range from six.moves import range
def bgr_to_rgb(source, width, height, alpha=False): def bgr_to_rgb(source, width, height, alpha=False):
length = len(source) length = len(source)
depth = length // (width * height) depth = length // (width * height)
@ -37,23 +39,24 @@ except ImportError:
for x in range(width): for x in range(width):
offset = y * row + x * depth offset = y * row + x * depth
for i in range(depth2): for i in range(depth2):
result[offset+i] = source[offset+depth2-i-1] result[offset + i] = source[offset + depth2 - i - 1]
if alpha: if alpha:
result[offset+depth2] = source[offset+depth2] result[offset + depth2] = source[offset + depth2]
return six.binary_type(result) return six.binary_type(result)
def flip_vertical(source, width, height): def flip_vertical(source, width, height):
length = len(source) length = len(source)
row = length // height row = length // height
result = bytearray(length) result = bytearray(length)
for y1 in range(height): for y1 in range(height):
y2 = height - y1 - 1 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) return six.binary_type(result)
else: else:
magick = False magick = False
__all__ = ['load_texture', 'load_clouds', 'load_image', 'pil_load'] __all__ = ['load_texture', 'load_clouds', 'load_image', 'get_best_texture']
id = 0 id = 0
cache = {} cache = {}
@ -76,11 +79,12 @@ def init():
import warnings import warnings
warnings.warn('Please update your graphics drivers if possible') 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: 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') 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) 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)) h, w = struct.unpack(">HH", jpeg.read(4))
break break
else: 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) b = jpeg.read(1)
width = int(w) width = int(w)
height = int(h) 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_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_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) gluBuild2DMipmaps(GL_TEXTURE_2D, depth, width, height, mode, GL_UNSIGNED_BYTE, texture)
cache[path] = id cache[path] = id
return 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): def load_clouds(file):
if os.path.isabs(file): if os.path.isabs(file):
path = file path = file
@ -275,3 +272,15 @@ def load_clouds(file):
cache[path] = id cache[path] = id
return 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')

View file

@ -286,19 +286,6 @@
"rotation": 57996, "rotation": 57996,
"pitch": -90, "pitch": -90,
"yaw": 28.32 "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": { "belts": {
@ -311,6 +298,15 @@
"rotation": 114536500 "rotation": 114536500
} }
}, },
"sky": {
"texture": "sky.jpg",
"rotation": 0,
"radius": 305000000,
"division": 30,
"pitch": 90,
"yaw": 30,
"roll": 180
},
"start": { "start": {
"z": "AU - 400", "z": "AU - 400",
"yaw": 180 "yaw": 180

View file

@ -18,27 +18,9 @@ try:
except ImportError: except ImportError:
from punyverse.model import model_list, load_model from punyverse.model import model_list, load_model
from punyverse.glgeom import *
from punyverse.entity import * from punyverse.entity import *
from punyverse.texture import *
from punyverse import texture 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): def load_world(file, callback=lambda message, completion: None):
return World(file, callback) return World(file, callback)
@ -61,9 +43,17 @@ class World(object):
self._parse(file) self._parse(file)
del self.callback # So it can't be used after loading finishes 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) 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): def _parse(self, file):
self.callback(self._phase, 'Loading configuration file...', 0) self.callback(self._phase, 'Loading configuration file...', 0)
with open(os.path.join(os.path.dirname(__file__), file)) as f: with open(os.path.join(os.path.dirname(__file__), file)) as f:
@ -88,12 +78,12 @@ class World(object):
if 'start' in root: if 'start' in root:
info = root['start'] info = root['start']
x = self._eval(info.get('x', 0)) x = self.evaluate(info.get('x', 0))
y = self._eval(info.get('y', 0)) y = self.evaluate(info.get('y', 0))
z = self._eval(info.get('z', 0)) z = self.evaluate(info.get('z', 0))
pitch = self._eval(info.get('pitch', 0)) pitch = self.evaluate(info.get('pitch', 0))
yaw = self._eval(info.get('yaw', 0)) yaw = self.evaluate(info.get('yaw', 0))
roll = self._eval(info.get('roll', 0)) roll = self.evaluate(info.get('roll', 0))
self.start = (x, y, z) self.start = (x, y, z)
self.direction = (pitch, yaw, roll) self.direction = (pitch, yaw, roll)
@ -112,134 +102,33 @@ class World(object):
message = 'Loading %s.' % name message = 'Loading %s.' % name
print(message) print(message)
self.callback(self._phase, message, float(self._current_object) / len(root['belts'])) 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): if 'sky' in root:
x = self._eval(info.get('x', 0)) self._phase = 'Loading sky...'
y = self._eval(info.get('y', 0)) message = 'Loading sky.'
z = self._eval(info.get('z', 0)) print(message)
radius = self._eval(info.get('radius', 0)) self.callback(self._phase, message, 0)
cross = self._eval(info.get('cross', 0)) self.tracker.append(Sky(self, root['sky']))
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): 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: if 'texture' in info:
texture = get_best_texture(info['texture']) body = SphericalBody(name, self, info, parent)
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)
elif 'model' in info: elif 'model' in info:
scale = info.get('scale', 1) body = ModelBody(name, self, info, parent)
object_id = model_list(load_model(info['model']), info.get('sx', scale), info.get('sy', scale),
info.get('sz', scale), (0, 0, 0))
else: else:
print('Nothing to load for %s.' % name) print('Nothing to load for %s.' % name)
return return
params = {'world': self, 'orbit_distance': orbit_distance, 'radius': None if background else radius} if parent:
if parent is None: parent.satellites.append(body)
type = Body
else: else:
x, y, z = parent.location self.tracker.append(body)
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))
for satellite, info in six.iteritems(info.get('satellites', {})): for satellite, info in six.iteritems(info.get('satellites', {})):
message = 'Loading %s, satellite of %s.' % (satellite, name) message = 'Loading %s, satellite of %s.' % (satellite, name)
print(message) print(message)
self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects), self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects),
message, float(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 self._current_object += 1