Compare commits

...

47 commits

Author SHA1 Message Date
Quantum 4a624e4a4f Added argparse to launcher.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-06 17:37:17 -05:00
Quantum 818f4bd0bb Added support for older launchers.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-06 17:32:31 -05:00
Quantum 8b3a6d7130 Slightly better formatting. 2013-11-05 19:18:22 -05:00
Quantum 6173cbe094 Better time handling: configurable ticks per second, and no dropped movement.
Note: the original method involves moving on only when update() is called, which is called only between frames. Hence less than the ticks per second setting if it can't paint as fast. This made movement completely FPS independent.

TODO: remove update, merge into on_draw.
2013-11-04 17:00:15 -05:00
Quantum 2a7e089edb Removed the two most complained about asteroids. 2013-11-04 17:02:23 -05:00
Quantum cab7974f2a Using hard code of 1024 asteroids in belt.
For that texture size kills modern computers.
2013-11-04 16:29:28 -05:00
Quantum 58bc2b9c67 Fixed fatal bugs that prevent time from stopping on demand. 2013-11-04 16:28:34 -05:00
Quantum 2ac84c0dbc Added in the textures for corona and solar "atmosphere".
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-02 15:17:30 -04:00
Quantum 5b28d5c5a1 Attempt at making diffuse and corona not intersect. 2013-11-02 15:13:05 -04:00
Quantum c7cee9a471 Added a diffuse texture on the sun. 2013-11-02 15:07:23 -04:00
Quantum b2ec3ef2b3 Added a sort of corona effect. 2013-11-02 15:00:47 -04:00
Quantum e210fa2fa8 Allowed control of displaying cloudmaps and atmospheres. 2013-11-02 12:57:10 -04:00
Quantum 38dc57c22f Moved mouse and keyboard into dictionaries. 2013-11-02 12:50:48 -04:00
Quantum 8cc74e8f22 Would you believe that the debug label drawing takes up 20% of drawing time? 2013-11-02 01:00:02 -04:00
Quantum 7befc8c14b Better sun texture. 2013-11-01 20:42:12 -04:00
Quantum 67c6becca3 Image shrinker now reads a list of files to resize, instead of using a hard coded list. This now doesn't need the launcher to be rebuilt for every new texture. 2013-11-01 19:26:25 -04:00
Quantum 4f4ec651c1 Added the main asteroid belt.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-01 18:42:50 -04:00
Quantum 4707aee221 Added mouse input support. 2013-11-01 12:30:10 -04:00
Quantum 83cd160ed4 Stops the sky from rotating.
Also: asteroids go front when you move back. Uranus rotates about the right axis.
2013-11-01 12:19:18 -04:00
Quantum fe0363627a Added Mercury and Venus.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-31 18:51:04 -04:00
Quantum 9e8f2f38c0 Added the sun.
Note that its texture is at minimum size.
2013-10-31 18:17:32 -04:00
Quantum c043a6fe88 Optimized surface division for speed. 2013-10-31 17:56:16 -04:00
Quantum 5d5f7cceb4 OMG did I just break standalone python usage of punyverse?
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-30 22:52:19 -04:00
Quantum 0375b5570f Updated description. 2013-10-30 19:29:33 -04:00
Quantum e46b09ba06 Updated the launcher and its utilities.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-30 19:09:04 -04:00
Quantum e45f8f6f85 Made cython model loader py2exe friendly.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-30 18:48:58 -04:00
Quantum de08850f24 Added more automatic small image generator.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-29 23:57:49 -04:00
Quantum a7ffda822e Added launcher generation and fixed all imports.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-29 18:52:35 -04:00
Quantum 118bcd02a7 Added better handling of orbits. 2013-10-28 22:03:49 -04:00
Quantum c21c0b9535 Added Saturn's moons.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-28 19:20:16 -04:00
Quantum d831e996e7 Added forsaken phobos.obj.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-28 18:04:04 -04:00
Quantum 70e12f180d Removed the hack module framedata. 2013-10-28 18:01:58 -04:00
Quantum a9aabc7465 Added pausing and proper entity speed. 2013-10-28 17:04:14 -04:00
Quantum 376628d9a6 Added proper rotation and proper camera moving. 2013-10-27 19:51:55 -04:00
Quantum 54a134c73e Added the Galilean moons, and fixed extreme sphere subdivision.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-27 18:29:11 -04:00
Quantum 46b46df60f Allow adjustment in speed. 2013-10-27 17:32:17 -04:00
Quantum 1bb8893635 Now uses km as radius. 2013-10-27 16:59:23 -04:00
Quantum 45d7142feb Fixed a nasty bug in texture loading.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-26 23:51:02 -04:00
Quantum c224ba4074 Used two different definitions of distance so objects can have realistic orbital speed but not huge orbits. 2013-10-26 16:35:47 -04:00
Quantum a034975928 Used mass to calculate orbital speed. Added phobos. 2013-10-26 16:25:45 -04:00
Quantum 8236efcffc Added orbital display. 2013-10-26 14:25:37 -04:00
Quantum f21499062d Fixed loading of models with texture. 2013-10-26 14:25:18 -04:00
Quantum 2cc82a2724 The author of the texture code, I mean the original, should know better how glTexImage2D works, because the GL documentation says otherwise. 2013-10-26 12:40:06 -04:00
Quantum d26c7dd536 Added handling of inclination, longitude of ascending node, and argument of periapsis.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-26 11:48:58 -04:00
Quantum b2e3a1d486 Attempts to maintain the balance between correctness and appearance.
Added mip mapping due to the increase in distance.
2013-10-25 20:48:03 -04:00
Quantum 3c6eed8f39 Added proper orbit calculations, and limited orbital plane calculations (only inclination is used). 2013-10-25 19:14:32 -04:00
Quantum 355f156295 Initial version.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-10-22 20:38:37 -04:00
63 changed files with 88614 additions and 0 deletions

17
.gitignore vendored
View file

