Compare commits

..

72 commits

Author SHA1 Message Date
Quantum 77647d688f Fixed handling of textures without fallback.
Earth now has same colour as atmosphere without a texture.
2013-11-25 17:04:27 -05:00
Quantum 5659439a8f Permanently banned all C files from being included in the repository.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-25 16:46:28 -05:00
Quantum 3fdef85957 Implemented normal mapped sphere in Cython.
Also:
 * Writes directly into the bytes object instead of malloc'ing.
 * Converted all OpenGL functions to nogil.

Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-25 16:34:21 -05:00
Quantum 35d77c2bc0 Backported all of Cython model loader to Python.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-23 12:36:27 -05:00
Quantum e9403fe746 Added compression support to wavefront loader.
Removed massive generated C file.
2013-11-23 12:12:48 -05:00
Quantum 878db3658a Used 3D rotation to calculate the normals. 2013-11-23 11:01:57 -05:00
Quantum 5435b594d4 Upgraded the normal map to 2048x1024. Should look better.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-21 23:22:24 -05:00
Quantum 2d9edde95c Switched to a triangle strip, should be much faster. 2013-11-21 20:59:33 -05:00
Quantum 8ae6186325 Added normal map for earth as proof of concept. 2013-11-21 20:47:29 -05:00
Quantum 16a07c01b0 Objects no longer flicker when being far (10x improvement). Close objects are not displayed very well. 2013-11-21 16:45:38 -05:00
Quantum 571ac1c443 Added Cassini around Saturn.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-20 18:38:53 -05:00
Quantum 7774330b8b The atmosphere now always faces the camera.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-20 16:43:54 -05:00
Quantum 0b0c6f8fba Added Hubble Space Telescope.
Signed-off-by: Xiaomao Chen <xiaomao5@live.com>
2013-11-17 01:09:53 -05:00
Quantum 3eba00caa6 Merge remote-tracking branch 'origin/master' 2013-11-14 12:42:49 -05:00
Quantum 0f6f0e4bc4 New asteroids and scaling. 2013-11-14 12:39:55 -05:00
Quantum d6c93bb739 Fixed world.json. Closes #100. 2013-11-12 16:43:53 -05:00
Quantum 5b67e045b1 Shrunk cursor greatly. 2013-11-09 18:57:07 -05:00
Quantum 6f450f37e7 gluBuild2DMipmaps doesn't seem to do anything. 2013-11-09 17:42:50 -05:00
Quantum b78582b27d Filtered cloud texture to remove noise. 2013-11-09 17:38:01 -05:00
Quantum 1667b1c672 New clouds that actually looks realistic. Closes #99. 2013-11-09 14:27:35 -05:00
Quantum 7943066fbb Removed stray glEnable(GL_BLEND). 2013-11-09 10:13:56 -05:00
Quantum 3fb601a2e7 Simplified implementation according to issue. Refs #98. 2013-11-09 10:08:59 -05:00
Quantum 06e6a803a2 Completely rewrite of ticking which guarantees update per frame. Refs #98. 2013-11-06 22:12:58 -05:00
Quantum ad942261f9 Updated description. 2013-11-06 21:39:42 -05:00
Quantum d5b69b8fc1 Pointed launcher to new release. 2013-11-06 17:46:35 -05:00
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
83 changed files with 2332 additions and 3112 deletions

22
.gitignore vendored
View file

@ -41,12 +41,10 @@ punyverse/*.c
*.html *.html
*.exp *.exp
*.lib *.lib
/punyverse/assets/textures/*_large.* punyverse/assets/textures/*_medium.*
/punyverse/assets/textures/*_medium.* punyverse/assets/textures/*_small.*
/punyverse/assets/textures/*_small.* punyverse/assets/textures/*/*_medium.*
/punyverse/assets/textures/*/*_large.* punyverse/assets/textures/*/*_small.*
/punyverse/assets/textures/*/*_medium.*
/punyverse/assets/textures/*/*_small.*
temp temp
# There is a py2exe package that can execute punyverse directly # There is a py2exe package that can execute punyverse directly
@ -54,15 +52,3 @@ library.zip
*.exe *.exe
*.log *.log
*.dll *.dll
# virtualenvs
/env
/env2
/env3
# Our special launcher
!/punyverse/launcher.c
# macOS
.DS_Store
._.DS_Store

View file

