Initial version.
9
.gitignore
vendored
|
@ -34,3 +34,12 @@ nosetests.xml
|
|||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
.idea
|
||||
*.obj
|
||||
*.html
|
||||
*.exp
|
||||
*.lib
|
||||
punyverse/assets/textures/*_medium.*
|
||||
punyverse/assets/textures/*_small.*
|
||||
temp
|
||||
|
|
1
punyverse/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
|
17
punyverse/__main__.py
Normal 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
|
@ -0,0 +1,4 @@
|
|||
from space_torus.__main__ import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1209
punyverse/_cyopengl.pxi
Normal file
3348
punyverse/_glgeom.c
Normal file
83
punyverse/_glgeom.pyx
Normal 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
345
punyverse/_model.pyx
Normal 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
|
4619
punyverse/assets/models/asteroids/01.obj
Normal file
18443
punyverse/assets/models/asteroids/02.obj
Normal file
18443
punyverse/assets/models/asteroids/03.obj
Normal file
18443
punyverse/assets/models/asteroids/04.obj
Normal file
4619
punyverse/assets/models/asteroids/05.obj
Normal file
11
punyverse/assets/models/asteroids/asteroid.mtl
Normal 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
|
110
punyverse/assets/models/satellites/iss.mtl
Normal 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
|
21459
punyverse/assets/models/satellites/iss.obj
Normal file
BIN
punyverse/assets/textures/atmosphere_earth.png
Normal file
After Width: | Height: | Size: 114 B |
BIN
punyverse/assets/textures/cloudmap.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
punyverse/assets/textures/earth.jpg
Normal file
After Width: | Height: | Size: 1.7 MiB |
BIN
punyverse/assets/textures/jupiter.jpg
Normal file
After Width: | Height: | Size: 482 KiB |
BIN
punyverse/assets/textures/mars.jpg
Normal file
After Width: | Height: | Size: 2 MiB |
BIN
punyverse/assets/textures/moon.jpg
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
punyverse/assets/textures/neptune.jpg
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
punyverse/assets/textures/ring_saturn.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
punyverse/assets/textures/ring_uranus.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
punyverse/assets/textures/saturn.jpg
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
punyverse/assets/textures/sky.jpg
Normal file
After Width: | Height: | Size: 7.2 MiB |
BIN
punyverse/assets/textures/uranus.jpg
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
punyverse/assets/textures/venus.jpg
Normal file
After Width: | Height: | Size: 176 KiB |
47
punyverse/camera.py
Normal 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
|
@ -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
|
@ -0,0 +1 @@
|
|||
tick = 0
|
293
punyverse/game.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|