@ -34,3 +34,20 @@ nosetests.xml
.mr.developer.cfg
.project
.pydevproject
.idea
punyverse/*.obj
*.html
*.exp
*.lib
punyverse/assets/textures/*_medium.*
punyverse/assets/textures/*_small.*
punyverse/assets/textures/*/*_medium.*
punyverse/assets/textures/*/*_small.*
temp
# There is a py2exe package that can execute punyverse directly
library.zip
*.exe
*.log
*.dll

View file

@ -2,3 +2,31 @@ punyverse
=========
Python simulator of a puny universe. (How many words can i stick into one?)
Installation
------------
To install, simply clone this repository, or download a copy [here]
(https://github.com/xiaomao5/punyverse/archive/master.zip).
After that, download the [launcher](https://github.com/xiaomao5/punyverse/releases/download/launcher0.2/launcher.exe),
put it into the repository directory and let it unpack in your repository (or copy).
You may start playing any time by running `punyverse.exe`, or `punyverse_debug.exe` if you desire a console.
### A Note on Textures
If your graphics card doesn't support the massive texture sizes this module comes with, you can shrink them.
You can run `small_images.exe` (or `small_images.py`, which requires either `PIL` or `pgmagick`) to generate
smaller versions of shipped textures.
###Advanced Install
You need a C compiler to compile `_model.c` and `_glgeom.c`, both requiring OpenGL headers and libraries.
You will also need to install `pyglet`.
You can now run the `punyverse` module using `python -mpunyverse`.
See above if you run into texture issues.

36
bootloader.py Normal file
View file

@ -0,0 +1,36 @@
import pyglet
import json
import os
import sys
import uuid
import imp
import argparse
def load_dll(dir, module):
name = 'punyverse.' + module
path = os.path.join(dir, 'punyverse', module + '.pyd')
if not os.path.exists(path):
path = os.path.join(dir, 'punyverse.%s.pyd' % module)
if not os.path.exists(path):
raise ImportError('No module named %s' % module)
return imp.load_dynamic(name, path)
if __name__ == '__main__':
try:
dir = os.path.dirname(sys.executable)
if sys.frozen == 'windows_exe':
sys.stderr = open(os.path.join(dir, 'punyverse.log'), 'a')
except AttributeError:
sys.exit('This is only meant to be ran frozen.')
sys.path.insert(0, dir)
# Model indirectly depends on _glgeom to handle textures
load_dll(dir, '_glgeom')
# Model path needs special handling
_model = load_dll(dir, '_model')
_model.model_base = os.path.join(dir, 'punyverse', 'assets', 'models')
with open('punyverse\__main__.py', 'r') as code:
exec(code)

29
launcher.py Normal file
View file

@ -0,0 +1,29 @@
from distutils.core import setup
import py2exe
from glob import glob
import sys
import os
import shutil
if len(sys.argv) < 2:
sys.argv.append('py2exe')
parent = os.path.dirname(__file__)
join = os.path.join
setup(
console=[{'dest_base': 'punyverse_debug', 'script': 'bootloader.py'}, 'small_images.py'],
windows=[{'dest_base': 'punyverse', 'script': 'bootloader.py'}],
options={'py2exe': {
'unbuffered': True, 'optimize': 2,
'excludes': [
'_ssl', 'unittest', 'doctest', 'PIL', 'email', 'distutils',
'pyglet.window.carbon', 'pyglet.window.xlib',
'pyglet.media.drivers.alsa',
'win32wnet', 'netbios', 'pgmagick'
],
'includes': ['punyverse._model', 'punyverse._glgeom'],
'dll_excludes': ['MPR.dll', 'w9xpopen.exe'],
}
}
)

1
punyverse/__init__.py Normal file
View file

@ -0,0 +1 @@

29
punyverse/__main__.py Normal file
View file

@ -0,0 +1,29 @@
#!/usr/bin/python
INITIAL_WIN_HEIGHT = 540
INITIAL_WIN_WIDTH = 700
WIN_TITLE = 'Punyverse'
def main():
try:
import argparse
except ImportError:
args = False
else:
parser = argparse.ArgumentParser(prog='punyverse', description='Python simulator of a puny universe.')
parser.add_argument('-t', '--ticks', help='Ticks per second for game, more means more responsive, but '
' may run slower, default is 20.', default=20, type=int)
args = parser.parse_args()
import pyglet
from punyverse import game
pyglet.options['shadow_window'] = False
if args:
game.TICKS_PER_SECOND = args.ticks
game.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 punyverse.__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

12938
punyverse/_model.c Normal file

File diff suppressed because it is too large Load diff

359
punyverse/_model.pyx Normal file
View file

@ -0,0 +1,359 @@
from libc.string cimport strcmp, strlen
from libc.stdlib cimport malloc, free, atof
from libc.stdio cimport fopen, fclose, fgets, FILE
cimport cython
from punyverse.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.abspath(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)
model_base = None
def load_model(path):
global model_base
if model_base is None:
import sys
if hasattr(sys, 'frozen'):
model_base = os.path.dirname(sys.executable)
else:
model_base = os.path.join(os.path.dirname(__file__), 'assets', 'models')
if not os.path.isabs(path):
path = os.path.join(model_base, 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 = load_texture(os.path.join(model.root, g.material.texture)) if (g.material and g.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 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()
if tex_id:
glBindTexture(GL_TEXTURE_2D, 0)
glDisable(GL_TEXTURE_2D)
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

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,11 @@
# Blender MTL File: 'mainbeltastroid.blend'
# Material Count: 1
newmtl Material
Ns 96.078431
Ka 0.000000 0.000000 0.000000
Kd 0.254837 0.254837 0.254837
Ks 0.159273 0.159273 0.159273
Ni 1.000000
d 1.000000
illum 2

View file

@ -0,0 +1,188 @@
# Blender v2.68 (sub 0) OBJ File: 'mainbeltastroid.blend'
# www.blender.org
mtllib mainbelt.mtl
g Cube
v 0.290193 -0.983926 0.474791
v 0.437387 -1.185402 -0.058906
v -0.278179 0.795311 -0.169923
v -0.176207 0.961698 -0.576060
v -0.749408 0.677473 -0.403865
v -0.843249 1.101376 0.368964
v 0.921024 0.543359 -0.373421
v 1.228522 0.313481 -0.796992
v 1.413710 0.491171 0.016800
v 0.824964 -0.186862 -0.430968
v 0.169830 0.339481 0.955115
v 0.788238 0.224175 0.864338
v 0.579672 -0.319492 0.985489
v -0.850858 0.163566 0.318706
v 0.631733 -0.588108 -0.649624
v -0.094402 -0.322324 -1.249913
v -0.249124 -1.065447 -0.286061
v -0.020992 -0.954752 -0.765611
v -0.632247 -1.107953 -0.512581
v 0.124837 1.315392 0.134372
v 0.577110 0.882123 0.035559
v 0.730517 0.855449 0.648931
v -0.248585 0.742805 0.757662
v 0.897809 -0.004470 0.247323
v 0.947047 -0.715323 0.120538
v 0.998978 -0.476379 0.574922
v -0.353813 -0.204947 1.105379
v -0.981584 -0.532307 0.798081
v -0.690767 0.347252 0.944776
v -1.260799 -0.267750 -0.576518
v -0.903255 -0.554117 -0.072930
v -0.873585 -0.350903 -0.897106
v -1.083754 0.162032 -0.623495
v -0.679472 0.071680 -0.834524
v -0.150468 0.605678 -1.224164
v -0.292701 -0.962443 0.791399
v 0.496173 1.124109 -0.437672
v 0.278706 -0.267373 1.389388
vn 0.529098 -0.735317 0.423514
vn 0.695321 -0.606928 -0.384925
vn 0.383061 -0.662029 0.644190
vn 0.511064 -0.783451 0.353579
vn -0.138461 0.908731 -0.393746
vn 0.593966 0.354629 -0.722110
vn -0.359523 0.891682 0.275039
vn 0.500172 0.865296 0.033023
vn 0.619478 -0.019645 -0.784768
vn 0.619649 0.775456 -0.121254
vn 0.983189 0.132696 0.125425
vn 0.171122 0.151303 0.973563
vn 0.197398 0.332706 0.922139
vn -0.945107 0.286186 0.157704
vn -0.676128 0.735533 -0.042912
vn 0.472294 -0.437683 -0.765096
vn 0.304249 0.044783 -0.951539
vn 0.283559 0.913995 -0.290184
vn 0.830567 -0.515214 0.211455
vn -0.311526 -0.600264 -0.736637
vn 0.483338 -0.690051 -0.538714
vn -0.198815 -0.976318 0.085294
vn -0.529631 -0.847817 -0.026400
vn -0.165780 0.630247 0.758489
vn 0.699114 0.677982 0.227114
vn 0.259894 0.628514 0.733093
vn 0.677448 -0.097416 0.729091
vn 0.987177 0.091683 -0.130671
vn 0.891772 -0.211464 -0.400032
vn -0.233827 -0.388331 0.891361
vn -0.437396 -0.005360 0.899253
vn 0.072347 -0.376917 0.923417
vn -0.408216 0.016368 0.912739
vn -0.926227 -0.356903 -0.121344
vn -0.988224 0.122316 -0.091933
vn -0.501469 0.749738 -0.431766
vn -0.483647 -0.150069 -0.862302
vn -0.612900 0.611035 -0.500988
vn -0.774342 0.425106 -0.468699
vn 0.338886 -0.661704 0.668808
vn 0.178682 -0.903205 0.390248
vn -0.801728 0.596145 0.042935
vn -0.110776 0.869208 -0.481877
vn -0.805509 0.589632 0.059074
vn 0.018409 0.877560 -0.479114
vn 0.759682 -0.149805 0.632805
vn 0.713637 -0.691918 0.109416
vn 0.786427 0.594041 0.169258
vn 0.282085 0.931005 -0.231641
vn 0.583533 -0.247678 0.773398
vn 0.732741 0.478045 0.484317
vn -0.004524 0.581410 0.813598
vn -0.006905 0.433901 0.900934
vn -0.991584 0.014936 -0.128596
vn -0.563344 -0.039656 0.825270
vn -0.858666 0.466868 0.211488
vn 0.423569 -0.519252 -0.742271
vn 0.187865 -0.972846 -0.135191
vn -0.222629 -0.496977 -0.838719
vn -0.081738 -0.961894 -0.260919
vn 0.052827 -0.993867 0.097149
vn -0.766365 -0.585261 0.264864
vn 0.669742 0.730079 -0.135756
vn 0.981787 0.097127 -0.163283
vn 0.963043 0.258645 0.075174
vn -0.116467 -0.992337 0.041265
vn -0.385386 -0.892604 -0.233959
vn -0.546778 0.362906 -0.754541
vn -0.849251 -0.203792 0.487075
vn -0.445795 0.086256 -0.890970
vn -0.893227 0.392020 0.220152
vn -0.412489 0.000355 -0.910963
usemtl Material
s off
f 1//1 2//1 25//1
f 2//2 15//2 25//2
f 1//3 26//3 13//3
f 1//4 25//4 26//4
f 20//5 37//5 4//5
f 37//6 7//6 35//6
f 4//7 5//7 3//7
f 3//8 6//8 23//8
f 7//9 9//9 10//9
f 7//10 21//10 22//10
f 22//11 12//11 24//11
f 11//12 13//12 12//12
f 11//13 12//13 22//13
f 14//14 28//14 29//14
f 14//15 29//15 23//15
f 16//16 8//16 15//16
f 16//17 35//17 8//17
f 35//18 7//18 8//18
f 15//19 8//19 10//19
f 16//20 18//20 32//20
f 18//21 15//21 2//21
f 17//22 2//22 36//22
f 36//23 28//23 31//23
f 20//24 11//24 22//24
f 21//25 37//25 20//25
f 20//26 23//26 11//26
f 26//27 12//27 13//27
f 24//28 25//28 10//28
f 25//29 15//29 10//29
f 27//30 28//30 36//30
f 27//31 29//31 28//31
f 36//32 13//32 27//32
f 27//33 38//33 29//33
f 32//34 19//34 31//34
f 31//35 28//35 14//35
f 35//36 5//36 4//36
f 34//37 33//37 35//37
f 35//38 33//38 5//38
f 34//39 35//39 32//39
f 36//40 1//40 13//40
f 2//41 1//41 36//41
f 3//42 20//42 4//42
f 4//43 37//43 35//43
f 20//44 3//44 23//44
f 3//45 5//45 6//45
f 8//46 7//46 10//46
f 9//47 24//47 10//47
f 21//48 7//48 37//48
f 9//49 7//49 22//49
f 9//50 22//50 24//50
f 11//51 38//51 13//51
f 38//52 11//52 29//52
f 11//53 23//53 29//53
f 5//54 14//54 6//54
f 6//55 14//55 23//55
f 14//56 5//56 33//56
f 15//57 18//57 16//57
f 17//58 19//58 18//58
f 18//59 19//59 32//59
f 17//60 18//60 2//60
f 19//61 17//61 36//61
f 19//62 36//62 31//62
f 21//63 20//63 22//63
f 25//64 24//64 26//64
f 26//65 24//65 12//65
f 38//66 27//66 13//66
f 31//67 30//67 32//67
f 30//68 34//68 32//68
f 30//69 31//69 14//69
f 30//70 33//70 34//70
f 30//71 14//71 33//71
f 35//72 16//72 32//72

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: 100 KiB

View file

@ -0,0 +1,20 @@
# Ns = Phong specular component. Ranges from 0 to 1000. (I've seen various statements about this range (see below))
# Kd = Diffuse color weighted by the diffuse coefficient.
# Ka = Ambient color weighted by the ambient coefficient.
# Ks = Specular color weighted by the specular coefficient.
# d = Dissolve factor (pseudo-transparency). Values are from 0-1. 0 is completely transparent, 1 is opaque.
# Ni = Refraction index. Values range from 1 upwards. A value of 1 will cause no refraction. A higher value implies refraction.
# illum = (0, 1, or 2) 0 to disable lighting, 1 for ambient & diffuse only (specular color set to black), 2 for full lighting (see below)
# sharpness = ? (see below)
# c
# map_Bump = Bump texture map.
# map_d = Opacity texture map.
# refl = reflection type and filename (?)
newmtl white
Ka 1 1 1
Kd 1 1 1
Ks 0 0 0
map_Kd phobos.jpg
illum 2
Ns 8

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
# This file contains all the textures that is bigger than the minimum OpenGL texture size
# and hence may need to be resized depending on the machine
mercury.jpg
earth.jpg
moon.jpg
mars.jpg
jupiter.jpg
saturn.jpg
moons/io.jpg
moons/europa.jpg
moons/ganymede.jpg
moons/callisto.jpg
moons/titan.jpg
moons/rhea.jpg
moons/iapetus.jpg
moons/dione.jpg
moons/tethys.jpg
moons/enceladus.jpg
moons/mimas.jpg

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: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

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: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

41
punyverse/camera.py Normal file
View file

@ -0,0 +1,41 @@
from math import sin, cos, radians
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, speed):
dx, dy, dz = self.direction()
self.x += dx * speed
self.y += dy * speed
self.z += dz * speed
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

128
punyverse/entity.py Normal file
View file

@ -0,0 +1,128 @@
from punyverse.orbit import KeplerOrbit
from pyglet.gl import *
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 Belt(Entity):
def __init__(self, *args, **kwargs):
self.rotation_angle = kwargs.pop('rotation_angle', 5)
self.world = kwargs.pop('world')
super(Belt, self).__init__(*args, **kwargs)
def update(self):
super(Belt, self).update()
pitch, yaw, roll = self.rotation
self.rotation = pitch, self.world.tick * self.rotation_angle % 360, roll
class Body(Entity):
def __init__(self, *args, **kwargs):
self.rotation_angle = kwargs.pop('rotation_angle', 5)
self.atmosphere = kwargs.pop('atmosphere', 0)
self.cloudmap = kwargs.pop('cloudmap', 0)
self.corona = kwargs.pop('corona', 0)
self.last_tick = 0
self.mass = kwargs.pop('mass', None)
self.world = kwargs.pop('world')
orbit_distance = kwargs.pop('orbit_distance', 40000) + .0
self.orbit_show = orbit_distance * 1.25
self.orbit_blend = orbit_distance / 4
self.orbit_opaque = orbit_distance
super(Body, self).__init__(*args, **kwargs)
self.initial_roll = self.rotation[2]
def update(self):
super(Body, self).update()
if self.last_tick != self.world.tick:
self.last_tick = self.world.tick
pitch, yaw, roll = self.rotation
roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360
self.rotation = pitch, yaw, roll
class Satellite(Body):
def __init__(self, *args, **kwargs):
self.parent = kwargs.pop('parent')
self.orbit_speed = kwargs.pop('orbit_speed', 1)
# Semi-major axis and eccentricity defines orbit
distance = kwargs.pop('distance', 100)
eccentricity = kwargs.pop('eccentricity', 0)
# Inclination, longitude of ascending node, and argument of periapsis defines orbital plane
inclination = kwargs.pop('inclination', 0)
longitude = kwargs.pop('longitude', 0)
argument = kwargs.pop('argument', 0)
# Orbit calculation
self.orbit_id = None
self.orbit_cache = None
self.theta = 0
# OpenGL's z-axis is reversed
self.orbit = KeplerOrbit(distance, eccentricity, inclination, longitude, argument)
super(Satellite, self).__init__(*args, **kwargs)
def get_orbit(self):
# Cache key is the three orbital plane parameters and eccentricity
cache = (self.orbit.eccentricity, self.orbit.longitude, self.orbit.inclination, self.orbit.argument)
if self.orbit_cache == cache:
return self.orbit_id
if self.orbit_id is not None:
glDeleteLists(self.orbit_id, 1)
id = glGenLists(1)
glNewList(id, GL_COMPILE)
glBegin(GL_LINE_LOOP)
for theta in xrange(360):
x, z, y = self.orbit.orbit(theta)
glVertex3f(x, y, z)
glEnd()
glEndList()
self.orbit_id = id
self.orbit_cache = cache
return id
def update(self):
super(Body, self).update() # Notice how the parent class is skipped
if self.last_tick != self.world.tick:
self.last_tick = self.world.tick
pitch, yaw, roll = self.rotation
roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360
self.rotation = pitch, yaw, roll
self.parent.update()
px, py, pz = self.parent.location
self.theta = self.world.tick * self.orbit_speed % 360
x, z, y = self.orbit.orbit(self.theta)
self.location = (x + px, y + py, z + pz)

397
punyverse/game.py Normal file
View file

@ -0,0 +1,397 @@
#!/usr/bin/python
from operator import attrgetter
from math import hypot, sqrt, atan2, degrees
from time import clock
import time
import random
from punyverse.camera import Camera
from punyverse.world import load_world
from punyverse.glgeom import *
from punyverse.entity import Asteroid
from punyverse import texture
try:
from punyverse._model import model_list, load_model
except ImportError:
from punyverse.model import model_list, load_model
from pyglet.gl import *
from pyglet.window import key, mouse
import pyglet
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
if self.moving:
cam.move(int(self.speed * dt * TICKS_PER_SECOND + 0.5))
if self.running:
self.world.tick += int(self.tick * dt * TICKS_PER_SECOND + 0.5)
for entity in self.world.tracker:
entity.update()
def __init__(self, *args, **kwargs):
super(Applet, self).__init__(*args, **kwargs)
texture.init()
start = clock()
self.fps = 0
self.world = load_world('world.json')
print 'Initializing game...'
self.speed = INITIAL_SPEED
self.keys = set()
self.info = True
self.debug = False
self.orbit = True
self.running = True
self.moving = True
self.info_precise = False
self.atmosphere = True
self.cloud = not texture.badcard
self.tick = self.world.tick_length / TICKS_PER_SECOND
self.ticks = [1, 2, 5, 10, 20, 40, 60, # Second range
120, 300, 600, 1200, 1800, 2700, 3600, # Minute range
7200, 14400, 21600, 43200, 86400, # Hour range
172800, 432000, 604800, # 2, 5, 7 days
1209600, 2592000, # 2 week, 1 month
5270400, 7884000, 15768000, 31536000, # 2, 3, 6, 12 months
63072000, 157680000, 315360000, # 2, 5, 10 years
630720000, 1576800000, 3153600000, # 20, 50, 100 years
]
self.ticks = [i / TICKS_PER_SECOND for i in self.ticks]
self.ticks = sorted(set(i for i in self.ticks if i))
self.__time_per_second_cache = None
self.__time_per_second_value = None
def speed_incrementer(object, increment):
def incrementer():
object.speed += increment
return incrementer
def attribute_toggler(object, attribute):
getter = attrgetter(attribute)
def toggler():
setattr(object, attribute, not getter(object))
return toggler
def increment_tick():
index = self.ticks.index(self.tick) + 1
if index < len(self.ticks):
self.tick = self.ticks[index]
def decrement_tick():
index = self.ticks.index(self.tick) - 1
if index >= 0:
self.tick = self.ticks[index]
self.key_handler = {
key.ESCAPE: pyglet.app.exit,
key.NUM_ADD: speed_incrementer(self, 1),
key.NUM_SUBTRACT: speed_incrementer(self, -1),
key.NUM_MULTIPLY: speed_incrementer(self, 10),
key.NUM_DIVIDE: speed_incrementer(self, -10),
key.PAGEUP: speed_incrementer(self, 100),
key.PAGEDOWN: speed_incrementer(self, -100),
key.HOME: speed_incrementer(self, 1000),
key.END: speed_incrementer(self, -1000),
key.I: attribute_toggler(self, 'info'),
key.D: attribute_toggler(self, 'debug'),
key.O: attribute_toggler(self, 'orbit'),
key.P: attribute_toggler(self, 'info_precise'),
key.C: attribute_toggler(self, 'cloud'),
key.X: attribute_toggler(self, 'atmosphere'),
key.ENTER: attribute_toggler(self, 'running'),
key.INSERT: increment_tick,
key.DELETE: decrement_tick,
key.SPACE: self.launch_meteor,
key.E: lambda: self.set_exclusive_mouse(False),
key.F: lambda: self.set_fullscreen(not self.fullscreen),
}
self.mouse_press_handler = {
mouse.LEFT: self.launch_meteor,
mouse.RIGHT: attribute_toggler(self, 'moving'),
}
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)
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))
print 'Loading asteroids...'
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)),
]
c = self.cam
c.x, c.y, c.z = self.world.start
c.pitch, c.yaw, c.roll = self.world.direction
print 'Updating entities...'
for entity in self.world.tracker:
entity.update()
print 'Loaded in %s seconds.' % (clock() - start)
def set_exclusive_mouse(self, exclusive):
super(Applet, self).set_exclusive_mouse(exclusive)
self.exclusive = exclusive
def launch_meteor(self):
c = self.cam
dx, dy, dz = c.direction()
speed = abs(self.speed) * 1.1 + 5
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)))
def on_mouse_press(self, x, y, button, modifiers):
if not self.exclusive:
self.set_exclusive_mouse(True)
else:
if button in self.mouse_press_handler:
self.mouse_press_handler[button]()
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 in self.key_handler:
self.key_handler[symbol]()
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_mouse_scroll(self, x, y, scroll_x, scroll_y):
self.speed += scroll_y * 50 + scroll_x * 500
def get_time_per_second(self):
if self.__time_per_second_cache == self.tick:
return self.__time_per_second_value
time = self.tick * TICKS_PER_SECOND + .0
unit = 'seconds'
for size, name in ((60, 'minutes'), (60, 'hours'), (24, 'days'), (365, 'years')):
if time < size:
break
time /= size
unit = name
result = '%s %s' % (round(time, 1), unit)
self.__time_per_second_cache = self.tick
self.__time_per_second_value = result
return result
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
get_distance = entity_distance(x, y, z)
if x != world.x or y != world.y or z != world.z:
world.tracker.sort(key=get_distance, 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()
has_corona = hasattr(entity, 'corona') and entity.corona
has_atmosphere = hasattr(entity, 'atmosphere') and entity.atmosphere
if self.atmosphere and (has_corona or has_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)
if has_atmosphere:
glCallList(entity.atmosphere)
if has_corona:
x, y, z = c.direction()
glTranslatef(-x, -y, -z)
glCallList(entity.corona)
glPopMatrix()
if self.cloud and 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()
if self.orbit and hasattr(entity, 'get_orbit') and hasattr(entity, 'parent'):
parent = entity.parent
distance = get_distance(parent)
if distance < parent.orbit_show:
glPushMatrix()
glTranslatef(*entity.parent.location)
glDisable(GL_LIGHTING)
glPushAttrib(GL_LINE_BIT | GL_CURRENT_BIT)
glColor4f(1, 1, 1, 1 if distance < parent.orbit_opaque else
(1 - (distance - parent.orbit_opaque) / parent.orbit_blend))
glLineWidth(1)
glCallList(entity.get_orbit())
glPopAttrib()
glEnable(GL_LIGHTING)
glPopMatrix()
glColor4f(1, 1, 1, 1)
glDisable(GL_TEXTURE_2D)
width, height = self.get_size()
if self.info:
ortho(width, height)
if self.info_precise:
info = ('%d FPS @ (x=%.2f, y=%.2f, z=%.2f) @ %s, %s/s\n'
'Direction(pitch=%.2f, yaw=%.2f, roll=%.2f)\nTick: %d' %
(self.fps, c.x, c.y, c.z, self.speed, self.get_time_per_second(),
c.pitch, c.yaw, c.roll, self.world.tick))
else:
info = ('%d FPS @ (x=%.2f, y=%.2f, z=%.2f) @ %s, %s/s\n' %
(self.fps, c.x, c.y, c.z, self.speed, self.get_time_per_second()))
self.label.text = info
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()

29
punyverse/gencheap.bat Normal file
View file

@ -0,0 +1,29 @@
@echo off
cd %~dp0assets\textures
call :convert mercury.jpg mercury_small.jpg 1024x512
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
call :convert moons\io.jpg moons\io_small.jpg 1024x512
call :convert moons\europa.jpg moons\europa_small.jpg 1024x512
call :convert moons\ganymede.jpg moons\ganymede_small.jpg 1024x512
call :convert moons\callisto.jpg moons\callisto_small.jpg 1024x512
call :convert moons\titan.jpg moons\titan_small.jpg 1024x512
call :convert moons\rhea.jpg moons\rhea_small.jpg 1024x512
call :convert moons\iapetus.jpg moons\iapetus_small.jpg 1024x512
call :convert moons\dione.jpg moons\dione_small.jpg 1024x512
call :convert moons\tethys.jpg moons\tethys_small.jpg 1024x512
call :convert moons\enceladus.jpg moons\enceladus_small.jpg 1024x512
call :convert moons\mimas.jpg moons\mimas_small.jpg 1024x512
goto :eof
:convert
echo Converting %1 to %2, size %3...
if not exist %2 gm convert %1 -resize %3 %2

224
punyverse/glgeom.py Normal file
View file

@ -0,0 +1,224 @@
from math import *
from pyglet.gl import *
from random import random, uniform
TWOPI = pi * 2
__all__ = ['compile', 'ortho', 'frustrum', 'crosshair', 'circle', 'disk', 'sphere', 'colourball', 'torus', 'belt',
'flare']
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 flare(rinner, router, res, prob, tex):
glEnable(GL_TEXTURE_2D)
glDisable(GL_LIGHTING)
glBindTexture(GL_TEXTURE_2D, tex)
last_x = 1
last_y = 0
last_theta = 0
factor = TWOPI / res
rdelta = (router - rinner)
glBegin(GL_QUADS)
for i in xrange(res + 1):
theta = last_theta + factor
x = cos(theta)
y = sin(theta)
if random() > prob:
distance = rinner + rdelta * random()
avg_theta = (last_theta + theta) / 2
x0, y0 = rinner * last_x, rinner * last_y
x1, y1 = rinner * x, rinner * y
x2, y2 = distance * cos(avg_theta), distance * sin(avg_theta)
glTexCoord2f(0, 0)
glVertex2f(x0, y0)
glTexCoord2f(0, 1)
glVertex2f(x1, y1)
glTexCoord2f(1, 0)
glVertex2f(x2, y2)
glTexCoord2f(1, 1)
glVertex2f(x2, y2)
last_theta = theta
last_x = x
last_y = y
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)
def belt(radius, cross, object, count):
for i in xrange(count):
theta = TWOPI * random()
x, y, z = cos(theta) * radius, 0, sin(theta) * radius
# Pretend to move horizontally around the x-axis by delta
# then I multiply it by a rotation matrix about angle theta
# the z-axis need not to be multiplied by the matrix, as it's 0
# Do note the rotation is counter clockwise
delta = cross * uniform(-1, 1)
x += cos(theta) * delta
z += sin(theta) * delta
y += cross * uniform(-1, 1)
glPushMatrix()
glTranslatef(x, y, z)
glCallList(object)
glPopMatrix()
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]][:2])
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

95
punyverse/orbit.py Normal file
View file

@ -0,0 +1,95 @@
from math import sin, cos, tan, atan, sqrt, radians, degrees
class KeplerOrbit(object):
def __init__(self, sma, eccentricity, inclination=0, longitude=0, argument=0):
self.sma = sma
self.eccentricity = eccentricity
self.inclination = inclination
self.longitude = longitude
self.argument = argument
self.__true_anomaly_factor = sqrt((1 + eccentricity)/(1 - eccentricity))
self.__distance_factor = sma * (1 - eccentricity ** 2)
@property
def inclination(self):
return degrees(self._inclination)
@inclination.setter
def inclination(self, value):
self.inclination_radian = radians(value)
@property
def inclination_radian(self):
return self._inclination
@inclination_radian.setter
def inclination_radian(self, value):
self._inclination = value
self.__sin_inclination = sin(self._inclination)
self.__cos_inclination = cos(self._inclination)
@property
def longitude(self):
return degrees(self._longitude)
@longitude.setter
def longitude(self, value):
self.longitude_radian = radians(value)
@property
def longitude_radian(self):
return self._longitude
@longitude_radian.setter
def longitude_radian(self, value):
self._longitude = value
self.__sin_longitude = sin(self._longitude)
self.__cos_longitude = cos(self._longitude)
@property
def argument(self):
return degrees(self._argument)
@argument.setter
def argument(self, value):
self.argument_radian = radians(value)
@property
def argument_radian(self):
return self._argument
@argument_radian.setter
def argument_radian(self, value):
self._argument = value
self.__sin_argument = sin(self._argument)
self.__cos_argument = cos(self._argument)
def eccentric_anomaly(self, mean_anomaly):
e1 = mean_anomaly
e2 = mean_anomaly + self.eccentricity * sin(e1)
while abs(e1 - e2) > 0.000001:
e1, e2 = e2, mean_anomaly + self.eccentricity * sin(e2)
return e2
def true_anomaly(self, mean_anomaly):
eccentric_anomaly = self.eccentric_anomaly(mean_anomaly)
return 2 * atan(self.__true_anomaly_factor * tan(eccentric_anomaly / 2))
def orbit(self, mean_anomaly):
mean_anomaly = radians(mean_anomaly)
phi = self.true_anomaly(mean_anomaly)
r = self.__distance_factor / (1 + self.eccentricity * cos(phi))
x = r * cos(phi)
y = r * sin(phi)
z = 0
# phi = longitude, theta = inclination, psi = argument
x, y = (x * self.__cos_longitude + y * self.__sin_longitude,
-x * self.__sin_longitude + y * self.__cos_longitude)
x, z = (x * self.__cos_inclination + z * self.__sin_inclination,
-x * self.__sin_inclination + z * self.__cos_inclination)
x, y = (x * self.__cos_argument + y * self.__sin_argument,
-x * self.__sin_argument + y * self.__cos_argument)
return x, y, z

222
punyverse/texture.py Normal file
View file

@ -0,0 +1,222 @@
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 os.path.isabs(file):
path = file
file = os.path.basename(path)
else:
path = os.path.join(os.path.dirname(__file__), "assets", "textures", file)
if path in cache:
return cache[path]
print "Loading image %s..." % 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
# 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:
mode = {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)
gluBuild2DMipmaps(GL_TEXTURE_2D, len(raw.format), width, height, mode, GL_UNSIGNED_BYTE, texture)
cache[path] = id
return id

303
punyverse/world.json Normal file
View file

@ -0,0 +1,303 @@
{
"comments": {
"au": "greatly shrunk so planets can actually be seen",
"tick": "real world second per game tick",
"length": "kilometre per world units for satellite distance and planetary radius",
"radius": "note that radius is equatorial, flattening will be implemented later",
"distance": "virtual distance to look better, in km",
"sma": "semi-major axis used with mass of parent to calculate orbit, in km",
"mass": "mass in kg",
"texture": "a group of texture to use, tried in that order. a list means a colour",
"model": "used to load a wavefront object instead of a textured sphere"
},
"au": 10000,
"tick": 3600,
"length": 63.7,
"bodies": {
"sun": {
"texture": ["sun.jpg", [0.99, 0.97, 0.66]],
"radius": 80000,
"pitch": -90,
"yaw": 7.25,
"mass": 1.9891e+30,
"rotation": 2164320,
"atmosphere": {
"corona_texture": "sun_corona.png",
"corona_size": 1500,
"corona_division": 100,
"corona_prob": 0.5,
"diffuse_texture": "sun_diffuse.png",
"diffuse_size": 300
}
},
"mercury": {
"texture": ["mercury.jpg", "mercury_small.jpg", [0.44, 0.43, 0.43]],
"radius": 2439.7,
"z": "0.466697 * AU",
"pitch": -90,
"yaw": 0.35,
"division": 30,
"rotation": 5067014
},
"venus": {
"texture": ["venus.jpg", [0.655, 0.38, 0.1]],
"radius": 6051.8,
"z": "0.723327 * AU",
"pitch": -90,
"yaw": 177.36,
"division": 30,
"rotation": -20996798
},
"earth": {
"texture": ["earth.jpg", "earth_medium.jpg", "earth_small.jpg", [0, 0.28, 1, 1]],
"radius": 6378.1,
"z": "AU",
"pitch": -90,
"yaw": 23.4,
"roll": -90,
"mass": 5.97219e+24,
"rotation": 86400,
"division": 70,
"atmosphere": {
"cloud_texture": "cloudmap.png",
"diffuse_texture": "atmosphere_earth.png",
"diffuse_size": 30
},
"orbit_distance": "AU",
"satellites": {
"moon": {
"texture": ["moon.jpg", "moon_medium.jpg", "moon_small.jpg", [0.53, 0.53, 0.53, 1]],
"radius": 1738.14,
"distance": 38439,
"sma": 384399,
"division": 30,
"eccentricity": 0.0549,
"inclination": 5.145,
"rotation": 0,
"pitch": -90,
"yaw": 6.68,
"roll": -90
},
"iss": {
"model": "satellites/iss.obj",
"inclination": 51.65,
"distance": 6800,
"scale": 5
}
}
},
"mars": {
"texture": ["mars.jpg", "mars_small.jpg", "mars_medium.jpg", [0.85, 0.47, 0.2, 1]],
"radius": 3396.2,
"z": "1.524 * AU",
"pitch": -90,
"yaw": 25.19,
"mass": 6.4185e+23,
"rotation": 88643,
"orbit_distance": "AU",
"division": 30,
"satellites": {
"phobos": {
"distance": 9377,
"inclination": 26.04,
"eccentricity": 0.0151,
"model": "satellites/phobos.obj"
}
}
},
"jupiter": {
"texture": ["jupiter.jpg", "jupiter_medium.jpg", "jupiter_small.jpg", [0.65, 0.36, 0.19, 1]],
"radius": 71492,
"mass": 1.8986e+27,
"z": "5.2 * AU",
"pitch": -90,
"yaw": 3.13,
"comment": "satellites here are 3/10 the virtual distance than physical, and five times the size",
"rotation": 35730,
"orbit_distance": "3 * AU",
"satellites": {
"io": {
"texture": ["moons/io.jpg", "moons/io_small.jpg", [0.62, 0.56, 0.35, 1]],
"radius": "1821.3 * 5",
"distance": 126510,
"sma": 421700,
"rotation": 0,
"pitch": -90,
"inclination": 2.21,
"eccentricity": 0.0041
},
"europa": {
"texture": ["moons/europa.jpg", "moons/europa_small.jpg", [0.77, 0.74, 0.65, 1]],
"radius": "1560.8 * 5",
"distance": 201270,
"sma": 670900,
"pitch": -90,
"rotation": 0,
"inclination": 2.71,
"eccentricity": 0.009
},
"ganymede": {
"texture": ["moons/ganymede.jpg", "moons/ganymede_small.jpg", [0.52, 0.47, 0.46, 1]],
"radius": "2634.1 * 5",
"distance": 321120,
"sma": 1070400,
"pitch": -90,
"rotation": 0,
"inclination": 2.51,
"eccentricity": 0.0013
},
"callisto": {
"texture": ["moons/callisto.jpg", "moons/callisto_small.jpg", [0.49, 0.43, 0.34, 1]],
"radius": "2410.3 * 5",
"distance": 564810,
"sma": 1882700,
"pitch": -90,
"rotation": 0,
"inclination": 0.192,
"eccentricity": 0.0074
}
}
},
"saturn": {
"texture": ["saturn.jpg", "saturn_medium.jpg", "saturn_small.jpg", [0.9, 0.8, 0.64, 1]],
"radius": 60268,
"mass": 5.6846e+26,
"z": "9.58 * AU",
"pitch": -90,
"yaw": 26.73,
"rotation": 38052,
"ring": {
"texture": "ring_saturn.png",
"distance": 1169,
"size": 2247
},
"orbit_distance": "4 * AU",
"satellites": {
"titan": {
"texture": ["moons/titan.jpg", "moons/titan_small.jpg", [0.52, 0.39, 0.23, 1]],
"radius": "2576 * 10",
"distance": "1221870 / 3 + 200000",
"sma": 1221870,
"pitch": -90,
"rotation": 0,
"inclination": 27.07854,
"eccentricity": 0.0288
},
"rhea": {
"texture": ["moons/rhea.jpg", "moons/rhea_small.jpg", [0.62, 0.60, 0.59, 1]],
"radius": "763.8 * 10",
"distance": "527108 / 3 + 200000",
"sma": 527108,
"pitch": -90,
"rotation": 0,
"inclination": 27.075,
"eccentricity": 0.0012583
},
"iapetus": {
"texture": ["moons/iapetus.jpg", "moons/iapetus_small.jpg", [0.62, 0.60, 0.59, 1]],
"radius": "734.5 * 10",
"distance": "3560820 / 3 + 200000",
"sma": 3560820,
"pitch": -90,
"rotation": 0,
"inclination": 17.28,
"eccentricity": 0.0286125
},
"dione": {
"texture": ["moons/dione.jpg", "moons/dione_small.jpg", [0.46, 0.46, 0.46, 1]],
"radius": "561.4 * 10",
"distance": "377396 / 3 + 200000",
"sma": 377396,
"pitch": -90,
"rotation": 0,
"inclination": 26.711,
"eccentricity": 0.0022
},
"tethys": {
"texture": ["moons/tethys.jpg", "moons/tethys_small.jpg", [0.68, 0.68, 0.66, 1]],
"radius": "531.1 * 10",
"distance": "294619 / 3 + 200000",
"sma": 294619,
"pitch": -90,
"rotation": 0,
"inclination": 25.61,
"eccentricity": 0.0001
},
"enceladus": {
"texture": ["moons/enceladus.jpg", "moons/enceladus_small.jpg", [0.74, 0.74, 0.74, 1]],
"radius": "252.1 * 10",
"distance": "237948 / 3 + 200000",
"sma": 237948,
"pitch": -90,
"rotation": 0,
"inclination": 26.711,
"eccentricity": 0.0047
},
"mimas": {
"texture": ["moons/mimas.jpg", "moons/mimas_small.jpg", [0.47, 0.47, 0.47, 1]],
"radius": "198.2 * 10",
"distance": "181902 / 3 + 200000",
"sma": 181902,
"pitch": -90,
"rotation": 0,
"inclination": 28.304,
"eccentricity": 0.0196
}
}
},
"uranus": {
"texture": ["uranus.jpg", [0, 0.53, 0.84, 1]],
"radius": 25559,
"mass": 8.6810e+25,
"z": "19.23 * AU",
"pitch": 7.77,
"rotation": -62064,
"orbit_distance": "6 * AU",
"ring": {
"texture": "ring_uranus.png",
"pitch": 0,
"yaw": 0,
"roll": 90,
"distance": 421,
"size": 781
}
},
"neptune": {
"texture": ["neptune.jpg", [0.31, 0.49, 0.59, 1]],
"radius": 24764,
"mass": 1.0243e+26,
"z": "30.5 * AU",
"orbit_distance": "6 * AU",
"rotation": 57996,
"pitch": -90,
"yaw": 28.32
},
"sky": {
"texture": "sky.jpg",
"rotation": 0,
"optional": true,
"lighting": false,
"radius": 305000000,
"division": 30,
"pitch": 90,
"yaw": 30,
"roll": 180,
"delta": 0,
"background": true
}
},
"belts": {
"main": {
"model": "asteroids/mainbelt.obj",
"radius": "2.362 * AU",
"cross": 1000,
"scale": 30,
"count": 1024,
"rotation": 114536500
}
},
"start": {
"z": "AU - 400",
"yaw": 180
}
}

223
punyverse/world.py Normal file
View file

@ -0,0 +1,223 @@
from collections import OrderedDict
import os.path
try:
import json
except ImportError:
try:
import simplejson as json
except ImportError:
raise SystemExit('No JSON module found')
try:
from punyverse._model import model_list, load_model
except ImportError:
from punyverse.model import model_list, load_model
from punyverse.glgeom import *
from punyverse.entity import *
from punyverse.texture import *
from punyverse import texture
from math import pi, sqrt
G = 6.67384e-11 # Gravitation Constant
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()
au = root.get('au', 2000)
e = lambda x: eval(str(x), {'__builtins__': None}, {'AU': au, 'TEXTURE': texture.max_texture})
tick = root.get('tick', 4320) # How many second is a tick?
length = root.get('length', 4320) # Satellite distance is in km, divide by this gets in world units
world.tick_length = tick
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))
rotation = e(info.get('rotation', 86400))
radius = e(info.get('radius', length)) / length
background = info.get('background', False)
orbit_distance = e(info.get('orbit_distance', au))
division = info.get('division', max(min(int(radius / 8), 60), 10))
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, division, division, texture)
else:
object_id = compile(sphere, radius, division, division, texture, lighting=lighting)
elif 'model' in info:
scale = info.get('scale', 1)
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
return
params = {'world': world, 'orbit_distance': orbit_distance}
if parent is None:
type = Body
else:
x, y, z = parent.location
distance = e(info.get('distance', 100)) # Semi-major axis when actually displayed in virtual space
sma = e(info.get('sma', distance)) # Semi-major axis used to calculate orbital speed
if hasattr(parent, 'mass') and parent.mass is not None:
period = 2 * pi * sqrt((sma * 1000) ** 3 / (G * parent.mass))
speed = 360 / (period + .0)
if not rotation: # Rotation = 0 assumes tidal lock
rotation = period
else:
speed = info.get('orbit_speed', 1)
type = Satellite
params.update(parent=parent, orbit_speed=speed,
distance=distance / length, eccentricity=info.get('eccentricity', 0),
inclination=info.get('inclination', 0), longitude=info.get('longitude', 0),
argument=info.get('argument', 0))
if 'mass' in info:
params['mass'] = info['mass']
atmosphere_id = 0
cloudmap_id = 0
corona_id = 0
if 'atmosphere' in info:
atmosphere_data = info['atmosphere']
atm_size = e(atmosphere_data.get('diffuse_size', None))
atm_texture = atmosphere_data.get('diffuse_texture', None)
cloud_texture = atmosphere_data.get('cloud_texture', None)
corona_texture = atmosphere_data.get('corona_texture', None)
if cloud_texture is not None:
cheap, _, cloud_texture = get_best_texture(cloud_texture)
if not cheap:
cloudmap_id = compile(sphere, radius + 2, division, division, cloud_texture,
lighting=False)
if corona_texture is not None:
cheap, _, corona = get_best_texture(corona_texture)
if not cheap:
corona_size = atmosphere_data.get('corona_size', radius / 2)
corona_division = atmosphere_data.get('corona_division', 100)
corona_ratio = atmosphere_data.get('corona_ratio', 0.5)
corona_id = compile(flare, radius, radius + corona_size, corona_division,
corona_ratio, corona)
if atm_texture is not None:
cheap, _, atm_texture = get_best_texture(atm_texture)
if not cheap:
atmosphere_id = compile(disk, radius, radius + atm_size, 30, atm_texture)
theta = 360 / (rotation + .0) if rotation else 0
object = type(object_id, (x, y, z), (pitch, yaw, roll), rotation_angle=theta,
atmosphere=atmosphere_id, cloudmap=cloudmap_id, background=background,
corona=corona_id, **params)
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), **params))
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['bodies'].iteritems():
print 'Loading %s.' % planet
body(planet, info)
for name, info in root['belts'].iteritems():
print 'Loading %s.' % name
x = e(info.get('x', 0))
y = e(info.get('y', 0))
z = e(info.get('z', 0))
radius = e(info.get('radius', 0))
cross = e(info.get('cross', 0))
count = int(e(info.get('count', 0)))
scale = info.get('scale', 1)
longitude = info.get('longitude', 0)
inclination = info.get('inclination', 0)
argument = info.get('argument', 0)
rotation = info.get('period', 31536000)
theta = 360 / (rotation + .0) if rotation else 0
object_id = model_list(load_model(info['model']), info.get('sx', scale), info.get('sy', scale),
info.get('sz', scale), (0, 0, 0))
world.tracker.append(Belt(compile(belt, radius, cross, object_id, count),
(x, y, z), (inclination, longitude, argument),
rotation_angle=theta, world=world))
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
self.tick_length = 1
self.tick = 0

128
small_images.py Normal file
View file

@ -0,0 +1,128 @@
import sys
import os
from pyglet.gl import GLint, glGetIntegerv, GL_MAX_TEXTURE_SIZE
from ctypes import byref
buf = GLint()
glGetIntegerv(GL_MAX_TEXTURE_SIZE, byref(buf))
max_texture = buf.value
del byref, GLint, glGetIntegerv, GL_MAX_TEXTURE_SIZE, buf
def resize(width, height, target):
factor = (target + .0) / max(width, height)
return int(width * factor), int(height * factor)
def fits(width, height):
return width < max_texture and height < max_texture
def make_name(image, suffix):
name, ext = os.path.splitext(image)
return '%s_%s%s' % (name, suffix, ext)
try:
import pgmagick
def get_image(image):
return pgmagick.Image(image)
def get_size(image):
size = image.size()
return size.width(), size.height()
def scale(image, width, height):
image.filterType(pgmagick.FilterTypes.LanczosFilter)
image.scale(pgmagick.Geometry(width, height))
return image
def save(image, file):
image.write(file)
except ImportError:
import Image
def get_image(image):
return Image.open(image)
def get_size(image):
return image.size
def scale(image, width, height):
original_width, original_height = image.size
if width * 3 < original_width and height * 3 < original_height:
image = image.resize((width * 2, height * 2))
return image.resize((width, height), Image.ANTIALIAS)
def save(image, file):
image.save(file)
def shrink(file):
image = get_image(file)
width, height = get_size(image)
if fits(width, height):
print 'no need'
return
width, height = resize(width, height, 2048)
if fits(width, height):
size = 'medium'
else:
width, height = resize(width, height, 1024) # 1024 is minimum
size = 'small'
print 'size %s, %dx%d...' % (size, width, height),
name = make_name(file, size)
if not os.path.exists(name):
image = scale(image, width, height)
print 'saved to:', os.path.basename(name)
save(image, name)
else:
print 'alrady there'
textures = [
'mercury.jpg',
'earth.jpg',
'moon.jpg',
'mars.jpg',
'jupiter.jpg',
'saturn.jpg',
'moons/io.jpg',
'moons/europa.jpg',
'moons/ganymede.jpg',
'moons/callisto.jpg',
'moons/titan.jpg',
'moons/rhea.jpg',
'moons/iapetus.jpg',
'moons/dione.jpg',
'moons/tethys.jpg',
'moons/enceladus.jpg',
'moons/mimas.jpg',
]
def frozen():
import imp
return (hasattr(sys, 'frozen') or # new py2exe
hasattr(sys, 'importers') # old py2exe
or imp.is_frozen('__main__')) # tools/freeze
def get_main_dir():
if frozen():
return os.path.dirname(sys.executable)
return os.path.dirname(__file__)
def main():
punyverse = os.path.join(get_main_dir(), 'punyverse')
try:
with open(os.path.join(punyverse, 'assets', 'textures.txt')) as f:
files = [i.strip() for i in f if not i.startswith('#') and i.strip()]
except IOError:
files = textures
texture = os.path.join(punyverse, 'assets', 'textures')
for file in files:
print 'Resizing %s:' % file,
file = os.path.join(texture, file.replace('/', os.sep))
if os.path.exists(file):
shrink(file)
else:
print 'exists not'
if __name__ == '__main__':
main()