Stop using OpenGL to do modelview matrices.

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

View file

@ -1,5 +1,8 @@
from math import sin, cos, radians, hypot from math import sin, cos, radians, hypot
from punyverse.glgeom import Matrix4f
from punyverse.utils import cached_property
class Camera(object): class Camera(object):
def __init__(self, x=0, y=0, z=0, pitch=0, yaw=0, roll=0): 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.y += dy * speed
self.z += dz * speed self.z += dz * speed
self.view_matrix = None
def mouse_move(self, dx, dy): def mouse_move(self, dx, dy):
if self.pitch > 90 or self.pitch < -90: if self.pitch > 90 or self.pitch < -90:
dx = -dx dx = -dx
@ -36,6 +41,8 @@ class Camera(object):
elif self.pitch > 180: elif self.pitch > 180:
self.pitch -= 360 self.pitch -= 360
self.view_matrix = None
def direction(self): def direction(self):
m = cos(radians(self.pitch)) m = cos(radians(self.pitch))
@ -46,14 +53,21 @@ class Camera(object):
def update(self, dt, move): def update(self, dt, move):
if self.roll_left: if self.roll_left:
self.roll += 4 * dt * 10
if self.roll:
self.roll -= 4 * dt * 10 self.roll -= 4 * dt * 10
self.view_matrix = None
if self.roll_right:
self.roll += 4 * dt * 10
self.view_matrix = None
if move: if move:
self.move(self.speed * 10 * dt) self.move(self.speed * 10 * dt)
def reset_roll(self): def reset_roll(self):
self.roll = 0 self.roll = 0
self.view_matrix = None
def distance(self, x, y, z): def distance(self, x, y, z):
return hypot(hypot(x - self.x, y - self.y), z - self.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 # noinspection PyUnresolvedReferences
from six.moves import range 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.model import load_model, WavefrontVBO
from punyverse.orbit import KeplerOrbit from punyverse.orbit import KeplerOrbit
from punyverse.texture import get_best_texture, load_clouds from punyverse.texture import get_best_texture, load_clouds
from punyverse.utils import cached_property
G = 6.67384e-11 # Gravitation Constant G = 6.67384e-11 # Gravitation Constant
@ -16,13 +17,19 @@ G = 6.67384e-11 # Gravitation Constant
class Entity(object): class Entity(object):
background = False 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.name = name
self.location = location self.location = location
self.rotation = rotation self.rotation = rotation
self.direction = direction self.direction = direction
@cached_property
def mv_matrix(self):
return self.world.view_matrix() * Matrix4f.from_angles(self.location, self.rotation)
def update(self): def update(self):
self.mv_matrix = None
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
@ -35,24 +42,25 @@ class Entity(object):
class Asteroid(Entity): class Asteroid(Entity):
def __init__(self, model, location, direction): def __init__(self, world, model, location, direction):
super(Asteroid, self).__init__('Asteroid', location, direction=direction) super(Asteroid, self).__init__(world, 'Asteroid', location, direction=direction)
self.model = model self.model = model
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, options): def draw(self, options):
with glMatrix(self.location, self.rotation): with glMatrix():
glLoadMatrixf(self.mv_matrix.as_gl())
self.model.draw() self.model.draw()
class AsteroidManager(object): class AsteroidManager(object):
def __init__(self): def __init__(self, world):
self.world = world
self.asteroids = [] self.asteroids = []
def __bool__(self): def __bool__(self):
@ -60,16 +68,14 @@ class AsteroidManager(object):
__nonzero__ = __bool__ __nonzero__ = __bool__
def load(self, file): 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): 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): class Belt(Entity):
def __init__(self, name, world, info): def __init__(self, name, world, info):
self.world = world
x = world.evaluate(info.get('x', 0)) x = world.evaluate(info.get('x', 0))
y = world.evaluate(info.get('y', 0)) y = world.evaluate(info.get('y', 0))
z = world.evaluate(info.get('z', 0)) z = world.evaluate(info.get('z', 0))
@ -86,12 +92,12 @@ class Belt(Entity):
models = [models] models = [models]
objects = [WavefrontVBO(load_model(model), info.get('sx', scale), info.get('sy', scale), 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.belt_id = compile(belt, radius, cross, objects, count)
self.rotation_angle = 360.0 / rotation if rotation else 0 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): def update(self):
super(Belt, self).update() super(Belt, self).update()
@ -99,7 +105,8 @@ class Belt(Entity):
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, options): 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) glCallList(self.belt_id)
@ -111,8 +118,7 @@ class Sky(Entity):
yaw = world.evaluate(info.get('yaw', 0)) yaw = world.evaluate(info.get('yaw', 0))
roll = world.evaluate(info.get('roll', 0)) roll = world.evaluate(info.get('roll', 0))
super(Sky, self).__init__('Sky', (0, 0, 0), (pitch, yaw, roll)) super(Sky, self).__init__(world, 'Sky', (0, 0, 0), (pitch, yaw, roll))
self.world = world
self.texture = get_best_texture(info['texture']) self.texture = get_best_texture(info['texture'])
division = info.get('division', 30) division = info.get('division', 30)
@ -120,7 +126,9 @@ class Sky(Entity):
def draw(self, options): def draw(self, options):
cam = self.world.cam 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_CULL_FACE)
glEnable(GL_TEXTURE_2D) glEnable(GL_TEXTURE_2D)
glDisable(GL_LIGHTING) glDisable(GL_LIGHTING)
@ -132,7 +140,6 @@ class Sky(Entity):
class Body(Entity): class Body(Entity):
def __init__(self, name, world, info, parent=None): def __init__(self, name, world, info, parent=None):
self.world = world
self.parent = parent self.parent = parent
self.satellites = [] self.satellites = []
@ -151,7 +158,7 @@ class Body(Entity):
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__(name, (x, y, z), (pitch, yaw, roll)) super(Body, self).__init__(world, name, (x, y, z), (pitch, yaw, roll))
self.initial_roll = roll self.initial_roll = roll
self.orbit = None self.orbit = None
@ -180,9 +187,14 @@ class Body(Entity):
self.orbit_vbo = None self.orbit_vbo = None
self.orbit_cache = None self.orbit_cache = None
@cached_property
def orbit_matrix(self):
return self.world.view_matrix() * Matrix4f.from_angles(self.location)
def update(self): def update(self):
super(Body, self).update() super(Body, self).update()
if self.rotation_angle:
pitch, yaw, roll = self.rotation pitch, yaw, roll = self.rotation
roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360 roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360
self.rotation = pitch, yaw, roll self.rotation = pitch, yaw, roll
@ -191,6 +203,7 @@ class Body(Entity):
px, py, pz = self.parent.location px, py, pz = self.parent.location
x, z, y = self.orbit.orbit(self.world.tick * self.orbit_speed % 360) x, z, y = self.orbit.orbit(self.world.tick * self.orbit_speed % 360)
self.location = (x + px, y + py, z + pz) self.location = (x + px, y + py, z + pz)
self.orbit_matrix = None
for satellite in self.satellites: for satellite in self.satellites:
satellite.update() satellite.update()
@ -212,7 +225,9 @@ class Body(Entity):
return self.orbit_vbo return self.orbit_vbo
def _draw_orbits(self, distance): 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) glDisable(GL_LIGHTING)
solid = distance < self.parent.orbit_opaque solid = distance < self.parent.orbit_opaque
glColor4f(1, 1, 1, 1 if solid else (1 - (distance - self.parent.orbit_opaque) / self.parent.orbit_blend)) 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) self.ring = Disk(distance, distance + size, 30)
def _draw_sphere(self, fv4=GLfloat * 4): 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) glEnable(GL_CULL_FACE)
glCullFace(GL_BACK) glCullFace(GL_BACK)
@ -301,14 +317,11 @@ class SphericalBody(Body):
self.sphere.draw() self.sphere.draw()
def _draw_atmosphere(self, glMatrixBuffer=GLfloat * 16): def _draw_atmosphere(self):
with glMatrix(self.location), glRestore(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_TEXTURE_BIT): with glMatrix(), glRestore(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_TEXTURE_BIT):
matrix = glMatrixBuffer() mv = self.mv_matrix.matrix
glGetFloatv(GL_MODELVIEW_MATRIX, matrix) matrix = Matrix4f([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, mv[12], mv[13], mv[14], 1])
matrix[0: 3] = [1, 0, 0] glLoadMatrixf(matrix.as_gl())
matrix[4: 7] = [0, 1, 0]
matrix[8:11] = [0, 0, 1]
glLoadMatrixf(matrix)
glDisable(GL_LIGHTING) glDisable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D) glEnable(GL_TEXTURE_2D)
@ -319,7 +332,8 @@ class SphericalBody(Body):
self.atmosphere.draw() self.atmosphere.draw()
def _draw_clouds(self): 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_BLEND)
glEnable(GL_ALPHA_TEST) glEnable(GL_ALPHA_TEST)
glEnable(GL_CULL_FACE) glEnable(GL_CULL_FACE)
@ -331,7 +345,8 @@ class SphericalBody(Body):
self.clouds.draw() self.clouds.draw()
def _draw_rings(self): 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) glDisable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D) glEnable(GL_TEXTURE_2D)
glEnable(GL_BLEND) glEnable(GL_BLEND)
@ -365,8 +380,9 @@ class ModelBody(Body):
scale = info.get('scale', 1) scale = info.get('scale', 1)
self.vbo = WavefrontVBO(load_model(info['model']), info.get('sx', scale), info.get('sy', scale), 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): def _draw(self, options):
with glMatrix(self.location, self.rotation): with glMatrix():
glLoadMatrixf(self.mv_matrix.as_gl())
self.vbo.draw() self.vbo.draw()

