Stop using OpenGL to do modelview matrices.

This commit is contained in:
Quantum 2018-08-25 17:44:00 -04:00
parent 8129edf3d8
commit c98a40cbe7
7 changed files with 147 additions and 48 deletions

View file

@ -1,5 +1,8 @@
from math import sin, cos, radians, hypot
from punyverse.glgeom import Matrix4f
from punyverse.utils import cached_property
class Camera(object):
def __init__(self, x=0, y=0, z=0, pitch=0, yaw=0, roll=0):
@ -20,6 +23,8 @@ class Camera(object):
self.y += dy * speed
self.z += dz * speed
self.view_matrix = None
def mouse_move(self, dx, dy):
if self.pitch > 90 or self.pitch < -90:
dx = -dx
@ -36,6 +41,8 @@ class Camera(object):
elif self.pitch > 180:
self.pitch -= 360
self.view_matrix = None
def direction(self):
m = cos(radians(self.pitch))
@ -46,14 +53,21 @@ class Camera(object):
def update(self, dt, move):
if self.roll_left:
self.roll += 4 * dt * 10
if self.roll:
self.roll -= 4 * dt * 10
self.view_matrix = None
if self.roll_right:
self.roll += 4 * dt * 10
self.view_matrix = None
if move:
self.move(self.speed * 10 * dt)
def reset_roll(self):
self.roll = 0
self.view_matrix = None
def distance(self, x, y, z):
return hypot(hypot(x - self.x, y - self.y), z - self.z)
@cached_property
def view_matrix(self):
return Matrix4f.from_angles((self.x, self.y, self.z), (self.pitch, self.yaw, self.roll), view=True)

View file

