Converted models to VBO.

Note that this actually makes things slower... at least until we move to shaders.
This commit is contained in:
Quantum 2018-08-24 23:21:28 -04:00
parent 8b070726c3
commit a2620fc3bc
4 changed files with 139 additions and 92 deletions

View file

@ -6,7 +6,7 @@ from pyglet.gl import *
from six.moves import range from six.moves import range
from punyverse.glgeom import compile, glMatrix, glRestore, belt, Sphere, Disk from punyverse.glgeom import compile, glMatrix, glRestore, belt, Sphere, Disk
from punyverse.model import model_list, load_model 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
@ -35,9 +35,9 @@ class Entity(object):
class Asteroid(Entity): class Asteroid(Entity):
def __init__(self, asteroid_id, location, direction): def __init__(self, model, location, direction):
super(Asteroid, self).__init__('Asteroid', location, direction=direction) super(Asteroid, self).__init__('Asteroid', location, direction=direction)
self.asteroid_id = asteroid_id self.model = model
def update(self): def update(self):
super(Asteroid, self).update() super(Asteroid, self).update()
@ -47,8 +47,8 @@ class Asteroid(Entity):
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), glRestore(GL_CURRENT_BIT): with glMatrix(self.location, self.rotation):
glCallList(self.asteroid_id) self.model.draw()
class AsteroidManager(object): class AsteroidManager(object):
@ -60,7 +60,7 @@ class AsteroidManager(object):
__nonzero__ = __bool__ __nonzero__ = __bool__
def load(self, file): def load(self, file):
self.asteroids.append(model_list(load_model(file), 5, 5, 5, (0, 0, 0))) self.asteroids.append(WavefrontVBO(load_model(file), 5, 5, 5, (0, 0, 0)))
def new(self, location, direction): def new(self, location, direction):
return Asteroid(random.choice(self.asteroids), location, direction) return Asteroid(random.choice(self.asteroids), location, direction)
@ -85,8 +85,8 @@ class Belt(Entity):
if not isinstance(models, list): if not isinstance(models, list):
models = [models] models = [models]
objects = [model_list(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), (0, 0, 0)) 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
@ -373,9 +373,9 @@ class ModelBody(Body):
super(ModelBody, self).__init__(name, world, info, parent) super(ModelBody, self).__init__(name, world, info, parent)
scale = info.get('scale', 1) scale = info.get('scale', 1)
self.object_id = model_list(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), (0, 0, 0))
def _draw(self, options): def _draw(self, options):
with glMatrix(self.location, self.rotation), glRestore(GL_CURRENT_BIT): with glMatrix(self.location, self.rotation):
glCallList(self.object_id) self.vbo.draw()

View file

@ -1,5 +1,5 @@
from array import array from array import array
from ctypes import c_int, c_float, byref, cast, POINTER, c_uint from ctypes import c_int, c_float, byref, cast, POINTER, c_uint, c_short, c_ushort
from math import * from math import *
from random import random, gauss, choice from random import random, gauss, choice
@ -84,6 +84,8 @@ def array_to_ctypes(arr):
'f': c_float, 'f': c_float,
'i': c_int, 'i': c_int,
'I': c_uint, 'I': c_uint,
'h': c_short,
'H': c_ushort,
}[arr.typecode])) }[arr.typecode]))
@ -219,7 +221,7 @@ def belt(radius, cross, object, count):
if scale < 0: if scale < 0:
scale = 1 scale = 1
glScalef(scale, scale, scale) glScalef(scale, scale, scale)
glCallList(choice(object)) choice(object).draw()
def progress_bar(x, y, width, height, filled): def progress_bar(x, y, width, height, filled):

View file