View file

@ -99,6 +99,59 @@ def array_to_gl_buffer(buffer, array_type='f'):
return vbo.value 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): def compile(pointer, *args, **kwargs):
display = glGenLists(1) display = glGenLists(1)
glNewList(display, GL_COMPILE) glNewList(display, GL_COMPILE)

View file

@ -9,7 +9,7 @@ from pyglet.gl import *
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from six.moves import range, zip 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 from punyverse.texture import load_texture
@ -221,10 +221,9 @@ class ModelVBO(object):
class WavefrontVBO(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._tex_cache = {}
self.vbos = [] self.vbos = []
self.rotation = rotation
self.scale = (sx, sy, sz) self.scale = (sx, sy, sz)
for m, material in six.iteritems(model.materials): 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))) self.vbos.append((group.material, self.process_group(group, vertices, normals, textures)))
def draw(self, fv4=GLfloat * 4): 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: for mat, vbo in self.vbos:
tex_id = self._tex_cache[mat.texture] if mat and mat.texture else 0 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): def on_draw(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity() glLoadMatrixf(self.world.view_matrix().as_gl())
c = self.world.cam c = self.world.cam
x, y, z = c.x, c.y, c.z 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) glEnable(GL_LIGHTING)
world = self.world 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.z = None
self.tick_length = 0 self.tick_length = 0
self.tick = 0 self.tick = 0
self.asteroids = AsteroidManager() self.asteroids = AsteroidManager(self)
self.cam = Camera() self.cam = Camera()
self.callback = callback self.callback = callback
@ -143,3 +143,6 @@ class World(object):
c.move(c.speed * 12 * dt) c.move(c.speed * 12 * dt)
else: else:
self._time_accumulate += delta self._time_accumulate += delta
def view_matrix(self):
return self.cam.view_matrix