punyverse/punyverse/entity.py

527 lines
20 KiB
Python
Raw Normal View History

import random
from math import sqrt, pi
2013-10-26 14:25:37 -04:00
from pyglet.gl import *
# noinspection PyUnresolvedReferences
from six.moves import range
2013-10-26 14:25:37 -04:00
2018-08-26 18:11:18 -04:00
from punyverse.glgeom import compile, glRestore, belt, Disk, OrbitVBO, Matrix4f, SimpleSphere, TangentSphere
from punyverse.model import load_model, WavefrontVBO
from punyverse.orbit import KeplerOrbit
from punyverse.texture import get_best_texture, load_clouds
from punyverse.utils import cached_property
G = 6.67384e-11 # Gravitation Constant
2013-10-22 20:38:37 -04:00
class Entity(object):
2018-08-23 22:05:56 -04:00
background = False
def __init__(self, world, name, location, rotation=(0, 0, 0), direction=(0, 0, 0)):
self.world = world
self.name = name
2013-10-22 20:38:37 -04:00
self.location = location
self.rotation = rotation
self.direction = direction
2018-08-26 03:38:26 -04:00
@cached_property
def model_matrix(self):
return Matrix4f.from_angles(self.location, self.rotation)
@cached_property
def mv_matrix(self):
2018-08-26 03:38:26 -04:00
return self.world.view_matrix() * self.model_matrix
2018-08-26 18:11:18 -04:00
@cached_property
def mvp_matrix(self):
return self.world.vp_matrix * self.model_matrix
2013-10-22 20:38:37 -04:00
def update(self):
2018-08-26 03:38:26 -04:00
self.model_matrix = None
self.mv_matrix = None
2018-08-26 18:11:18 -04:00
self.mvp_matrix = None
2013-10-22 20:38:37 -04:00
x, y, z = self.location
dx, dy, dz = self.direction
self.location = x + dx, y + dy, z + dz
2014-02-21 12:45:33 -05:00
def collides(self, x, y, z):
return False
2013-10-22 20:38:37 -04:00
2018-08-24 14:44:11 -04:00
def draw(self, options):
raise NotImplementedError()
2013-10-22 20:38:37 -04:00
class Asteroid(Entity):
def __init__(self, world, model, location, direction):
super(Asteroid, self).__init__(world, 'Asteroid', location, direction=direction)
self.model = model
2013-10-22 20:38:37 -04:00
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
2018-08-24 14:44:11 -04:00
def draw(self, options):
glLoadMatrixf(self.mv_matrix)
self.model.draw()
2013-10-22 20:38:37 -04:00
2018-08-24 14:44:11 -04:00
class AsteroidManager(object):
def __init__(self, world):
self.world = world
2018-08-24 14:44:11 -04:00
self.asteroids = []
def __bool__(self):
return bool(self.asteroids)
__nonzero__ = __bool__
def load(self, file):
self.asteroids.append(WavefrontVBO(load_model(file), 5, 5, 5))
2018-08-24 14:44:11 -04:00
def new(self, location, direction):
return Asteroid(self.world, random.choice(self.asteroids), location, direction)
2018-08-24 14:44:11 -04:00
2013-11-01 18:42:50 -04:00
class Belt(Entity):
def __init__(self, name, world, info):
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 = [WavefrontVBO(load_model(model), info.get('sx', scale), info.get('sy', scale),
info.get('sz', scale)) 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__(world, name, (x, y, z), (inclination, longitude, argument))
2013-11-01 18:42:50 -04:00
def update(self):
super(Belt, self).update()
pitch, yaw, roll = self.rotation
self.rotation = pitch, self.world.tick * self.rotation_angle % 360, roll
2018-08-24 14:44:11 -04:00
def draw(self, options):
glLoadMatrixf(self.mv_matrix)
glCallList(self.belt_id)
class Sky(Entity):
2018-08-23 22:05:56 -04:00
background = True
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__(world, 'Sky', (0, 0, 0), (pitch, yaw, roll))
2018-08-24 19:37:50 -04:00
self.texture = get_best_texture(info['texture'])
division = info.get('division', 30)
2018-08-25 20:53:21 -04:00
self.sphere = SimpleSphere(division, division)
2018-08-24 14:44:11 -04:00
def draw(self, options):
cam = self.world.cam
2018-08-25 20:53:21 -04:00
shader = self.world.activate_shader('sky')
shader.uniform_mat4('u_mvpMatrix', self.world.projection_matrix() *
Matrix4f.from_angles(rotation=(cam.pitch, cam.yaw, cam.roll)) *
Matrix4f.from_angles(rotation=self.rotation))
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, self.texture)
shader.uniform_texture('u_skysphere', 0)
glBindBuffer(GL_ARRAY_BUFFER, self.sphere.vbo)
shader.vertex_attribute('a_direction', self.sphere.direction_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.direction_offset)
shader.vertex_attribute('a_uv', self.sphere.uv_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.uv_offset)
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.sphere.vertex_count)
shader.deactivate_attributes()
glBindBuffer(GL_ARRAY_BUFFER, 0)
self.world.activate_shader(None)
2013-11-01 18:42:50 -04:00
2013-10-27 17:32:17 -04:00
class Body(Entity):
def __init__(self, name, world, info, parent=None):
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)))
2013-10-28 22:03:49 -04:00
self.orbit_show = orbit_distance * 1.25
self.orbit_blend = orbit_distance / 4
self.orbit_opaque = orbit_distance
2013-10-22 20:38:37 -04:00
super(Body, self).__init__(world, name, (x, y, z), (pitch, yaw, roll))
self.initial_roll = roll
2013-10-22 20:38:37 -04:00
self.orbit = None
self.orbit_speed = None
2013-10-22 20:38:37 -04:00
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))
2013-10-22 20:38:37 -04:00
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)
self.orbit = KeplerOrbit(distance / world.length, info.get('eccentricity', 0), info.get('inclination', 0),
info.get('longitude', 0), info.get('argument', 0))
self.rotation_angle = 360.0 / rotation if rotation else 0
2013-10-26 14:25:37 -04:00
# Orbit calculation
2018-08-25 00:06:33 -04:00
self.orbit_vbo = None
2013-10-26 14:25:37 -04:00
self.orbit_cache = None
@cached_property
def orbit_matrix(self):
return self.world.view_matrix() * Matrix4f.from_angles(self.location)
def update(self):
super(Body, self).update()
if self.rotation_angle:
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)
self.orbit_matrix = None
for satellite in self.satellites:
satellite.update()
2013-10-22 20:38:37 -04:00
2013-10-26 14:25:37 -04:00
def get_orbit(self):
if not self.orbit:
return
2013-10-26 14:25:37 -04:00
# 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:
2018-08-25 00:06:33 -04:00
return self.orbit_vbo
2013-10-26 14:25:37 -04:00
2018-08-25 00:06:33 -04:00
if self.orbit_vbo is not None:
self.orbit_vbo.close()
2013-10-26 14:25:37 -04:00
2018-08-25 00:06:33 -04:00
self.orbit_vbo = OrbitVBO(self.orbit)
2013-10-26 14:25:37 -04:00
self.orbit_cache = cache
2018-08-25 00:06:33 -04:00
return self.orbit_vbo
2013-10-26 14:25:37 -04:00
def _draw_orbits(self, distance):
with glRestore(GL_ENABLE_BIT | GL_LINE_BIT | GL_CURRENT_BIT):
glLoadMatrixf(self.parent.orbit_matrix)
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)
2018-08-25 00:06:33 -04:00
self.get_orbit().draw()
2018-08-24 14:44:11 -04:00
def draw(self, options):
self._draw(options)
if options.orbit and self.orbit:
2018-08-24 14:44:11 -04:00
dist = self.world.cam.distance(*self.parent.location)
if dist < self.parent.orbit_show:
self._draw_orbits(dist)
for satellite in self.satellites:
2018-08-24 14:44:11 -04:00
satellite.draw(options)
2018-08-24 14:44:11 -04:00
def _draw(self, 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):
2018-08-26 03:38:26 -04:00
_sphere_cache = {}
@classmethod
2018-08-26 03:49:02 -04:00
def _get_sphere(cls, division, tangent=True):
if (division, tangent) in cls._sphere_cache:
return cls._sphere_cache[division, tangent]
cls._sphere_cache[division, tangent] = sphere = \
(TangentSphere if tangent else SimpleSphere)(division, division)
2018-08-26 03:38:26 -04:00
return sphere
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))
2018-08-26 03:38:26 -04:00
2018-08-23 22:08:47 -04:00
self.light_source = info.get('light_source', False)
2018-08-26 03:38:26 -04:00
self.shininess = info.get('shininess', 0)
self.type = info.get('type', 'planet')
2018-08-26 03:49:02 -04:00
self.texture = get_best_texture(info['texture'])
2018-08-26 03:59:14 -04:00
self.normal_texture = None
2018-08-26 04:42:46 -04:00
self.specular_texture = None
2018-08-26 04:06:09 -04:00
self.emission_texture = None
2018-08-26 03:49:02 -04:00
self.sphere = self._get_sphere(division, tangent=self.type == 'planet')
2018-08-24 20:57:48 -04:00
self.atmosphere = None
2018-08-24 19:37:50 -04:00
self.clouds = None
2018-08-24 20:57:48 -04:00
self.ring = 0
2018-08-26 03:59:14 -04:00
if 'normal_map' in info:
self.normal_texture = get_best_texture(info['normal_map'])
2018-08-26 04:42:46 -04:00
if 'specular_map' in info:
self.specular_texture = get_best_texture(info['specular_map'])
2018-08-26 04:06:09 -04:00
if 'emission_map' in info:
self.emission_texture = get_best_texture(info['emission_map'])
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)
if cloud_texture is not None:
2018-08-26 18:11:18 -04:00
self.cloud_transparency = get_best_texture(cloud_texture, loader=load_clouds)
self.cloud_radius = self.radius + 2
self.clouds = self._get_sphere(division, tangent=False)
if atm_texture is not None:
2018-08-24 20:57:48 -04:00
self.atm_texture = get_best_texture(atm_texture, clamp=True)
self.atmosphere = Disk(self.radius, self.radius + atm_size, 30)
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))
2013-10-22 20:38:37 -04:00
pitch, yaw, roll = self.rotation
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
2013-10-22 20:38:37 -04:00
2018-08-24 20:57:48 -04:00
self.ring_texture = get_best_texture(info['ring'].get('texture', None), clamp=True)
self.ring = Disk(distance, distance + size, 30)
2018-08-26 03:38:26 -04:00
def _draw_planet(self):
shader = self.world.activate_shader('planet')
shader.uniform_float('u_radius', self.radius)
shader.uniform_mat4('u_modelMatrix', self.model_matrix)
shader.uniform_mat4('u_mvMatrix', self.mv_matrix)
2018-08-26 18:11:18 -04:00
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
2018-08-24 19:37:50 -04:00
2018-08-26 03:38:26 -04:00
glActiveTexture(GL_TEXTURE0)
2018-08-26 03:49:02 -04:00
glBindTexture(GL_TEXTURE_2D, self.texture)
2018-08-26 03:38:26 -04:00
shader.uniform_texture('u_planet.diffuseMap', 0)
2018-08-24 19:37:50 -04:00
2018-08-26 03:59:14 -04:00
shader.uniform_bool('u_planet.hasNormal', self.normal_texture)
if self.normal_texture:
glActiveTexture(GL_TEXTURE1)
glBindTexture(GL_TEXTURE_2D, self.normal_texture)
shader.uniform_texture('u_planet.normalMap', 1)
2018-08-26 04:42:46 -04:00
shader.uniform_bool('u_planet.hasSpecular', self.specular_texture)
if self.specular_texture:
glActiveTexture(GL_TEXTURE2)
2018-08-26 14:15:12 -04:00
glBindTexture(GL_TEXTURE_2D, self.specular_texture)
2018-08-26 04:42:46 -04:00
shader.uniform_texture('u_planet.specularMap', 2)
2018-08-26 14:15:12 -04:00
shader.uniform_vec3('u_planet.specular', 1, 1, 1)
shader.uniform_float('u_planet.shininess', 10)
2018-08-26 04:42:46 -04:00
else:
shader.uniform_vec3('u_planet.specular', 0, 0, 0)
shader.uniform_float('u_planet.shininess', 0)
2018-08-26 03:38:26 -04:00
2018-08-26 04:06:09 -04:00
shader.uniform_bool('u_planet.hasEmission', self.emission_texture)
if self.emission_texture:
glActiveTexture(GL_TEXTURE3)
glBindTexture(GL_TEXTURE_2D, self.emission_texture)
shader.uniform_texture('u_planet.emissionMap', 3)
shader.uniform_vec3('u_planet.ambient', 0, 0, 0)
shader.uniform_vec3('u_planet.emission', 1, 1, 1)
else:
shader.uniform_vec3('u_planet.ambient', 1, 1, 1)
shader.uniform_vec3('u_planet.emission', 0, 0, 0)
2018-08-26 03:38:26 -04:00
shader.uniform_vec3('u_planet.diffuse', 1, 1, 1)
glBindBuffer(GL_ARRAY_BUFFER, self.sphere.vbo)
shader.vertex_attribute('a_normal', self.sphere.direction_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.direction_offset)
shader.vertex_attribute('a_tangent', self.sphere.tangent_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.tangent_offset)
shader.vertex_attribute('a_uv', self.sphere.uv_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.uv_offset)
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.sphere.vertex_count)
shader.deactivate_attributes()
glBindBuffer(GL_ARRAY_BUFFER, 0)
self.world.activate_shader(None)
glActiveTexture(GL_TEXTURE0)
2018-08-24 19:37:50 -04:00
2018-08-26 03:49:02 -04:00
def _draw_star(self):
shader = self.world.activate_shader('star')
shader.uniform_float('u_radius', self.radius)
2018-08-26 18:11:18 -04:00
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
2018-08-26 03:49:02 -04:00
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, self.texture)
shader.uniform_texture('u_emission', 0)
glBindBuffer(GL_ARRAY_BUFFER, self.sphere.vbo)
shader.vertex_attribute('a_normal', self.sphere.direction_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.direction_offset)
shader.vertex_attribute('a_uv', self.sphere.uv_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.uv_offset)
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.sphere.vertex_count)
shader.deactivate_attributes()
glBindBuffer(GL_ARRAY_BUFFER, 0)
self.world.activate_shader(None)
2018-08-26 03:38:26 -04:00
def _draw_sphere(self):
if self.type == 'planet':
self._draw_planet()
2018-08-26 03:49:02 -04:00
elif self.type == 'star':
self._draw_star()
else:
raise ValueError('Invalid type: %s' % self.type)
def _draw_atmosphere(self):
with glRestore(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_TEXTURE_BIT):
mv = self.mv_matrix.matrix
matrix = Matrix4f([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, mv[12], mv[13], mv[14], 1])
glLoadMatrixf(matrix)
2018-08-24 20:57:48 -04:00
glDisable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
glDisable(GL_CULL_FACE)
2018-08-24 20:57:48 -04:00
glBindTexture(GL_TEXTURE_2D, self.atm_texture)
self.atmosphere.draw()
def _draw_clouds(self):
2018-08-26 18:11:18 -04:00
glEnable(GL_BLEND)
shader = self.world.activate_shader('clouds')
shader.uniform_float('u_radius', self.cloud_radius)
shader.uniform_mat4('u_modelMatrix', self.model_matrix)
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
2018-08-24 19:37:50 -04:00
2018-08-26 18:11:18 -04:00
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, self.cloud_transparency)
shader.uniform_texture('u_transparency', 0)
shader.uniform_vec3('u_diffuse', 1, 1, 1)
shader.uniform_vec3('u_ambient', 0.1, 0.1, 0.1)
glBindBuffer(GL_ARRAY_BUFFER, self.clouds.vbo)
shader.vertex_attribute('a_normal', self.clouds.direction_size, self.clouds.type, GL_FALSE,
self.clouds.stride, self.clouds.direction_offset)
shader.vertex_attribute('a_uv', self.clouds.uv_size, self.clouds.type, GL_FALSE,
self.clouds.stride, self.clouds.uv_offset)
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.clouds.vertex_count)
shader.deactivate_attributes()
glBindBuffer(GL_ARRAY_BUFFER, 0)
self.world.activate_shader(None)
glDisable(GL_BLEND)
def _draw_rings(self):
glEnable(GL_BLEND)
shader = self.world.activate_shader('ring')
shader.uniform_mat4('u_modelMatrix', self.model_matrix)
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
shader.uniform_vec3('u_planet', *self.location)
shader.uniform_vec3('u_sun', 0, 0, 0)
shader.uniform_float('u_planetRadius', self.radius)
shader.uniform_float('u_ambient', 0.1)
2018-08-24 20:57:48 -04:00
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, self.ring_texture)
shader.uniform_texture('u_texture', 0)
glBindBuffer(GL_ARRAY_BUFFER, self.ring.vbo)
shader.vertex_attribute('a_position', self.ring.position_size, self.ring.type, GL_FALSE,
self.ring.stride, self.ring.position_offset)
shader.vertex_attribute('a_u', self.ring.u_size, self.ring.type, GL_FALSE,
self.ring.stride, self.ring.u_offset)
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.ring.vertex_count)
shader.deactivate_attributes()
glBindBuffer(GL_ARRAY_BUFFER, 0)
self.world.activate_shader(None)
glDisable(GL_BLEND)
2018-08-24 14:44:11 -04:00
def _draw(self, options):
self._draw_sphere()
2018-08-24 20:57:48 -04:00
if options.atmosphere and self.atmosphere:
2018-08-24 14:44:11 -04:00
self._draw_atmosphere()
2018-08-24 19:37:50 -04:00
if options.cloud and self.clouds:
self._draw_clouds()
2018-08-24 20:57:48 -04:00
if self.ring:
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.vbo = WavefrontVBO(load_model(info['model']), info.get('sx', scale), info.get('sy', scale),
info.get('sz', scale))
2013-10-22 20:38:37 -04:00
2018-08-24 14:44:11 -04:00
def _draw(self, options):
glLoadMatrixf(self.mv_matrix)
self.vbo.draw()