@ -1,17 +0,0 @@
#!/bin/bash
set -e -x
# Install a system package required by our library
yum install -y atlas-devel mesa-libGL-devel mesa-libGLU-devel
# Compile wheels
for PYBIN in /opt/python/*/bin; do
"${PYBIN}/pip" install -r /io/dev-requirements.txt
"${PYBIN}/pip" wheel /io/ -w wheelhouse/
done
# Bundle external shared libraries into the wheels
for whl in wheelhouse/punyverse*.whl; do
auditwheel repair "$whl" -w /io/wheelhouse/
done

View file

@ -1,27 +0,0 @@
branches:
only:
- master
- /^v\d+\./
matrix:
include:
- sudo: required
services:
- docker
env: DOCKER_IMAGE=quay.io/pypa/manylinux1_x86_64
- sudo: required
services:
- docker
env: DOCKER_IMAGE=quay.io/pypa/manylinux1_i686 PRE_CMD=linux32
install:
- docker pull $DOCKER_IMAGE
script:
- docker run --rm -v `pwd`:/io $DOCKER_IMAGE $PRE_CMD /io/.travis-wheels.sh
- ls wheelhouse/
deploy:
provider: releases
api_key:
secure: kNH5YcvrRmDmFI61Wu0tS4QdKrCbLIJQrVsiU5Cyzqz+hP9FG64uGVHN+J/IJ6Dbt2ny8rnk47pPln8kigfEY3EtnvvFsxexJ1JuefFqZXRPlvzqSm4BEcti5Iui7jMTHczK2eUOY6M2bjC0X4CHMa2dGh16JmfG0RAdUaAhvDCSXkbanoMkOkqyN3go7CAny7aZpyRYwv0GGBDAjXBom4kh5C8wqpXcXlVCmdvrvnmIr2EvMKzvup7FV4FlCkSr03F1APdvTK+GgIE8pLjI9sOw3zo8+tm7krwQsItbqTmPRb0TwL4CD4m2zQTREsLWtYHMy5Z+nqAhADWQqhO0mZDZSozs0TLevasmYdirsPaIufjwE3njWCud/7Mq2ahiBjDhTPHDPk1LzVyrMLo6SVpYru37Ft+Be9oWldz8m+t53RiusfFUVh6MH4+crS+FpdgL9FnQsiNiB+wpb8m6c4tDTWBvsNn2PF2w9NCTlu4u4B+EicM4QoZDK0Pmyg29d6nkEx6EI6Ts6Oz9WIE/z0ZLI3JhTZFZO7be8+xGQ8C3UrCNVS5BOlnyyjcXvnO1Vz56iXKjNLWybeLtMqw7DH58qmSBkRcPcl2+oMdqqIH53BdMHxTfSc9IoCbD1gXekpYPbfo7E2bA/9o4QiphnojWsMw1Yzxs4sXy4vMoxNY=
file_glob: true
file: wheelhouse/*
on:
tags: true

View file

@ -1,9 +0,0 @@
include MANIFEST.in
include LICENSE
include README.md
include punyverse/world.json
graft punyverse/assets
include punyverse/shaders/*.glsl
include punyverse/*.c
include punyverse/*.h
exclude punyverse/*.pyx

View file

@ -1,40 +1,37 @@
# punyverse [![Linux Build Status](https://img.shields.io/travis/quantum5/punyverse.svg?logo=linux&logoColor=white)](https://travis-ci.org/quantum5/punyverse) [![Windows Build Status](https://img.shields.io/appveyor/ci/quantum5/punyverse.svg?logo=windows)](https://ci.appveyor.com/project/quantum5/punyverse) [![PyPI](https://img.shields.io/pypi/v/punyverse.svg)](https://pypi.org/project/punyverse/) [![PyPI - Format](https://img.shields.io/pypi/format/punyverse.svg)](https://pypi.org/project/punyverse/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/punyverse.svg)](https://pypi.org/project/punyverse/)
Python simulator of a puny universe. (How many words can I stick into one?)
![Punyverse Preview](https://guanzhong.ca/assets/projects/punyverse-1.0-7e302d32fb62574e7f7a04acaf9c54e8658821614654280e26e54fab4a840254.png)
## Installation
To install, run `pip install punyverse`.
If you are on Windows, run `punyverse_make_launcher`. This should create special launchers that runs `punyverse` on
your dedicated graphics card, should it exist.
Your graphics card might not support some of the larger textures used by `punyverse`, and so startup might fail.
To solve this problem, run `punyverse_small_images`. It will do nothing if your graphics card supports all the
textures, so when in doubt, run `punyverse_small_images` after installation.
Then, run `punyverse` to launch the simulator, or `punyversew` to launch without the console.
### Summary
```bash
pip install punyverse
punyverse_make_launcher
punyverse_small_images
# Installation finished. Run:
punyverse punyverse
``` =========
## Troubleshooting Python simulator of a puny universe. (How many words can i stick into one?)
If `punyverse` does not work, try upgrading your graphics card drivers. Installation
------------
If your graphics card does not appear to support OpenGL 3.3, then you cannot run the latest version of `punyverse`. To install, simply clone this repository, or download a copy [here]
You can try `pip install -U punyverse==0.5` to install the last version of `punyverse` to support legacy devices. (https://github.com/xiaomao5/punyverse/archive/master.zip).
You can download the wheels manually from [the PyPI page](https://pypi.org/project/punyverse/0.5/).
If the problem is unrelated to your graphics card, and it persists, try running punyverse under debug mode. To do this, After that, download the [launcher](https://github.com/xiaomao5/punyverse/releases/download/launcher0.3/launcher.exe),
run `punyverse` as `punyverse --debug`. Then paste the entirety of the output into a new GitHub issue put it into the repository directory and let it unpack in your repository (or copy).
[here](https://github.com/quantum5/punyverse/issues/new).
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`, if you have python) to generate smaller versions of
shipped textures, which requires either `PIL` or `pgmagick` to process the images.
### Advanced Install
If you wish to use your own python installation, to run `punyverse`, you can clone the code.
Here are the things you need:
* Python 2.7, I have no Python 2.6 install to test this.
* a C compiler to compile `_model.c` and `_glgeom.c`
* requires OpenGL headers and libraries.
* not really necessary, but it runs way faster with these.
* install `pyglet`
After getting the dependencies done, 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)

View file

@ -1 +0,0 @@
cython

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'],
}
}
)

View file

@ -1,4 +1,29 @@
from punyverse.main import main #!/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__': if __name__ == '__main__':
main() main()

View file

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

View file

@ -1,4 +1,8 @@
cdef extern from "glwrapper.h": IF UNAME_SYSNAME == "Windows":
cdef extern from "windows.h":
pass
cdef extern from "GL/gl.h":
ctypedef unsigned int GLenum ctypedef unsigned int GLenum
ctypedef unsigned char GLboolean ctypedef unsigned char GLboolean
ctypedef unsigned int GLbitfield ctypedef unsigned int GLbitfield
@ -383,6 +387,74 @@ cdef extern from "glwrapper.h":
void glDrawElements(GLenum mode, GLsizei count, GLenum type, GLvoid *indices) nogil void glDrawElements(GLenum mode, GLsizei count, GLenum type, GLvoid *indices) nogil
void glInterleavedArrays(GLenum format, GLsizei stride, GLvoid *pointer) nogil void glInterleavedArrays(GLenum format, GLsizei stride, GLvoid *pointer) nogil
cdef extern from "stddef.h":
ctypedef unsigned int wchar_t
cdef extern from "GL/glu.h":
ctypedef struct GLUnurbs:
pass
ctypedef struct GLUquadric:
pass
ctypedef struct GLUtesselator:
pass
ctypedef GLUnurbs GLUnurbsObj
ctypedef GLUquadric GLUquadricObj
ctypedef GLUtesselator GLUtesselatorObj
ctypedef GLUtesselator GLUtriangulatorObj
void gluBeginCurve(GLUnurbs* nurb) nogil
void gluBeginPolygon(GLUtesselator* tess) nogil
void gluBeginSurface(GLUnurbs* nurb) nogil
void gluBeginTrim(GLUnurbs* nurb) nogil
GLint gluBuild1DMipmaps(GLenum target, GLint internalFormat, GLsizei width, GLenum format, GLenum type, void *data) nogil
GLint gluBuild2DMipmaps(GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, void *data) nogil
void gluCylinder(GLUquadric* quad, GLdouble base, GLdouble top, GLdouble height, GLint slices, GLint stacks) nogil
void gluDeleteNurbsRenderer(GLUnurbs* nurb) nogil
void gluDeleteQuadric(GLUquadric* quad) nogil
void gluDeleteTess(GLUtesselator* tess) nogil
void gluDisk(GLUquadric* quad, GLdouble inner, GLdouble outer, GLint slices, GLint loops) nogil
void gluEndCurve(GLUnurbs* nurb) nogil
void gluEndPolygon(GLUtesselator* tess) nogil
void gluEndSurface(GLUnurbs* nurb) nogil
void gluEndTrim(GLUnurbs* nurb) nogil
GLubyte * gluErrorString(GLenum error)
wchar_t * gluErrorUnicodeStringEXT(GLenum error)
void gluGetNurbsProperty(GLUnurbs* nurb, GLenum property, GLfloat* data) nogil
GLubyte * gluGetString(GLenum name)
void gluGetTessProperty(GLUtesselator* tess, GLenum which, GLdouble* data) nogil
void gluLoadSamplingMatrices(GLUnurbs* nurb, GLfloat *model, GLfloat *perspective, GLint *view) nogil
void gluLookAt(GLdouble eyeX, GLdouble eyeY, GLdouble eyeZ, GLdouble centerX, GLdouble centerY, GLdouble centerZ, GLdouble upX, GLdouble upY, GLdouble upZ) nogil
GLUnurbs* gluNewNurbsRenderer()
GLUquadric* gluNewQuadric()
GLUtesselator* gluNewTess()
void gluNextContour(GLUtesselator* tess, GLenum type) nogil
void gluNurbsCurve(GLUnurbs* nurb, GLint knotCount, GLfloat *knots, GLint stride, GLfloat *control, GLint order, GLenum type) nogil
void gluNurbsProperty(GLUnurbs* nurb, GLenum property, GLfloat value) nogil
void gluNurbsSurface(GLUnurbs* nurb, GLint sKnotCount, GLfloat* sKnots, GLint tKnotCount, GLfloat* tKnots, GLint sStride, GLint tStride, GLfloat* control, GLint sOrder, GLint tOrder, GLenum type) nogil
void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top) nogil
void gluPartialDisk(GLUquadric* quad, GLdouble inner, GLdouble outer, GLint slices, GLint loops, GLdouble start, GLdouble sweep) nogil
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar) nogil
void gluPickMatrix(GLdouble x, GLdouble y, GLdouble delX, GLdouble delY, GLint *viewport) nogil
GLint gluProject(GLdouble objX, GLdouble objY, GLdouble objZ, GLdouble *model, GLdouble *proj, GLint *view, GLdouble* winX, GLdouble* winY, GLdouble* winZ)
void gluPwlCurve(GLUnurbs* nurb, GLint count, GLfloat* data, GLint stride, GLenum type) nogil
void gluQuadricDrawStyle(GLUquadric* quad, GLenum draw) nogil
void gluQuadricNormals(GLUquadric* quad, GLenum normal) nogil
void gluQuadricOrientation(GLUquadric* quad, GLenum orientation) nogil
void gluQuadricTexture(GLUquadric* quad, GLboolean texture) nogil
GLint gluScaleImage(GLenum format, GLsizei wIn, GLsizei hIn, GLenum typeIn, void *dataIn, GLsizei wOut, GLsizei hOut, GLenum typeOut, GLvoid* dataOut) nogil
void gluSphere(GLUquadric* quad, GLdouble radius, GLint slices, GLint stacks) nogil
void gluTessBeginContour(GLUtesselator* tess) nogil
void gluTessBeginPolygon(GLUtesselator* tess, GLvoid* data) nogil
void gluTessEndContour(GLUtesselator* tess) nogil
void gluTessEndPolygon(GLUtesselator* tess) nogil
void gluTessNormal(GLUtesselator* tess, GLdouble valueX, GLdouble valueY, GLdouble valueZ) nogil
void gluTessProperty(GLUtesselator* tess, GLenum which, GLdouble data) nogil
void gluTessVertex(GLUtesselator* tess, GLdouble *location, GLvoid* data) nogil
GLint gluUnProject(GLdouble winX, GLdouble winY, GLdouble winZ, GLdouble *model, GLdouble *proj, GLint *view, GLdouble* objX, GLdouble* objY, GLdouble* objZ)
GLint gluUnProject4(GLdouble winX, GLdouble winY, GLdouble winZ, GLdouble clipW, GLdouble *model, GLdouble *proj, GLint *view, GLdouble nearVal, GLdouble farVal, GLdouble* objX, GLdouble* objY, GLdouble* objZ, GLdouble* objW)
cdef enum: cdef enum:
GL_FALSE = 0x0 GL_FALSE = 0x0
GL_TRUE = 0x1 GL_TRUE = 0x1
@ -985,3 +1057,153 @@ cdef enum:
GL_CLIENT_VERTEX_ARRAY_BIT = 0x00000002 GL_CLIENT_VERTEX_ARRAY_BIT = 0x00000002
GL_ALL_CLIENT_ATTRIB_BITS = 0xFFFFFFFF GL_ALL_CLIENT_ATTRIB_BITS = 0xFFFFFFFF
GL_CLIENT_ALL_ATTRIB_BITS = 0xFFFFFFFF GL_CLIENT_ALL_ATTRIB_BITS = 0xFFFFFFFF
# Boolean
GLU_FALSE = 0
GLU_TRUE = 1
# StringName
GLU_VERSION = 100800
GLU_EXTENSIONS = 100801
# ErrorCode
GLU_INVALID_ENUM = 100900
GLU_INVALID_VALUE = 100901
GLU_OUT_OF_MEMORY = 100902
GLU_INVALID_OPERATION = 100904
# NurbsDisplay
# GLU_FILL
GLU_OUTLINE_POLYGON = 100240
GLU_OUTLINE_PATCH = 100241
# NurbsError
GLU_NURBS_ERROR1 = 100251
GLU_NURBS_ERROR2 = 100252
GLU_NURBS_ERROR3 = 100253
GLU_NURBS_ERROR4 = 100254
GLU_NURBS_ERROR5 = 100255
GLU_NURBS_ERROR6 = 100256
GLU_NURBS_ERROR7 = 100257
GLU_NURBS_ERROR8 = 100258
GLU_NURBS_ERROR9 = 100259
GLU_NURBS_ERROR10 = 100260
GLU_NURBS_ERROR11 = 100261
GLU_NURBS_ERROR12 = 100262
GLU_NURBS_ERROR13 = 100263
GLU_NURBS_ERROR14 = 100264
GLU_NURBS_ERROR15 = 100265
GLU_NURBS_ERROR16 = 100266
GLU_NURBS_ERROR17 = 100267
GLU_NURBS_ERROR18 = 100268
GLU_NURBS_ERROR19 = 100269
GLU_NURBS_ERROR20 = 100270
GLU_NURBS_ERROR21 = 100271
GLU_NURBS_ERROR22 = 100272
GLU_NURBS_ERROR23 = 100273
GLU_NURBS_ERROR24 = 100274
GLU_NURBS_ERROR25 = 100275
GLU_NURBS_ERROR26 = 100276
GLU_NURBS_ERROR27 = 100277
GLU_NURBS_ERROR28 = 100278
GLU_NURBS_ERROR29 = 100279
GLU_NURBS_ERROR30 = 100280
GLU_NURBS_ERROR31 = 100281
GLU_NURBS_ERROR32 = 100282
GLU_NURBS_ERROR33 = 100283
GLU_NURBS_ERROR34 = 100284
GLU_NURBS_ERROR35 = 100285
GLU_NURBS_ERROR36 = 100286
GLU_NURBS_ERROR37 = 100287
# NurbsProperty
GLU_AUTO_LOAD_MATRIX = 100200
GLU_CULLING = 100201
GLU_SAMPLING_TOLERANCE = 100203
GLU_DISPLAY_MODE = 100204
GLU_PARAMETRIC_TOLERANCE = 100202
GLU_SAMPLING_METHOD = 100205
GLU_U_STEP = 100206
GLU_V_STEP = 100207
# NurbsSampling
GLU_PATH_LENGTH = 100215
GLU_PARAMETRIC_ERROR = 100216
GLU_DOMAIN_DISTANCE = 100217
# NurbsTrim
GLU_MAP1_TRIM_2 = 100210
GLU_MAP1_TRIM_3 = 100211
# QuadricDrawStyle
GLU_POINT = 100010
GLU_LINE = 100011
GLU_FILL = 100012
GLU_SILHOUETTE = 100013
# QuadricCallback
GLU_ERROR = 100103
# QuadricNormal
GLU_SMOOTH = 100000
GLU_FLAT = 100001
GLU_NONE = 100002
# QuadricOrientation
GLU_OUTSIDE = 100020
GLU_INSIDE = 100021
# TessCallback
GLU_TESS_BEGIN = 100100
GLU_BEGIN = 100100
GLU_TESS_VERTEX = 100101
GLU_VERTEX = 100101
GLU_TESS_END = 100102
GLU_END = 100102
GLU_TESS_ERROR = 100103
GLU_TESS_EDGE_FLAG = 100104
GLU_EDGE_FLAG = 100104
GLU_TESS_COMBINE = 100105
GLU_TESS_BEGIN_DATA = 100106
GLU_TESS_VERTEX_DATA = 100107
GLU_TESS_END_DATA = 100108
GLU_TESS_ERROR_DATA = 100109
GLU_TESS_EDGE_FLAG_DATA = 100110
GLU_TESS_COMBINE_DATA = 100111
# TessContour
GLU_CW = 100120
GLU_CCW = 100121
GLU_INTERIOR = 100122
GLU_EXTERIOR = 100123
GLU_UNKNOWN = 100124
# TessProperty
GLU_TESS_WINDING_RULE = 100140
GLU_TESS_BOUNDARY_ONLY = 100141
GLU_TESS_TOLERANCE = 100142
# TessError
GLU_TESS_ERROR1 = 100151
GLU_TESS_ERROR2 = 100152
GLU_TESS_ERROR3 = 100153
GLU_TESS_ERROR4 = 100154
GLU_TESS_ERROR5 = 100155
GLU_TESS_ERROR6 = 100156
GLU_TESS_ERROR7 = 100157
GLU_TESS_ERROR8 = 100158
GLU_TESS_MISSING_BEGIN_POLYGON = 100151
GLU_TESS_MISSING_BEGIN_CONTOUR = 100152
GLU_TESS_MISSING_END_POLYGON = 100153
GLU_TESS_MISSING_END_CONTOUR = 100154
GLU_TESS_COORD_TOO_LARGE = 100155
GLU_TESS_NEED_COMBINE_CALLBACK = 100156
# TessWinding
GLU_TESS_WINDING_ODD = 100130
GLU_TESS_WINDING_NONZERO = 100131
GLU_TESS_WINDING_POSITIVE = 100132
GLU_TESS_WINDING_NEGATIVE = 100133
GLU_TESS_WINDING_ABS_GEQ_TWO = 100134
cdef float GLU_TESS_MAX_COORD = 1.0e150

View file

@ -1,4 +1,7 @@
from libc.math cimport sin, cos, sqrt
from libc.stdlib cimport malloc, free
from libc.string cimport memcpy from libc.string cimport memcpy
cimport cython
include "_cyopengl.pxi" include "_cyopengl.pxi"
cdef float PI = 3.1415926535897932324626 cdef float PI = 3.1415926535897932324626
@ -9,31 +12,150 @@ cdef extern from "Python.h":
const char* PyBytes_AsString(bytes o) const char* PyBytes_AsString(bytes o)
cpdef bytes bgr_to_rgb(bytes buffer, int width, int height, bint alpha=0): @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
cdef float a0, a1, x0, y0, x1, y1, b, c, r, z, m, x, y, z2
cdef int i, j
with nogil:
major_s = TWOPI / n_major
minor_s = TWOPI / n_minor
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()
@cython.cdivision(True)
cpdef normal_sphere(double r, int divide, GLuint tex, normal, bint lighting=True):
from texture import pil_load
print 'Loading normal map: %s...' % normal,
normal_map = pil_load(normal)
normal = normal_map.load()
print 'Loaded'
cdef int width, height
width, height = normal_map.size
cdef bint gray_scale = len(normal[0, 0]) == 1
glEnable(GL_TEXTURE_2D)
if lighting:
glDisable(GL_BLEND)
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, [1, 1, 1, 0])
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [1, 1, 1, 0])
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 125)
else:
glDisable(GL_LIGHTING)
glBindTexture(GL_TEXTURE_2D, tex)
cdef double twopi_divide, pi_divide
cdef int i, j
cdef double phi1, phi2
cdef double theta, s, t
cdef int u, v
cdef double x, y, z
cdef double dx, dy, xz
cdef double nx, ny, nz
twopi_divide = TWOPI / divide
pi_divide = PI / divide
glBegin(GL_TRIANGLE_STRIP)
for j in xrange(divide + 1):
phi1 = j * twopi_divide
phi2 = (j + 1) * twopi_divide
for i in xrange(divide + 1):
theta = i * pi_divide
s = phi2 / TWOPI
u = min(<int>(s * width), width - 1)
t = theta / PI
v = min(<int>(t * height), height - 1)
if gray_scale:
x = y = z = normal[u, v]
else:
x, y, z = normal[u, v]
dx, dy, dz = sin(theta) * cos(phi2), sin(theta) * sin(phi2), cos(theta)
nx, ny, nz = x / 127.5 - 1, y / 127.5 - 1, z / 127.5 - 1 # Make into [-1, 1]
nx, nz = cos(theta) * nx + sin(theta) * nz, -sin(theta) * nx + cos(theta) * nz
nx, ny = cos(phi2) * nx - sin(phi2) * ny, sin(phi2) * nx + cos(phi2) * ny
glNormal3f(nx, ny, nz)
glTexCoord2f(s, 1 - t) # GL is bottom up
glVertex3f(r * dx, r * dy, r * dz)
s = phi1 / TWOPI # x
u = min(<int>(s * width), width - 1)
if gray_scale:
x = y = z = normal[u, v]
else:
x, y, z = normal[u, v]
dx, dy = sin(theta) * cos(phi1), sin(theta) * sin(phi1)
nx, ny, nz = x / 127.5 - 1, y / 127.5 - 1, z / 127.5 - 1
nx, nz = cos(theta) * nx + sin(theta) * nz, -sin(theta) * nx + cos(theta) * nz
nx, ny = cos(phi1) * nx - sin(phi1) * ny, sin(phi1) * nx + cos(phi1) * ny
glNormal3f(nx, ny, nz)
glTexCoord2f(s, 1 - t)
glVertex3f(r * dx, r * dy, r * dz)
glEnd()
glDisable(GL_TEXTURE_2D)
glEnable(GL_LIGHTING)
glEnable(GL_BLEND)
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 length = len(buffer)
cdef int depth = length / (width * height) cdef int depth = length / (width * height)
cdef int depth2 = depth - alpha cdef int depth2 = depth - alpha
cdef object final = PyBytes_FromStringAndSize(NULL, length) cdef object final = PyBytes_FromStringAndSize(NULL, length)
cdef char *result = PyBytes_AsString(final) cdef char *result = PyBytes_AsString(final)
cdef const char *source = PyBytes_AsString(buffer) cdef const char *source = PyBytes_AsString(buffer)
cdef int x, y, offset, i, row = width * depth cdef int x, y, ioffset, ooffset, i, row = width * depth
for y in xrange(height): for y in xrange(height):
for x in xrange(width): for x in xrange(width):
offset = y * row + x * depth ioffset = y * width * depth + x * depth
ooffset = (height - y - 1 if bottom_up else y) * row + x * depth
for i in xrange(depth2): for i in xrange(depth2):
result[offset+i] = source[offset+depth2-i-1] result[ooffset+i] = source[ioffset+depth2-i-1]
if alpha: if alpha:
result[offset+depth2] = source[offset+depth2] result[ooffset+depth2] = source[ioffset+depth2]
return final
cpdef bytes flip_vertical(bytes buffer, int width, int height):
cdef int length = len(buffer)
cdef object final = PyBytes_FromStringAndSize(NULL, length)
cdef char *result = PyBytes_AsString(final)
cdef const char *source = PyBytes_AsString(buffer)
cdef int y1, y2, row = length / height
for y1 in xrange(height):
y2 = height - y1 - 1
memcpy(result + y1 * row, source + y2 * row, row)
return final return final

362
punyverse/_model.pyx Normal file
View file

@ -0,0 +1,362 @@
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
import gzip
import bz2
import zipfile
def zip_open(file):
zip = zipfile.ZipFile(file)
return zip.open(zip.namelist()[0])
openers = {
'gz': gzip.open,
'bz2': bz2.BZ2File,
'zip': zip_open,
}
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 bint material(self, list words) except False:
return 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 bint perform_io(self, unicode file) except False:
cdef const char *type
cdef list words
cdef int hash, length
ext = os.path.splitext(file)[1].lstrip('.')
reader = openers.get(ext, open)(file)
with reader:
for buf in reader:
if not buf or buf.startswith(('\r', '\n', '#')):
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)
return True
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.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

View file

@ -1,6 +1,6 @@
# Blender v2.68 (sub 0) OBJ File: '' # Blender v2.68 (sub 0) OBJ File: ''
# www.blender.org # www.blender.org
mtllib hst.mtl mtllib HST.mtl
v 0.325496 -2.594603 -0.788680 v 0.325496 -2.594603 -0.788680
v 0.472583 -2.594603 -0.710061 v 0.472583 -2.594603 -0.710061
v 0.601506 -2.594603 -0.604257 v 0.601506 -2.594603 -0.604257

View file

@ -1,26 +1,13 @@
# This file contains all the textures that is bigger than the minimum OpenGL texture size # 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 # and hence may need to be resized depending on the machine
sun.jpg
mercury.jpg mercury.jpg
venus.jpg
earth.jpg earth.jpg
earth_normal.jpg
earth_emission.jpg
earth_specular.jpg
cloudmap.jpg cloudmap.jpg
moon.jpg moon.jpg
mars.jpg mars.jpg
jupiter.jpg jupiter.jpg
saturn.jpg saturn.jpg
uranus.jpg
neptune.jpg
sky_px.jpg
sky_py.jpg
sky_pz.jpg
sky_nx.jpg
sky_ny.jpg
sky_nz.jpg
moons/io.jpg moons/io.jpg
moons/europa.jpg moons/europa.jpg
moons/ganymede.jpg moons/ganymede.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

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.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 981 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View file

@ -1,9 +1,4 @@
from __future__ import division from math import sin, cos, radians
from math import sin, cos, radians, hypot, tan
from punyverse.glgeom import Matrix4f
from punyverse.utils import cached_property
class Camera(object): class Camera(object):
@ -15,23 +10,12 @@ class Camera(object):
self.yaw = yaw self.yaw = yaw
self.roll = roll self.roll = roll
self.fov = radians(45)
self.aspect = 1
self.znear = 1
self.zfar = 3000000
self.speed = 0
self.roll_left = False
self.roll_right = False
def move(self, speed): def move(self, speed):
dx, dy, dz = self.direction() dx, dy, dz = self.direction()
self.x += dx * speed self.x += dx * speed
self.y += dy * speed self.y += dy * speed
self.z += dz * speed self.z += dz * speed
self.view_matrix = None
def mouse_move(self, dx, dy): def mouse_move(self, dx, dy):
if self.pitch > 90 or self.pitch < -90: if self.pitch > 90 or self.pitch < -90:
dx = -dx dx = -dx
@ -48,8 +32,6 @@ class Camera(object):
elif self.pitch > 180: elif self.pitch > 180:
self.pitch -= 360 self.pitch -= 360
self.view_matrix = None
def direction(self): def direction(self):
m = cos(radians(self.pitch)) m = cos(radians(self.pitch))
@ -57,33 +39,3 @@ class Camera(object):
dx = cos(radians(self.yaw - 90)) * m dx = cos(radians(self.yaw - 90)) * m
dz = sin(radians(self.yaw - 90)) * m dz = sin(radians(self.yaw - 90)) * m
return dx, dy, dz return dx, dy, dz
def update(self, dt, move):
if self.roll_left:
self.roll -= 4 * dt * 10
self.view_matrix = None
if self.roll_right:
self.roll += 4 * dt * 10
self.view_matrix = None
if move:
self.move(self.speed * 10 * dt)
def reset_roll(self):
self.roll = 0
self.view_matrix = None
def distance(self, x, y, z):
return hypot(hypot(x - self.x, y - self.y), z - self.z)
@cached_property
def view_matrix(self):
return Matrix4f.from_angles((self.x, self.y, self.z), (self.pitch, self.yaw, self.roll), view=True)
def projection_matrix(self):
scale_y = 1 / tan(self.fov / 2)
scale_x = scale_y / self.aspect
frustrum = self.znear - self.zfar
return Matrix4f([scale_x, 0, 0, 0,
0, scale_y, 0, 0,
0, 0, (self.znear + self.zfar) / frustrum, -1,
0, 0, (2 * self.znear * self.zfar) / frustrum, 0])

View file

@ -1,60 +1,25 @@
import random from punyverse.orbit import KeplerOrbit
from math import sqrt, pi
from pyglet.gl import * from pyglet.gl import *
# noinspection PyUnresolvedReferences
from six.moves import range
from punyverse.glgeom import *
from punyverse.model import load_model, WavefrontVBO
from punyverse.orbit import KeplerOrbit
from punyverse.texture import get_best_texture, load_alpha_mask, get_cube_map, load_texture_1d
from punyverse.utils import cached_property
G = 6.67384e-11 # Gravitation Constant
class Entity(object): class Entity(object):
background = False def __init__(self, id, location, rotation=(0, 0, 0), direction=(0, 0, 0), background=False):
self.id = id
def __init__(self, world, name, location, rotation=(0, 0, 0), direction=(0, 0, 0)):
self.world = world
self.name = name
self.location = location self.location = location
self.rotation = rotation self.rotation = rotation
self.direction = direction self.direction = direction
self.background = background
@cached_property
def model_matrix(self):
return Matrix4f.from_angles(self.location, self.rotation)
@cached_property
def mv_matrix(self):
return self.world.view_matrix() * self.model_matrix
@cached_property
def mvp_matrix(self):
return self.world.vp_matrix * self.model_matrix
def update(self): def update(self):
self.model_matrix = None
self.mv_matrix = None
self.mvp_matrix = None
x, y, z = self.location x, y, z = self.location
dx, dy, dz = self.direction dx, dy, dz = self.direction
self.location = x + dx, y + dy, z + dz self.location = x + dx, y + dy, z + dz
def collides(self, x, y, z):
return False
def draw(self, options):
raise NotImplementedError()
class Asteroid(Entity): class Asteroid(Entity):
def __init__(self, world, model, location, direction): def __init__(self, *args, **kwargs):
super(Asteroid, self).__init__(world, 'Asteroid', location, direction=direction) super(Asteroid, self).__init__(*args, **kwargs)
self.model = model
def update(self): def update(self):
super(Asteroid, self).update() super(Asteroid, self).update()
@ -62,536 +27,102 @@ class Asteroid(Entity):
# Increment all axis to 'spin' # Increment all axis to 'spin'
self.rotation = rx + 1, ry + 1, rz + 1 self.rotation = rx + 1, ry + 1, rz + 1
def draw(self, options):
shader = self.world.activate_shader('model')
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
shader.uniform_mat4('u_mvMatrix', self.mv_matrix)
shader.uniform_mat4('u_modelMatrix', self.model_matrix)
self.model.draw(shader)
class AsteroidManager(object):
def __init__(self, world):
self.world = world
self.asteroids = []
def __bool__(self):
return bool(self.asteroids)
__nonzero__ = __bool__
def load(self, file):
shader = self.world.activate_shader('model')
self.asteroids.append(WavefrontVBO(load_model(file), shader, 5, 5, 5))
def new(self, location, direction):
return Asteroid(self.world, random.choice(self.asteroids), location, direction)
class Belt(Entity): class Belt(Entity):
def __init__(self, name, world, info): def __init__(self, *args, **kwargs):
x = world.evaluate(info.get('x', 0)) self.rotation_angle = kwargs.pop('rotation_angle', 5)
y = world.evaluate(info.get('y', 0)) self.world = kwargs.pop('world')
z = world.evaluate(info.get('z', 0)) super(Belt, self).__init__(*args, **kwargs)
radius = world.evaluate(info.get('radius', 0))
cross = world.evaluate(info.get('cross', 0))
count = int(world.evaluate(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)
models = info['model']
self.rotation_angle = 360.0 / rotation if rotation else 0
shader = world.activate_shader('belt')
if not isinstance(models, list):
models = [models]
self.belt = BeltVBO(radius, cross, len(models), count)
self.objects = [
WavefrontVBO(load_model(model), shader, info.get('sx', scale),
info.get('sy', scale), info.get('sz', scale))
for model in models
]
def callback():
glBindBuffer(GL_ARRAY_BUFFER, vbo)
shader.vertex_attribute('a_translate', self.belt.location_size, self.belt.type, GL_FALSE,
self.belt.stride, self.belt.location_offset, divisor=1)
shader.vertex_attribute('a_scale', self.belt.scale_size, self.belt.type, GL_FALSE,
self.belt.stride, self.belt.scale_offset, divisor=1)
glBindBuffer(GL_ARRAY_BUFFER, 0)
for model, vbo, count in zip(self.objects, self.belt.vbo, self.belt.sizes):
model.additional_attributes(callback)
super(Belt, self).__init__(world, name, (x, y, z), (inclination, longitude, argument))
def update(self): def update(self):
super(Belt, self).update() super(Belt, self).update()
pitch, yaw, roll = self.rotation pitch, yaw, roll = self.rotation
self.rotation = pitch, self.world.tick * self.rotation_angle % 360, roll self.rotation = pitch, self.world.tick * self.rotation_angle % 360, roll
def draw(self, options):
shader = self.world.activate_shader('belt')
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
shader.uniform_mat4('u_mvMatrix', self.mv_matrix)
shader.uniform_mat4('u_modelMatrix', self.model_matrix)
for object, vbo, count in zip(self.objects, self.belt.vbo, self.belt.sizes):
object.draw(shader, instances=count)
class Sky(Entity):
background = True
def __init__(self, world, info, callback=None):
pitch = world.evaluate(info.get('pitch', 0))
yaw = world.evaluate(info.get('yaw', 0))
roll = world.evaluate(info.get('roll', 0))
super(Sky, self).__init__(world, 'Sky', (0, 0, 0), [pitch, yaw, roll])
self.texture = get_best_texture(info['texture'], loader=get_cube_map, callback=callback)
self.constellation = get_cube_map(info['constellation'])
self.cube = Cube()
self.vao = VAO()
shader = self.world.activate_shader('sky')
with self.vao:
glBindBuffer(GL_ARRAY_BUFFER, self.cube.vbo)
shader.vertex_attribute('a_direction', self.cube.direction_size, self.cube.type, GL_FALSE,
self.cube.stride, self.cube.direction_offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def draw(self, options):
cam = self.world.cam
shader = self.world.activate_shader('sky')
shader.uniform_mat4('u_mvpMatrix', self.world.projection_matrix() *
Matrix4f.from_angles(rotation=(cam.pitch, cam.yaw, cam.roll)) *
Matrix4f.from_angles(rotation=self.rotation))
glBindTexture(GL_TEXTURE_CUBE_MAP, self.texture)
shader.uniform_texture('u_skysphere', 0)
glActiveTexture(GL_TEXTURE1)
glBindTexture(GL_TEXTURE_CUBE_MAP, self.constellation)
shader.uniform_texture('u_constellation', 1)
shader.uniform_bool('u_lines', options.constellations)
with self.vao:
glDrawArrays(GL_TRIANGLES, 0, self.cube.vertex_count)
glActiveTexture(GL_TEXTURE0)
class Body(Entity): class Body(Entity):
def __init__(self, name, world, info, parent=None): def __init__(self, *args, **kwargs):
self.parent = parent self.rotation_angle = kwargs.pop('rotation_angle', 5)
self.satellites = [] self.atmosphere = kwargs.pop('atmosphere', 0)
self.cloudmap = kwargs.pop('cloudmap', 0)
x = world.evaluate(info.get('x', 0)) self.corona = kwargs.pop('corona', 0)
y = world.evaluate(info.get('y', 0)) self.last_tick = 0
z = world.evaluate(info.get('z', 0)) self.mass = kwargs.pop('mass', None)
pitch = world.evaluate(info.get('pitch', 0)) self.world = kwargs.pop('world')
yaw = world.evaluate(info.get('yaw', 0)) orbit_distance = kwargs.pop('orbit_distance', 40000) + .0
roll = world.evaluate(info.get('roll', 0))
rotation = world.evaluate(info.get('rotation', 86400))
self.mass = info.get('mass')
orbit_distance = float(world.evaluate(info.get('orbit_distance', world.au)))
self.orbit_show = orbit_distance * 1.25 self.orbit_show = orbit_distance * 1.25
self.orbit_blend = orbit_distance / 4 self.orbit_blend = orbit_distance / 4
self.orbit_opaque = orbit_distance self.orbit_opaque = orbit_distance
super(Body, self).__init__(*args, **kwargs)
super(Body, self).__init__(world, name, (x, y, z), (pitch, yaw, roll)) self.initial_roll = self.rotation[2]
self.initial_roll = roll
self.orbit = None
self.orbit_speed = None
if parent:
# Semi-major axis when actually displayed in virtual space
distance = world.evaluate(info.get('distance', 100))
# Semi-major axis used to calculate orbital speed
sma = world.evaluate(info.get('sma', distance))
if hasattr(parent, 'mass') and parent.mass is not None:
period = 2 * pi * sqrt((sma * 1000) ** 3 / (G * parent.mass))
self.orbit_speed = 360.0 / period
if not rotation: # Rotation = 0 assumes tidal lock
rotation = period
else:
self.orbit_speed = info.get('orbit_speed', 1)
self.orbit = KeplerOrbit(distance / world.length, info.get('eccentricity', 0), info.get('inclination', 0),
info.get('longitude', 0), info.get('argument', 0))
self.rotation_angle = 360.0 / rotation if rotation else 0
# Orbit calculation
self.orbit_vbo = None
self.orbit_vao = None
self.orbit_cache = None
@cached_property
def orbit_matrix(self):
return self.world.view_matrix() * Matrix4f.from_angles(self.location)
def update(self): def update(self):
super(Body, self).update() super(Body, self).update()
if self.rotation_angle: if self.last_tick != self.world.tick:
self.last_tick = self.world.tick
pitch, yaw, roll = self.rotation pitch, yaw, roll = self.rotation
roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360 roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360
self.rotation = pitch, yaw, roll self.rotation = pitch, yaw, roll
if self.orbit:
px, py, pz = self.parent.location
x, z, y = self.orbit.orbit(self.world.tick * self.orbit_speed % 360)
self.location = (x + px, y + py, z + pz)
self.orbit_matrix = None
for satellite in self.satellites: class Satellite(Body):
satellite.update() def __init__(self, *args, **kwargs):
self.parent = kwargs.pop('parent')
self.orbit_speed = kwargs.pop('orbit_speed', 1)
def get_orbit(self, shader): # Semi-major axis and eccentricity defines orbit
if not self.orbit: distance = kwargs.pop('distance', 100)
return 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 key is the three orbital plane parameters and eccentricity
cache = (self.orbit.eccentricity, self.orbit.longitude, self.orbit.inclination, self.orbit.argument) cache = (self.orbit.eccentricity, self.orbit.longitude, self.orbit.inclination, self.orbit.argument)
if self.orbit_cache == cache: if self.orbit_cache == cache:
return self.orbit_vbo, self.orbit_vao return self.orbit_id
if self.orbit_vbo is not None: if self.orbit_id is not None:
self.orbit_vbo.close() glDeleteLists(self.orbit_id, 1)
if self.orbit_vao is not None: id = glGenLists(1)
self.orbit_vao.close() glNewList(id, GL_COMPILE)
glBegin(GL_LINE_LOOP)
self.orbit_vbo = OrbitVBO(self.orbit) for theta in xrange(360):
self.orbit_vao = VAO() x, z, y = self.orbit.orbit(theta)
glVertex3f(x, y, z)
with self.orbit_vao: glEnd()
glBindBuffer(GL_ARRAY_BUFFER, self.orbit_vbo.vbo) glEndList()
shader.vertex_attribute('a_position', self.orbit_vbo.position_size, self.orbit_vbo.type, GL_FALSE,
self.orbit_vbo.stride, self.orbit_vbo.position_offset)
self.orbit_id = id
self.orbit_cache = cache self.orbit_cache = cache
return self.orbit_vbo, self.orbit_vao return id
def _draw_orbits(self, distance): def update(self):
shader = self.world.activate_shader('line') super(Body, self).update() # Notice how the parent class is skipped
solid = distance < self.parent.orbit_opaque
alpha = 1 if solid else (1 - (distance - self.parent.orbit_opaque) / self.parent.orbit_blend)
shader.uniform_vec4('u_color', 1, 1, 1, alpha)
shader.uniform_mat4('u_mvpMatrix', self.world.projection_matrix() * self.parent.orbit_matrix)
if not solid:
glEnable(GL_BLEND)
vbo, vao = self.get_orbit(shader)
with vao:
glDrawArrays(GL_LINE_LOOP, 0, vbo.vertex_count)
if not solid:
glDisable(GL_BLEND)
def draw(self, options):
self._draw(options)
if options.orbit and self.orbit:
dist = self.world.cam.distance(*self.parent.location)
if dist < self.parent.orbit_show:
self._draw_orbits(dist)
for satellite in self.satellites:
satellite.draw(options)
def _draw(self, options):
raise NotImplementedError()
def collides(self, x, y, z):
return self._collides(x, y, z) or any(satellite.collides(x, y, z) for satellite in self.satellites)
def _collides(self, x, y, z):
return False
class SphericalBody(Body):
_sphere_cache = {}
@classmethod
def _get_sphere(cls, division, tangent=True):
if (division, tangent) in cls._sphere_cache:
return cls._sphere_cache[division, tangent]
cls._sphere_cache[division, tangent] = sphere = \
(TangentSphere if tangent else SimpleSphere)(division, division)
return sphere
def __init__(self, name, world, info, parent=None):
super(SphericalBody, self).__init__(name, world, info, parent)
self.radius = world.evaluate(info.get('radius', world.length)) / world.length
division = info.get('division', max(min(int(self.radius / 8), 60), 10))
self.light_source = info.get('light_source', False)
self.shininess = info.get('shininess', 0)
self.type = info.get('type', 'planet')
self.texture = get_best_texture(info['texture'])
self.normal_texture = None
self.specular_texture = None
self.emission_texture = None
self.sphere = self._get_sphere(division, tangent=self.type == 'planet')
self.vao = VAO()
if self.type == 'planet':
shader = self.world.activate_shader('planet')
with self.vao:
glBindBuffer(GL_ARRAY_BUFFER, self.sphere.vbo)
shader.vertex_attribute('a_normal', self.sphere.direction_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.direction_offset)
shader.vertex_attribute('a_tangent', self.sphere.tangent_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.tangent_offset)
shader.vertex_attribute('a_uv', self.sphere.uv_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.uv_offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
elif self.type == 'star':
shader = self.world.activate_shader('star')
with self.vao:
glBindBuffer(GL_ARRAY_BUFFER, self.sphere.vbo)
shader.vertex_attribute('a_normal', self.sphere.direction_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.direction_offset)
shader.vertex_attribute('a_uv', self.sphere.uv_size, self.sphere.type, GL_FALSE,
self.sphere.stride, self.sphere.uv_offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
else:
raise ValueError('Invalid type: %s' % self.type)
self.atmosphere = None
self.clouds = None
self.ring = 0
if 'normal_map' in info:
self.normal_texture = get_best_texture(info['normal_map'])
if 'specular_map' in info:
self.specular_texture = get_best_texture(info['specular_map'])
if 'emission_map' in info:
self.emission_texture = get_best_texture(info['emission_map'])
if 'atmosphere' in info:
atmosphere_data = info['atmosphere']
atm_size = world.evaluate(atmosphere_data.get('glow_size', None))
atm_texture = atmosphere_data.get('glow_texture', None)
atm_color = atmosphere_data.get('glow_color', None)
cloud_texture = atmosphere_data.get('cloud_texture', None)
if cloud_texture is not None:
self.cloud_transparency = get_best_texture(cloud_texture, loader=load_alpha_mask)
self.cloud_radius = self.radius + 2
self.clouds = self._get_sphere(division, tangent=False)
self.cloud_vao = VAO()
shader = self.world.activate_shader('clouds')
with self.cloud_vao:
glBindBuffer(GL_ARRAY_BUFFER, self.clouds.vbo)
shader.vertex_attribute('a_normal', self.clouds.direction_size, self.clouds.type, GL_FALSE,
self.clouds.stride, self.clouds.direction_offset)
shader.vertex_attribute('a_uv', self.clouds.uv_size, self.clouds.type, GL_FALSE,
self.clouds.stride, self.clouds.uv_offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
if atm_texture is not None and atm_color is not None:
self.atm_texture = load_texture_1d(atm_texture, clamp=True)
self.atm_color = atm_color
self.atmosphere = Disk(self.radius, self.radius + atm_size, 30)
self.atmosphere_vao = VAO()
shader = self.world.activate_shader('atmosphere')
with self.atmosphere_vao:
glBindBuffer(GL_ARRAY_BUFFER, self.atmosphere.vbo)
shader.vertex_attribute('a_position', self.atmosphere.position_size, self.atmosphere.type, GL_FALSE,
self.atmosphere.stride, self.atmosphere.position_offset)
shader.vertex_attribute('a_u', self.atmosphere.u_size, self.atmosphere.type, GL_FALSE,
self.atmosphere.stride, self.atmosphere.u_offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
if 'ring' in info:
distance = world.evaluate(info['ring'].get('distance', self.radius * 1.2))
size = world.evaluate(info['ring'].get('size', self.radius / 2))
if self.last_tick != self.world.tick:
self.last_tick = self.world.tick
pitch, yaw, roll = self.rotation pitch, yaw, roll = self.rotation
pitch = world.evaluate(info['ring'].get('pitch', pitch)) roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360
yaw = world.evaluate(info['ring'].get('yaw', yaw)) self.rotation = pitch, yaw, roll
roll = world.evaluate(info['ring'].get('roll', roll))
self.ring_rotation = pitch, yaw, roll
self.ring_texture = load_texture_1d(info['ring'].get('texture'), clamp=True) self.parent.update()
self.ring = Disk(distance, distance + size, 30) 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)
self.ring_vao = VAO()
shader = self.world.activate_shader('ring')
with self.ring_vao:
glBindBuffer(GL_ARRAY_BUFFER, self.ring.vbo)
shader.vertex_attribute('a_position', self.ring.position_size, self.ring.type, GL_FALSE,
self.ring.stride, self.ring.position_offset)
shader.vertex_attribute('a_u', self.ring.u_size, self.ring.type, GL_FALSE,
self.ring.stride, self.ring.u_offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def _draw_planet(self):
shader = self.world.activate_shader('planet')
shader.uniform_float('u_radius', self.radius)
shader.uniform_mat4('u_modelMatrix', self.model_matrix)
shader.uniform_mat4('u_mvMatrix', self.mv_matrix)
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
glBindTexture(GL_TEXTURE_2D, self.texture)
shader.uniform_texture('u_planet.diffuseMap', 0)
shader.uniform_bool('u_planet.hasNormal', self.normal_texture)
if self.normal_texture:
glActiveTexture(GL_TEXTURE1)
glBindTexture(GL_TEXTURE_2D, self.normal_texture)
shader.uniform_texture('u_planet.normalMap', 1)
shader.uniform_bool('u_planet.hasSpecular', self.specular_texture)
if self.specular_texture:
glActiveTexture(GL_TEXTURE2)
glBindTexture(GL_TEXTURE_2D, self.specular_texture)
shader.uniform_texture('u_planet.specularMap', 2)
shader.uniform_vec3('u_planet.specular', 1, 1, 1)
shader.uniform_float('u_planet.shininess', 10)
else:
shader.uniform_vec3('u_planet.specular', 0, 0, 0)
shader.uniform_float('u_planet.shininess', 0)
shader.uniform_bool('u_planet.hasEmission', self.emission_texture)
if self.emission_texture:
glActiveTexture(GL_TEXTURE3)
glBindTexture(GL_TEXTURE_2D, self.emission_texture)
shader.uniform_texture('u_planet.emissionMap', 3)
shader.uniform_vec3('u_planet.ambient', 0, 0, 0)
shader.uniform_vec3('u_planet.emission', 1, 1, 1)
else:
shader.uniform_vec3('u_planet.ambient', 1, 1, 1)
shader.uniform_vec3('u_planet.emission', 0, 0, 0)
shader.uniform_vec3('u_planet.diffuse', 1, 1, 1)
with self.vao:
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.sphere.vertex_count)
glActiveTexture(GL_TEXTURE0)
def _draw_star(self):
shader = self.world.activate_shader('star')
shader.uniform_float('u_radius', self.radius)
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
glBindTexture(GL_TEXTURE_2D, self.texture)
shader.uniform_texture('u_emission', 0)
with self.vao:
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.sphere.vertex_count)
def _draw_sphere(self):
if self.type == 'planet':
self._draw_planet()
elif self.type == 'star':
self._draw_star()
def _draw_atmosphere(self):
glEnable(GL_BLEND)
glDisable(GL_CULL_FACE)
shader = self.world.activate_shader('atmosphere')
mv = self.mv_matrix.matrix
matrix = Matrix4f([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, mv[12], mv[13], mv[14], 1])
shader.uniform_mat4('u_mvpMatrix', self.world.projection_matrix() * matrix)
glBindTexture(GL_TEXTURE_1D, self.atm_texture)
shader.uniform_texture('u_transparency', 0)
shader.uniform_vec3('u_color', *self.atm_color)
with self.atmosphere_vao:
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.atmosphere.vertex_count)
glDisable(GL_BLEND)
glEnable(GL_CULL_FACE)
def _draw_clouds(self):
glEnable(GL_BLEND)
shader = self.world.activate_shader('clouds')
shader.uniform_float('u_radius', self.cloud_radius)
shader.uniform_mat4('u_modelMatrix', self.model_matrix)
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
glBindTexture(GL_TEXTURE_2D, self.cloud_transparency)
shader.uniform_texture('u_transparency', 0)
shader.uniform_vec3('u_diffuse', 1, 1, 1)
shader.uniform_vec3('u_ambient', 0.1, 0.1, 0.1)
with self.cloud_vao:
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.clouds.vertex_count)
glDisable(GL_BLEND)
def _draw_rings(self):
glEnable(GL_BLEND)
glDisable(GL_CULL_FACE)
shader = self.world.activate_shader('ring')
shader.uniform_mat4('u_modelMatrix', self.model_matrix)
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
shader.uniform_vec3('u_planet', *self.location)
shader.uniform_vec3('u_sun', 0, 0, 0)
shader.uniform_float('u_planetRadius', self.radius)
shader.uniform_float('u_ambient', 0.1)
glBindTexture(GL_TEXTURE_1D, self.ring_texture)
shader.uniform_texture('u_texture', 0)
with self.ring_vao:
glDrawArrays(GL_TRIANGLE_STRIP, 0, self.ring.vertex_count)
glDisable(GL_BLEND)
glEnable(GL_CULL_FACE)
def _draw(self, options):
self._draw_sphere()
if options.atmosphere and self.atmosphere:
self._draw_atmosphere()
if options.cloud and self.clouds:
self._draw_clouds()
if self.ring:
self._draw_rings()
def _collides(self, x, y, z):
ox, oy, oz = self.location
dx, dy, dz = x - ox, y - oy, z - oz
distance = sqrt(dx * dx + dy * dy + dz * dz)
return distance <= self.radius
class ModelBody(Body):
def __init__(self, name, world, info, parent=None):
super(ModelBody, self).__init__(name, world, info, parent)
scale = info.get('scale', 1)
shader = world.activate_shader('model')
self.vbo = WavefrontVBO(load_model(info['model']), shader, info.get('sx', scale),
info.get('sy', scale), info.get('sz', scale))
def _draw(self, options):
shader = self.world.activate_shader('model')
shader.uniform_mat4('u_mvpMatrix', self.mvp_matrix)
shader.uniform_mat4('u_mvMatrix', self.mv_matrix)
shader.uniform_mat4('u_modelMatrix', self.model_matrix)
self.vbo.draw(shader)

396
punyverse/game.py Normal file
View file

@ -0,0 +1,396 @@
#!/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
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 __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
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.__time_per_second_cache = None
self.__time_per_second_value = None
self.__time_accumulate = 0
pyglet.clock.schedule(self.update)
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.R: lambda: setattr(self.cam, 'roll', 0),
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
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.2)
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), 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 + .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 update(self, dt):
c = self.cam
if self.exclusive:
if key.A in self.keys:
c.roll += 4 * dt * 10
if key.S in self.keys:
c.roll -= 4 * dt * 10
if self.moving:
c.move(self.speed * 10 * dt)
if self.running:
delta = self.tick * dt
update = int(delta + self.__time_accumulate + 0.5)
if update:
self.__time_accumulate = 0
self.world.tick += update
for entity in self.world.tracker:
entity.update()
else:
self.__time_accumulate += delta
def on_draw(self, glMatrix=GLfloat * 16):
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)
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
glTranslatef(x0, y0, z0)
matrix = glMatrix()
glGetFloatv(GL_MODELVIEW_MATRIX, matrix)
matrix[0: 3] = [1, 0, 0]
matrix[4: 7] = [0, 1, 0]
matrix[8:11] = [0, 0, 1]
glLoadMatrixf(matrix)
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_BLEND)
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)
glDisable(GL_BLEND)
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' %
(pyglet.clock.get_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' %
(pyglet.clock.get_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
glColor4f(0, 1, 0, 1)
circle(10, 20, (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

View file

@ -1,349 +1,292 @@
from __future__ import division
from array import array
from ctypes import c_int, c_float, byref, cast, POINTER, c_uint, c_short, c_ushort
from math import * from math import *
from random import random, gauss, choice
from pyglet.gl import * from pyglet.gl import *
# noinspection PyUnresolvedReferences from random import random, gauss, choice
from six.moves import range
TWOPI = pi * 2 TWOPI = pi * 2
__all__ = ['FontEngine', 'Matrix4f', 'Disk', 'OrbitVBO', 'SimpleSphere', __all__ = ['compile', 'ortho', 'frustrum', 'crosshair', 'circle', 'disk', 'sphere', 'colourball', 'torus', 'belt',
'TangentSphere', 'Cube', 'Circle', 'BeltVBO', 'VAO'] 'flare', 'normal_sphere']
def array_to_ctypes(arr): def compile(pointer, *args, **kwargs):
return cast(arr.buffer_info()[0], POINTER({ display = glGenLists(1)
'f': c_float, glNewList(display, GL_COMPILE)
'i': c_int, pointer(*args, **kwargs)
'I': c_uint, glEndList()
'h': c_short, return display
'H': c_ushort,
}[arr.typecode]))
def array_to_gl_buffer(buffer): def ortho(width, height):
vbo = c_uint() glDisable(GL_LIGHTING)
glGenBuffers(1, byref(vbo)) glDisable(GL_DEPTH_TEST)
glBindBuffer(GL_ARRAY_BUFFER, vbo.value) glMatrixMode(GL_PROJECTION)
glBufferData(GL_ARRAY_BUFFER, buffer.itemsize * len(buffer), array_to_ctypes(buffer), GL_STATIC_DRAW) glPushMatrix()
glBindBuffer(GL_ARRAY_BUFFER, 0) glLoadIdentity()
return vbo.value glOrtho(0, width, 0, height, -1, 1)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
def list_to_gl_buffer(buffer, array_type='f'): def frustrum():
return array_to_gl_buffer(array(array_type, buffer)) glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_LIGHTING)
glEnable(GL_DEPTH_TEST)
class Matrix4f(object): def crosshair(size, (cx, cy)):
def __init__(self, matrix): glBegin(GL_LINES)
self.matrix = array('f', matrix) glVertex2f(cx - size, cy)
assert len(self.matrix) == 16 glVertex2f(cx + size, cy)
glVertex2f(cx, cy - size)
@classmethod glVertex2f(cx, cy + size)
def from_angles(cls, location=(0, 0, 0), rotation=(0, 0, 0), view=False): glEnd()
m = [0] * 16
x, y, z = location
pitch, yaw, roll = rotation
sp, sy, sr = sin(radians(pitch)), sin(radians(yaw)), sin(radians(roll))
cp, cy, cr = cos(radians(pitch)), cos(radians(yaw)), cos(radians(roll))
m[0x0] = cy * cr
m[0x1] = sp * sy * cr + cp * sr
m[0x2] = sp * sr - cp * sy * cr
m[0x3] = 0
m[0x4] = -cy * sr
m[0x5] = cp * cr - sp * sy * sr
m[0x6] = cp * sy * sr + sp * cr
m[0x7] = 0
m[0x8] = sy
m[0x9] = -sp * cy
m[0xA] = cp * cy
m[0xB] = 0
if view:
m[0xC] = m[0x0] * -x + m[0x4] * -y + m[0x8] * -z
m[0xD] = m[0x1] * -x + m[0x5] * -y + m[0x9] * -z
m[0xE] = m[0x2] * -x + m[0x6] * -y + m[0xA] * -z
else:
m[0xC] = x
m[0xD] = y
m[0xE] = z
m[0xF] = 1
return cls(m)
@property
def _as_parameter_(self):
return array_to_ctypes(self.matrix)
@property
def bytes(self):
return self.matrix.itemsize * 16
def __mul__(self, other):
if not isinstance(other, Matrix4f):
return NotImplemented
rows = ((0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15))
cols = ((0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11), (12, 13, 14, 15))
a, b = self.matrix, other.matrix
return type(self)(sum(a[i] * b[j] for i, j in zip(r, c)) for c in cols for r in rows)
class Circle(object): def circle(r, seg, (cx, cy)):
type = GL_FLOAT glBegin(GL_LINE_LOOP)
stride = 2 * 4 for i in xrange(seg):
position_offset = 0 theta = TWOPI * i / seg
position_size = 2 glVertex2f(cx + cos(theta) * r, cy + sin(theta) * r)
glEnd()
def __init__(self, r, segs, shader):
self.vertex_count = segs
buffer = segs * 2 * [0]
delta = 2 * pi / segs
for i in range(segs):
theta = delta * i
buffer[2*i:2*i+2] = [cos(theta) * r, sin(theta) * r]
self.vbo = list_to_gl_buffer(buffer)
self.vao = VAO()
with self.vao:
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
shader.vertex_attribute('a_position', self.position_size, self.type, GL_FALSE,
self.stride, self.position_offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
class Disk(object): def disk(rinner, router, segs, tex):
type = GL_FLOAT glEnable(GL_TEXTURE_2D)
stride = 3 * 4 glDisable(GL_LIGHTING)
position_offset = 0 glBindTexture(GL_TEXTURE_2D, tex)
position_size = 2
u_offset = position_size * 4
u_size = 1
def __init__(self, rinner, router, segs):
res = segs * 5 res = segs * 5
delta = 2 * pi / res
self.vertex_count = (res + 1) * 2 glBegin(GL_TRIANGLE_STRIP)
# Need padding to make the last vertex render correctly... why? texture = 0
buffer = self.vertex_count * 3 * [0] factor = TWOPI / res
for i in range(res): theta = 0
theta = delta * i for n in xrange(res + 1):
x, y = cos(theta), sin(theta) theta += factor
buffer[6*i:6*i+6] = [rinner * x, rinner * y, 0, router * x, router * y, 1] x = cos(theta)
buffer[6*res:6*res+6] = buffer[:6] y = sin(theta)
self.vbo = list_to_gl_buffer(buffer) 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)
class SimpleSphere(object): def flare(rinner, router, res, prob, tex):
type = GL_FLOAT glEnable(GL_TEXTURE_2D)
stride = 5 * 4 glDisable(GL_LIGHTING)
direction_offset = 0 glBindTexture(GL_TEXTURE_2D, tex)
direction_size = 3 last_x = 1
uv_offset = direction_size * 4 last_y = 0
uv_size = 2 last_theta = 0
factor = TWOPI / res
def __init__(self, lats, longs): rdelta = (router - rinner)
tau = pi * 2 glBegin(GL_QUADS)
phi_div = tau / longs for i in xrange(res + 1):
theta_div = pi / lats theta = last_theta + factor
x = cos(theta)
self.vertex_count = (lats + 1) * (longs + 1) * 2 y = sin(theta)
buffer = self.vertex_count * 5 * [0] if random() > prob:
index = 0 distance = rinner + rdelta * random()
reverse = False avg_theta = (last_theta + theta) / 2
for i in range(longs + 1): x0, y0 = rinner * last_x, rinner * last_y
phi1, phi2 = i * phi_div, (i + 1) * phi_div x1, y1 = rinner * x, rinner * y
if reverse: x2, y2 = distance * cos(avg_theta), distance * sin(avg_theta)
phi1, phi2 = phi2, phi1 glTexCoord2f(0, 0)
for j in range(lats + 1): glVertex2f(x0, y0)
theta = j * theta_div glTexCoord2f(0, 1)
if reverse: glVertex2f(x1, y1)
theta = pi - theta glTexCoord2f(1, 0)
sine = sin(theta) glVertex2f(x2, y2)
dz = cos(theta) glTexCoord2f(1, 1)
t = 1 - theta / pi glVertex2f(x2, y2)
buffer[index:index + 10] = [sine * cos(phi2), sine * sin(phi2), dz, phi2 / tau, t, last_theta = theta
sine * cos(phi1), sine * sin(phi1), dz, phi1 / tau, t] last_x = x
index += 10 last_y = y
reverse ^= True glEnd()
glEnable(GL_LIGHTING)
self.vbo = list_to_gl_buffer(buffer) glDisable(GL_TEXTURE_2D)
class TangentSphere(object): def sphere(r, lats, longs, tex, lighting=True, fv4=GLfloat * 4):
type = GL_FLOAT """
stride = 7 * 4 Sphere function from the OpenGL red book.
direction_offset = 0 """
direction_size = 3 sphere = gluNewQuadric()
tangent_offset = direction_size * 4 gluQuadricDrawStyle(sphere, GLU_FILL)
tangent_size = 2 gluQuadricTexture(sphere, True)
uv_offset = tangent_offset + tangent_size * 4 if lighting:
uv_size = 2 gluQuadricNormals(sphere, GLU_SMOOTH)
def __init__(self, lats, longs): glEnable(GL_TEXTURE_2D)
tau = pi * 2 if lighting:
phi_div = tau / longs glDisable(GL_BLEND)
theta_div = pi / lats 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)
self.vertex_count = (lats + 1) * (longs + 1) * 2 gluSphere(sphere, r, lats, longs)
buffer = self.vertex_count * 8 * [0]
index = 0 glBindTexture(GL_TEXTURE_2D, 0)
reverse = False glDisable(GL_TEXTURE_2D)
for i in range(longs + 1): glEnable(GL_LIGHTING)
phi1, phi2 = i * phi_div, (i + 1) * phi_div glEnable(GL_BLEND)
if reverse: gluDeleteQuadric(sphere)
phi1, phi2 = phi2, phi1
for j in range(lats + 1):
theta = j * theta_div
if reverse:
theta = pi - theta
sine = sin(theta)
dz = cos(theta)
t = 1 - theta / pi
sphi2, cphi2 = sin(phi2), cos(phi2)
sphi1, cphi1 = sin(phi1), cos(phi1)
buffer[index:index + 14] = [
sine * cphi2, sine * sphi2, dz, sine * -sphi2, sine * cphi2, phi2 / tau, t,
sine * cphi1, sine * sphi1, dz, sine * -sphi1, sine * cphi1, phi1 / tau, t,
]
index += 14
reverse ^= True
self.vbo = list_to_gl_buffer(buffer)
class Cube(object): def colourball(r, lats, longs, colour, fv4=GLfloat * 4):
type = GL_SHORT """
stride = 3 * 2 Sphere function from the OpenGL red book.
direction_offset = 0 """
direction_size = 3 sphere = gluNewQuadric()
vertex_count = 36
def __init__(self): glDisable(GL_BLEND)
self.vbo = list_to_gl_buffer([ glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fv4(*colour))
-1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(1, 1, 1, 1))
-1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 125)
1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1,
-1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1 gluSphere(sphere, r, lats, longs)
], 'h')
glEnable(GL_BLEND)
gluDeleteQuadric(sphere)
class OrbitVBO(object): try:
type = GL_FLOAT from _glgeom import normal_sphere
stride = 3 * 4 except ImportError:
position_offset = 0 import warnings
position_size = 3 warnings.warn('Large sphere drawing in Python is slow')
vertex_count = 360
def __init__(self, orbit): def normal_sphere(r, divide, tex, normal, lighting=True, fv4=GLfloat * 4):
buffer = 360 * 3 * [0] from texture import pil_load
for theta in range(360): print 'Loading normal map: %s...' % normal,
x, z, y = orbit.orbit(theta) normal_map = pil_load(normal)
buffer[3*theta:3*theta+3] = [x, y, z] normal = normal_map.load()
print
width, height = normal_map.size
gray_scale = len(normal[0, 0]) == 1
self.vbo = list_to_gl_buffer(buffer) 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)
def close(self): twopi_divide = TWOPI / divide
if self.vbo is not None: pi_divide = pi / divide
vbo = c_uint(self.vbo) glBegin(GL_TRIANGLE_STRIP)
glDeleteBuffers(1, byref(vbo)) for j in xrange(divide + 1):
self.vbo = None phi1 = j * twopi_divide
phi2 = (j + 1) * twopi_divide
def __del__(self): for i in xrange(divide + 1):
self.close() theta = i * pi_divide
s = phi2 / TWOPI
u = min(int(s * width), width - 1)
t = theta / pi
v = min(int(t * height), height - 1)
if gray_scale:
x = y = z = normal[u, v]
else:
x, y, z = normal[u, v]
dx, dy, dz = sin(theta) * cos(phi2), sin(theta) * sin(phi2), cos(theta)
nx, ny, nz = x / 127.5 - 1, y / 127.5 - 1, z / 127.5 - 1 # Make into [-1, 1]
nx, nz = cos(theta) * nx + sin(theta) * nz, -sin(theta) * nx + cos(theta) * nz
nx, ny = cos(phi2) * nx - sin(phi2) * ny, sin(phi2) * nx + cos(phi2) * ny
glNormal3f(nx, ny, nz)
glTexCoord2f(s, 1 - t) # GL is bottom up
glVertex3f(r * dx, r * dy, r * dz)
s = phi1 / TWOPI # x
u = min(int(s * width), width - 1)
if gray_scale:
x = y = z = normal[u, v]
else:
x, y, z = normal[u, v]
dx, dy = sin(theta) * cos(phi1), sin(theta) * sin(phi1)
nx, ny, nz = x / 127.5 - 1, y / 127.5 - 1, z / 127.5 - 1
nx, nz = cos(theta) * nx + sin(theta) * nz, -sin(theta) * nx + cos(theta) * nz
nx, ny = cos(phi1) * nx - sin(phi1) * ny, sin(phi1) * nx + cos(phi1) * ny
glNormal3f(nx, ny, nz)
glTexCoord2f(s, 1 - t)
glVertex3f(r * dx, r * dy, r * dz)
glEnd()
glDisable(GL_TEXTURE_2D)
glEnable(GL_LIGHTING)
glEnable(GL_BLEND)
class FontEngine(object): def belt(radius, cross, object, count):
type = GL_SHORT for i in xrange(count):
stride = 4 * 2
position_offset = 0
position_size = 2
tex_offset = position_size * 2
tex_size = 2
def __init__(self, shader, max_length=256):
self.storage = array('h', max_length * 24 * [0])
vbo = GLuint()
glGenBuffers(1, byref(vbo))
self.vbo = vbo.value
self.vertex_count = None
self.vao = VAO()
with self.vao:
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
shader.vertex_attribute('a_rc', self.position_size, self.type, GL_FALSE,
self.stride, self.position_offset)
shader.vertex_attribute('a_tex', self.tex_size, self.type, GL_FALSE,
self.stride, self.tex_offset)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def draw(self, string):
index = 0
row = 0
col = 0
for c in string:
if c == '\n':
row += 1
col = 0
continue
o = ord(c)
if 32 <= o < 128:
self.storage[24*index:24*index+24] = array('h', [
row, col, o - 32, 1,
row + 1, col, o - 32, 0,
row + 1, col + 1, o - 31, 0,
row, col, o - 32, 1,
row + 1, col + 1, o - 31, 0,
row, col + 1, o - 31, 1,
])
index += 1
col += 1
self.vertex_count = index * 6
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
glBufferData(GL_ARRAY_BUFFER, self.storage.itemsize * len(self.storage),
array_to_ctypes(self.storage), GL_STREAM_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, 0)
class BeltVBO(object):
type = GL_FLOAT
stride = 4 * 4
location_offset = 0
location_size = 3
scale_offset = location_size * 4
scale_size = 1
def __init__(self, radius, cross, objects, count):
arrays = [array('f') for i in range(objects)]
for i in range(count):
theta = TWOPI * random() theta = TWOPI * random()
r = gauss(radius, cross) r = gauss(radius, cross)
x, y, z = cos(theta) * r, gauss(0, cross), sin(theta) * r x, y, z = cos(theta) * r, gauss(0, cross), sin(theta) * r
glPushMatrix()
glTranslatef(x, y, z)
scale = gauss(1, 0.5) scale = gauss(1, 0.5)
if scale < 0: if scale < 0:
scale = 1 scale = 1
choice(arrays).extend((x, y, z, scale)) glScalef(scale, scale, scale)
glCallList(choice(object))
self.vbo = [] glPopMatrix()
self.sizes = []
for a in arrays:
self.vbo.append(array_to_gl_buffer(a))
self.sizes.append(len(a) // 4)
class VAO(object): try:
def __init__(self): from _glgeom import torus
buffer = GLuint() except ImportError:
glGenVertexArrays(1, byref(buffer)) def torus(major_radius, minor_radius, n_major, n_minor, material, shininess=125, fv4=GLfloat * 4):
self.vao = buffer """
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)
def __enter__(self): major_s = TWOPI / n_major
glBindVertexArray(self.vao) minor_s = TWOPI / n_minor
def __exit__(self, exc_type, exc_val, exc_tb): def n(x, y, z):
glBindVertexArray(0) 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()

View file

@ -1,9 +0,0 @@
#ifdef _MSC_VER
# include <windows.h>
#endif
#ifdef __APPLE__
# include <OpenGL/gl.h>
#else
# include <GL/gl.h>
#endif

View file

@ -1,28 +0,0 @@
#include <stdlib.h>
#include <Windows.h>
#include <Python.h>
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 0x00000001;
#ifdef GUI
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
#else
int main()
#endif
{
#if PY_MAJOR_VERSION >= 3
int argc;
LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
#else
int argc = __argc;
char **argv = __argv;
#endif
Py_SetProgramName(argv[0]);
Py_Initialize();
PySys_SetArgvEx(argc, argv, 0);
PyRun_SimpleString("from punyverse.main import main; main()");
Py_Finalize();
return 0;
}

View file

@ -1,31 +0,0 @@
import os
import shutil
import sys
def main():
if os.name != 'nt':
print('Not on Windows. Nothing to do.')
return
source_dir = os.path.dirname(__file__)
dest_dir = os.path.join(sys.prefix, 'Scripts')
launcher_exe = os.path.join(source_dir, 'launcher.exe')
launcherw_exe = os.path.join(source_dir, 'launcherw.exe')
punyverse_exe = os.path.join(dest_dir, 'punyverse.exe')
punyversew_exe = os.path.join(dest_dir, 'punyversew.exe')
assert os.path.isfile(launcher_exe)
assert os.path.isfile(launcherw_exe)
assert os.path.isfile(punyverse_exe)
assert os.path.isfile(punyversew_exe)
def copy(src, dst):
print('Copying %s to %s...' % (src, dst))
shutil.copy(src, dst)
copy(launcher_exe, punyverse_exe)
copy(launcherw_exe, punyversew_exe)
if __name__ == '__main__':
main()

View file

@ -1,190 +0,0 @@
from __future__ import print_function
import os
import sys
import time
import pyglet
from pyglet.gl import *
from six.moves import zip_longest
from punyverse.world import World
class glContext(object):
def __init__(self, context):
self.new_context = context
def __enter__(self):
self.old_context = get_current_context()
self.new_context.set_current()
def __exit__(self, exc_type, exc_val, exc_tb):
self.old_context.set_current()
class glSection(object):
def __init__(self, type):
self.type = type
def __enter__(self):
glBegin(self.type)
def __exit__(self, exc_type, exc_val, exc_tb):
glEnd()
def progress_bar(x, y, width, height, filled):
glPushAttrib(GL_ENABLE_BIT)
glDisable(GL_TEXTURE_2D)
glDisable(GL_BLEND)
x1 = x
x2 = x + width
y1 = y
y2 = y - height
y3 = 0.65 * y1 + 0.35 * y2
y4 = 0.25 * y1 + 0.75 * y2
glColor3f(0.6, 0.6, 0.6)
with glSection(GL_LINE_LOOP):
glVertex2f(x1, y1)
glVertex2f(x1, y2)
glVertex2f(x2, y2)
glVertex2f(x2, y1)
x1 += 1
y1 -= 1
x2 = x + width * filled - 1
with glSection(GL_TRIANGLE_STRIP):
glColor3f(0.81, 1, 0.82)
glVertex2f(x1, y1)
glVertex2f(x2, y1)
glColor3f(0, 0.83, 0.16)
glVertex2f(x1, y3)
glVertex2f(x2, y3)
glVertex2f(x1, y4)
glVertex2f(x2, y4)
glColor3f(0.37, 0.92, 0.43)
glVertex2f(x1, y2)
glVertex2f(x2, y2)
glPopAttrib()
def get_context_info(context):
info = [' %-22s %s' % (key + ':', value)
for key, value in context.config.get_gl_attributes()]
info = ['%-30s %-30s' % group for group in
zip_longest(info[::2], info[1::2], fillvalue='')]
with glContext(context):
gl_info.remove_active_context()
gl_info.set_active_context()
return '\n'.join([
'Graphics Vendor: ' + gl_info.get_vendor(),
'Graphics Version: ' + gl_info.get_version(),
'Graphics Renderer: ' + gl_info.get_renderer(),
]) + '\n\n' + 'OpenGL configuration:\n' + '\n'.join(info)
class LoaderWindow(pyglet.window.Window):
MONOSPACE = ('Consolas', 'Droid Sans Mono', 'Courier', 'Courier New', 'Dejavu Sans Mono')
def __init__(self, *args, **kwargs):
super(LoaderWindow, self).__init__(*args, **kwargs)
# work around pyglet bug: decoding font names as utf-8 instead of mbcs when using EnumFontsA.
stderr = sys.stderr
sys.stderr = open(os.devnull, 'w')
pyglet.font.have_font(self.MONOSPACE[0])
sys.stderr = stderr
self.loading_phase = pyglet.text.Label(
font_name=self.MONOSPACE, font_size=20, x=10, y=self.height - 50,
color=(255, 255, 255, 255), width=self.width - 20, align='center',
multiline=True, text='Punyverse is starting...'
)
self.loading_label = pyglet.text.Label(
font_name=self.MONOSPACE, font_size=16, x=10, y=self.height - 120,
color=(255, 255, 255, 255), width=self.width - 20, align='center',
multiline=True
)
self.info_label = pyglet.text.Label(
font_name=self.MONOSPACE, font_size=13, x=10, y=self.height - 220,
color=(255, 255, 255, 255), width=self.width - 20,
multiline=True
)
self.progress = 0
self._main_context = None
def set_main_context(self, context):
self._main_context = context
self.info_label.text = get_context_info(context)
print(self.info_label.text)
def _load_callback(self, phase, message, progress):
print(message)
with glContext(self.context):
self.loading_phase.text = phase
self.loading_label.text = message
self.progress = progress
self.on_draw()
self.flip()
self.dispatch_events()
def load(self, **kwargs):
start = time.clock()
with glContext(self._main_context):
world = World('world.json', self._load_callback, **kwargs)
print('Loaded in %s seconds.' % (time.clock() - start))
return world
def on_draw(self):
glClear(GL_COLOR_BUFFER_BIT)
glLoadIdentity()
self.loading_phase.draw()
self.loading_label.draw()
progress_bar(10, self.height - 140, self.width - 20, 50, self.progress)
self.info_label.draw()
def main_is_initializing(self):
self._load_callback('Loading main window...', '', 0)
class LoaderConsole(object):
def __init__(self):
from ctypes import windll
self._own_console = False
if windll.kernel32.AllocConsole():
self._own_console = True
self._output = open('CONOUT$', 'w')
else:
self._output = sys.stdout
self._main_context = None
def _load_callback(self, phase, message, progress):
print(message, file=self._output)
def load(self, **kwargs):
start = time.clock()
with glContext(self._main_context):
world = World('world.json', self._load_callback, **kwargs)
print('Loaded in %s seconds.' % (time.clock() - start), file=self._output)
return world
def set_main_context(self, context):
self._main_context = context
print(get_context_info(context), file=self._output)
print('=' * 79, file=self._output)
print("You cannot see the normal loading screen because you are using Intel integrated graphics.",
file=self._output)
print('Please attempt to set python to execute with your dedicated graphics, if available.', file=self._output)
print('=' * 79, file=self._output)
def close(self):
if self._own_console:
self._output.close()
from ctypes import windll
windll.kernel32.FreeConsole()

View file

@ -1,87 +0,0 @@
import argparse
import sys
import pyglet
INITIAL_WIN_HEIGHT = 540
INITIAL_WIN_WIDTH = 700
DEBUG = False
def main():
macos = sys.platform == 'darwin'
parser = argparse.ArgumentParser(prog='punyverse', description='''
Python simulator of a puny universe.
''')
parser.set_defaults(sky=not macos)
parser.add_argument('-D', '--debug', help='Enable pyglet OpenGL debugging', action='store_true')
parser.add_argument('-d', '--high-depth', help='Use a larger depth buffer',
const=32, default=24, dest='depth', nargs='?', type=int)
parser.add_argument('-m', '--multisample', help='Use multisampled image, optional samples',
const=2, default=0, nargs='?', type=int)
parser.add_argument('-v', '--no-vsync', help='Disables vsync',
action='store_false', dest='vsync')
parser.add_argument('-n', '--normal', help='Enables the use of normal maps',
action='store_true')
parser.add_argument('-s', '--sky', help='Enables the sky', dest='sky',
action='store_true')
parser.add_argument('-S', '--no-sky', help='Disables the sky', dest='sky',
action='store_false')
args = parser.parse_args()
versioning = dict(major_version=3, minor_version=3)
pyglet.options['debug_gl'] = args.debug
if macos:
pyglet.options['shadow_window'] = False
versioning = dict(major_version=4, minor_version=1, forward_compatible=True)
template = pyglet.gl.Config(depth_size=args.depth, double_buffer=True,
sample_buffers=args.multisample > 1,
samples=args.multisample, **versioning)
platform = pyglet.window.get_platform()
display = platform.get_default_display()
screen = display.get_default_screen()
try:
config = screen.get_best_config(template)
except pyglet.window.NoSuchConfigException:
raise SystemExit('Graphics configuration not supported.')
create_args = dict(width=INITIAL_WIN_WIDTH, height=INITIAL_WIN_HEIGHT,
caption='Punyverse', resizable=True, vsync=args.vsync, visible=False)
from pyglet.gl import gl_info
from punyverse.loader import LoaderWindow, LoaderConsole
from punyverse.ui import Punyverse
if pyglet.compat_platform in ('win32', 'cygwin') and gl_info.get_vendor() == 'Intel':
# pyglet has some code that tries to create without ARB on Intel.
# Of course, all that achieves is the message that you can't create OpenGL 3 contexts.
# So we force create an ARB context.
from pyglet.gl.win32 import Win32ARBContext
context = Win32ARBContext(config, None)
# We use the console loader since using the GUI loader makes all sorts of wonderful things happen on Intel:
# Access violations, mouse events going nowhere, you name it.
loader = LoaderConsole()
punyverse = Punyverse(context=context, **create_args)
else:
context = config.create_context(None)
loader = LoaderWindow(width=INITIAL_WIN_WIDTH, height=INITIAL_WIN_HEIGHT,
caption='Punyverse is loading...')
punyverse = Punyverse(context=context, **create_args)
loader.context.set_current()
loader.set_main_context(punyverse.context)
world = loader.load(sky=args.sky)
punyverse.context.set_current()
punyverse.initialize(world)
loader.close()
punyverse.set_visible(True)
pyglet.app.run()
if __name__ == '__main__':
main()

View file

@ -1,17 +1,12 @@
import bz2
import gzip
import os
import zipfile
from collections import defaultdict
import six
from pyglet.gl import *
# noinspection PyUnresolvedReferences
from six.moves import range, zip
from punyverse.glgeom import list_to_gl_buffer, VAO
from punyverse.texture import load_texture from punyverse.texture import load_texture
from pyglet.gl import *
from uuid import uuid4
import os
import gzip
import bz2
import zipfile
def zip_open(file): def zip_open(file):
zip = zipfile.ZipFile(file) zip = zipfile.ZipFile(file)
@ -24,19 +19,22 @@ openers = {
} }
class Face(object): FACE_TRIANGLES = 0
__slots__ = ('verts', 'norms', 'texs', 'size') FACE_QUADS = 1
def __init__(self, verts, norms, texs):
class Face(object):
def __init__(self, type, verts, norms, texs, vertices, normals, textures):
self.type = type
self.verts = verts self.verts = verts
self.norms = norms self.norms = norms
self.texs = texs self.texs = texs
self.size = len(verts) self.vertices = vertices
self.normals = normals
self.textures = textures
class Material(object): class Material(object):
__slots__ = ('name', 'texture', 'Ka', 'Kd', 'Ks', 'shininess')
def __init__(self, name, texture=None, Ka=(0, 0, 0), Kd=(0, 0, 0), Ks=(0, 0, 0), shininess=0.0): def __init__(self, name, texture=None, Ka=(0, 0, 0), Kd=(0, 0, 0), Ks=(0, 0, 0), shininess=0.0):
self.name = name self.name = name
self.texture = texture self.texture = texture
@ -47,11 +45,28 @@ class Material(object):
class Group(object): class Group(object):
__slots__ = ('material', 'faces') def __init__(self, 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 __init__(self, material=None, faces=None): def pack(self):
self.material = material min_x, min_y, min_z = 0, 0, 0
self.faces = faces or [] 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)
class WavefrontObject(object): class WavefrontObject(object):
@ -67,9 +82,8 @@ class WavefrontObject(object):
self.perform_io(self.path) self.perform_io(self.path)
def new_material(self, words): def new_material(self, words):
name = words[1].decode('utf-8') material = Material(words[1])
material = Material(name) self.materials[words[1]] = material
self.materials[name] = material
self.current_material = material self.current_material = material
def Ka(self, words): def Ka(self, words):
@ -85,7 +99,7 @@ class WavefrontObject(object):
self.current_material.shininess = min(float(words[1]), 125) self.current_material.shininess = min(float(words[1]), 125)
def material_texture(self, words): def material_texture(self, words):
self.current_material.texture = words[-1].decode('utf-8') self.current_material.texture = words[-1]
def vertex(self, words): def vertex(self, words):
self.vertices.append((float(words[1]), float(words[2]), float(words[3]))) self.vertices.append((float(words[1]), float(words[2]), float(words[3])))
@ -95,37 +109,54 @@ class WavefrontObject(object):
def texture(self, words): def texture(self, words):
l = len(words) l = len(words)
u, v = 0, 0 x, y, z = 0, 0, 0
if l >= 2: if l >= 2:
u = float(words[1]) x = float(words[1])
if l >= 3: if l >= 3:
# OBJ origin is at upper left, OpenGL origin is at lower left # OBJ origin is at upper left, OpenGL origin is at lower left
v = 1 - float(words[2]) y = 1 - float(words[2])
self.textures.append((u, v)) if l >= 4:
z = float(words[3])
self.textures.append((x, y, z))
def face(self, words): def face(self, words):
l = len(words) l = len(words)
type = -1
vertex_count = l - 1 vertex_count = l - 1
face_vertices = []
face_normals = []
face_textures = []
if vertex_count == 3:
type = FACE_TRIANGLES
else:
type = FACE_QUADS
current_value = -1
texture_len = len(self.textures)
vindices = [] vindices = []
nindices = [] nindices = []
tindices = [] tindices = []
for i in range(1, vertex_count + 1): for i in xrange(1, vertex_count + 1):
raw_faces = words[i].split(b'/') raw_faces = words[i].split('/')
l = len(raw_faces) l = len(raw_faces)
vindices.append(int(raw_faces[0]) - 1) 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]: if l >= 2 and raw_faces[1]:
tindices.append(int(raw_faces[1]) - 1) current_value = int(raw_faces[1])
else: if current_value <= texture_len:
tindices.append(None) tindices.append(current_value - 1)
face_textures.append(self.textures[current_value - 1])
if l >= 3 and raw_faces[2]: if l >= 3 and raw_faces[2]:
nindices.append(int(raw_faces[2]) - 1) current_value = int(raw_faces[2])
else: nindices.append(current_value - 1)
nindices.append(None) face_normals.append(self.normals[current_value - 1])
if self.current_group is None: if self.current_group is None:
self.current_group = group = Group() self.current_group = group = Group()
@ -133,50 +164,61 @@ class WavefrontObject(object):
else: else:
group = self.current_group group = self.current_group
group.faces.append(Face(vindices, nindices, tindices)) 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))
def material(self, words): def material(self, words):
self.perform_io(os.path.join(self.root, words[1].decode('utf-8'))) self.perform_io(os.path.join(self.root, words[1]))
def use_material(self, words): def use_material(self, words):
mat = words[1].decode('utf-8') mat = words[1]
try: try:
self.current_group.material = self.materials[mat] self.current_group.material = self.materials[mat]
except KeyError: except KeyError:
print("Warning: material %s undefined, only %s defined." % (mat, self.materials)) print "Warning: material %s undefined, only %s defined." % (mat, self.materials)
except AttributeError: except AttributeError:
print("Warning: no group") print "Warning: no group"
def group(self, words): def group(self, words):
group = Group() name = words[1]
group = Group(name)
if self.groups:
self.current_group.pack()
self.groups.append(group) self.groups.append(group)
self.current_group = group self.current_group = group
def perform_io(self, file): def perform_io(self, file):
ext = os.path.splitext(file)[1].lstrip('.') ext = os.path.splitext(file)[1].lstrip('.')
reader = openers.get(ext, lambda x: open(x, 'rb'))(file) reader = openers.get(ext, open)(file)
dispatcher = { dispatcher = {
b'v': self.vertex, 'v': self.vertex,
b'vn': self.normal, 'vn': self.normal,
b'vt': self.texture, 'vt': self.texture,
b'f': self.face, 'f': self.face,
b'mtllib': self.material, 'mtllib': self.material,
b'usemtl': self.use_material, 'usemtl': self.use_material,
b'g': self.group, 'g': self.group,
b'o': self.group, 'o': self.group,
b'newmtl': self.new_material, 'newmtl': self.new_material,
b'Ka': self.Ka, 'Ka': self.Ka,
b'Kd': self.Kd, 'Kd': self.Kd,
b'Ks': self.Ks, 'Ks': self.Ks,
b'Ns': self.material_shininess, 'Ns': self.material_shininess,
b'map_Kd': self.material_texture, 'map_Kd': self.material_texture,
} }
default = lambda words: None default = lambda words: None
with reader: with reader:
for buf in reader: for buf in reader:
if not buf or buf.startswith((b'\r', b'\n', b'#')): if not buf or buf.startswith(('\r', '\n', '#')):
continue # Empty or comment continue # Empty or comment
words = buf.split() words = buf.split()
type = words[0] type = words[0]
@ -184,163 +226,103 @@ class WavefrontObject(object):
dispatcher.get(type, default)(words) dispatcher.get(type, default)(words)
return True return True
import sys
model_base = os.path.join(os.path.dirname(__file__), 'assets', 'models') if hasattr(sys, 'frozen'):
model_base = os.path.dirname(sys.executable)
else:
model_base = os.path.join(os.path.dirname(__file__), 'assets', 'models')
def load_model(path): def load_model(path):
if not os.path.isabs(path): if not os.path.isabs(path):
path = os.path.join(model_base, path) path = os.path.join(model_base, path)
if isinstance(path, six.binary_type): if not isinstance(path, unicode):
path = path.decode('mbcs' if os.name == 'nt' else 'utf8') path = path.decode('mbcs')
return WavefrontObject(path) return WavefrontObject(path)
class ModelVBO(object): def model_list(model, sx=1, sy=1, sz=1, rotation=(0, 0, 0)):
__slots__ = ('has_normal', 'has_texture', 'data_buf', 'index_buf', 'offset_type', 'vertex_count', 'vao') for m, text in model.materials.iteritems():
if text.texture:
load_texture(os.path.join(model.root, text.texture))
def __init__(self): display = glGenLists(1)
self.vao = VAO()
def build_vao(self, shader): glNewList(display, GL_COMPILE)
stride = (3 + self.has_normal * 3 + self.has_texture * 2) * 4 glPushMatrix()
glPushAttrib(GL_CURRENT_BIT)
with self.vao: pitch, yaw, roll = rotation
glBindBuffer(GL_ARRAY_BUFFER, self.data_buf) glPushAttrib(GL_TRANSFORM_BIT)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.index_buf) glRotatef(pitch, 1, 0, 0)
glRotatef(yaw, 0, 1, 0)
shader.vertex_attribute('a_position', 3, GL_FLOAT, GL_FALSE, stride, 0) glRotatef(roll, 0, 0, 1)
if self.has_normal: glPopAttrib()
shader.vertex_attribute('a_normal', 3, GL_FLOAT, GL_FALSE, stride, 3 * 4)
if self.has_texture:
shader.vertex_attribute('a_uv', 2, GL_FLOAT, GL_FALSE, stride, (6 if self.has_normal else 3) * 4)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def draw(self, shader, instances=None):
with self.vao:
if not self.has_normal:
shader.vertex_attribute_vec3('a_normal', 0, 0, 0)
if not self.has_texture:
shader.vertex_attribute_vec2('a_uv', 0, 0)
if instances:
glDrawElementsInstanced(GL_TRIANGLES, self.vertex_count, self.offset_type, 0, instances)
else:
glDrawElements(GL_TRIANGLES, self.vertex_count, self.offset_type, 0)
class WavefrontVBO(object):
def __init__(self, model, shader, sx=1, sy=1, sz=1):
self._tex_cache = {}
self.vbos = []
self.scale = (sx, sy, sz)
for m, material in six.iteritems(model.materials):
if material.texture and material.texture not in self._tex_cache:
self._tex_cache[material.texture] = load_texture(os.path.join(model.root, material.texture))
vertices = model.vertices vertices = model.vertices
textures = model.textures textures = model.textures
normals = model.normals normals = model.normals
for group in self.merge_groups(model): for g in model.groups:
processed = self.process_group(group, vertices, normals, textures) material = g.material
self.vbos.append((group.material, processed))
processed.build_vao(shader)
def additional_attributes(self, callback): tex_id = load_texture(os.path.join(model.root, material.texture)) if (material and material.texture) else 0
for _, group in self.vbos:
with group.vao:
callback()
def draw(self, shader, instances=None):
for mat, vbo in self.vbos:
tex_id = self._tex_cache[mat.texture] if mat and mat.texture else 0
if tex_id: if tex_id:
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, tex_id) glBindTexture(GL_TEXTURE_2D, tex_id)
shader.uniform_bool('u_material.hasDiffuse', True)
shader.uniform_texture('u_material.diffuseMap', 0)
else: else:
shader.uniform_bool('u_material.hasDiffuse', False) glBindTexture(GL_TEXTURE_2D, 0)
glDisable(GL_TEXTURE_2D)
if mat and mat.Ka: if material:
shader.uniform_vec3('u_material.ambient', *mat.Ka) fv4 = GLfloat * 4
else:
shader.uniform_vec3('u_material.ambient', 0.2, 0.2, 0.2)
if mat and mat.Kd: if material.Ka:
shader.uniform_vec3('u_material.diffuse', *mat.Kd) kx, ky, kz = material.Ka
else: glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, fv4(kx, ky, kz, 1))
shader.uniform_vec3('u_material.diffuse', 0.8, 0.8, 0.8) 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)
if mat and mat.Ks: type = -1
shader.uniform_vec3('u_material.specular', *mat.Ks)
else:
shader.uniform_vec3('u_material.specular', 0, 0, 0)
if mat: def point(f, vertices, normals, textures, n):
shader.uniform_float('u_material.shininess', mat.shininess) if f.norms:
else: glNormal3f(*normals[f.norms[n]])
shader.uniform_float('u_material.shininess', 0) if tex_id:
glTexCoord2f(*textures[f.texs[n]][:2])
vbo.draw(shader, instances=instances) x, y, z = vertices[f.verts[n]]
glVertex3f(x * sx, y * sy, z * sz)
def merge_groups(self, model): for f in g.faces:
by_mat = defaultdict(list) if type != f.type:
for g in model.groups: if type != -1:
if g.faces: glEnd()
by_mat[g.material].append(g) glBegin(GL_TRIANGLES)
type = f.type
groups = [] point(f, vertices, normals, textures, 0)
for mat, gs in six.iteritems(by_mat): point(f, vertices, normals, textures, 1)
faces = [] point(f, vertices, normals, textures, 2)
for g in gs:
faces += g.faces
groups.append(Group(mat, faces))
return groups
def process_group(self, group, vertices, normals, textures): if type == FACE_QUADS:
sx, sy, sz = self.scale point(f, vertices, normals, textures, 2)
max_texture = len(textures) point(f, vertices, normals, textures, 3)
has_texture = bool(textures) and any(any(n is not None for n in f.texs) for f in group.faces) point(f, vertices, normals, textures, 0)
has_normal = bool(normals) and any(any(n is not None for n in f.norms) for f in group.faces) glEnd()
buffer = []
indices = []
offsets = {}
for f in group.faces: if tex_id:
verts = [] glBindTexture(GL_TEXTURE_2D, 0)
for v, n, t in zip(f.verts, f.norms, f.texs): glDisable(GL_TEXTURE_2D)
# Blender defines texture coordinates on faces even without textures.
if t is not None and t >= max_texture:
t = None
if (v, n, t) in offsets:
verts.append(offsets[v, n, t])
else:
index = len(offsets)
verts.append(index)
x, y, z = vertices[v]
item = [sx * x, sy * y, sz * z]
if has_normal:
item += [0, 0, 0] if n is None else list(normals[n])
if has_texture:
item += [0, 0] if t is None else list(textures[t])
offsets[v, n, t] = index
buffer += item
for a, b in zip(verts[1:], verts[2:]): glPopAttrib()
indices += [verts[0], a, b] glPopMatrix()
result = ModelVBO() glEndList()
result.has_normal = has_normal return display
result.has_texture = has_texture
result.offset_type = GL_UNSIGNED_SHORT if len(offsets) < 65536 else GL_UNSIGNED_INT
result.data_buf = list_to_gl_buffer(buffer, 'f')
result.index_buf = list_to_gl_buffer(indices, {
GL_UNSIGNED_SHORT: 'H',
GL_UNSIGNED_INT: 'I',
}[result.offset_type])
result.vertex_count = len(indices)
return result

View file

@ -66,11 +66,10 @@ class KeplerOrbit(object):
self.__cos_argument = cos(self._argument) self.__cos_argument = cos(self._argument)
def eccentric_anomaly(self, mean_anomaly): def eccentric_anomaly(self, mean_anomaly):
e1 = 0 e1 = mean_anomaly
e2 = mean_anomaly e2 = mean_anomaly + self.eccentricity * sin(e1)
while abs(e1 - e2) > 0.000001: while abs(e1 - e2) > 0.000001:
e1, e2 = e2, e2 - ((e2 - mean_anomaly - self.eccentricity * sin(e2)) / e1, e2 = e2, mean_anomaly + self.eccentricity * sin(e2)
(1 - self.eccentricity * cos(e2)))
return e2 return e2
def true_anomaly(self, mean_anomaly): def true_anomaly(self, mean_anomaly):

View file

@ -1,127 +0,0 @@
from __future__ import print_function
import os
import sys
from ctypes import pointer, byref, create_string_buffer, POINTER, cast
from pyglet.gl import *
# noinspection PyUnresolvedReferences
from six.moves import range
SHADERS_DIR = os.path.join(os.path.dirname(__file__), 'shaders')
class CompileError(ValueError):
pass
class glShader(object):
def __init__(self, type):
self.type = type
def __enter__(self):
self.shader = glCreateShader(self.type)
return self.shader
def __exit__(self, exc_type, exc_val, exc_tb):
glDeleteShader(self.shader)
class Program(object):
@classmethod
def load_file(cls, file):
with open(os.path.join(SHADERS_DIR, file), 'rb') as f:
return f.read()
@classmethod
def compile_shader(cls, shader, source):
buffer = create_string_buffer(source)
glShaderSource(shader, 1, cast(pointer(pointer(buffer)), POINTER(POINTER(GLchar))), None)
glCompileShader(shader)
succeeded = GLint()
log_length = GLint()
glGetShaderiv(shader, GL_COMPILE_STATUS, byref(succeeded))
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, byref(log_length))
buffer = create_string_buffer(log_length.value + 1)
glGetShaderInfoLog(shader, log_length.value, None, buffer)
if not succeeded:
raise CompileError(buffer.value.decode('utf-8'))
elif log_length.value:
print('Warning:', file=sys.stderr)
print(buffer.value.decode('utf-8'), file=sys.stderr)
def __init__(self, vertex_file, fragment_file):
with glShader(GL_VERTEX_SHADER) as vertex_shader, glShader(GL_FRAGMENT_SHADER) as fragment_shader:
self.compile_shader(vertex_shader, self.load_file(vertex_file))
self.compile_shader(fragment_shader, self.load_file(fragment_file))
program = glCreateProgram()
glAttachShader(program, vertex_shader)
glAttachShader(program, fragment_shader)
glLinkProgram(program)
succeeded = GLint()
log_length = GLint()
glGetProgramiv(program, GL_LINK_STATUS, byref(succeeded))
if not succeeded:
glGetProgramiv(program, GL_INFO_LOG_LENGTH, byref(log_length))
buffer = create_string_buffer(log_length.value + 1)
glGetProgramInfoLog(program, log_length.value, None, buffer)
glDeleteProgram(program)
raise CompileError(buffer.value)
glDetachShader(program, vertex_shader)
glDetachShader(program, fragment_shader)
self.program = program
self.attributes = self._variable_locations(GL_ACTIVE_ATTRIBUTES, glGetActiveAttrib, glGetAttribLocation)
self.uniforms = self._variable_locations(GL_ACTIVE_UNIFORMS, glGetActiveUniform, glGetUniformLocation)
def vertex_attribute(self, name, size, type, normalized, stride, offset, divisor=None):
location = self.attributes[name]
glVertexAttribPointer(location, size, type, normalized, stride, offset)
glEnableVertexAttribArray(location)
if divisor:
glVertexAttribDivisor(location, divisor)
def vertex_attribute_vec2(self, name, a, b):
glVertexAttrib2f(self.attributes[name], a, b)
def vertex_attribute_vec3(self, name, a, b, c):
glVertexAttrib3f(self.attributes[name], a, b, c)
def uniform_mat4(self, name, matrix):
glUniformMatrix4fv(self.uniforms[name], 1, GL_FALSE, matrix)
def uniform_texture(self, name, index):
glUniform1i(self.uniforms[name], index)
def uniform_float(self, name, value):
glUniform1f(self.uniforms[name], value)
def uniform_bool(self, name, value):
glUniform1i(self.uniforms[name], bool(value))
def uniform_vec2(self, name, a, b):
glUniform2f(self.uniforms[name], a, b)
def uniform_vec3(self, name, a, b, c):
glUniform3f(self.uniforms[name], a, b, c)
def uniform_vec4(self, name, a, b, c, d):
glUniform4f(self.uniforms[name], a, b, c, d)
def _variable_locations(self, count_type, get_func, loc_func):
variables = {}
count = GLint()
glGetProgramiv(self.program, count_type, byref(count))
buffer = create_string_buffer(256)
size = GLint()
type = GLenum()
for index in range(count.value):
get_func(self.program, index, 256, None, byref(size), byref(type), buffer)
variables[buffer.value.decode('ascii')] = loc_func(self.program, buffer)
return variables

View file

@ -1,12 +0,0 @@
#version 330 core
in float v_u;
out vec4 o_fragColor;
uniform vec3 u_color;
uniform sampler1D u_transparency;
void main() {
o_fragColor = vec4(u_color, texture(u_transparency, v_u).r);
}

View file

@ -1,13 +0,0 @@
#version 330 core
in vec2 a_position;
in float a_u;
out float v_u;
uniform mat4 u_mvpMatrix;
void main() {
gl_Position = u_mvpMatrix * vec4(a_position, 0, 1);
v_u = a_u;
}

View file

@ -1,28 +0,0 @@
#version 330 core
in vec3 a_position;
in vec3 a_normal;
in vec2 a_uv;
in vec3 a_translate;
in float a_scale;
out vec2 v_uv;
out vec3 v_normal;
out vec3 v_position;
out vec3 v_camDirection;
uniform mat4 u_mvpMatrix;
uniform mat4 u_mvMatrix;
uniform mat4 u_modelMatrix;
void main() {
mat4 matrix = mat4(mat3(a_scale));
matrix[3].xyz = a_translate;
mat4 modelMatrix = u_modelMatrix * matrix;
gl_Position = u_mvpMatrix * matrix * vec4(a_position, 1);
v_normal = normalize(vec3(modelMatrix * vec4(a_normal, 0)));
v_uv = a_uv;
v_position = (modelMatrix * vec4(a_position, 1)).xyz;
v_camDirection = (u_mvMatrix * matrix * vec4(a_position, 1)).xyz;
}

View file

@ -1,19 +0,0 @@
#version 330 core
in vec2 v_uv;
in vec3 v_normal;
in vec3 v_position;
out vec4 o_fragColor;
uniform vec3 u_ambient;
uniform vec3 u_diffuse;
uniform vec3 u_sun;
uniform sampler2D u_transparency;
void main() {
vec3 incident = normalize(u_sun - v_position);
vec3 diffuse = u_diffuse * clamp(dot(v_normal, incident) + 0.2, 0.0, 1.0);
o_fragColor = vec4(u_ambient + diffuse, texture(u_transparency, v_uv).r);
}

View file

@ -1,20 +0,0 @@
#version 330 core
in vec3 a_normal;
in vec2 a_uv;
out vec2 v_uv;
out vec3 v_normal;
out vec3 v_position;
uniform float u_radius;
uniform mat4 u_mvpMatrix;
uniform mat4 u_modelMatrix;
void main() {
vec3 position = u_radius * a_normal;
v_uv = a_uv;
v_normal = (u_modelMatrix * vec4(a_normal, 0)).xyz;
v_position = (u_modelMatrix * vec4(position, 1)).xyz;
gl_Position = u_mvpMatrix * vec4(position, 1);
}

View file

@ -1,8 +0,0 @@
#version 330 core
out vec4 o_fragColor;
uniform vec4 u_color;
void main() {
o_fragColor = u_color;
}

View file

@ -1,8 +0,0 @@
#version 330 core
in vec3 a_position;
uniform mat4 u_mvpMatrix;
void main() {
gl_Position = u_mvpMatrix * vec4(a_position, 1);
}

View file

@ -1,43 +0,0 @@
#version 330 core
in vec2 v_uv;
in vec3 v_normal;
in vec3 v_position;
in vec3 v_camDirection;
out vec4 o_fragColor;
struct Material {
bool hasDiffuse;
sampler2D diffuseMap;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
struct Sun {
vec3 ambient;
vec3 diffuse;
vec3 specular;
vec3 position;
float intensity;
};
uniform Sun u_sun;
uniform Material u_material;
void main() {
vec3 incident = normalize(u_sun.position - v_position);
vec3 reflected = normalize(reflect(-incident, v_normal));
float diffuseIntensity = max(dot(v_normal, incident), 0.0);
float shininess = pow(max(dot(normalize(v_camDirection), reflected), 0), u_material.shininess);
vec3 diffuse = u_material.hasDiffuse ? texture(u_material.diffuseMap, v_uv).rgb : vec3(1);
vec3 ambient = u_material.ambient * u_sun.ambient * diffuse;
vec3 specular = u_material.specular * u_sun.specular * max(shininess, 0) * diffuseIntensity;
diffuse *= u_material.diffuse * u_sun.diffuse * diffuseIntensity;
o_fragColor = vec4((ambient + diffuse + specular) * u_sun.intensity, 1);
}

View file

@ -1,22 +0,0 @@
#version 330 core
in vec3 a_position;
in vec3 a_normal;
in vec2 a_uv;
out vec2 v_uv;
out vec3 v_normal;
out vec3 v_position;
out vec3 v_camDirection;
uniform mat4 u_mvpMatrix;
uniform mat4 u_mvMatrix;
uniform mat4 u_modelMatrix;
void main() {
gl_Position = u_mvpMatrix * vec4(a_position, 1);
v_normal = normalize(vec3(u_modelMatrix * vec4(a_normal, 0)));
v_uv = a_uv;
v_position = (u_modelMatrix * vec4(a_position, 1)).xyz;
v_camDirection = (u_mvMatrix * vec4(a_position, 1)).xyz;
}

View file

@ -1,55 +0,0 @@
#version 330 core
in vec2 v_uv;
in vec3 v_normal;
in vec3 v_position;
in vec3 v_camDirection;
in mat3 v_TBN;
out vec4 o_fragColor;
struct Surface {
sampler2D diffuseMap;
bool hasNormal;
sampler2D normalMap;
bool hasSpecular;
sampler2D specularMap;
bool hasEmission;
sampler2D emissionMap;
vec3 ambient;
vec3 diffuse;
vec3 specular;
vec3 emission;
float shininess;
};
struct Sun {
vec3 ambient;
vec3 diffuse;
vec3 specular;
vec3 position;
float intensity;
};
uniform Sun u_sun;
uniform Surface u_planet;
void main() {
vec3 normal = u_planet.hasNormal ? normalize(v_TBN * texture(u_planet.normalMap, v_uv).rgb * 2 - 1) : v_normal;
vec3 diffuse = texture(u_planet.diffuseMap, v_uv).rgb;
vec3 specular = u_planet.hasSpecular ? texture(u_planet.specularMap, v_uv).rgb : vec3(1);
vec3 emission = u_planet.hasEmission ? texture(u_planet.emissionMap, v_uv).rgb : vec3(1);
vec3 incident = normalize(u_sun.position - v_position);
vec3 reflected = normalize(reflect(-incident, normal));
float diffuseIntensity = max(dot(normal, incident), 0.0);
float shininess = pow(max(dot(normalize(v_camDirection), reflected), 0), u_planet.shininess);
vec3 ambient = u_planet.ambient * u_sun.ambient * diffuse;
diffuse *= u_planet.diffuse * u_sun.diffuse * diffuseIntensity;
emission *= u_planet.emission * (1 - min(diffuseIntensity * 2, 1));
specular *= u_planet.specular * u_sun.specular * max(shininess, 0) * diffuseIntensity;
o_fragColor = vec4((ambient + diffuse + emission + specular) * u_sun.intensity, 1);
}

View file

@ -1,30 +0,0 @@
#version 330 core
in vec3 a_normal;
in vec2 a_tangent;
in vec2 a_uv;
out vec2 v_uv;
out vec3 v_normal;
out vec3 v_position;
out vec3 v_camDirection;
out mat3 v_TBN;
uniform float u_radius;
uniform mat4 u_mvpMatrix;
uniform mat4 u_mvMatrix;
uniform mat4 u_modelMatrix;
void main() {
vec3 position = u_radius * a_normal;
gl_Position = u_mvpMatrix * vec4(position, 1);
v_normal = normalize(vec3(u_modelMatrix * vec4(a_normal, 0)));
v_uv = a_uv;
v_position = (u_modelMatrix * vec4(position, 1)).xyz;
v_camDirection = (u_mvMatrix * vec4(position, 1)).xyz;
vec3 tangent = normalize((u_modelMatrix * vec4(a_tangent, a_normal.z, 0)).xyz);
v_TBN = mat3(tangent, cross(tangent, v_normal), v_normal);
}

View file

@ -1,22 +0,0 @@
#version 330 core
in vec3 v_position;
in float v_u;
out vec4 o_fragColor;
uniform vec3 u_sun;
uniform vec3 u_planet;
uniform float u_planetRadius;
uniform float u_ambient;
uniform sampler1D u_texture;
void main() {
vec3 incident = v_position - u_sun;
vec3 plane_normal = u_planet - u_sun;
vec3 plane_intersect = dot(plane_normal, plane_normal) / dot(incident, plane_normal) * incident;
o_fragColor = texture(u_texture, v_u);
if (length(plane_intersect) < length(incident) &&
distance(plane_intersect, plane_normal) <= u_planetRadius)
o_fragColor.rgb *= u_ambient;
}

View file

@ -1,16 +0,0 @@
#version 330 core
in vec2 a_position;
in float a_u;
out vec3 v_position;
out float v_u;
uniform mat4 u_mvpMatrix;
uniform mat4 u_modelMatrix;
void main() {
gl_Position = u_mvpMatrix * vec4(a_position, 0, 1);
v_position = (u_modelMatrix * vec4(a_position, 0, 1)).xyz;
v_u = a_u;
}

View file

@ -1,13 +0,0 @@
#version 330 core
in vec3 v_direction;
out vec4 o_fragColor;
uniform bool u_lines;
uniform samplerCube u_skysphere;
uniform samplerCube u_constellation;
void main() {
o_fragColor = texture(u_skysphere, v_direction);
if (u_lines)
o_fragColor += texture(u_constellation, v_direction);
}

View file

@ -1,10 +0,0 @@
#version 330 core
in vec3 a_direction;
out vec3 v_direction;
uniform mat4 u_mvpMatrix;
void main() {
gl_Position = (u_mvpMatrix * vec4(a_direction, 1)).xyww;
v_direction = a_direction;
}

View file

@ -1,9 +0,0 @@
#version 330 core
in vec2 v_uv;
out vec4 o_fragColor;
uniform sampler2D u_emission;
void main() {
o_fragColor = vec4(texture(u_emission, v_uv).rgb, 1);
}

View file

@ -1,14 +0,0 @@
#version 330 core
in vec3 a_normal;
in vec2 a_uv;
out vec2 v_uv;
uniform float u_radius;
uniform mat4 u_mvpMatrix;
void main() {
gl_Position = u_mvpMatrix * vec4(u_radius * a_normal, 1);
v_uv = a_uv;
}

View file

@ -1,12 +0,0 @@
#version 330 core
in vec2 v_uv;
out vec4 o_fragColor;
uniform sampler2D u_alpha;
uniform vec3 u_color;
void main() {
o_fragColor = vec4(u_color, texture(u_alpha, v_uv).r);
}

View file

@ -1,14 +0,0 @@
#version 330 core
in vec2 a_rc;
in vec2 a_tex;
out vec2 v_uv;
uniform mat4 u_projMatrix;
uniform vec2 u_start;
void main() {
gl_Position = u_projMatrix * vec4(a_rc.y * 8 + u_start.x, a_rc.x * 16 + u_start.y, 0, 1);
v_uv = vec2(a_tex.x * 8 / 1024, a_tex.y);
}

View file

@ -1,95 +0,0 @@
from __future__ import print_function, division
import os
from PIL import Image
from punyverse.texture import max_texture_size
max_texture = max_texture_size()
def resize(width, height, target):
factor = target / 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)
def shrink(file):
image = Image.open(file)
width, height = image.size
if fits(width, height):
print('no need')
return
for attempt, new_size in [(4096, 'large'), (2048, 'medium')]:
width, height = resize(width, height, attempt)
if fits(width, height):
size = new_size
break
else:
width, height = resize(width, height, 1024) # 1024 is minimum
size = 'small'
print('size %s, %dx%d...' % (size, width, height), end=' ')
name = make_name(file, size)
if not os.path.exists(name):
original_width, original_height = image.size
if width * 3 < original_width and height * 3 < original_height:
image = image.resize((width * 2, height * 2))
image.resize((width, height), Image.ANTIALIAS).save(name)
print('saved to:', os.path.basename(name))
else:
print('already 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 main():
punyverse = os.path.dirname(__file__)
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, end=' ')
file = os.path.join(texture, file.replace('/', os.sep))
if os.path.exists(file):
shrink(file)
else:
print('exists not')
if __name__ == '__main__':
main()

View file

@ -1,93 +1,117 @@
from __future__ import print_function
import os.path
import struct
from ctypes import c_int, byref
from io import BytesIO
import six
from pyglet import image from pyglet import image
from pyglet.gl import * from pyglet.gl import *
from six.moves import range from ctypes import c_int, byref, c_ulong
import os.path
import struct
import itertools
try: try:
from ._glgeom import bgr_to_rgb, flip_vertical from _glgeom import bgr_to_rgb
except ImportError: except ImportError:
import warnings import warnings
warnings.warn('Compile _glgeom.c, or double the start up time.') 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): def bgr_to_rgb(source, width, height, alpha=False, bottom_up=True):
length = len(source) length = len(source)
depth = length // (width * height) depth = length / (width * height)
depth2 = depth - alpha depth2 = depth - alpha
result = bytearray(length) result = bytearray(length)
row = width * depth row = width * depth
for y in range(height): for y in xrange(height):
for x in range(width): for x in xrange(width):
offset = y * row + x * depth ioffset = y * width * depth + x * depth
for i in range(depth2): ooffset = (height - y - 1 if bottom_up else y) * row + x * depth
result[offset + i] = source[offset + depth2 - i - 1] for i in xrange(depth2):
result[ooffset+i] = source[ioffset+depth2-i-1]
if alpha: if alpha:
result[offset + depth2] = source[offset + depth2] result[ooffset+depth2] = source[ioffset+depth2]
return six.binary_type(result) return str(result)
else:
magick = False
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
def flip_vertical(source, width, height): __all__ = ['load_texture', 'load_clouds', 'load_image', 'pil_load']
length = len(source)
row = length // height
result = bytearray(length)
for y1 in range(height):
y2 = height - y1 - 1
result[y1 * row:y1 * row + row] = source[y2 * row:y2 * row + row]
return six.binary_type(result)
__all__ = ['load_texture', 'load_alpha_mask', 'load_image', 'get_best_texture', 'max_texture_size',
'get_cube_map', 'load_texture_1d']
id = 0 id = 0
cache = {} cache = {}
max_texture = None
power_of_two = None
badcard = False
bgra = False
def is_power2(num):
return num != 0 and ((num & (num - 1)) == 0) 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): def image_info(data):
data = six.binary_type(data) data = str(data)
size = len(data) size = len(data)
height = -1 height = -1
width = -1 width = -1
content_type = '' content_type = ''
# handle GIFs # handle GIFs
if size >= 10 and data[:6] in (b'GIF87a', b'GIF89a'): if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
# Check to see if content_type is correct # Check to see if content_type is correct
content_type = 'image/gif' content_type = 'image/gif'
w, h = struct.unpack('<HH', data[6:10]) w, h = struct.unpack("<HH", data[6:10])
width = int(w) width = int(w)
height = int(h) height = int(h)
# See PNG 2. Edition spec (http://www.w3.org/TR/PNG/) # See PNG 2. Edition spec (http://www.w3.org/TR/PNG/)
# Bytes 0-7 are below, 4-byte chunk length, then 'IHDR' # Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
# and finally the 4-byte width, height # and finally the 4-byte width, height
elif size >= 24 and data.startswith(b'\211PNG\r\n\032\n') and data[12:16] == b'IHDR': elif ((size >= 24) and data.startswith('\211PNG\r\n\032\n')
and (data[12:16] == 'IHDR')):
content_type = 'image/png' content_type = 'image/png'
w, h = struct.unpack('>LL', data[16:24]) w, h = struct.unpack(">LL", data[16:24])
width = int(w) width = int(w)
height = int(h) height = int(h)
# Maybe this is for an older PNG version. # Maybe this is for an older PNG version.
elif size >= 16 and data.startswith(b'\211PNG\r\n\032\n'): 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' content_type = 'image/png'
w, h = struct.unpack('>LL', data[8:16]) w, h = struct.unpack(">LL", data[8:16])
width = int(w) width = int(w)
height = int(h) height = int(h)
# handle JPEGs # handle JPEGs
elif size >= 2 and data.startswith(b'\377\330'): elif (size >= 2) and data.startswith('\377\330'):
content_type = 'image/jpeg' content_type = 'image/jpeg'
jpeg = BytesIO(data) jpeg = StringIO(data)
jpeg.read(2) jpeg.read(2)
b = jpeg.read(1) b = jpeg.read(1)
try: try:
@ -98,11 +122,13 @@ def image_info(data):
b = jpeg.read(1) b = jpeg.read(1)
if 0xC0 <= ord(b) <= 0xC3: if 0xC0 <= ord(b) <= 0xC3:
jpeg.read(3) jpeg.read(3)
height, width = struct.unpack('>HH', jpeg.read(4)) h, w = struct.unpack(">HH", jpeg.read(4))
break break
else: else:
jpeg.read(int(struct.unpack('>H', jpeg.read(2))[0]) - 2) jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
b = jpeg.read(1) b = jpeg.read(1)
width = int(w)
height = int(h)
except struct.error: except struct.error:
pass pass
except ValueError: except ValueError:
@ -111,58 +137,57 @@ def image_info(data):
return content_type, width, height return content_type, width, height
def glGetInteger(index):
buf = c_int()
glGetIntegerv(index, byref(buf))
return buf.value
def max_texture_size():
size = glGetInteger(GL_MAX_TEXTURE_SIZE)
if gl_info.get_vendor() == 'Intel':
# Intel can't seem to handle more than 4096
size = min(size, 4096)
return size
def check_size(width, height): def check_size(width, height):
max_texture = max_texture_size() init()
if width > max_texture or height > max_texture: if width > max_texture or height > max_texture:
print('too large') print 'too large'
raise ValueError('Texture 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_image(file, path): def load_image(file, path):
print('Loading image %s...' % file, end=' ') print "Loading image %s..." % file,
try: try:
file = open(path, 'rb') file = open(path, 'rb')
except IOError: except IOError:
print('does not exist') print 'exists not'
raise ValueError('Texture does not exist') raise ValueError('Texture exists not')
type, width, height = image_info(file.read(65536)) type, width, height = image_info(file.read(65536))
file.seek(0, 0) file.seek(0, 0)
if type: if type:
check_size(width, height) 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: try:
raw = image.load(path, file=file) raw = image.load(path, file=file)
except Exception: except IOError:
print('cannot be loaded') print 'exists not'
raise ValueError('cannot be loaded') raise ValueError('Texture exists not')
width, height = raw.width, raw.height width, height = raw.width, raw.height
check_size(width, height) check_size(width, height)
print() print
mode = GL_RGBA if 'A' in raw.format else GL_RGB mode = GL_RGBA if 'A' in raw.format else GL_RGB
# Flip from BGR to RGB # Flip from BGR to RGB
if raw.format in ('BGR', 'BGRA'): if raw.format in ('BGR', 'BGRA'):
if gl_info.have_extension('GL_EXT_bgra'): if bgra:
mode = GL_BGRA if 'A' in raw.format else GL_BGR mode = {GL_RGBA: GL_BGRA, GL_RGB: GL_BGR}[mode]
texture = raw.data texture = raw.data
else: else:
texture = bgr_to_rgb(raw.data, width, height, 'A' in raw.format) texture = bgr_to_rgb(raw.data, width, height, 'A' in raw.format)
@ -170,150 +195,72 @@ def load_image(file, path):
texture = raw.data texture = raw.data
else: else:
texture = raw.get_data('RGBA', width * 4) texture = raw.get_data('RGBA', width * 4)
return path, width, height, len(raw.format), mode, texture
return path, width, height, len(raw.format), mode, flip_vertical(texture, width, height)
def get_file_path(file): def load_texture(file):
if os.path.isabs(file): if os.path.isabs(file):
path = file path = file
file = os.path.basename(path) file = os.path.basename(path)
else: else:
path = os.path.join(os.path.dirname(__file__), 'assets', 'textures', file) path = os.path.join(os.path.dirname(__file__), 'assets', 'textures', file)
return path, file
def create_texture():
buffer = GLuint()
glGenTextures(1, byref(buffer))
id = buffer.value
return id
def delete_texture(id):
buffer = GLuint(id)
glDeleteTextures(1, byref(buffer))
def get_internal_mode(mode):
return {
GL_RGB: GL_RGB8,
GL_BGR: GL_RGB8,
GL_RGBA: GL_RGBA8,
GL_BGRA: GL_RGBA8,
}[mode]
def load_texture(file, clamp=False):
path, file = get_file_path(file)
if path in cache: if path in cache:
return cache[path] return cache[path]
path, width, height, depth, mode, texture = load_image(file, path) path, width, height, depth, mode, texture = load_image(file, path)
id = create_texture() buffer = c_ulong()
glBindTexture(GL_TEXTURE_2D, id)
glTexImage2D(GL_TEXTURE_2D, 0, get_internal_mode(mode), width, height, 0, mode, GL_UNSIGNED_BYTE, texture)
glGenerateMipmap(GL_TEXTURE_2D)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
if clamp:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
if gl_info.have_extension('GL_EXT_texture_filter_anisotropic'):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, glGetInteger(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT))
cache[path] = id
return id
def load_texture_1d(file, clamp=False):
path, file = get_file_path(file)
path, width, height, depth, mode, texture = load_image(file, path)
id = create_texture()
glBindTexture(GL_TEXTURE_1D, id)
glTexImage1D(GL_TEXTURE_1D, 0, get_internal_mode(mode), width, 0, mode, GL_UNSIGNED_BYTE, texture)
glGenerateMipmap(GL_TEXTURE_1D)
if clamp:
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
if gl_info.have_extension('GL_EXT_texture_filter_anisotropic'):
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAX_ANISOTROPY_EXT, glGetInteger(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT))
return id
def load_alpha_mask(file, clamp=False):
path, file = get_file_path(file)
path, width, height, depth, mode, texture = load_image(file, path)
if depth != 1:
texture = texture[::depth]
buffer = GLuint()
glGenTextures(1, byref(buffer)) glGenTextures(1, byref(buffer))
id = buffer.value id = buffer.value
glBindTexture(GL_TEXTURE_2D, id) glBindTexture(GL_TEXTURE_2D, id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, texture)
glGenerateMipmap(GL_TEXTURE_2D)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) filter = GL_NEAREST if badcard else GL_LINEAR
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter)
#gluBuild2DMipmaps(GL_TEXTURE_2D, depth, width, height, mode, GL_UNSIGNED_BYTE, texture)
glTexImage2D(GL_TEXTURE_2D, 0, mode, width, height, 0, mode, GL_UNSIGNED_BYTE, texture)
if clamp: cache[path] = id
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
if gl_info.have_extension('GL_EXT_texture_filter_anisotropic'):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, glGetInteger(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT))
return id return id
def get_cube_map(files, callback=None): def pil_load(file):
assert len(files) == 6 import Image
callback = callback or (lambda index, file: None) return Image.open(os.path.join(os.path.dirname(__file__), 'assets', 'textures', file))
id = create_texture()
glBindTexture(GL_TEXTURE_CUBE_MAP, id)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0)
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, 0)
for index, (file, part) in enumerate(zip(files, [
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
])):
try:
path, file = get_file_path(file)
callback(index, file)
path, width, height, depth, mode, texture = load_image(file, path)
except Exception:
delete_texture(id)
raise
glTexImage2D(part, 0, get_internal_mode(mode), width, height, 0, mode, GL_UNSIGNED_BYTE, texture)
return id
def get_best_texture(info, loader=load_texture, optional=False, **kwargs): def load_clouds(file):
if isinstance(info, list): if os.path.isabs(file):
for item in info: path = file
try: file = os.path.basename(path)
return loader(item, **kwargs)
except ValueError:
pass
else: else:
return loader(info, **kwargs) path = os.path.join(os.path.dirname(__file__), 'assets', 'textures', file)
if not optional:
raise ValueError('No texture found') if path in cache:
return cache[path]
path, width, height, depth, mode, texture = load_image(file, path)
buffer = c_ulong()
glGenTextures(1, byref(buffer))
id = buffer.value
pixels = bytearray(len(texture) * 4)
white = chr(255)
pixels[:] = itertools.chain.from_iterable(itertools.izip(itertools.repeat(white), itertools.repeat(white),
itertools.repeat(white),
itertools.islice(texture, 0, None, depth)))
glBindTexture(GL_TEXTURE_2D, id)
filter = 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, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, str(pixels))
cache[path] = id
return id

View file

@ -1,279 +0,0 @@
#!/usr/bin/python
from __future__ import division
import os
import time
from math import hypot
from operator import attrgetter
import pyglet
import six
from pyglet.gl import *
from pyglet.window import key, mouse
from punyverse.glgeom import *
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 Punyverse(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super(Punyverse, self).__init__(*args, **kwargs)
self.fps = 0
self.info = True
self.debug = False
self.orbit = True
self.running = True
self.moving = True
self.info_precise = False
self.atmosphere = True
self.cloud = True
self.constellations = False
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.key_handler = {}
self.mouse_press_handler = {}
self.exclusive = False
self.modifiers = 0
self.world = None
def initialize(self, world):
self.world = world
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.world.tick_length) + 1
if index < len(self.ticks):
self.world.tick_length = self.ticks[index]
def decrement_tick():
index = self.ticks.index(self.world.tick_length) - 1
if index >= 0:
self.world.tick_length = self.ticks[index]
self.key_handler = {
key.ESCAPE: pyglet.app.exit,
key.NUM_ADD: speed_incrementer(self.world.cam, 1),
key.NUM_SUBTRACT: speed_incrementer(self.world.cam, -1),
key.NUM_MULTIPLY: speed_incrementer(self.world.cam, 10),
key.NUM_DIVIDE: speed_incrementer(self.world.cam, -10),
key.PAGEUP: speed_incrementer(self.world.cam, 100),
key.PAGEDOWN: speed_incrementer(self.world.cam, -100),
key.HOME: speed_incrementer(self.world.cam, 1000),
key.END: speed_incrementer(self.world.cam, -1000),
key.R: self.world.cam.reset_roll,
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.L: attribute_toggler(self, 'constellations'),
key.ENTER: attribute_toggler(self, 'running'),
key.INSERT: increment_tick,
key.DELETE: decrement_tick,
key.SPACE: self.world.spawn_asteroid,
key.E: lambda: self.set_exclusive_mouse(False),
key.F: lambda: self.set_fullscreen(not self.fullscreen),
}
self.mouse_press_handler = {
mouse.LEFT: self.world.spawn_asteroid,
mouse.RIGHT: attribute_toggler(self, 'moving'),
}
glClearColor(0, 0, 0, 1)
glClearDepth(1.0)
glDepthFunc(GL_LEQUAL)
glEnable(GL_DEPTH_TEST)
glShadeModel(GL_SMOOTH)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
self.info_engine = FontEngine(self.world.activate_shader('text'))
self.circle = Circle(10, 20, self.world.activate_shader('line'))
pyglet.clock.schedule(self.update)
self.on_resize(self.width, self.height) # On resize handler does nothing unless it's loaded
def screenshot(self):
image = pyglet.image.get_buffer_manager().get_color_buffer()
if hasattr(self, '_hwnd') and not self.modifiers & key.MOD_CTRL:
from ctypes import windll
from PIL import Image
import tempfile
CF_BITMAP = 2
image = Image.frombytes(image.format, (image.width, image.height), image.get_image_data().data)
image = image.convert('RGB').transpose(Image.FLIP_TOP_BOTTOM)
fd, filename = tempfile.mkstemp('.bmp')
try:
with os.fdopen(fd, 'wb') as file:
image.save(file, 'BMP')
if isinstance(filename, six.binary_type):
filename = filename.decode('mbcs' if os.name == 'nt' else 'utf8')
image = windll.user32.LoadImageW(None, filename, 0, 0, 0, 0x10)
windll.user32.OpenClipboard(self._hwnd)
windll.user32.EmptyClipboard()
windll.user32.SetClipboardData(CF_BITMAP, image)
windll.user32.CloseClipboard()
finally:
os.remove(filename)
else:
image.save(os.path.expanduser('~/punyverse.png'))
def set_exclusive_mouse(self, exclusive):
super(Punyverse, self).set_exclusive_mouse(exclusive)
self.exclusive = exclusive
def on_mouse_press(self, x, y, button, modifiers):
self.modifiers = 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.world.cam.mouse_move(dx * MOUSE_SENSITIVITY, dy * MOUSE_SENSITIVITY)
def on_key_press(self, symbol, modifiers):
self.modifiers = modifiers
if symbol == key.Q:
self.screenshot()
if self.exclusive: # Only handle keyboard input if mouse is grabbed
if symbol in self.key_handler:
self.key_handler[symbol]()
elif symbol == key.A:
self.world.cam.roll_left = True
elif symbol == key.S:
self.world.cam.roll_right = True
def on_key_release(self, symbol, modifiers):
if symbol == key.A:
self.world.cam.roll_left = False
elif symbol == key.S:
self.world.cam.roll_right = False
def on_resize(self, width, height):
if not width or not height:
# Sometimes this happen for no reason?
return
if hasattr(self, 'get_viewport_size'):
width, height = self.get_viewport_size()
glViewport(0, 0, width, height)
self.world.resize(width, height)
def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
self.world.cam.speed += scroll_y * 50 + scroll_x * 500
def get_time_per_second(self):
time = self.world.tick_length
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)
return result
def update(self, dt):
self.world.update(dt, move=self.exclusive and self.moving, tick=self.running)
def on_draw(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
c = self.world.cam
x, y, z = c.x, c.y, c.z
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:
entity.draw(self)
if self.info:
width, height = self.get_size()
projection = Matrix4f([
2 / width, 0, 0, 0,
0, -2 / height, 0, 0,
0, 0, -1, 0,
-1, 1, 0, 1,
])
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' %
(pyglet.clock.get_fps(), c.x, c.y, c.z, self.world.cam.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' %
(pyglet.clock.get_fps(), c.x, c.y, c.z, self.world.cam.speed, self.get_time_per_second()))
glEnable(GL_BLEND)
shader = self.world.activate_shader('text')
shader.uniform_mat4('u_projMatrix', projection)
glBindTexture(GL_TEXTURE_2D, self.world.font_tex)
shader.uniform_texture('u_alpha', 0)
shader.uniform_vec3('u_color', 1, 1, 1)
shader.uniform_vec2('u_start', 10, 10)
self.info_engine.draw(info)
with self.info_engine.vao:
glDrawArrays(GL_TRIANGLES, 0, self.info_engine.vertex_count)
glDisable(GL_BLEND)
glLineWidth(2)
mvp = projection * Matrix4f.from_angles((width / 2, height /2, 0))
shader = self.world.activate_shader('line')
shader.uniform_vec4('u_color', 0, 1, 0, 1)
shader.uniform_mat4('u_mvpMatrix', mvp)
with self.circle.vao:
glDrawArrays(GL_LINE_LOOP, 0, self.circle.vertex_count)
glLineWidth(1)

View file

@ -1,18 +0,0 @@
class cached_property(object):
def __init__(self, func, name=None):
self.func = func
self.name = name or func.__name__
self.__doc__ = getattr(func, '__doc__')
def __get__(self, instance, owner=None):
if instance is None:
return self
result = instance.__dict__[self.name] = self.func(instance)
return result
def __set__(self, instance, value):
if value is None:
if self.name in instance.__dict__:
del instance.__dict__[self.name]
else:
instance.__dict__[self.name] = value

View file

@ -7,7 +7,7 @@
"distance": "virtual distance to look better, in km", "distance": "virtual distance to look better, in km",
"sma": "semi-major axis used with mass of parent to calculate orbit, in km", "sma": "semi-major axis used with mass of parent to calculate orbit, in km",
"mass": "mass in kg", "mass": "mass in kg",
"texture": "a group of texture to use, tried in that order", "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" "model": "used to load a wavefront object instead of a textured sphere"
}, },
"au": 10000, "au": 10000,
@ -15,22 +15,23 @@
"length": 63.7, "length": 63.7,
"bodies": { "bodies": {
"sun": { "sun": {
"texture": ["sun.jpg"], "texture": ["sun.jpg", [0.99, 0.97, 0.66, 1]],
"radius": 80000, "radius": 80000,
"pitch": -90, "pitch": -90,
"yaw": 7.25, "yaw": 7.25,
"mass": 1.9891e+30, "mass": 1.9891e+30,
"rotation": 2164320, "rotation": 2164320,
"light_source": true,
"type": "star",
"atmosphere": { "atmosphere": {
"glow_color": [0.92, 0.92, 0.82], "corona_texture": "sun_corona.png",
"glow_texture": "glow.png", "corona_size": 1500,
"glow_size": 300 "corona_division": 100,
"corona_prob": 0.5,
"diffuse_texture": "sun_diffuse.png",
"diffuse_size": 300
} }
}, },
"mercury": { "mercury": {
"texture": ["mercury.jpg", "mercury_small.jpg"], "texture": ["mercury.jpg", "mercury_small.jpg", [0.44, 0.43, 0.43, 1]],
"radius": 2439.7, "radius": 2439.7,
"z": "0.466697 * AU", "z": "0.466697 * AU",
"pitch": -90, "pitch": -90,
@ -39,7 +40,7 @@
"rotation": 5067014 "rotation": 5067014
}, },
"venus": { "venus": {
"texture": ["venus.jpg"], "texture": ["venus.jpg", [0.655, 0.38, 0.1, 1]],
"radius": 6051.8, "radius": 6051.8,
"z": "0.723327 * AU", "z": "0.723327 * AU",
"pitch": -90, "pitch": -90,
@ -48,7 +49,7 @@
"rotation": -20996798 "rotation": -20996798
}, },
"earth": { "earth": {
"texture": ["earth.jpg", "earth_large.jpg", "earth_medium.jpg", "earth_small.jpg"], "texture": ["earth.jpg", "earth_medium.jpg", "earth_small.jpg", [0.11, 0.32, 0.43, 1]],
"radius": 6378.1, "radius": 6378.1,
"z": "AU", "z": "AU",
"pitch": -90, "pitch": -90,
@ -56,20 +57,17 @@
"roll": -90, "roll": -90,
"mass": 5.97219e+24, "mass": 5.97219e+24,
"rotation": 86400, "rotation": 86400,
"division": 90, "division": 250,
"normal_map": ["earth_normal.jpg", "earth_normal_small.jpg"], "normal": "earth_normal.jpg",
"specular_map": ["earth_specular.jpg", "earth_specular_small.jpg"],
"emission_map": ["earth_emission.jpg", "earth_emission_medium.jpg", "earth_emission_small.jpg"],
"atmosphere": { "atmosphere": {
"cloud_texture": ["cloudmap.jpg", "cloudmap_small.jpg"], "cloud_texture": ["cloudmap.jpg", "cloudmap_small.jpg"],
"glow_color": [0.11, 0.32, 0.43], "diffuse_texture": "atmosphere_earth.png",
"glow_texture": "glow.png", "diffuse_size": 30
"glow_size": 30
}, },
"orbit_distance": "AU", "orbit_distance": "AU",
"satellites": { "satellites": {
"moon": { "moon": {
"texture": ["moon.jpg", "moon_medium.jpg", "moon_small.jpg"], "texture": ["moon.jpg", "moon_medium.jpg", "moon_small.jpg", [0.53, 0.53, 0.53, 1]],
"radius": 1738.14, "radius": 1738.14,
"distance": 38439, "distance": 38439,
"sma": 384399, "sma": 384399,
@ -96,7 +94,7 @@
} }
}, },
"mars": { "mars": {
"texture": ["mars.jpg", "mars_large.jpg", "mars_medium.jpg", "mars_small.jpg"], "texture": ["mars.jpg", "mars_small.jpg", "mars_medium.jpg", [0.85, 0.47, 0.2, 1]],
"radius": 3396.2, "radius": 3396.2,
"z": "1.524 * AU", "z": "1.524 * AU",
"pitch": -90, "pitch": -90,
@ -115,7 +113,7 @@
} }
}, },
"jupiter": { "jupiter": {
"texture": ["jupiter.jpg", "jupiter_medium.jpg", "jupiter_small.jpg"], "texture": ["jupiter.jpg", "jupiter_medium.jpg", "jupiter_small.jpg", [0.65, 0.36, 0.19, 1]],
"radius": 71492, "radius": 71492,
"mass": 1.8986e+27, "mass": 1.8986e+27,
"z": "5.2 * AU", "z": "5.2 * AU",
@ -126,7 +124,7 @@
"orbit_distance": "3 * AU", "orbit_distance": "3 * AU",
"satellites": { "satellites": {
"io": { "io": {
"texture": ["moons/io.jpg", "moons/io_small.jpg"], "texture": ["moons/io.jpg", "moons/io_small.jpg", [0.62, 0.56, 0.35, 1]],
"radius": "1821.3 * 5", "radius": "1821.3 * 5",
"distance": 126510, "distance": 126510,
"sma": 421700, "sma": 421700,
@ -136,7 +134,7 @@
"eccentricity": 0.0041 "eccentricity": 0.0041
}, },
"europa": { "europa": {
"texture": ["moons/europa.jpg", "moons/europa_small.jpg"], "texture": ["moons/europa.jpg", "moons/europa_small.jpg", [0.77, 0.74, 0.65, 1]],
"radius": "1560.8 * 5", "radius": "1560.8 * 5",
"distance": 201270, "distance": 201270,
"sma": 670900, "sma": 670900,
@ -146,7 +144,7 @@
"eccentricity": 0.009 "eccentricity": 0.009
}, },
"ganymede": { "ganymede": {
"texture": ["moons/ganymede.jpg", "moons/ganymede_small.jpg"], "texture": ["moons/ganymede.jpg", "moons/ganymede_small.jpg", [0.52, 0.47, 0.46, 1]],
"radius": "2634.1 * 5", "radius": "2634.1 * 5",
"distance": 321120, "distance": 321120,
"sma": 1070400, "sma": 1070400,
@ -156,7 +154,7 @@
"eccentricity": 0.0013 "eccentricity": 0.0013
}, },
"callisto": { "callisto": {
"texture": ["moons/callisto.jpg", "moons/callisto_small.jpg"], "texture": ["moons/callisto.jpg", "moons/callisto_small.jpg", [0.49, 0.43, 0.34, 1]],
"radius": "2410.3 * 5", "radius": "2410.3 * 5",
"distance": 564810, "distance": 564810,
"sma": 1882700, "sma": 1882700,
@ -168,7 +166,7 @@
} }
}, },
"saturn": { "saturn": {
"texture": ["saturn.jpg", "saturn_medium.jpg", "saturn_small.jpg"], "texture": ["saturn.jpg", "saturn_medium.jpg", "saturn_small.jpg", [0.9, 0.8, 0.64, 1]],
"radius": 60268, "radius": 60268,
"mass": 5.6846e+26, "mass": 5.6846e+26,
"z": "9.58 * AU", "z": "9.58 * AU",
@ -183,7 +181,7 @@
"orbit_distance": "4 * AU", "orbit_distance": "4 * AU",
"satellites": { "satellites": {
"titan": { "titan": {
"texture": ["moons/titan.jpg", "moons/titan_small.jpg"], "texture": ["moons/titan.jpg", "moons/titan_small.jpg", [0.52, 0.39, 0.23, 1]],
"radius": "2576 * 10", "radius": "2576 * 10",
"distance": "1221870 / 3 + 200000", "distance": "1221870 / 3 + 200000",
"sma": 1221870, "sma": 1221870,
@ -193,7 +191,7 @@
"eccentricity": 0.0288 "eccentricity": 0.0288
}, },
"rhea": { "rhea": {
"texture": ["moons/rhea.jpg", "moons/rhea_small.jpg"], "texture": ["moons/rhea.jpg", "moons/rhea_small.jpg", [0.62, 0.60, 0.59, 1]],
"radius": "763.8 * 10", "radius": "763.8 * 10",
"distance": "527108 / 3 + 200000", "distance": "527108 / 3 + 200000",
"sma": 527108, "sma": 527108,
@ -203,7 +201,7 @@
"eccentricity": 0.0012583 "eccentricity": 0.0012583
}, },
"iapetus": { "iapetus": {
"texture": ["moons/iapetus.jpg", "moons/iapetus_small.jpg"], "texture": ["moons/iapetus.jpg", "moons/iapetus_small.jpg", [0.62, 0.60, 0.59, 1]],
"radius": "734.5 * 10", "radius": "734.5 * 10",
"distance": "3560820 / 3 + 200000", "distance": "3560820 / 3 + 200000",
"sma": 3560820, "sma": 3560820,
@ -213,7 +211,7 @@
"eccentricity": 0.0286125 "eccentricity": 0.0286125
}, },
"dione": { "dione": {
"texture": ["moons/dione.jpg", "moons/dione_small.jpg"], "texture": ["moons/dione.jpg", "moons/dione_small.jpg", [0.46, 0.46, 0.46, 1]],
"radius": "561.4 * 10", "radius": "561.4 * 10",
"distance": "377396 / 3 + 200000", "distance": "377396 / 3 + 200000",
"sma": 377396, "sma": 377396,
@ -223,7 +221,7 @@
"eccentricity": 0.0022 "eccentricity": 0.0022
}, },
"tethys": { "tethys": {
"texture": ["moons/tethys.jpg", "moons/tethys_small.jpg"], "texture": ["moons/tethys.jpg", "moons/tethys_small.jpg", [0.68, 0.68, 0.66, 1]],
"radius": "531.1 * 10", "radius": "531.1 * 10",
"distance": "294619 / 3 + 200000", "distance": "294619 / 3 + 200000",
"sma": 294619, "sma": 294619,
@ -233,7 +231,7 @@
"eccentricity": 0.0001 "eccentricity": 0.0001
}, },
"enceladus": { "enceladus": {
"texture": ["moons/enceladus.jpg", "moons/enceladus_small.jpg"], "texture": ["moons/enceladus.jpg", "moons/enceladus_small.jpg", [0.74, 0.74, 0.74, 1]],
"radius": "252.1 * 10", "radius": "252.1 * 10",
"distance": "237948 / 3 + 200000", "distance": "237948 / 3 + 200000",
"sma": 237948, "sma": 237948,
@ -243,7 +241,7 @@
"eccentricity": 0.0047 "eccentricity": 0.0047
}, },
"mimas": { "mimas": {
"texture": ["moons/mimas.jpg", "moons/mimas_small.jpg"], "texture": ["moons/mimas.jpg", "moons/mimas_small.jpg", [0.47, 0.47, 0.47, 1]],
"radius": "198.2 * 10", "radius": "198.2 * 10",
"distance": "181902 / 3 + 200000", "distance": "181902 / 3 + 200000",
"sma": 181902, "sma": 181902,
@ -263,7 +261,7 @@
} }
}, },
"uranus": { "uranus": {
"texture": ["uranus.jpg"], "texture": ["uranus.jpg", [0, 0.53, 0.84, 1]],
"radius": 25559, "radius": 25559,
"mass": 8.6810e+25, "mass": 8.6810e+25,
"z": "19.23 * AU", "z": "19.23 * AU",
@ -280,7 +278,7 @@
} }
}, },
"neptune": { "neptune": {
"texture": ["neptune.jpg"], "texture": ["neptune.jpg", [0.31, 0.49, 0.59, 1]],
"radius": 24764, "radius": 24764,
"mass": 1.0243e+26, "mass": 1.0243e+26,
"z": "30.5 * AU", "z": "30.5 * AU",
@ -288,6 +286,19 @@
"rotation": 57996, "rotation": 57996,
"pitch": -90, "pitch": -90,
"yaw": 28.32 "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": { "belts": {
@ -296,25 +307,10 @@
"radius": "2.362 * AU", "radius": "2.362 * AU",
"cross": 1000, "cross": 1000,
"scale": 30, "scale": 30,
"count": 4096, "count": 2048,
"rotation": 114536500 "rotation": 114536500
} }
}, },
"sky": {
"texture": [
["sky_px.jpg", "sky_nx.jpg", "sky_py.jpg", "sky_ny.jpg", "sky_pz.jpg", "sky_nz.jpg"]
],
"constellation": [
"constellation_px.png", "constellation_nx.png", "constellation_py.png",
"constellation_ny.png", "constellation_pz.png", "constellation_nz.png"
],
"rotation": 0,
"division": 30,
"pitch": -119.3,
"yaw": -97
},
"asteroids": ["asteroids/01.obj", "asteroids/02.obj", "asteroids/03.obj"],
"font": "font.png",
"start": { "start": {
"z": "AU - 400", "z": "AU - 400",
"yaw": 180 "yaw": 180

View file

@ -1,218 +1,234 @@
from __future__ import division
import json
import os
from collections import OrderedDict from collections import OrderedDict
import os.path
import six try:
import json
except ImportError:
try:
import simplejson as json
except ImportError:
raise SystemExit('No JSON module found')
from punyverse import texture try:
from punyverse.camera import Camera 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.entity import *
from punyverse.shader import Program from punyverse.texture import *
from punyverse import texture
from math import pi, sqrt
G = 6.67384e-11 # Gravitation Constant
def load_world(file, callback=lambda message, completion: None): def get_best_texture(info, optional=False, loader=load_texture):
return World(file, callback) 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 = loader(item)
except ValueError:
pass
else:
break
else:
cheap = True
texture = [1, 1, 1, 1]
else:
try:
texture = loader(info)
except ValueError:
if optional:
skip = True
else:
cheap = True
texture = [1, 1, 1, 1]
return cheap, skip, texture
class World(object): def load_world(file):
PROGRAMS = {
'sky': ('sky.vertex.glsl', 'sky.fragment.glsl'),
'planet': ('planet.vertex.glsl', 'planet.fragment.glsl'),
'clouds': ('clouds.vertex.glsl', 'clouds.fragment.glsl'),
'star': ('star.vertex.glsl', 'star.fragment.glsl'),
'ring': ('ring.vertex.glsl', 'ring.fragment.glsl'),
'atmosphere': ('atmosphere.vertex.glsl', 'atmosphere.fragment.glsl'),
'text': ('text.vertex.glsl', 'text.fragment.glsl'),
'line': ('line.vertex.glsl', 'line.fragment.glsl'),
'model': ('model.vertex.glsl', 'model.fragment.glsl'),
'belt': ('belt.vertex.glsl', 'model.fragment.glsl'),
}
def __init__(self, file, callback, sky=True):
self.tracker = []
self.x = None
self.y = None
self.z = None
self.tick_length = 0
self.tick = 0
self.asteroids = AsteroidManager(self)
self.cam = Camera()
self._sky = sky
self._program = None
self.callback = callback
self.programs = self._load_programs()
self._parse(file)
del self.callback # So it can't be used after loading finishes
self._time_accumulate = 0
self._projection_matrix = self.cam.projection_matrix()
for entity in self.tracker:
entity.update()
for name in ('planet', 'model', 'belt'):
shader = self.activate_shader(name)
shader.uniform_vec3('u_sun.ambient', 0.1, 0.1, 0.1)
shader.uniform_vec3('u_sun.diffuse', 1, 1, 1)
shader.uniform_vec3('u_sun.specular', 0.5, 0.5, 0.5)
shader.uniform_vec3('u_sun.position', 0, 0, 0)
shader.uniform_float('u_sun.intensity', 1)
shader = self.activate_shader('clouds')
shader.uniform_vec3('u_sun', 0, 0, 0)
self.activate_shader(None)
def _load_programs(self):
programs = {}
count = len(self.PROGRAMS)
for i, (name, (vertex, fragment)) in enumerate(six.iteritems(self.PROGRAMS)):
self.callback('Loading shaders (%d of %d)...' % (i, count),
'Loading shader "%s" (%s, %s).' % (name, vertex, fragment), i / count)
programs[name] = Program(vertex, fragment)
return programs
def evaluate(self, value):
return eval(str(value), {'__builtins__': None}, self._context)
@property
def length(self):
return self._length
@property
def au(self):
return self._au
def _parse(self, file):
self.callback('Parsing configuration...', 'Loading configuration file...', 0)
with open(os.path.join(os.path.dirname(__file__), file)) as f: with open(os.path.join(os.path.dirname(__file__), file)) as f:
root = json.load(f, object_pairs_hook=OrderedDict) root = json.load(f, object_pairs_hook=OrderedDict)
self._au = root.get('au', 2000)
self._length = root.get('length', 4320)
self._context = {'AU': self._au, 'TEXTURE': texture.max_texture_size(), 'KM': 1.0 / self._length}
self.tick_length = root.get('tick', 4320) # How many second is a tick? world = World()
au = root.get('au', 2000)
# Need to know how many objects are being loaded e = lambda x: eval(str(x), {'__builtins__': None}, {'AU': au, 'TEXTURE': texture.max_texture})
self._objects = 0 tick = root.get('tick', 4320) # How many second is a tick?
self._current_object = 0 length = root.get('length', 4320) # Satellite distance is in km, divide by this gets in world units
world.tick_length = tick
def count_objects(bodies):
for body in six.itervalues(bodies):
self._objects += 1
count_objects(body.get('satellites', {}))
count_objects(root['bodies'])
if 'start' in root: if 'start' in root:
info = root['start'] info = root['start']
self.cam.x = self.evaluate(info.get('x', 0)) x = e(info.get('x', 0))
self.cam.y = self.evaluate(info.get('y', 0)) y = e(info.get('y', 0))
self.cam.z = self.evaluate(info.get('z', 0)) z = e(info.get('z', 0))
self.cam.pitch = self.evaluate(info.get('pitch', 0)) pitch = e(info.get('pitch', 0))
self.cam.yaw = self.evaluate(info.get('yaw', 0)) yaw = e(info.get('yaw', 0))
self.cam.roll = self.evaluate(info.get('roll', 0)) roll = e(info.get('roll', 0))
world.start = (x, y, z)
world.direction = (pitch, yaw, roll)
for planet, info in six.iteritems(root['bodies']): def body(name, info, parent=None):
self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects), lighting = info.get('lighting', True)
'Loading %s.' % planet, self._current_object / self._objects) x = e(info.get('x', 0))
self._body(planet, info) y = e(info.get('y', 0))
self._current_object += 1 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 'belts' in root:
belt_count = len(root['belts'])
for i, (name, info) in enumerate(six.iteritems(root['belts']), 1):
self.callback('Loading belts (%d of %d)...' % (i, belt_count),
'Loading %s.' % name, i / belt_count)
self.tracker.append(Belt(name, self, info))
if 'sky' in root and self._sky:
def callback(index, file):
self.callback('Loading sky...', 'Loading %s.' % file, index / 6)
self.tracker.append(Sky(self, root['sky'], callback))
if 'asteroids' in root:
asteroids = root['asteroids']
for i, file in enumerate(asteroids):
self.callback('Loading asteroids...', 'Loading %s...' % file, i / len(asteroids))
self.asteroids.load(file)
self.font_tex = load_alpha_mask(root['font'], clamp=True)
def _body(self, name, info, parent=None):
if 'texture' in info: if 'texture' in info:
body = SphericalBody(name, self, info, parent) 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:
if 'normal' in info:
object_id = compile(normal_sphere, radius, division, texture, info['normal'], lighting=lighting)
else:
object_id = compile(sphere, radius, division, division, texture, lighting=lighting)
elif 'model' in info: elif 'model' in info:
body = ModelBody(name, self, info, parent) 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: else:
raise ValueError('Nothing to load for %s.' % name) print 'Nothing to load for %s.' % name
return
if parent: params = {'world': world, 'orbit_distance': orbit_distance}
parent.satellites.append(body) if parent is None:
type = Body
else: else:
self.tracker.append(body) x, y, z = parent.location
distance = e(info.get('distance', 100)) # Semi-major axis when actually displayed in virtual space
for satellite, info in six.iteritems(info.get('satellites', {})): sma = e(info.get('sma', distance)) # Semi-major axis used to calculate orbital speed
self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects), if hasattr(parent, 'mass') and parent.mass is not None:
'Loading %s, satellite of %s.' % (satellite, name), self._current_object / self._objects) period = 2 * pi * sqrt((sma * 1000) ** 3 / (G * parent.mass))
self._body(satellite, info, body) speed = 360 / (period + .0)
self._current_object += 1 if not rotation: # Rotation = 0 assumes tidal lock
rotation = period
def spawn_asteroid(self):
if self.asteroids:
c = self.cam
dx, dy, dz = c.direction()
speed = abs(self.cam.speed) * 1.1 + 5
self.tracker.append(self.asteroids.new((c.x, c.y - 3, c.z + 5), (dx * speed, dy * speed, dz * speed)))
def update(self, dt, move, tick):
c = self.cam
c.update(dt, move)
self.vp_matrix = None
if tick:
delta = self.tick_length * dt
update = int(delta + self._time_accumulate + 0.5)
if update:
self._time_accumulate = 0
self.tick += update
for entity in self.tracker:
entity.update()
collision = entity.collides(c.x, c.y, c.z)
if collision:
c.speed *= -1
c.move(c.speed * 12 * dt)
else: else:
self._time_accumulate += delta 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))
def view_matrix(self): if 'mass' in info:
return self.cam.view_matrix params['mass'] = info['mass']
def projection_matrix(self): atmosphere_id = 0
return self._projection_matrix 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, loader=load_clouds)
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)
@cached_property if atm_texture is not None:
def vp_matrix(self): cheap, _, atm_texture = get_best_texture(atm_texture)
return self._projection_matrix * self.cam.view_matrix if not cheap:
atmosphere_id = compile(disk, radius, radius + atm_size, 30, atm_texture)
def resize(self, width, height): theta = 360 / (rotation + .0) if rotation else 0
self.cam.aspect = width / max(height, 1) object = type(object_id, (x, y, z), (pitch, yaw, roll), rotation_angle=theta,
self._projection_matrix = self.cam.projection_matrix() atmosphere=atmosphere_id, cloudmap=cloudmap_id, background=background,
self.vp_matrix = None corona=corona_id, **params)
world.tracker.append(object)
def activate_shader(self, name): if 'ring' in info:
program = None ring_data = info['ring']
if self._program != name: texture = ring_data.get('texture', None)
if name is None: distance = e(ring_data.get('distance', radius * 1.2))
glUseProgram(0) size = e(ring_data.get('size', radius / 2))
else: pitch = e(ring_data.get('pitch', pitch))
program = self.programs[name] yaw = e(ring_data.get('yaw', yaw))
glUseProgram(program.program) roll = e(ring_data.get('roll', roll))
self._program = name
elif self._program is not None: cheap, _, texture = get_best_texture(texture)
program = self.programs[self._program] if not cheap:
return program 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
models = info['model']
if not isinstance(models, list):
models = [models]
objects = []
for model in models:
objects.append(model_list(load_model(model), info.get('sx', scale), info.get('sy', scale),
info.get('sz', scale), (0, 0, 0)))
world.tracker.append(Belt(compile(belt, radius, cross, objects, 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

179
setup.py
View file

@ -1,179 +0,0 @@
from __future__ import print_function
import os
import sys
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
from setuptools.extension import Library
has_pyx = os.path.exists(os.path.join(os.path.dirname(__file__), 'punyverse', '_glgeom.pyx'))
try:
from Cython.Build import cythonize
except ImportError:
if has_pyx:
print('You need to install cython first before installing punyverse.', file=sys.stderr)
print('Run: pip install cython', file=sys.stderr)
print('Or if you do not have pip: easy_install cython', file=sys.stderr)
sys.exit(1)
cythonize = lambda x: x
if has_pyx:
pyx_path = lambda x: x
else:
pyx_path = lambda x: x.replace('.pyx', '.c')
if os.name == 'nt':
gl_libs = ['opengl32']
elif sys.platform == 'darwin':
gl_libs = []
else:
gl_libs = ['GL']
if sys.platform == 'darwin':
extra_compile_args = extra_link_args = ['-framework', 'OpenGL']
else:
extra_compile_args = extra_link_args = []
with open(os.path.join(os.path.dirname(__file__), 'README.md')) as f:
long_description = f.read()
if os.name == 'nt':
class SimpleExecutable(Library, object):
executable_names = set()
def __init__(self, name, *args, **kwargs):
super(SimpleExecutable, self).__init__(name, *args, **kwargs)
self.executable_names.add(name)
if '.' in name:
self.executable_names.add(name.split('.')[-1])
def link_shared_object(
self, objects, output_libname, output_dir=None, libraries=None,
library_dirs=None, runtime_library_dirs=None, export_symbols=None,
debug=0, extra_preargs=None, extra_postargs=None, build_temp=None,
target_lang=None):
self.link(
self.EXECUTABLE, objects, output_libname,
output_dir, libraries, library_dirs, runtime_library_dirs,
export_symbols, debug, extra_preargs, extra_postargs,
build_temp, target_lang
)
def make_manifest_get_embed_info(old_func):
def manifest_get_embed_info(self, target_desc, ld_args):
temp_manifest, mfid = old_func(target_desc, ld_args)
if not os.path.exists(temp_manifest):
return None
return temp_manifest, mfid
return manifest_get_embed_info.__get__(old_func.__self__)
class build_ext_exe(build_ext, object):
def get_ext_filename(self, fullname):
ext = self.ext_map[fullname]
if isinstance(ext, SimpleExecutable):
return fullname.replace('.', os.sep) + '.exe'
return super(build_ext_exe, self).get_ext_filename(fullname)
def get_export_symbols(self, ext):
if isinstance(ext, SimpleExecutable):
return ext.export_symbols
return super(build_ext_exe, self).get_export_symbols(ext)
def build_extension(self, ext):
if isinstance(ext, SimpleExecutable):
old = self.shlib_compiler.link_shared_object
self.shlib_compiler.link_shared_object = link_shared_object.__get__(self.shlib_compiler)
patched = False
if hasattr(self.shlib_compiler, 'manifest_get_embed_info'):
self.shlib_compiler.manifest_get_embed_info = \
make_manifest_get_embed_info(self.shlib_compiler.manifest_get_embed_info)
patched = True
super(build_ext_exe, self).build_extension(ext)
self.shlib_compiler.link_shared_object = old
if patched:
del self.shlib_compiler.manifest_get_embed_info
else:
super(build_ext_exe, self).build_extension(ext)
extra_libs = [
SimpleExecutable('punyverse.launcher', sources=['punyverse/launcher.c'], libraries=['shell32']),
SimpleExecutable('punyverse.launcherw', sources=['punyverse/launcher.c'],
libraries=['shell32'], define_macros=[('GUI', 1)]),
]
build_ext = build_ext_exe
else:
extra_libs = []
setup(
name='punyverse',
version='1.2',
packages=['punyverse'],
package_data={
'punyverse': [
'world.json',
'shaders/*.glsl',
'assets/textures.txt',
'assets/textures/*.jpg',
'assets/textures/*.png',
'assets/textures/moons/*',
'assets/models/asteroids/*',
'assets/models/satellites/*.mtl',
'assets/models/satellites/*.obj',
'assets/models/satellites/*.jpg',
'assets/models/satellites/*.png',
'assets/models/satellites/cassini/*',
],
},
ext_modules=cythonize([
Extension('punyverse._glgeom', sources=[pyx_path('punyverse/_glgeom.pyx')], libraries=gl_libs,
extra_compile_args=extra_compile_args, extra_link_args=extra_link_args),
]) + extra_libs,
cmdclass={'build_ext': build_ext},
entry_points={
'console_scripts': [
'punyverse = punyverse.main:main',
'punyverse_make_launcher = punyverse.launcher:main',
'punyverse_small_images = punyverse.small_images:main',
],
'gui_scripts': [
'punyversew = punyverse.main:main'
]
},
install_requires=['pyglet<1.4', 'Pillow', 'six'],
author='quantum',
author_email='quantum2048@gmail.com',
url='https://github.com/quantum5/punyverse',
description='Python simulator of a puny universe.',
long_description=long_description,
long_description_content_type='text/markdown',
keywords='universe simulator',
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Win32 (MS Windows)',
'Environment :: X11 Applications',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Games/Entertainment :: Simulation',
'Topic :: Multimedia :: Graphics :: 3D Rendering',
'Topic :: Scientific/Engineering :: Visualization',
],
)

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()