Initial version.

Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
This commit is contained in:
Quantum 2013-10-22 20:38:37 -04:00
parent 457ce20a12
commit 355f156295
42 changed files with 105539 additions and 0 deletions

9
.gitignore vendored
View file

@ -34,3 +34,12 @@ nosetests.xml
.mr.developer.cfg .mr.developer.cfg
.project .project
.pydevproject .pydevproject
.idea
*.obj
*.html
*.exp
*.lib
punyverse/assets/textures/*_medium.*
punyverse/assets/textures/*_small.*
temp

1
punyverse/__init__.py Normal file
View file

@ -0,0 +1 @@

17
punyverse/__main__.py Normal file
View file

@ -0,0 +1,17 @@
#!/usr/bin/python
INITIAL_WIN_HEIGHT = 540
INITIAL_WIN_WIDTH = 700
WIN_TITLE = "Punyverse"
def main():
import pyglet
from punyverse.game import Applet
pyglet.options['shadow_window'] = False
Applet(width=INITIAL_WIN_WIDTH, height=INITIAL_WIN_HEIGHT, caption=WIN_TITLE, resizable=True, vsync=0)
pyglet.app.run()
if __name__ == '__main__':
main()

4
punyverse/__main__.pyw Normal file
View file

@ -0,0 +1,4 @@
from space_torus.__main__ import main
if __name__ == '__main__':
main()

1209
punyverse/_cyopengl.pxi Normal file

File diff suppressed because it is too large Load diff

3348
punyverse/_glgeom.c Normal file

File diff suppressed because it is too large Load diff

83
punyverse/_glgeom.pyx Normal file
View file

@ -0,0 +1,83 @@
from libc.math cimport sin, cos, sqrt
from libc.stdlib cimport malloc, free
from libc.string cimport memcpy
cimport cython
include "_cyopengl.pxi"
cdef float PI = 3.1415926535897932324626
cdef float TWOPI = PI * 2
cdef extern from "Python.h":
object PyBytes_FromStringAndSize(const char *s, Py_ssize_t len)
const char* PyBytes_AsString(bytes o)
@cython.cdivision(True)
cpdef torus(float major_radius, float minor_radius, int n_major, int n_minor, tuple material, int shininess=125):
'''
Torus function from the OpenGL red book.
'''
glPushAttrib(GL_CURRENT_BIT)
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, [material[0], material[1], material[2], material[3]])
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [1, 1, 1, 1])
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
assert n_major > 0 and n_minor > 0
assert minor_radius > 0 and major_radius > 0
cdef float major_s, minor_s
major_s = TWOPI / n_major
minor_s = TWOPI / n_minor
cdef float a0, a1, x0, y0, x1, y1, b, c, r, z, m, x, y, z2
cdef int i, j
for i in xrange(n_major):
a0 = i * major_s
a1 = a0 + major_s
x0 = cos(a0)
y0 = sin(a0)
x1 = cos(a1)
y1 = sin(a1)
glBegin(GL_TRIANGLE_STRIP)
for j in xrange(n_minor + 1):
b = j * minor_s
c = cos(b)
r = minor_radius * c + major_radius
z = minor_radius * sin(b)
x = x0 * c
y = y0 * c
z2 = z / minor_radius
m = 1.0 / sqrt(x * x + y * y + z2 * z2)
glNormal3f(x * m, y * z, z2 * m)
glVertex3f(x0 * r, y0 * r, z)
x = x1 * c
y = y1 * c
m = 1.0 / sqrt(x * x + y * y + z2 * z2)
glNormal3f(x * m, y * z, z2 * m)
glVertex3f(x1 * r, y1 * r, z)
glEnd()
glPopAttrib()
cpdef bytes bgr_to_rgb(bytes buffer, int width, int height, bint alpha=0, bint bottom_up=1):
cdef int length = len(buffer)
cdef int depth = length / (width * height)
cdef int depth2 = depth - alpha
cdef char *result = <char*>malloc(length)
cdef const char *source = PyBytes_AsString(buffer)
cdef int x, y, ioffset, ooffset, i, row = width * depth
for y in xrange(height):
for x in xrange(width):
ioffset = y * width * depth + x * depth
ooffset = (height - y - 1 if bottom_up else y) * row + x * depth
for i in xrange(depth2):
result[ooffset+i] = source[ioffset+depth2-i-1]
if alpha:
result[ooffset+depth2] = source[ioffset+depth2]
cdef object final = PyBytes_FromStringAndSize(result, length)
free(result)
return final

12675
punyverse/_model.c Normal file

File diff suppressed because it is too large Load diff

345
punyverse/_model.pyx Normal file
View file

@ -0,0 +1,345 @@
from libc.string cimport strcmp, strlen
from libc.stdlib cimport malloc, free, atof
from libc.stdio cimport fopen, fclose, fgets, FILE
cimport cython
from texture import load_texture
include "_cyopengl.pxi"
from uuid import uuid4
import os
cdef enum:
FACE_TRIANGLES
FACE_QUADS
cdef class Face(object):
cdef public int type
cdef public list verts, norms, texs, vertices, normals, textures
def __init__(self, int type, list verts, list norms, list texs,
list vertices, list normals, list textures):
self.type = type
self.verts = verts
self.norms = norms
self.texs = texs
self.vertices = vertices
self.normals = normals
self.textures = textures
cdef class Material(object):
cdef public str name, texture
cdef public tuple Ka, Kd, Ks
cdef public double shininess
def __init__(self, str name, str texture=None, tuple Ka=(0, 0, 0),
tuple Kd=(0, 0, 0), tuple Ks=(0, 0, 0), double shininess=0.0):
self.name = name
self.texture = texture
self.Ka = Ka
self.Kd = Kd
self.Ks = Ks
self.shininess = shininess
cdef class Group(object):
cdef public str name
cdef public tuple min
cdef public Material material
cdef public list faces, indices, vertices, normals, textures
cdef public int idx_count
def __init__(self, str name=None):
if name is None:
self.name = str(uuid4())
else:
self.name = name
self.min = ()
self.material = None
self.faces = []
self.indices = []
self.vertices = []
self.normals = []
self.textures = []
self.idx_count = 0
def pack(self):
min_x, min_y, min_z = 0, 0, 0
for face in self.faces:
for x, y, z in face.vertices:
min_x = max(min_x, abs(x))
min_y = max(min_y, abs(y))
min_z = max(min_x, abs(z))
self.min = (min_x, min_y, min_z)
cdef class WavefrontObject(object):
cdef unicode root
cdef public list vertices, normals, textures, groups
cdef public dict materials
cdef unicode path
cdef Material current_material
cdef Group current_group
def __init__(self, unicode path):
self.path = path
self.root = os.path.dirname(path)
self.vertices = []
self.normals = []
self.textures = []
self.groups = []
self.materials = {}
self.perform_io(self.path)
cdef void new_material(self, list words):
material = Material(words[1])
self.materials[words[1]] = material
self.current_material = material
cdef void Ka(self, list words):
self.current_material.Ka = (float(words[1]), float(words[2]), float(words[3]))
cdef void Kd(self, list words):
self.current_material.Kd = (float(words[1]), float(words[2]), float(words[3]))
cdef void Ks(self, list words):
self.current_material.Ks = (float(words[1]), float(words[2]), float(words[3]))
cdef void material_shininess(self, list words):
self.current_material.shininess = min(float(words[1]), 125)
cdef void material_texture(self, list words):
self.current_material.texture = words[-1]
@cython.nonecheck(False)
cdef void vertex(self, list words):
self.vertices.append((float(words[1]), float(words[2]), float(words[3])))
@cython.nonecheck(False)
cdef void normal(self, list words):
self.normals.append((float(words[1]), float(words[2]), float(words[3])))
cdef void texture(self, list words):
cdef int l = len(words)
cdef object x = 0, y = 0, z = 0
if l >= 2:
x = float(words[1])
if l >= 3:
# OBJ origin is at upper left, OpenGL origin is at lower left
y = 1 - float(words[2])
if l >= 4:
z = float(words[3])
self.textures.append((x, y, z))
cdef void face(self, list words):
cdef int l = len(words)
cdef int type = -1
cdef int vertex_count = l - 1
cdef list face_vertices = [], face_normals = [], face_textures = []
if vertex_count == 3:
type = FACE_TRIANGLES
else:
type = FACE_QUADS
cdef int current_value = -1, texture_len = len(self.textures)
cdef list raw_faces, vindices = [], nindices = [], tindices = []
for i in xrange(1, vertex_count + 1):
raw_faces = words[i].split('/')
l = len(raw_faces)
current_value = int(raw_faces[0])
vindices.append(current_value - 1)
face_vertices.append(self.vertices[current_value - 1])
if l == 1:
continue
if l >= 2 and raw_faces[1]:
current_value = int(raw_faces[1])
if current_value <= texture_len:
tindices.append(current_value - 1)
face_textures.append(self.textures[current_value - 1])
if l >= 3 and raw_faces[2]:
current_value = int(raw_faces[2])
nindices.append(current_value - 1)
face_normals.append(self.normals[current_value - 1])
cdef Group group
if self.current_group is None:
self.current_group = group = Group()
self.groups.append(group)
else:
group = self.current_group
group.vertices += face_vertices
group.normals += face_normals
group.textures += face_textures
idx_count = group.idx_count
group.indices += (idx_count + 1, idx_count + 2, idx_count + 3)
group.idx_count += 3
group.faces.append(Face(type, vindices, nindices, tindices, face_vertices, face_normals, face_textures))
cdef void material(self, list words):
self.perform_io(os.path.join(self.root, words[1]))
cdef void use_material(self, list words):
mat = words[1]
try:
self.current_group.material = self.materials[mat]
except KeyError:
print "Warning: material %s undefined, only %s defined." % (mat, self.materials)
except AttributeError:
print "Warning: no group"
cdef void group(self, list words):
name = words[1]
group = Group(name)
if self.groups:
self.current_group.pack()
self.groups.append(group)
self.current_group = group
cdef inline void perform_io(self, unicode file):
mbcsfile = file.encode('mbcs')
cdef char* fname = mbcsfile
cdef FILE* cfile
cfile = fopen(fname, 'rb')
if cfile == NULL:
raise IOError(2, "No such file or directory: '%s'" % file)
cdef size_t bufsize = 2048
cdef char *buf = <char*>malloc(bufsize)
cdef ssize_t read
cdef char *type
cdef list words
cdef int hash, length
while fgets(buf, bufsize, cfile):
if buf[0] in (0, 10, 13, 35):
continue # Empty or comment
words = buf.split()
type = words[0]
length = strlen(type)
if not length:
continue
elif length < 3:
hash = type[0] << 8 | type[1]
if hash == 0x7600: # v\0
self.vertex(words)
elif hash == 0x766e: # vn
self.normal(words)
elif hash == 0x7674: # vt
self.texture(words)
elif hash == 0x6600: # f
self.face(words)
elif hash == 0x6700: # g
self.group(words)
elif hash == 0x6f00: # o
self.group(words)
elif hash == 0x4b61: # Ka
self.Ka(words)
elif hash == 0x4b64: # Kd
self.Kd(words)
elif hash == 0x4b73: # Ks
self.Ks(words)
elif hash == 0x4e73: # Ns
self.material_shininess(words)
elif strcmp(type, b'mtllib') == 0:
self.material(words)
elif strcmp(type, b'usemtl') == 0:
self.use_material(words)
elif strcmp(type, b'newmtl') == 0:
self.new_material(words)
elif strcmp(type, b'map_Kd') == 0:
self.material_texture(words)
free(buf)
fclose(cfile)
def load_model(path):
path = os.path.join(os.path.dirname(__file__), 'assets', 'models', path)
if not isinstance(path, unicode):
path = path.decode('mbcs')
return WavefrontObject(path)
@cython.nonecheck(False)
cdef inline void point(Face f, WavefrontObject m, int tex_id, float sx, float sy, float sz, int n):
cdef float x, y, z
cdef tuple normal, texture
if f.norms:
normal = m.normals[f.norms[n]]
glNormal3f(normal[0], normal[1], normal[2])
if tex_id:
texture = m.textures[f.texs[n]]
glTexCoord2f(texture[0], texture[1])
x, y, z = m.vertices[f.verts[n]]
glVertex3f(x * sx, y * sy, z * sz)
cpdef int model_list(WavefrontObject model, float sx=1, float sy=1, float sz=1, object rotation=(0, 0, 0)):
for m, text in model.materials.iteritems():
if text.texture:
load_texture(os.path.join(model.root, text.texture))
cdef int display = glGenLists(1)
glNewList(display, GL_COMPILE)
glPushMatrix()
glPushAttrib(GL_CURRENT_BIT)
cdef float pitch, yaw, roll
cdef float kx, ky, kz
pitch, yaw, roll = rotation
glPushAttrib(GL_TRANSFORM_BIT)
glRotatef(pitch, 1, 0, 0)
glRotatef(yaw, 0, 1, 0)
glRotatef(roll, 0, 0, 1)
glPopAttrib()
cdef Face f
cdef Group g
cdef int tex_id
for g in model.groups:
tex_id = 0
if tex_id:
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, tex_id)
else:
glBindTexture(GL_TEXTURE_2D, 0)
glDisable(GL_TEXTURE_2D)
if g.material is not None:
if g.material.texture is not None:
tex_id = load_texture(os.path.join(model.root, g.material.texture))
if g.material.Ka:
kx, ky, kz = g.material.Ka
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [kx, ky, kz, 1])
if g.material.Kd:
kx, ky, kz = g.material.Kd
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [kx, ky, kz, 1])
if g.material.Ks:
kx, ky, kz = g.material.Ks
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [kx, ky, kz, 1])
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, g.material.shininess)
glBegin(GL_TRIANGLES)
for f in g.faces:
point(f, model, tex_id, sx, sy, sz, 0)
point(f, model, tex_id, sx, sy, sz, 1)
point(f, model, tex_id, sx, sy, sz, 2)
if f.type == FACE_QUADS:
point(f, model, tex_id, sx, sy, sz, 2)
point(f, model, tex_id, sx, sy, sz, 3)
point(f, model, tex_id, sx, sy, sz, 0)
glEnd()
glPopAttrib()
glPopMatrix()
glEndList()
return display

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
# Blender MTL File: 'None'
# Material Count: 1
newmtl Material
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.520996 0.473531 0.337164
Ks 0.079916 0.069096 0.039636
Ni 1.000000
d 1.000000
illum 2

View file

@ -0,0 +1,110 @@
# Blender MTL File: 'None'
# Material Count: 12
newmtl Brown
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.238431 0.189074 0.102853
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl ISSDarkMetal
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.070138 0.070138 0.070138
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl ISSGray2
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.241098 0.241098 0.241098
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl ISSMetal1
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.381373 0.381373 0.381373
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl ISSSolarPanels
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.043620 0.023622 0.301177
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl ISSWhiteMetal
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.561093 0.561093 0.561093
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl _Brown
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.238431 0.189074 0.102853
Ks 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl _ISSDarkMetal
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.070138 0.070138 0.070138
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl _ISSGray2
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.241098 0.241098 0.241098
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl _ISSMetal1
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.381373 0.381373 0.381373
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl _ISSSolarPanels
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.043620 0.023622 0.301177
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2
newmtl _ISSWhiteMetal
Ns 25
Ka 0.000000 0.000000 0.000000
Kd 0.561093 0.561093 0.561093
Ks 0.500000 0.500000 0.500000
Ni 1.000000
d 1.000000
illum 2

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

47
punyverse/camera.py Normal file
View file

@ -0,0 +1,47 @@
from math import *
class Camera(object):
def __init__(self, x=0, y=0, z=0, pitch=0, yaw=0, roll=0):
self.x = x
self.y = y
self.z = z
self.pitch = pitch
self.yaw = yaw
self.roll = roll
def move(self, dx, dy, dz):
pitch = self.pitch
yaw = self.yaw
if pitch > 90 or pitch < -90:
pitch = -pitch
dx = -dx
dy = -dy
dz = -dz
self.z += dx * cos(radians(yaw - 90)) + dz * cos(radians(yaw))
self.x -= dx * sin(radians(yaw - 90)) + dz * sin(radians(yaw))
self.y += dy * sin(radians(pitch - 90)) + dz * sin(radians(pitch))
def mouse_move(self, dx, dy):
if self.pitch > 90 or self.pitch < -90:
dx = -dx
if self.yaw + dx >= 360:
self.yaw = self.yaw + dx - 360
elif self.yaw + dx < 0:
self.yaw = 360 - self.yaw + dx
else:
self.yaw += dx
self.pitch -= dy
if self.pitch < -180:
self.pitch += 360
elif self.pitch > 180:
self.pitch -= 360
def direction(self):
m = cos(radians(self.pitch))
dy = -sin(radians(self.pitch))
dx = cos(radians(self.yaw - 90)) * m
dz = sin(radians(self.yaw - 90)) * m
return dx, dy, dz

72
punyverse/entity.py Normal file
View file

@ -0,0 +1,72 @@
from punyverse import framedata
from math import radians, sin, cos
class Entity(object):
def __init__(self, id, location, rotation=(0, 0, 0), direction=(0, 0, 0), background=False):
self.id = id
self.location = location
self.rotation = rotation
self.direction = direction
self.background = background
def update(self):
x, y, z = self.location
dx, dy, dz = self.direction
self.location = x + dx, y + dy, z + dz
class Asteroid(Entity):
def __init__(self, *args, **kwargs):
super(Asteroid, self).__init__(*args, **kwargs)
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
class Planet(Entity):
def __init__(self, *args, **kwargs):
self.delta = kwargs.pop('delta', 5)
self.atmosphere = kwargs.pop('atmosphere', 0)
self.cloudmap = kwargs.pop('cloudmap', 0)
self.last_tick = 0
super(Planet, self).__init__(*args, **kwargs)
def update(self):
super(Planet, self).update()
if self.last_tick != framedata.tick:
self.last_tick = framedata.tick
pitch, yaw, roll = self.rotation
roll += self.delta / 100.0
self.rotation = pitch, yaw, roll
class Satellite(Planet):
def __init__(self, *args, **kwargs):
self.parent = kwargs.pop('parent')
self.orbit_speed = kwargs.pop('orbit_speed', 1)
self.inclination = kwargs.pop('inclination', 0)
self.distance = kwargs.pop('distance', 100)
self.theta = 0
super(Satellite, self).__init__(*args, **kwargs)
def update(self):
super(Planet, self).update()
if self.last_tick != framedata.tick:
self.last_tick = framedata.tick
pitch, yaw, roll = self.rotation
roll += self.delta / 100.0
self.rotation = pitch, yaw, roll
self.parent.update()
x, y, z = self.location
px, py, pz = self.parent.location
self.theta += self.orbit_speed
x = cos(radians(self.theta)) * self.distance + px
z = sin(radians(self.theta)) * self.distance + pz
self.location = (x, y, z)

1
punyverse/framedata.py Normal file
View file

@ -0,0 +1 @@
tick = 0

293
punyverse/game.py Normal file
View file

@ -0,0 +1,293 @@
#!/usr/bin/python
from operator import attrgetter
import sys
from camera import Camera
from widgets import *
try:
from _model import *
except ImportError:
from model import *
from world import *
import texture
try:
from pyglet.gl import *
from pyglet.gl.glu import *
from pyglet.window import key, mouse
import pyglet
except ImportError:
print "Pyglet not installed correctly, or at all."
sys.exit()
from punyverse.glgeom import *
from punyverse import framedata
from math import *
import time
import random
from time import clock
INITIAL_SPEED = 0 # The initial speed of the player
TICKS_PER_SECOND = 20 # How many times to update game per second
MOUSE_SENSITIVITY = 0.3 # Mouse sensitivity, 0..1, none...hyperspeed
MAX_DELTA = 5
SEED = int(time.time())
def entity_distance(x0, y0, z0):
def distance(entity):
x1, y1, z1 = entity.location
return hypot(hypot(x1 - x0, y1 - y0), z1 - z0)
return distance
class Applet(pyglet.window.Window):
def update(self, dt):
cam = self.cam
if self.exclusive:
if key.A in self.keys:
cam.roll += 4
if key.S in self.keys:
cam.roll -= 4
cam.move(0, 0, -self.speed * 128 * 0.003)
framedata.tick += 1
for entity in self.world.tracker:
entity.update()
def __init__(self, *args, **kwargs):
super(Applet, self).__init__(*args, **kwargs)
l = clock()
self.fps = 0
self.world = load_world("world.json")
self.speed = INITIAL_SPEED
self.keys = set()
self.info = True
self.debug = False
self.label = pyglet.text.Label('', font_name='Consolas', font_size=12, x=10, y=self.height - 20,
color=(255,) * 4, multiline=True, width=600)
self.cam = Camera()
self.exclusive = False
pyglet.clock.schedule_interval(self.update, 1.0 / TICKS_PER_SECOND)
def update_fps(dt):
self.fps = pyglet.clock.get_fps()
pyglet.clock.schedule_interval(update_fps, 1)
glClearColor(0, 0, 0, 1)
glClearDepth(1.0)
texture.init()
if not texture.badcard:
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_LINE_SMOOTH)
glEnable(GL_POLYGON_SMOOTH)
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST)
glAlphaFunc(GL_GEQUAL, 0.9)
glDepthFunc(GL_LEQUAL)
glEnable(GL_DEPTH_TEST)
glShadeModel(GL_SMOOTH)
glMatrixMode(GL_MODELVIEW)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_LIGHT1)
glEnable(GL_POLYGON_OFFSET_FILL)
fv4 = GLfloat * 4
glLightfv(GL_LIGHT0, GL_POSITION, fv4(.5, .5, 1, 0))
glLightfv(GL_LIGHT0, GL_SPECULAR, fv4(.5, .5, 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_DIFFUSE, fv4(.5, .5, .5, 1))
glLightfv(GL_LIGHT1, GL_SPECULAR, fv4(1, 1, 1, 1))
self.asteroid_ids = [model_list(load_model(r"asteroids\01.obj"), 5, 5, 5, (0, 0, 0)),
model_list(load_model(r"asteroids\02.obj"), 5, 5, 5, (0, 0, 0)),
model_list(load_model(r"asteroids\03.obj"), 5, 5, 5, (0, 0, 0)),
model_list(load_model(r"asteroids\04.obj"), 5, 5, 5, (0, 0, 0)),
model_list(load_model(r"asteroids\05.obj"), 5, 5, 5, (0, 0, 0))]
c = self.cam
c.x, c.y, c.z = self.world.start
c.pitch, c.yaw, c.roll = self.world.direction
for entity in self.world.tracker:
entity.update()
print "Loaded in %s seconds." % (clock() - l)
def set_exclusive_mouse(self, exclusive):
super(Applet, self).set_exclusive_mouse(exclusive) # Pass to parent
self.exclusive = exclusive # Toggle flag
def on_mouse_press(self, x, y, button, modifiers):
if not self.exclusive:
self.set_exclusive_mouse(True)
def on_mouse_motion(self, x, y, dx, dy):
if self.exclusive: # Only handle camera movement if mouse is grabbed
self.cam.mouse_move(dx * MOUSE_SENSITIVITY, dy * MOUSE_SENSITIVITY)
def on_key_press(self, symbol, modifiers):
if self.exclusive: # Only handle keyboard input if mouse is grabbed
if symbol == key.ESCAPE:
pyglet.app.exit()
elif symbol == key.E:
self.set_exclusive_mouse(False) # Escape mouse
elif symbol == key.F:
self.set_fullscreen(not self.fullscreen)
elif symbol == key.NUM_ADD:
self.speed += 1
elif symbol == key.NUM_SUBTRACT:
self.speed -= 1
elif symbol == key.NUM_MULTIPLY:
self.speed += 10
elif symbol == key.NUM_DIVIDE:
self.speed -= 10
elif symbol == key.PAGEUP:
self.speed += 100
elif symbol == key.PAGEDOWN:
self.speed -= 100
elif symbol == key.I:
self.info = not self.info
elif symbol == key.D:
self.debug = not self.debug
elif symbol == key.Q:
c = self.cam
dx, dy, dz = c.direction()
speed = max(1, abs(self.speed) * 0.6)
dx *= speed
dy *= speed
dz *= speed
self.world.tracker.append(
Asteroid(random.choice(self.asteroid_ids), (c.x, c.y - 3, c.z + 5), direction=(dx, dy, dz)))
else:
self.keys.add(symbol)
def on_key_release(self, symbol, modifiers):
if symbol in self.keys:
self.keys.remove(symbol)
def on_resize(self, width, height):
height = max(height, 1) # Prevent / by 0
self.label.y = height - 20
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
# A field of view of 45
gluPerspective(45.0, width / float(height), 0.1, 50000000.0)
glMatrixMode(GL_MODELVIEW)
def on_draw(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
c = self.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)
glEnable(GL_BLEND)
world = self.world
if x != world.x or y != world.y or z != world.z:
world.tracker.sort(key=entity_distance(x, y, z), reverse=True)
world.tracker.sort(key=attrgetter('background'), reverse=True)
world.x, world.y, world.z = x, y, z
for entity in world.tracker:
x, y, z = entity.location
pitch, yaw, roll = entity.rotation
glPushMatrix()
glTranslatef(x, y, z)
glRotatef(pitch, 1, 0, 0)
glRotatef(yaw, 0, 1, 0)
glRotatef(roll, 0, 0, 1)
glPushAttrib(GL_CURRENT_BIT)
glCallList(entity.id)
if self.debug:
glPushMatrix()
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)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
glEnable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D)
glPopMatrix()
glPopAttrib()
glPopMatrix()
if hasattr(entity, 'atmosphere') and entity.atmosphere:
glPushMatrix()
x0, y0, z0 = entity.location
dx, dy, dz = x - x0, y - y0, z - z0
distance = sqrt(dz * dz + dx * dx)
pitch = (360 - degrees(atan2(dy, distance)))
yaw = degrees(atan2(dx, dz))
glTranslatef(x0, y0, z0)
glRotatef(pitch, 1, 0, 0)
glRotatef(yaw, 0, 1, 0)
glCallList(entity.atmosphere)
glPopMatrix()
if hasattr(entity, "cloudmap") and entity.cloudmap:
glPushMatrix()
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)
glDisable(GL_ALPHA_TEST)
glPopMatrix()
glColor4f(1, 1, 1, 1)
glDisable(GL_TEXTURE_2D)
width, height = self.get_size()
if self.info:
ortho(width, height)
self.label.text = ('%d FPS @ (x=%.2f, y=%.2f, z=%.2f) @ %s\n'
'Direction(pitch=%.2f, yaw=%.2f, roll=%.2f)') % (
self.fps, c.x, c.y, c.z, self.speed, c.pitch, c.yaw, c.roll)
self.label.draw()
glPushAttrib(GL_CURRENT_BIT | GL_LINE_BIT)
glLineWidth(2)
cx, cy = width / 2, height / 2
glColor3f(0, 0, 1)
crosshair(15, (cx, cy))
glColor4f(0, 1, 0, 1)
circle(20, 30, (cx, cy))
glPopAttrib()
frustrum()

17
punyverse/gencheap.bat Normal file
View file

@ -0,0 +1,17 @@
@echo off
cd %~dp0assets\textures
call :convert earth.jpg earth_medium.jpg 2048x1024
call :convert earth.jpg earth_small.jpg 1024x512
call :convert moon.jpg moon_medium.jpg 2048x1024
call :convert moon.jpg moon_small.jpg 1024x512
call :convert mars.jpg mars_medium.jpg 2048x1024
call :convert mars.jpg mars_small.jpg 1024x512
call :convert jupiter.jpg jupiter_medium.jpg 2048x1024
call :convert jupiter.jpg jupiter_small.jpg 1024x512
call :convert saturn.jpg saturn_medium.jpg 2048x1024
call :convert saturn.jpg saturn_small.jpg 1024x512
goto :eof
:convert
echo Converting %1 to %2, size %3...
if not exist %2 gm convert %1 -resize %3 %2

166
punyverse/glgeom.py Normal file
View file

@ -0,0 +1,166 @@
from math import *
from pyglet.gl import *
from pyglet.gl.glu import *
TWOPI = pi * 2
def compile(pointer, *args, **kwargs):
display = glGenLists(1)
glNewList(display, GL_COMPILE)
pointer(*args, **kwargs)
glEndList()
return display
def ortho(width, height):
glDisable(GL_LIGHTING)
glDisable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glOrtho(0, width, 0, height, -1, 1)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
def frustrum():
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_LIGHTING)
glEnable(GL_DEPTH_TEST)
def crosshair(size, (cx, cy)):
glBegin(GL_LINES)
glVertex2f(cx - size, cy)
glVertex2f(cx + size, cy)
glVertex2f(cx, cy - size)
glVertex2f(cx, cy + size)
glEnd()
def circle(r, seg, (cx, cy)):
glBegin(GL_LINE_LOOP)
for i in xrange(seg):
theta = TWOPI * i / seg
glVertex2f(cx + cos(theta) * r, cy + sin(theta) * r)
glEnd()
def disk(rinner, router, segs, tex):
glEnable(GL_TEXTURE_2D)
glDisable(GL_LIGHTING)
glBindTexture(GL_TEXTURE_2D, tex)
res = segs * 5
glBegin(GL_TRIANGLE_STRIP)
texture = 0
factor = TWOPI / res
theta = 0
for n in xrange(res + 1):
theta += factor
x = cos(theta)
y = sin(theta)
glTexCoord2f(0, texture)
glVertex2f(rinner * x, rinner * y)
glTexCoord2f(1, texture)
glVertex2f(router * x, router * y)
texture ^= 1
glEnd()
glEnable(GL_LIGHTING)
glDisable(GL_TEXTURE_2D)
def sphere(r, lats, longs, tex, lighting=True, fv4=GLfloat * 4):
'''
Sphere function from the OpenGL red book.
'''
sphere = gluNewQuadric()
gluQuadricDrawStyle(sphere, GLU_FILL)
gluQuadricTexture(sphere, True)
if lighting:
gluQuadricNormals(sphere, GLU_SMOOTH)
glEnable(GL_TEXTURE_2D)
if lighting:
glDisable(GL_BLEND)
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fv4(1, 1, 1, 0))
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(1, 1, 1, 0))
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 125)
else:
glDisable(GL_LIGHTING)
glBindTexture(GL_TEXTURE_2D, tex)
gluSphere(sphere, r, lats, longs)
glBindTexture(GL_TEXTURE_2D, 0)
glDisable(GL_TEXTURE_2D)
glEnable(GL_LIGHTING)
glEnable(GL_BLEND)
gluDeleteQuadric(sphere)
def colourball(r, lats, longs, colour, fv4=GLfloat * 4):
'''
Sphere function from the OpenGL red book.
'''
sphere = gluNewQuadric()
glDisable(GL_BLEND)
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fv4(*colour))
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(1, 1, 1, 1))
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 125)
gluSphere(sphere, r, lats, longs)
glEnable(GL_BLEND)
gluDeleteQuadric(sphere)
try:
from _glgeom import torus
except ImportError:
def torus(major_radius, minor_radius, n_major, n_minor, material, shininess=125, fv4=GLfloat * 4):
'''
Torus function from the OpenGL red book.
'''
glPushAttrib(GL_CURRENT_BIT)
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fv4(*material))
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(1, 1, 1, 1))
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
major_s = TWOPI / n_major
minor_s = TWOPI / n_minor
def n(x, y, z):
m = 1.0 / sqrt(x * x + y * y + z * z)
return x * m, y * m, z * m
for i in xrange(n_major):
a0 = i * major_s
a1 = a0 + major_s
x0 = cos(a0)
y0 = sin(a0)
x1 = cos(a1)
y1 = sin(a1)
glBegin(GL_TRIANGLE_STRIP)
for j in xrange(n_minor + 1):
b = j * minor_s
c = cos(b)
r = minor_radius * c + major_radius
z = minor_radius * sin(b)
glNormal3f(*n(x0 * c, y0 * c, z / minor_radius))
glVertex3f(x0 * r, y0 * r, z)
glNormal3f(*n(x1 * c, y1 * c, z / minor_radius))
glVertex3f(x1 * r, y1 * r, z)
glEnd()
glPopAttrib()

288
punyverse/model.py Normal file
View file

@ -0,0 +1,288 @@
from time import clock
import os.path
from pyglet.gl import *
from punyverse.texture import load_texture
FACE_TRIANGLES = 0
FACE_QUADS = 1
def model_list(model, sx=1, sy=1, sz=1, rotation=(0, 0, 0)):
for m, text in model.materials.iteritems():
if text.texture:
load_texture(os.path.join(model.root, text.texture))
display = glGenLists(1)
glNewList(display, GL_COMPILE)
glPushMatrix()
glPushAttrib(GL_CURRENT_BIT)
pitch, yaw, roll = rotation
glPushAttrib(GL_TRANSFORM_BIT)
glRotatef(pitch, 1, 0, 0)
glRotatef(yaw, 0, 1, 0)
glRotatef(roll, 0, 0, 1)
glPopAttrib()
vertices = model.vertices
textures = model.textures
normals = model.normals
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
if tex_id:
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, tex_id)
else:
glBindTexture(GL_TEXTURE_2D, 0)
glDisable(GL_TEXTURE_2D)
if material:
fv4 = GLfloat * 4
if material.Ka:
kx, ky, kz = material.Ka
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)
type = -1
def point(f, vertices, normals, textures, n):
if f.norms:
glNormal3f(*normals[f.norms[n]])
if tex_id:
glTexCoord2f(*textures[f.texs[n]])
x, y, z = vertices[f.verts[n]]
glVertex3f(x * sx, y * sy, z * sz)
for f in g.faces:
if type != f.type:
if type != -1:
glEnd()
glBegin(GL_TRIANGLES)
type = f.type
point(f, vertices, normals, textures, 0)
point(f, vertices, normals, textures, 1)
point(f, vertices, normals, textures, 2)
if type == FACE_QUADS:
point(f, vertices, normals, textures, 2)
point(f, vertices, normals, textures, 3)
point(f, vertices, normals, textures, 0)
glEnd()
glPopAttrib()
glPopMatrix()
glEndList()
return display
def load_model(path):
path = os.path.join(os.path.dirname(__file__), "assets", "models", path)
root = os.path.dirname(path)
vertices = []
normals = []
textures = []
groups = []
materials = {}
# Allez au diable, Python
current_group = [None]
current_material = [None]
def dispatch(p, commands):
with open(p, 'r') as file:
for line in file:
line = line.strip()
if not line or line[0] == '#':
continue # Empty or comment
words = line.split()
type = words[0]
if type in commands:
commands[type](words)
def newmtl(words):
material = Material(words[1])
materials[words[1]] = material
current_material[0] = material
def Ka(words):
current_material[0].Ka = (float(words[1]), float(words[2]), float(words[3]))
def Kd(words):
current_material[0].Kd = (float(words[1]), float(words[2]), float(words[3]))
def Ks(words):
current_material[0].Ks = (float(words[1]), float(words[2]), float(words[3]))
def Ns(words):
current_material[0].shininess = min(float(words[1]), 125) # Seems to sometimes be > 125. TODO: find out why
def map_Kd(words):
current_material[0].texture = words[-1]
def v(words):
vertices.append((float(words[1]), float(words[2]), float(words[3])))
def vn(words):
normals.append((float(words[1]), float(words[2]), float(words[3])))
def vt(words):
l = len(words)
x, y, z = 0, 0, 0
if l >= 2:
x = float(words[1])
if l >= 3:
# OBJ origin is at upper left, OpenGL origin is at lower left
y = 1 - float(words[2])
if l >= 4:
z = float(words[3])
textures.append((x, y, z))
def f(words):
l = len(words)
type = -1
face_vertices = []
face_normals = []
face_textures = []
vertex_count = l - 1
if vertex_count == 3:
type = FACE_TRIANGLES
else:
type = FACE_QUADS
raw_faces = []
current_value = -1
vindices = []
nindices = []
tindices = []
for i in xrange(1, vertex_count + 1):
raw_faces = words[i].split('/')
l = len(raw_faces)
current_value = int(raw_faces[0])
vindices.append(current_value - 1)
face_vertices.append(vertices[current_value - 1])
if l == 1:
continue
if l >= 2 and raw_faces[1]:
current_value = int(raw_faces[1])
if current_value <= len(textures):
tindices.append(current_value - 1)
face_textures.append(textures[current_value - 1])
if l >= 3 and raw_faces[2]:
current_value = int(raw_faces[2])
nindices.append(current_value - 1)
face_normals.append(normals[current_value - 1])
if not groups:
group = Group()
groups.append(group)
else:
group = groups[-1]
group.vertices += face_vertices
group.normals += face_normals
group.textures += face_textures
idx_count = group.idx_count
group.indices += (idx_count + 1, idx_count + 2, idx_count + 3)
group.idx_count += 3
group.faces.append(Face(type, vindices, nindices, tindices, face_vertices, face_normals, face_textures))
mtllib = lambda words: dispatch(os.path.join(root, words[1]), obj)
def usemtl(words):
mat = words[1]
if mat in materials:
groups[-1].material = materials[mat]
else:
print "Warning: material %s undefined." % mat
g = lambda words: groups.append(Group(words[1]))
obj = {
'v': v,
'vn': vn,
'vt': vt,
'f': f,
'mtllib': mtllib,
'usemtl': usemtl,
'g': g,
'o': g, # TODO: o is not really g
'newmtl': newmtl,
'Ka': Ka,
'Kd': Kd,
'Ks': Ks,
'Ns': Ns,
'map_Kd': map_Kd
}
dispatch(path, obj)
return WavefrontObject(root, vertices, normals, textures, groups, materials)
class WavefrontObject(object):
def __init__(self, root, vertices=None, normals=None, textures=None, groups=None, materials=None):
self.root = root
self.vertices = vertices or []
self.normals = normals or []
self.textures = textures or []
self.groups = groups or []
self.materials = materials or []
class Group(object):
def __init__(self, name=None):
if not name:
name = clock()
self.name = name
self.material = None
self.faces = []
self.indices = []
self.vertices = []
self.normals = []
self.textures = []
self.idx_count = 0
class Face(object):
def __init__(self, type, verts, norms, texs, vertices, normals, textures):
self.type = type
self.verts = verts
self.norms = norms
self.texs = texs
self.vertices = vertices
self.normals = normals
self.textures = textures
class Material(object):
def __init__(self, name, texture=None, Ka=0, Kd=0, Ks=0, shininess=0.0):
self.name = name
self.texture = texture
self.Ka = Ka
self.Kd = Kd
self.Ks = Ks
self.shininess = shininess

305
punyverse/model.pyx Normal file
View file

@ -0,0 +1,305 @@
from time import clock
import os.path
include "_cyopengl.pxi"
from space_torus.texture import load_texture
cdef enum:
FACE_TRIANGLES
FACE_QUADS
cdef inline point(Face f, list vertices, list normals, list textures, float sx, float sy, float sz, int n, int tex_id):
if f.norms:
normal = normals[f.norms[n]]
glNormal3f(normal[0], normal[1], normal[2])
if tex_id:
tex = textures[f.texs[n]]
glTexCoord2f(tex[0], tex[1])
x, y, z = vertices[f.verts[n]]
glVertex3f(x * sx, y * sy, z * sz)
cpdef model_list(WavefrontObject model, float sx=1, float sy=1, float sz=1, object rotation=(0, 0, 0)):
for m, text in model.materials.iteritems():
if text.texture:
load_texture(os.path.join(model.root, text.texture))
display = glGenLists(1)
glNewList(display, GL_COMPILE)
glPushMatrix()
glPushAttrib(GL_CURRENT_BIT)
cdef float pitch, yaw, roll
cdef kx, ky, kz
pitch, yaw, roll = rotation
glPushAttrib(GL_TRANSFORM_BIT)
glRotatef(pitch, 1, 0, 0)
glRotatef(yaw, 0, 1, 0)
glRotatef(roll, 0, 0, 1)
glPopAttrib()
vertices = model.vertices
textures = model.textures
normals = model.normals
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
if tex_id:
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, tex_id)
else:
glBindTexture(GL_TEXTURE_2D, 0)
glDisable(GL_TEXTURE_2D)
if material:
if material.Ka:
kx, ky, kz = material.Ka
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [kx, ky, kz, 1])
if material.Kd:
kx, ky, kz = material.Kd
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [kx, ky, kz, 1])
if material.Ks:
kx, ky, kz = material.Ks
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [kx, ky, kz, 1])
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material.shininess)
type = -1
for f in g.faces:
if type != f.type:
if type != -1:
glEnd()
glBegin(GL_TRIANGLES)
type = f.type
point(f, vertices, normals, textures, sx, sy, sz, 0, tex_id)
point(f, vertices, normals, textures, sx, sy, sz, 1, tex_id)
point(f, vertices, normals, textures, sx, sy, sz, 2, tex_id)
if type == FACE_QUADS:
point(f, vertices, normals, textures, sx, sy, sz, 2, tex_id)
point(f, vertices, normals, textures, sx, sy, sz, 3, tex_id)
point(f, vertices, normals, textures, sx, sy, sz, 0, tex_id)
glEnd()
glPopAttrib()
glPopMatrix()
glEndList()
return display
def load_model(path):
print "Loading model %s..." % path
return WavefrontObject(os.path.join(os.path.dirname(__file__), 'assets', 'models', path))
cdef class WavefrontObject(object):
cdef public str root
cdef public list vertices, normals, textures, groups
cdef public dict materials
cdef Material current_material
cdef inline dispatch(self, str p):
with open(p, 'r') as file:
for line in file:
line = line.strip()
if not line or line[0] == '#':
continue # Empty or comment
words = line.split()
type = words[0]
if type == 'v':
self.v(words)
elif type == 'vn':
self.vn(words)
elif type == 'vt':
self.vt(words)
elif type == 'f':
self.f(words)
elif type == 'mtllib':
self.mtllib(words)
elif type == 'usemtl':
self.usemtl(words)
elif type == 'g' or type == 'o':
self.g(words)
elif type == 'newmtl':
self.newmtl(words)
elif type == 'Ka':
self.Ka(words)
elif type == 'Kd':
self.Kd(words)
elif type == 'Ks':
self.Ks(words)
elif type == 'Ns':
self.Ns(words)
elif type == 'map_Kd':
self.map_Kd(words)
cdef inline newmtl(self, list words):
material = Material(words[1])
self.materials[words[1]] = material
self.current_material = material
cdef inline Ka(self, list words):
self.current_material.Ka = (float(words[1]), float(words[2]), float(words[3]))
cdef inline Kd(self, list words):
self.current_material.Kd = (float(words[1]), float(words[2]), float(words[3]))
cdef inline Ks(self, list words):
self.current_material.Ks = (float(words[1]), float(words[2]), float(words[3]))
cdef inline Ns(self, list words):
self.current_material.shininess = min(float(words[1]), 125) # Seems to sometimes be > 125. TODO: find out why
cdef inline map_Kd(self, list words):
self.current_material.texture = words[-1]
cdef inline v(self, list words):
self.vertices.append((float(words[1]), float(words[2]), float(words[3])))
cdef inline vn(self, list words):
self.normals.append((float(words[1]), float(words[2]), float(words[3])))
cdef inline vt(self, list words):
l = len(words)
x, y, z = 0, 0, 0
if l >= 2:
x = float(words[1])
if l >= 3:
# OBJ origin is at upper left, OpenGL origin is at lower left
y = 1 - float(words[2])
if l >= 4:
z = float(words[3])
self.textures.append((x, y, z))
cdef inline f(self, list words):
l = len(words)
type = -1
cdef list face_vertices, face_normals, face_textures, raw_faces, vindices, nindices, tindicies
cdef int current_value
face_vertices = []
face_normals = []
face_textures = []
vertex_count = l - 1
if vertex_count == 3:
type = FACE_TRIANGLES
else:
type = FACE_QUADS
raw_faces = []
current_value = -1
vindices = []
nindices = []
tindices = []
for i in xrange(1, vertex_count + 1):
raw_faces = words[i].split('/')
l = len(raw_faces)
current_value = int(raw_faces[0])
vindices.append(current_value - 1)
face_vertices.append(self.vertices[current_value - 1])
if l == 1:
continue
if l >= 2 and raw_faces[1]:
current_value = int(raw_faces[1])
if current_value <= len(self.textures):
tindices.append(current_value - 1)
face_textures.append(self.textures[current_value - 1])
if l >= 3 and raw_faces[2]:
current_value = int(raw_faces[2])
nindices.append(current_value - 1)
face_normals.append(self.normals[current_value - 1])
if not self.groups:
group = Group()
self.groups.append(group)
else:
group = self.groups[-1]
group.vertices += face_vertices
group.normals += face_normals
group.textures += face_textures
idx_count = group.idx_count
group.indices += (idx_count + 1, idx_count + 2, idx_count + 3)
group.idx_count += 3
group.faces.append(Face(type, vindices, nindices, tindices, face_vertices, face_normals, face_textures))
cdef inline mtllib(self, list words):
self.dispatch(os.path.join(self.root, words[1]))
cdef inline usemtl(self, list words):
mat = words[1]
if mat in self.materials:
self.groups[-1].material = self.materials[mat]
else:
print "Warning: material %s undefined." % mat
cdef inline g(self, list words):
self.groups.append(Group(words[1]))
def __init__(self, str path):
self.root = os.path.dirname(path)
self.vertices = []
self.normals = []
self.textures = []
self.groups = []
self.materials = {}
self.dispatch(path)
cdef class Group(object):
cdef public str name
cdef public tuple min
cdef public Material material
cdef public list faces, indices, vertices, normals, textures
cdef public int idx_count
def __init__(self, str name=None):
if not name:
name = clock()
self.name = name
self.material = None
self.faces = []
self.indices = []
self.vertices = []
self.normals = []
self.textures = []
self.idx_count = 0
cdef class Face(object):
cdef public int type
cdef public list verts, norms, texs, vertices, normals, textures
def __init__(self, int type, list verts, list norms, list texs, list vertices, list normals, list textures):
self.type = type
self.verts = verts
self.norms = norms
self.texs = texs
self.vertices = vertices
self.normals = normals
self.textures = textures
cdef class Material(object):
cdef public str name, texture
cdef public tuple Ka, Kd, Ks
cdef public double shininess
def __init__(self, str name, str texture=None, tuple Ka=(0, 0, 0), tuple Kd=(0, 0, 0), tuple Ks=(0, 0, 0),
double shininess=0.0):
self.name = name
self.texture = texture
self.Ka = Ka
self.Kd = Kd
self.Ks = Ks
self.shininess = shininess

220
punyverse/texture.py Normal file
View file

@ -0,0 +1,220 @@
from pyglet import image
from pyglet.gl import *
from ctypes import c_int, byref, c_ulong
import os.path
import struct
try:
from _glgeom import bgr_to_rgb
except ImportError:
import warnings
warnings.warn('Compile _glgeom.c, or double the start up time.')
# Use magick when _glgeom is not compiled (is actually slower)
try:
from pgmagick import Blob, Image
except ImportError:
magick = False
else:
magick = True
def bgr_to_rgb(source, width, height, alpha=False, bottom_up=True):
length = len(source)
depth = length / (width * height)
depth2 = depth - alpha
result = bytearray(length)
row = width * depth
for y in xrange(height):
for x in xrange(width):
ioffset = y * width * depth + x * depth
ooffset = (height - y - 1 if bottom_up else y) * row + x * depth
for i in xrange(depth2):
result[ooffset+i] = source[ioffset+depth2-i-1]
if alpha:
result[ooffset+depth2] = source[ioffset+depth2]
return str(result)
else:
magick = False
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
__all__ = ['load_texture']
id = 0
cache = {}
max_texture = None
power_of_two = None
badcard = False
bgra = False
def init():
global max_texture, power_of_two, badcard, bgra, magick
if max_texture is None:
buf = c_int()
glGetIntegerv(GL_MAX_TEXTURE_SIZE, byref(buf))
max_texture = buf.value
badcard = gl_info.get_renderer() in ('GDI Generic',)
if badcard:
import warnings
warnings.warn('Please update your graphics drivers if possible')
#extensions = gl_info.get_extensions()
#bgra = 'GL_EXT_bgra' in extensions
#if bgra and magick:
# magick = False # Disable magick because BGRA needs it not
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')
is_power2 = lambda num: num != 0 and ((num & (num - 1)) == 0)
def image_info(data):
data = str(data)
size = len(data)
height = -1
width = -1
content_type = ''
# handle GIFs
if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
# Check to see if content_type is correct
content_type = 'image/gif'
w, h = struct.unpack("<HH", data[6:10])
width = int(w)
height = int(h)
# See PNG 2. Edition spec (http://www.w3.org/TR/PNG/)
# Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
# and finally the 4-byte width, height
elif ((size >= 24) and data.startswith('\211PNG\r\n\032\n')
and (data[12:16] == 'IHDR')):
content_type = 'image/png'
w, h = struct.unpack(">LL", data[16:24])
width = int(w)
height = int(h)
# Maybe this is for an older PNG version.
elif (size >= 16) and data.startswith('\211PNG\r\n\032\n'):
# Check to see if we have the right content type
content_type = 'image/png'
w, h = struct.unpack(">LL", data[8:16])
width = int(w)
height = int(h)
# handle JPEGs
elif (size >= 2) and data.startswith('\377\330'):
content_type = 'image/jpeg'
jpeg = StringIO(data)
jpeg.read(2)
b = jpeg.read(1)
try:
while b and ord(b) != 0xDA:
while ord(b) != 0xFF:
b = jpeg.read(1)
while ord(b) == 0xFF:
b = jpeg.read(1)
if 0xC0 <= ord(b) <= 0xC3:
jpeg.read(3)
h, w = struct.unpack(">HH", jpeg.read(4))
break
else:
jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
b = jpeg.read(1)
width = int(w)
height = int(h)
except struct.error:
pass
except ValueError:
pass
return content_type, width, height
def check_size(width, height):
init()
if width > max_texture or height > max_texture:
print 'too large'
raise ValueError('Texture too large')
elif not power_of_two:
if not is_power2(width) or not is_power2(height):
print 'not power of two'
raise ValueError('Texture not power of two')
def load_texture(file):
if file in cache:
return cache[file]
print "Loading image %s..." % file,
path = os.path.join(os.path.dirname(__file__), "assets", "textures", file)
try:
file = open(path, 'rb')
except IOError:
print 'exists not'
raise ValueError('Texture exists not')
type, width, height = image_info(file.read(8192))
file.seek(0, 0)
if type:
check_size(width, height)
if magick:
file.close()
file = Image(path.encode('mbcs' if os.name == 'nt' else 'utf8'))
geo = file.size()
check_size(geo.width(), geo.height())
print
blob = Blob()
file.flip()
file.write(blob, 'RGBA')
texture = blob.data
mode = GL_RGBA
else:
try:
raw = image.load(path, file=file)
except IOError:
print 'exists not'
raise ValueError('Texture exists not')
width, height = raw.width, raw.height
check_size(width, height)
print
mode = GL_RGBA if 'A' in raw.format else GL_RGB
mode2 = mode
# Flip from BGR to RGB
# I hate you too, Pyglet...
# REGULAR EXPRESSIONS ARE NOT MEANT TO PARSE BINARY DATA!!!
#texture = raw.get_data('RGBA', width * 4) if safe else raw.data[::-1] if 'BGR' in raw.format else raw.data
if raw.format in ('BGR', 'BGRA'):
if bgra:
mode2 = {GL_RGBA: GL_BGRA, GL_RGB: GL_BGR}[mode]
texture = raw.data
else:
texture = bgr_to_rgb(raw.data, width, height, 'A' in raw.format)
elif raw.format in ('RGB', 'RGBA'):
texture = raw.data
else:
texture = raw.get_data('RGBA', width * 4)
buffer = c_ulong()
glGenTextures(1, byref(buffer))
id = buffer.value
glBindTexture(GL_TEXTURE_2D, id)
filter = GL_NEAREST if badcard else GL_LINEAR
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter)
glTexImage2D(GL_TEXTURE_2D, 0, mode, width, height, 0, mode2, GL_UNSIGNED_BYTE, texture)
cache[file] = id
return id

33
punyverse/widgets.py Normal file
View file

@ -0,0 +1,33 @@
from pyglet.gl import *
VERTICAL = 0
HORIZONTAL = 1
def _progress_bar_vertices(x, y, w, h):
glColor3f(1, 1, 1)
glVertex2f(x, y)
glVertex2f(x + w, y)
glColor3f(0, 0, 1)
glVertex2f(x + w, y + h)
glVertex2f(x, y + h)
def progress_bar(x, y, width, height, progress, min=0, max=100, type=HORIZONTAL):
glPushAttrib(GL_CURRENT_BIT | GL_LINE_BIT)
glLineWidth(1)
glBegin(GL_LINE_LOOP)
if type == VERTICAL:
_progress_bar_vertices(x, y, width, height * max)
else:
_progress_bar_vertices(x, y, width * max, height)
glEnd()
glBegin(GL_QUADS)
if type == VERTICAL:
_progress_bar_vertices(x, y, width, height * progress)
else:
_progress_bar_vertices(x, y, width * max, height)
glEnd()
glPopAttrib()

94
punyverse/world.json Normal file
View file

@ -0,0 +1,94 @@
{
"planets": {
"earth": {
"texture": ["earth.jpg", "earth_medium.jpg", "earth_small.jpg", [0, 0.28, 1, 1]],
"radius": 100,
"z": "2 * AU",
"pitch": -90,
"yaw": 23.4,
"roll": -90,
"atmosphere": {
"cloud_texture": "cloudmap.png",
"diffuse_texture": "atmosphere_earth.png",
"diffuse_size": 30
},
"satellites": {
"moon": {
"texture": ["moon.jpg", "moon_medium.jpg", "moon_small.jpg", [0.53, 0.53, 0.53, 1]],
"radius": 25,
"distance": 200,
"pitch": -90,
"yaw": 6.68
},
"iss": {
"model": "satellites/iss.obj",
"distance": 110,
"scale": 10
}
}
},
"mars": {
"texture": ["mars.jpg", "mars_small.jpg", "mars_medium.jpg", [0.85, 0.47, 0.2, 1]],
"radius": 80,
"z": "4 * AU",
"pitch": -90,
"yaw": 25.19
},
"jupiter": {
"texture": ["jupiter.jpg", "jupiter_medium.jpg", "jupiter_small.jpg", [0.65, 0.36, 0.19, 1]],
"radius": 150,
"z": "6 * AU",
"pitch": -90,
"yaw": 3.13
},
"saturn": {
"texture": ["saturn.jpg", "saturn_medium.jpg", "saturn_small.jpg", [0.9, 0.8, 0.64, 1]],
"radius": 140,
"z": "8 * AU",
"pitch": -90,
"yaw": 26.73,
"ring": {
"texture": "ring_saturn.png",
"distance": 200,
"size": 150
}
},
"uranus": {
"texture": ["uranus.jpg", [0, 0.53, 0.84, 1]],
"radius": 130,
"z": "10 * AU",
"pitch": -90,
"yaw": 97.77,
"ring": {
"texture": "ring_uranus.png",
"pitch": 0,
"yaw": 0,
"roll": 90,
"distance": 200,
"size": 100
}
},
"neptune": {
"texture": ["neptune.jpg", [0.31, 0.49, 0.59, 1]],
"radius": 120,
"z": "12 * AU",
"pitch": -90,
"yaw": 28.32
},
"sky": {
"texture": "sky.jpg",
"optional": true,
"lighting": false,
"radius": 190000,
"pitch": 90,
"yaw": 30,
"roll": 180,
"delta": 0,
"background": true
}
},
"start": {
"z": "2 * AU - 400",
"yaw": 180
}
}

165
punyverse/world.py Normal file
View file

@ -0,0 +1,165 @@
from bisect import bisect_left
from collections import OrderedDict
from operator import itemgetter
from functools import partial
import hashlib
import os.path
import random
try:
import json
except ImportError:
try:
import simplejson as json
except ImportError:
print "No compatible JSON decoder found. Translation: you're fucked."
try:
from _model import model_list, load_model
except ImportError:
from model import model_list, load_model
from punyverse.glgeom import *
from punyverse.entity import *
from punyverse.texture import *
AU = 2000
def get_best_texture(info, optional=False):
cheap = False
skip = False
texture = None
if isinstance(info, list):
for item in info:
if isinstance(item, list):
if len(item) == 4:
cheap = True
texture = item
break
continue
try:
texture = load_texture(item)
except ValueError:
pass
else:
break
else:
try:
texture = load_texture(info)
except ValueError:
if optional:
skip = True
else:
cheap = True
texture = [1, 1, 1, 1]
return cheap, skip, texture
def load_world(file):
with open(os.path.join(os.path.dirname(__file__), file)) as f:
root = json.load(f, object_pairs_hook=OrderedDict)
world = World()
e = lambda x: eval(str(x), {'__builtins__': None}, {'AU': AU})
if 'start' in root:
info = root['start']
x = e(info.get('x', 0))
y = e(info.get('y', 0))
z = e(info.get('z', 0))
pitch = e(info.get('pitch', 0))
yaw = e(info.get('yaw', 0))
roll = e(info.get('roll', 0))
world.start = (x, y, z)
world.direction = (pitch, yaw, roll)
def body(name, info, parent=None):
lighting = info.get('lighting', True)
x = e(info.get('x', 0))
y = e(info.get('y', 0))
z = e(info.get('z', 0))
pitch = e(info.get('pitch', 0))
yaw = e(info.get('yaw', 0))
roll = e(info.get('roll', 0))
delta = e(info.get('delta', 5))
radius = e(info.get('radius', None))
background = info.get('background', False)
if 'texture' in info:
cheap, skip, texture = get_best_texture(info['texture'], optional=info.get('optional', False))
if skip:
return
if cheap:
object_id = compile(colourball, radius, int(radius / 2), int(radius / 2), texture)
else:
object_id = compile(sphere, radius, int(radius / 2), int(radius / 2), texture, lighting=lighting)
elif 'model' in info:
scale = info.get('scale', 10)
object_id = model_list(load_model(info['model']), info.get('sx', scale), info.get('sy', scale),
info.get('sz', scale), (0, 0, 0))
else:
print 'Nothing to load for %s.' % name
if parent is None:
type = Planet
else:
x, y, z = parent.location
distance = e(info.get('distance', 100))
x -= distance
type = partial(Satellite, parent=parent, distance=distance, inclination=e(info.get('inclination', 0)),
orbit_speed=e(info.get('orbit_speed', 1)))
atmosphere_id = 0
cloudmap_id = 0
if 'atmosphere' in info:
atmosphere_data = info['atmosphere']
size = e(atmosphere_data.get('diffuse_size', None))
atm_texture = atmosphere_data.get('diffuse_texture', None)
cloud_texture = atmosphere_data.get('cloud_texture', None)
cheap, _, cloud_texture = get_best_texture(cloud_texture)
if not cheap:
cloudmap_id = compile(sphere, radius + 2, int(radius / 2), int(radius / 2), cloud_texture,
lighting=False)
cheap, _, atm_texture = get_best_texture(atm_texture)
if not cheap:
atmosphere_id = compile(disk, radius, radius + size, 30, atm_texture)
object = type(object_id, (x, y, z), (pitch, yaw, roll), delta=delta,
atmosphere=atmosphere_id, cloudmap=cloudmap_id, background=background)
world.tracker.append(object)
if 'ring' in info:
ring_data = info['ring']
texture = ring_data.get('texture', None)
distance = e(ring_data.get('distance', radius * 1.2))
size = e(ring_data.get('size', radius / 2))
pitch = e(ring_data.get('pitch', pitch))
yaw = e(ring_data.get('yaw', yaw))
roll = e(ring_data.get('roll', roll))
cheap, _, texture = get_best_texture(texture)
if not cheap:
world.tracker.append(
type(compile(disk, distance, distance + size, 30, texture), (x, y, z),
(pitch, yaw, roll)))
for satellite, info in info.get('satellites', {}).iteritems():
print "Loading %s, satellite of %s." % (satellite, name)
body(satellite, info, object)
for planet, info in root['planets'].iteritems():
print "Loading %s." % planet
body(planet, info)
return world
class World(object):
def __init__(self):
self.tracker = []
self.start = (0, 0, 0)
self.direction = (0, 0, 0)
self.x = None
self.y = None
self.z = None