@ -5,10 +5,11 @@ from pyglet.gl import *
# noinspection PyUnresolvedReferences
from six.moves import range
from punyverse.glgeom import compile, glMatrix, glRestore, belt, Sphere, Disk, OrbitVBO
from punyverse.glgeom import compile, glMatrix, glRestore, belt, Sphere, Disk, OrbitVBO, Matrix4f
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
@ -16,13 +17,19 @@ G = 6.67384e-11 # Gravitation Constant
class Entity(object):
background = False
def __init__(self, name, location, rotation=(0, 0, 0), direction=(0, 0, 0)):
def __init__(self, world, name, location, rotation=(0, 0, 0), direction=(0, 0, 0)):
self.world = world
self.name = name
self.location = location
self.rotation = rotation
self.direction = direction
@cached_property
def mv_matrix(self):
return self.world.view_matrix() * Matrix4f.from_angles(self.location, self.rotation)
def update(self):
self.mv_matrix = None
x, y, z = self.location
dx, dy, dz = self.direction
self.location = x + dx, y + dy, z + dz
@ -35,24 +42,25 @@ class Entity(object):
class Asteroid(Entity):
def __init__(self, model, location, direction):
super(Asteroid, self).__init__('Asteroid', location, direction=direction)
def __init__(self, world, model, location, direction):
super(Asteroid, self).__init__(world, 'Asteroid', location, direction=direction)
self.model = model
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, options):
with glMatrix(self.location, self.rotation):
with glMatrix():
glLoadMatrixf(self.mv_matrix.as_gl())
self.model.draw()
class AsteroidManager(object):
def __init__(self):
def __init__(self, world):
self.world = world
self.asteroids = []
def __bool__(self):
@ -60,16 +68,14 @@ class AsteroidManager(object):
__nonzero__ = __bool__
def load(self, file):
self.asteroids.append(WavefrontVBO(load_model(file), 5, 5, 5, (0, 0, 0)))
self.asteroids.append(WavefrontVBO(load_model(file), 5, 5, 5))
def new(self, location, direction):
return Asteroid(random.choice(self.asteroids), location, direction)
return Asteroid(self.world, random.choice(self.asteroids), location, direction)
class Belt(Entity):
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))
@ -86,12 +92,12 @@ class Belt(Entity):
models = [models]
objects = [WavefrontVBO(load_model(model), info.get('sx', scale), info.get('sy', scale),
info.get('sz', scale), (0, 0, 0)) for model in models]
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__(name, (x, y, z), (inclination, longitude, argument))
super(Belt, self).__init__(world, name, (x, y, z), (inclination, longitude, argument))
def update(self):
super(Belt, self).update()
@ -99,7 +105,8 @@ class Belt(Entity):
self.rotation = pitch, self.world.tick * self.rotation_angle % 360, roll
def draw(self, options):
with glMatrix(self.location, self.rotation), glRestore(GL_CURRENT_BIT):
with glMatrix(), glRestore(GL_CURRENT_BIT):
glLoadMatrixf(self.mv_matrix.as_gl())
glCallList(self.belt_id)
@ -111,8 +118,7 @@ class Sky(Entity):
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
super(Sky, self).__init__(world, 'Sky', (0, 0, 0), (pitch, yaw, roll))
self.texture = get_best_texture(info['texture'])
division = info.get('division', 30)
@ -120,7 +126,9 @@ class Sky(Entity):
def draw(self, options):
cam = self.world.cam
with glMatrix((-cam.x, -cam.y, -cam.z), self.rotation), glRestore(GL_TEXTURE_BIT | GL_ENABLE_BIT):
with glMatrix(), glRestore(GL_TEXTURE_BIT | GL_ENABLE_BIT):
matrix = self.world.view_matrix() * Matrix4f.from_angles((-cam.x, -cam.y, -cam.z), self.rotation)
glLoadMatrixf(matrix.as_gl())
glEnable(GL_CULL_FACE)
glEnable(GL_TEXTURE_2D)
glDisable(GL_LIGHTING)
@ -132,7 +140,6 @@ class Sky(Entity):
class Body(Entity):
def __init__(self, name, world, info, parent=None):
self.world = world
self.parent = parent
self.satellites = []
@ -151,7 +158,7 @@ class Body(Entity):
self.orbit_blend = orbit_distance / 4
self.orbit_opaque = orbit_distance
super(Body, self).__init__(name, (x, y, z), (pitch, yaw, roll))
super(Body, self).__init__(world, name, (x, y, z), (pitch, yaw, roll))
self.initial_roll = roll
self.orbit = None
@ -180,9 +187,14 @@ class Body(Entity):
self.orbit_vbo = None
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
@ -191,6 +203,7 @@ class Body(Entity):
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()
@ -212,7 +225,9 @@ class Body(Entity):
return self.orbit_vbo
def _draw_orbits(self, distance):
with glMatrix(self.parent.location), glRestore(GL_ENABLE_BIT | GL_LINE_BIT | GL_CURRENT_BIT):
with glMatrix(), glRestore(GL_ENABLE_BIT | GL_LINE_BIT | GL_CURRENT_BIT):
glLoadMatrixf(self.parent.orbit_matrix.as_gl())
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))
@ -284,7 +299,8 @@ class SphericalBody(Body):
self.ring = Disk(distance, distance + size, 30)
def _draw_sphere(self, fv4=GLfloat * 4):
with glMatrix(self.location, self.rotation), glRestore(GL_LIGHTING_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT):
with glMatrix(), glRestore(GL_LIGHTING_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT):
glLoadMatrixf(self.mv_matrix.as_gl())
glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
@ -301,14 +317,11 @@ class SphericalBody(Body):
self.sphere.draw()
def _draw_atmosphere(self, glMatrixBuffer=GLfloat * 16):
with glMatrix(self.location), glRestore(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_TEXTURE_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)
def _draw_atmosphere(self):
with glMatrix(), 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.as_gl())
glDisable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D)
@ -319,7 +332,8 @@ class SphericalBody(Body):
self.atmosphere.draw()
def _draw_clouds(self):
with glMatrix(self.location, self.rotation), glRestore(GL_ENABLE_BIT | GL_TEXTURE_BIT):
with glMatrix(), glRestore(GL_ENABLE_BIT | GL_TEXTURE_BIT):
glLoadMatrixf(self.mv_matrix.as_gl())
glEnable(GL_BLEND)
glEnable(GL_ALPHA_TEST)
glEnable(GL_CULL_FACE)
@ -331,7 +345,8 @@ class SphericalBody(Body):
self.clouds.draw()
def _draw_rings(self):
with glMatrix(self.location, self.ring_rotation), glRestore(GL_ENABLE_BIT | GL_TEXTURE_BIT):
with glMatrix(), glRestore(GL_ENABLE_BIT | GL_TEXTURE_BIT):
glLoadMatrixf(self.mv_matrix.as_gl())
glDisable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND)
@ -365,8 +380,9 @@ class ModelBody(Body):
scale = info.get('scale', 1)
self.vbo = WavefrontVBO(load_model(info['model']), info.get('sx', scale), info.get('sy', scale),
info.get('sz', scale), (0, 0, 0))
info.get('sz', scale))
def _draw(self, options):
with glMatrix(self.location, self.rotation):
with glMatrix():
glLoadMatrixf(self.mv_matrix.as_gl())
self.vbo.draw()

View file