@ -2,13 +2,14 @@ import bz2
import gzip import gzip
import os import os
import zipfile import zipfile
from uuid import uuid4 from collections import defaultdict
import six import six
from pyglet.gl import * 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.texture import load_texture from punyverse.texture import load_texture
@ -46,15 +47,11 @@ class Material(object):
class Group(object): class Group(object):
__slots__ = ('name', 'material', 'faces') __slots__ = ('material', 'faces')
def __init__(self, name=None): def __init__(self, material=None, faces=None):
if name is None: self.material = material
self.name = str(uuid4()) self.faces = faces or []
else:
self.name = name
self.material = None
self.faces = []
class WavefrontObject(object): class WavefrontObject(object):
@ -98,15 +95,13 @@ class WavefrontObject(object):
def texture(self, words): def texture(self, words):
l = len(words) l = len(words)
x, y, z = 0, 0, 0 u, v = 0, 0
if l >= 2: if l >= 2:
x = float(words[1]) u = float(words[1])
if l >= 3: if l >= 3:
# OBJ origin is at upper left, OpenGL origin is at lower left # OBJ origin is at upper left, OpenGL origin is at lower left
y = 1 - float(words[2]) v = 1 - float(words[2])
if l >= 4: self.textures.append((u, v))
z = float(words[3])
self.textures.append((x, y, z))
def face(self, words): def face(self, words):
l = len(words) l = len(words)
@ -153,7 +148,7 @@ class WavefrontObject(object):
print("Warning: no group") print("Warning: no group")
def group(self, words): def group(self, words):
group = Group(words[1].decode('utf-8')) group = Group()
self.groups.append(group) self.groups.append(group)
self.current_group = group self.current_group = group
@ -201,78 +196,128 @@ def load_model(path):
return WavefrontObject(path) return WavefrontObject(path)
def model_list(model, sx=1, sy=1, sz=1, rotation=(0, 0, 0)): class ModelVBO(object):
for m, text in six.iteritems(model.materials): __slots__ = ('has_normal', 'has_texture', 'data_buf', 'index_buf', 'offset_type', 'vertex_count')
if text.texture:
load_texture(os.path.join(model.root, text.texture))
display = glGenLists(1) def draw(self):
with glRestoreClient(GL_CLIENT_VERTEX_ARRAY_BIT):
stride = (3 + self.has_normal * 3 + self.has_texture * 2) * 4
glNewList(display, GL_COMPILE) glBindBuffer(GL_ARRAY_BUFFER, self.data_buf)
glPushMatrix() glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.index_buf)
glPushAttrib(GL_CURRENT_BIT)
pitch, yaw, roll = rotation glEnableClientState(GL_VERTEX_ARRAY)
glPushAttrib(GL_TRANSFORM_BIT) glVertexPointer(3, GL_FLOAT, stride, 0)
glRotatef(pitch, 1, 0, 0) if self.has_normal:
glRotatef(yaw, 0, 1, 0) glEnableClientState(GL_NORMAL_ARRAY)
glRotatef(roll, 0, 0, 1) glNormalPointer(GL_FLOAT, stride, 3 * 4)
glPopAttrib() if self.has_texture:
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glTexCoordPointer(3, GL_FLOAT, stride, (6 if self.has_normal else 3) * 4)
vertices = model.vertices glDrawElements(GL_TRIANGLES, self.vertex_count, self.offset_type, 0)
textures = model.textures glBindBuffer(GL_ARRAY_BUFFER, 0)
normals = model.normals glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
for g in model.groups:
material = g.material
tex_id = load_texture(os.path.join(model.root, material.texture)) if (material and material.texture) else 0 class WavefrontVBO(object):
def __init__(self, model, sx=1, sy=1, sz=1, rotation=(0, 0, 0)):
self._tex_cache = {}
self.vbos = []
self.rotation = rotation
self.scale = (sx, sy, sz)
if tex_id: for m, material in six.iteritems(model.materials):
glEnable(GL_TEXTURE_2D) if material.texture and material.texture not in self._tex_cache:
glBindTexture(GL_TEXTURE_2D, tex_id) self._tex_cache[material.texture] = load_texture(os.path.join(model.root, material.texture))
else:
glBindTexture(GL_TEXTURE_2D, 0)
glDisable(GL_TEXTURE_2D)
if material: vertices = model.vertices
fv4 = GLfloat * 4 textures = model.textures
normals = model.normals
if material.Ka: for group in self.merge_groups(model):
kx, ky, kz = material.Ka self.vbos.append((group.material, self.process_group(group, vertices, normals, textures)))
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, fv4(kx, ky, kz, 1))
if material.Kd:
kx, ky, kz = material.Kd
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, fv4(kx, ky, kz, 1))
if material.Ks:
kx, ky, kz = material.Ks
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(kx, ky, kz, 1))
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material.shininess)
def point(f, n, max_texture=len(textures)): def draw(self, fv4=GLfloat * 4):
normal = f.norms[n] with glMatrix(rotation=self.rotation), glRestore(GL_TEXTURE_BIT | GL_ENABLE_BIT):
if normal is not None: for mat, vbo in self.vbos:
glNormal3f(*normals[normal]) tex_id = self._tex_cache[mat.texture] if mat and mat.texture else 0
texture = f.texs[n]
if texture is not None and texture < max_texture:
glTexCoord2f(*textures[texture][:2])
x, y, z = vertices[f.verts[n]]
glVertex3f(x * sx, y * sy, z * sz)
glBegin(GL_TRIANGLES) if tex_id:
for f in g.faces: glEnable(GL_TEXTURE_2D)
for a, b in zip(range(1, f.size), range(2, f.size)): glBindTexture(GL_TEXTURE_2D, tex_id)
point(f, 0) else:
point(f, a) glBindTexture(GL_TEXTURE_2D, 0)
point(f, b) glDisable(GL_TEXTURE_2D)
glEnd()
if tex_id: if mat:
glBindTexture(GL_TEXTURE_2D, 0) if mat.Ka:
glDisable(GL_TEXTURE_2D) kx, ky, kz = mat.Ka
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, fv4(kx, ky, kz, 1))
if mat.Kd:
kx, ky, kz = mat.Kd
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, fv4(kx, ky, kz, 1))
if mat.Ks:
kx, ky, kz = mat.Ks
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(kx, ky, kz, 1))
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, mat.shininess)
glPopAttrib() vbo.draw()
glPopMatrix()
glEndList() def merge_groups(self, model):
return display by_mat = defaultdict(list)
for g in model.groups:
if g.faces:
by_mat[g.material].append(g)
groups = []
for mat, gs in six.iteritems(by_mat):
faces = []
for g in gs:
faces += g.faces
groups.append(Group(mat, faces))
return groups
def process_group(self, group, vertices, normals, textures):
sx, sy, sz = self.scale
max_texture = len(textures)
has_texture = bool(textures) and any(any(n is not None for n in f.texs) for f in group.faces)
has_normal = bool(normals) and any(any(n is not None for n in f.norms) for f in group.faces)
buffer = []
indices = []
offsets = {}
for f in group.faces:
verts = []
for v, n, t in zip(f.verts, f.norms, f.texs):
# Blender defines texture coordinates on faces even without textures.
if t is not None and t >= max_texture:
t = None
if (v, n, t) in offsets:
verts.append(offsets[v, n, t])
else:
index = len(offsets)
verts.append(index)
x, y, z = vertices[v]
item = [sx * x, sy * y, sz * z]
if has_normal:
item += [0, 0, 0] if n is None else list(normals[n])
if has_texture:
item += [0, 0] if t is None else list(textures[t])
offsets[v, n, t] = index
buffer += item
for a, b in zip(verts[1:], verts[2:]):
indices += [verts[0], a, b]
result = ModelVBO()
result.has_normal = has_normal
result.has_texture = has_texture
result.offset_type = GL_UNSIGNED_SHORT if len(offsets) < 65536 else GL_UNSIGNED_INT
result.data_buf = array_to_gl_buffer(buffer, 'f')
result.index_buf = array_to_gl_buffer(indices, {
GL_UNSIGNED_SHORT: 'H',
GL_UNSIGNED_INT: 'I',
}[result.offset_type])
result.vertex_count = len(indices)
return result

View file

@ -291,7 +291,7 @@
"radius": "2.362 * AU", "radius": "2.362 * AU",
"cross": 1000, "cross": 1000,
"scale": 30, "scale": 30,
"count": 1024, "count": 256,
"rotation": 114536500 "rotation": 114536500
} }
}, },