@ -99,6 +99,59 @@ def array_to_gl_buffer(buffer, array_type='f'):
return vbo.value
class Matrix4f(object):
def __init__(self, matrix):
self.matrix = array('f', matrix)
assert len(self.matrix) == 16
@classmethod
def from_angles(cls, location=(0, 0, 0), rotation=(0, 0, 0), view=False):
m = [0] * 16
x, y, z = location
pitch, yaw, roll = rotation
sp, sy, sr = sin(radians(pitch)), sin(radians(yaw)), sin(radians(roll))
cp, cy, cr = cos(radians(pitch)), cos(radians(yaw)), cos(radians(roll))
m[0x0] = cy * cr
m[0x1] = sp * sy * cr + cp * sr
m[0x2] = sp * sr - cp * sy * cr
m[0x3] = 0
m[0x4] = -cy * sr
m[0x5] = cp * cr - sp * sy * sr
m[0x6] = cp * sy * sr + sp * cr
m[0x7] = 0
m[0x8] = sy
m[0x9] = -sp * cy
m[0xA] = cp * cy
m[0xB] = 0
if view:
m[0xC] = m[0x0] * -x + m[0x4] * -y + m[0x8] * -z
m[0xD] = m[0x1] * -x + m[0x5] * -y + m[0x9] * -z
m[0xE] = m[0x2] * -x + m[0x6] * -y + m[0xA] * -z
else:
m[0xC] = x
m[0xD] = y
m[0xE] = z
m[0xF] = 1
return cls(m)
def as_gl(self):
return array_to_ctypes(self.matrix)
@property
def bytes(self):
return self.matrix.itemsize * 16
def __mul__(self, other):
if not isinstance(other, Matrix4f):
return NotImplemented
rows = ((0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15))
cols = ((0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11), (12, 13, 14, 15))
a, b = self.matrix, other.matrix
return type(self)(sum(a[i] * b[j] for i, j in zip(r, c)) for c in cols for r in rows)
def compile(pointer, *args, **kwargs):
display = glGenLists(1)
glNewList(display, GL_COMPILE)

View file

@ -9,7 +9,7 @@ from pyglet.gl import *
# noinspection PyUnresolvedReferences
from six.moves import range, zip
from punyverse.glgeom import array_to_gl_buffer, glRestoreClient, glMatrix, glRestore
from punyverse.glgeom import array_to_gl_buffer, glRestoreClient, glRestore
from punyverse.texture import load_texture
@ -221,10 +221,9 @@ class ModelVBO(object):
class WavefrontVBO(object):
def __init__(self, model, sx=1, sy=1, sz=1, rotation=(0, 0, 0)):
def __init__(self, model, sx=1, sy=1, sz=1):
self._tex_cache = {}
self.vbos = []
self.rotation = rotation
self.scale = (sx, sy, sz)
for m, material in six.iteritems(model.materials):
@ -239,7 +238,7 @@ class WavefrontVBO(object):
self.vbos.append((group.material, self.process_group(group, vertices, normals, textures)))
def draw(self, fv4=GLfloat * 4):
with glMatrix(rotation=self.rotation), glRestore(GL_TEXTURE_BIT | GL_ENABLE_BIT):
with glRestore(GL_TEXTURE_BIT | GL_ENABLE_BIT):
for mat, vbo in self.vbos:
tex_id = self._tex_cache[mat.texture] if mat and mat.texture else 0

View file

@ -236,14 +236,10 @@ class Punyverse(pyglet.window.Window):
def on_draw(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
glLoadMatrixf(self.world.view_matrix().as_gl())
c = self.world.cam
x, y, z = c.x, c.y, c.z
glRotatef(c.pitch, 1, 0, 0)
glRotatef(c.yaw, 0, 1, 0)
glRotatef(c.roll, 0, 0, 1)
glTranslatef(-x, -y, -z)
glEnable(GL_LIGHTING)
world = self.world

18
punyverse/utils.py Normal file
View file

@ -0,0 +1,18 @@
class cached_property(object):
def __init__(self, func, name=None):
self.func = func
self.name = name or func.__name__
self.__doc__ = getattr(func, '__doc__')
def __get__(self, instance, owner=None):
if instance is None:
return self
result = instance.__dict__[self.name] = self.func(instance)
return result
def __set__(self, instance, value):
if value is None:
if self.name in instance.__dict__:
del instance.__dict__[self.name]
else:
instance.__dict__[self.name] = value

View file

@ -23,7 +23,7 @@ class World(object):
self.z = None
self.tick_length = 0
self.tick = 0
self.asteroids = AsteroidManager()
self.asteroids = AsteroidManager(self)
self.cam = Camera()
self.callback = callback
@ -143,3 +143,6 @@ class World(object):
c.move(c.speed * 12 * dt)
else:
self._time_accumulate += delta
def view_matrix(self):
return self.cam.view_matrix