Compare commits
67 commits
Author | SHA1 | Date | |
---|---|---|---|
|
e77d4661cc | ||
|
7cb34c9142 | ||
|
e72a3bc623 | ||
|
9cdf3b8513 | ||
|
885194c7a0 | ||
|
8739120036 | ||
|
d5297d3619 | ||
|
4d58450429 | ||
|
2a9eacf37e | ||
|
98b894c846 | ||
|
7c3c644a6a | ||
|
85d4bd4efc | ||
|
52c1a328aa | ||
|
4430eb7d75 | ||
|
8317201492 | ||
|
b4c96ebf90 | ||
|
f70a725a58 | ||
|
e91bc2c21c | ||
|
674289a8b3 | ||
|
7023ed83ea | ||
|
9e65004128 | ||
|
4857b5487c | ||
|
91fce520b9 | ||
|
9d73059ad0 | ||
|
f11e0b6fc7 | ||
|
168434ce37 | ||
|
d41fcb7d7f | ||
|
1319514cdc | ||
|
a28074558b | ||
|
9608d30462 | ||
|
f7797949d9 | ||
|
3770ec617b | ||
|
ac7a4d0f13 | ||
|
04758850f5 | ||
|
25e28c3c12 | ||
|
c3176ce7f6 | ||
|
3f927427ae | ||
|
fbcdb4d9b0 | ||
|
66c54a9dbb | ||
|
999dc78410 | ||
|
517a7584ca | ||
|
0607ab4d4c | ||
|
bfdefe43ef | ||
|
a2620fc3bc | ||
|
8b070726c3 | ||
|
dde4e1541d | ||
|
825e2ecf6a | ||
|
338b7fb66c | ||
|
1524a6f27e | ||
|
9193f1bc57 | ||
|
13e247bea3 | ||
|
e579086ec5 | ||
|
cbe3c3dd7e | ||
|
054721fc0f | ||
|
1b6d422447 | ||
|
b094dd8905 | ||
|
e33fad8bbb | ||
|
13867d7fd0 | ||
|
b54601f9f7 | ||
|
a862302408 | ||
|
2aed23c4ae | ||
|
3079c612f6 | ||
|
f9d83a2add | ||
|
63d42bca55 | ||
|
1b973b0082 | ||
|
50b8d39b71 | ||
|
2dfab195f1 |
17
.gitignore
vendored
|
@ -41,10 +41,12 @@ punyverse/*.c
|
||||||
*.html
|
*.html
|
||||||
*.exp
|
*.exp
|
||||||
*.lib
|
*.lib
|
||||||
punyverse/assets/textures/*_medium.*
|
/punyverse/assets/textures/*_large.*
|
||||||
punyverse/assets/textures/*_small.*
|
/punyverse/assets/textures/*_medium.*
|
||||||
punyverse/assets/textures/*/*_medium.*
|
/punyverse/assets/textures/*_small.*
|
||||||
punyverse/assets/textures/*/*_small.*
|
/punyverse/assets/textures/*/*_large.*
|
||||||
|
/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
|
||||||
|
@ -57,3 +59,10 @@ library.zip
|
||||||
/env
|
/env
|
||||||
/env2
|
/env2
|
||||||
/env3
|
/env3
|
||||||
|
|
||||||
|
# Our special launcher
|
||||||
|
!/punyverse/launcher.c
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
._.DS_Store
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- /^v\d+\./
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- sudo: required
|
- sudo: required
|
||||||
|
|
|
@ -3,5 +3,7 @@ include LICENSE
|
||||||
include README.md
|
include README.md
|
||||||
include punyverse/world.json
|
include punyverse/world.json
|
||||||
graft punyverse/assets
|
graft punyverse/assets
|
||||||
|
include punyverse/shaders/*.glsl
|
||||||
include punyverse/*.c
|
include punyverse/*.c
|
||||||
|
include punyverse/*.h
|
||||||
exclude punyverse/*.pyx
|
exclude punyverse/*.pyx
|
||||||
|
|
33
README.md
|
@ -1,17 +1,40 @@
|
||||||
# punyverse [](https://travis-ci.org/quantum5/punyverse) [](https://ci.appveyor.com/project/quantum5/punyverse) [](https://pypi.org/project/punyverse/) [](https://pypi.org/project/punyverse/) [](https://pypi.org/project/punyverse/)
|
# punyverse [](https://travis-ci.org/quantum5/punyverse) [](https://ci.appveyor.com/project/quantum5/punyverse) [](https://pypi.org/project/punyverse/) [](https://pypi.org/project/punyverse/) [](https://pypi.org/project/punyverse/)
|
||||||
|
|
||||||
Python simulator of a puny universe. (How many words can I stick into one?)
|
Python simulator of a puny universe. (How many words can I stick into one?)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
To install, run `pip install punyverse`.
|
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.
|
Then, run `punyverse` to launch the simulator, or `punyversew` to launch without the console.
|
||||||
|
|
||||||
### A Note on Textures
|
### Summary
|
||||||
|
|
||||||
If your graphics card doesn't support the massive texture sizes this module comes with, you can shrink them.
|
```bash
|
||||||
|
pip install punyverse
|
||||||
|
punyverse_make_launcher
|
||||||
|
punyverse_small_images
|
||||||
|
# Installation finished. Run:
|
||||||
|
punyverse
|
||||||
|
```
|
||||||
|
|
||||||
To do this, run `punyverse_small_images`.
|
## Troubleshooting
|
||||||
|
|
||||||
|
If `punyverse` does not work, try upgrading your graphics card drivers.
|
||||||
|
|
||||||
|
If your graphics card does not appear to support OpenGL 3.3, then you cannot run the latest version of `punyverse`.
|
||||||
|
You can try `pip install -U punyverse==0.5` to install the last version of `punyverse` to support legacy devices.
|
||||||
|
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,
|
||||||
|
run `punyverse` as `punyverse --debug`. Then paste the entirety of the output into a new GitHub issue
|
||||||
|
[here](https://github.com/quantum5/punyverse/issues/new).
|
||||||
|
|
|
@ -1,55 +1,4 @@
|
||||||
#!/usr/bin/python
|
from punyverse.main import main
|
||||||
INITIAL_WIN_HEIGHT = 540
|
|
||||||
INITIAL_WIN_WIDTH = 700
|
|
||||||
WIN_TITLE = 'Punyverse'
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
parser = argparse.ArgumentParser(prog='punyverse', description='''
|
|
||||||
Python simulator of a puny universe.
|
|
||||||
''')
|
|
||||||
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')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
import pyglet
|
|
||||||
pyglet.options['shadow_window'] = False
|
|
||||||
|
|
||||||
template = pyglet.gl.Config(depth_size=args.depth, double_buffer=True,
|
|
||||||
sample_buffers=args.multisample > 1,
|
|
||||||
samples=args.multisample)
|
|
||||||
|
|
||||||
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.')
|
|
||||||
else:
|
|
||||||
if hasattr(config, '_attribute_names'):
|
|
||||||
print('OpenGL configuration:')
|
|
||||||
for key in config._attribute_names:
|
|
||||||
print(' %-17s %s' % (key + ':', getattr(config, key)))
|
|
||||||
|
|
||||||
world_options = {
|
|
||||||
'normal': args.normal,
|
|
||||||
}
|
|
||||||
|
|
||||||
from punyverse import game
|
|
||||||
game.Applet(width=INITIAL_WIN_WIDTH, height=INITIAL_WIN_HEIGHT,
|
|
||||||
caption=WIN_TITLE, resizable=True, vsync=args.vsync,
|
|
||||||
config=config, world_options=world_options)
|
|
||||||
pyglet.app.run()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
|
@ -1,4 +1,4 @@
|
||||||
from punyverse.__main__ import main
|
from punyverse.main import main
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
IF UNAME_SYSNAME == "Windows":
|
cdef extern from "glwrapper.h":
|
||||||
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
|
||||||
|
@ -387,74 +383,6 @@ cdef extern from "GL/gl.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
|
||||||
|
@ -1057,153 +985,3 @@ 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
|
|
|
@ -1,7 +1,4 @@
|
||||||
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
|
||||||
|
@ -12,60 +9,6 @@ cdef extern from "Python.h":
|
||||||
const char* PyBytes_AsString(bytes o)
|
const char* PyBytes_AsString(bytes o)
|
||||||
|
|
||||||
|
|
||||||
@cython.cdivision(True)
|
|
||||||
cpdef torus(float major_radius, float minor_radius, int n_major, int n_minor, tuple material, int shininess=125):
|
|
||||||
"""
|
|
||||||
Torus function from the OpenGL red book.
|
|
||||||
"""
|
|
||||||
glPushAttrib(GL_CURRENT_BIT)
|
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, [material[0], material[1], material[2], material[3]])
|
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [1, 1, 1, 1])
|
|
||||||
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
|
|
||||||
|
|
||||||
assert n_major > 0 and n_minor > 0
|
|
||||||
assert minor_radius > 0 and major_radius > 0
|
|
||||||
|
|
||||||
cdef float major_s, minor_s
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
cpdef bytes bgr_to_rgb(bytes buffer, int width, int height, bint alpha=0):
|
cpdef bytes bgr_to_rgb(bytes buffer, int width, int height, bint alpha=0):
|
||||||
cdef int length = len(buffer)
|
cdef int length = len(buffer)
|
||||||
cdef int depth = length / (width * height)
|
cdef int depth = length / (width * height)
|
||||||
|
|
|
@ -1,359 +0,0 @@
|
||||||
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 unicode name, texture
|
|
||||||
cdef public tuple Ka, Kd, Ks
|
|
||||||
cdef public double shininess
|
|
||||||
|
|
||||||
def __init__(self, unicode name, unicode 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 unicode 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, unicode name=None):
|
|
||||||
if name is None:
|
|
||||||
self.name = unicode(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):
|
|
||||||
name = words[1].decode('utf-8')
|
|
||||||
material = Material(name)
|
|
||||||
self.materials[name] = 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].decode('utf-8')
|
|
||||||
|
|
||||||
@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(b'/')
|
|
||||||
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].decode('utf-8')))
|
|
||||||
|
|
||||||
cdef void use_material(self, list words):
|
|
||||||
mat = words[1].decode('utf-8')
|
|
||||||
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].decode('utf-8')
|
|
||||||
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, lambda x: open(x, 'rb'))(file)
|
|
||||||
with reader:
|
|
||||||
for buf in reader:
|
|
||||||
if not buf or buf.startswith((b'\r', b'\n', b'#')):
|
|
||||||
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:
|
|
||||||
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' if os.name == 'nt' else 'utf8')
|
|
||||||
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
|
|
|
@ -1,13 +1,26 @@
|
||||||
# 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
|
||||||
|
|
Before Width: | Height: | Size: 114 B |
BIN
punyverse/assets/textures/constellation_nx.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
punyverse/assets/textures/constellation_ny.png
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
punyverse/assets/textures/constellation_nz.png
Normal file
After Width: | Height: | Size: 98 KiB |
BIN
punyverse/assets/textures/constellation_px.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
punyverse/assets/textures/constellation_py.png
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
punyverse/assets/textures/constellation_pz.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
punyverse/assets/textures/earth_emission.jpg
Normal file
After Width: | Height: | Size: 328 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 522 KiB |
BIN
punyverse/assets/textures/earth_specular.jpg
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
punyverse/assets/textures/font.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
punyverse/assets/textures/glow.png
Normal file
After Width: | Height: | Size: 98 B |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 281 KiB |
Before Width: | Height: | Size: 7.2 MiB |
BIN
punyverse/assets/textures/sky_nx.jpg
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
punyverse/assets/textures/sky_ny.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
punyverse/assets/textures/sky_nz.jpg
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
punyverse/assets/textures/sky_px.jpg
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
punyverse/assets/textures/sky_py.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
punyverse/assets/textures/sky_pz.jpg
Normal file
After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 640 B |
Before Width: | Height: | Size: 111 B |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 981 KiB |
|
@ -1,4 +1,9 @@
|
||||||
from math import sin, cos, radians
|
from __future__ import division
|
||||||
|
|
||||||
|
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):
|
||||||
|
@ -10,12 +15,23 @@ 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
|
||||||
|
@ -32,6 +48,8 @@ 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))
|
||||||
|
|
||||||
|
@ -39,3 +57,33 @@ 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])
|
||||||
|
|
|
@ -1,20 +1,45 @@
|
||||||
from math import sqrt
|
import random
|
||||||
|
from math import sqrt, pi
|
||||||
|
|
||||||
from six.moves import range
|
|
||||||
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.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):
|
||||||
def __init__(self, id, location, rotation=(0, 0, 0), direction=(0, 0, 0), background=False):
|
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
|
||||||
|
@ -22,10 +47,14 @@ class Entity(object):
|
||||||
def collides(self, x, y, z):
|
def collides(self, x, y, z):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def draw(self, options):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class Asteroid(Entity):
|
class Asteroid(Entity):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, world, model, location, direction):
|
||||||
super(Asteroid, self).__init__(*args, **kwargs)
|
super(Asteroid, self).__init__(world, 'Asteroid', location, direction=direction)
|
||||||
|
self.model = model
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
super(Asteroid, self).update()
|
super(Asteroid, self).update()
|
||||||
|
@ -33,115 +62,536 @@ 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, *args, **kwargs):
|
def __init__(self, name, world, info):
|
||||||
self.rotation_angle = kwargs.pop('rotation_angle', 5)
|
x = world.evaluate(info.get('x', 0))
|
||||||
self.world = kwargs.pop('world')
|
y = world.evaluate(info.get('y', 0))
|
||||||
super(Belt, self).__init__(*args, **kwargs)
|
z = world.evaluate(info.get('z', 0))
|
||||||
|
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, *args, **kwargs):
|
def __init__(self, name, world, info, parent=None):
|
||||||
self.rotation_angle = kwargs.pop('rotation_angle', 5)
|
self.parent = parent
|
||||||
self.atmosphere = kwargs.pop('atmosphere', 0)
|
self.satellites = []
|
||||||
self.cloudmap = kwargs.pop('cloudmap', 0)
|
|
||||||
self.corona = kwargs.pop('corona', 0)
|
x = world.evaluate(info.get('x', 0))
|
||||||
self.last_tick = 0
|
y = world.evaluate(info.get('y', 0))
|
||||||
self.mass = kwargs.pop('mass', None)
|
z = world.evaluate(info.get('z', 0))
|
||||||
self.radius = kwargs.pop('radius', 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)
|
|
||||||
self.initial_roll = self.rotation[2]
|
super(Body, self).__init__(world, name, (x, y, z), (pitch, yaw, roll))
|
||||||
|
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.last_tick != self.world.tick:
|
if self.rotation_angle:
|
||||||
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
|
||||||
|
|
||||||
def collides(self, x, y, z):
|
if self.orbit:
|
||||||
if self.radius is None:
|
px, py, pz = self.parent.location
|
||||||
return False
|
x, z, y = self.orbit.orbit(self.world.tick * self.orbit_speed % 360)
|
||||||
ox, oy, oz = self.location
|
self.location = (x + px, y + py, z + pz)
|
||||||
dx, dy, dz = x - ox, y - oy, z - oz
|
self.orbit_matrix = None
|
||||||
distance = sqrt(dx*dx + dy*dy + dz*dz)
|
|
||||||
if distance > self.radius:
|
|
||||||
return False
|
|
||||||
return (ox + dx * self.radius / distance,
|
|
||||||
oy + dy * self.radius / distance,
|
|
||||||
oz + dz * self.radius / distance)
|
|
||||||
|
|
||||||
|
for satellite in self.satellites:
|
||||||
|
satellite.update()
|
||||||
|
|
||||||
class Satellite(Body):
|
def get_orbit(self, shader):
|
||||||
def __init__(self, *args, **kwargs):
|
if not self.orbit:
|
||||||
self.parent = kwargs.pop('parent')
|
return
|
||||||
self.orbit_speed = kwargs.pop('orbit_speed', 1)
|
|
||||||
|
|
||||||
# Semi-major axis and eccentricity defines orbit
|
|
||||||
distance = kwargs.pop('distance', 100)
|
|
||||||
eccentricity = kwargs.pop('eccentricity', 0)
|
|
||||||
|
|
||||||
# Inclination, longitude of ascending node, and argument of periapsis defines orbital plane
|
|
||||||
inclination = kwargs.pop('inclination', 0)
|
|
||||||
longitude = kwargs.pop('longitude', 0)
|
|
||||||
argument = kwargs.pop('argument', 0)
|
|
||||||
|
|
||||||
# Orbit calculation
|
|
||||||
self.orbit_id = None
|
|
||||||
self.orbit_cache = None
|
|
||||||
|
|
||||||
self.theta = 0
|
|
||||||
# OpenGL's z-axis is reversed
|
|
||||||
self.orbit = KeplerOrbit(distance, eccentricity, inclination, longitude, argument)
|
|
||||||
super(Satellite, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_orbit(self):
|
|
||||||
# Cache key is the three orbital plane parameters and eccentricity
|
# Cache 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_id
|
return self.orbit_vbo, self.orbit_vao
|
||||||
|
|
||||||
if self.orbit_id is not None:
|
if self.orbit_vbo is not None:
|
||||||
glDeleteLists(self.orbit_id, 1)
|
self.orbit_vbo.close()
|
||||||
|
|
||||||
id = glGenLists(1)
|
if self.orbit_vao is not None:
|
||||||
glNewList(id, GL_COMPILE)
|
self.orbit_vao.close()
|
||||||
glBegin(GL_LINE_LOOP)
|
|
||||||
for theta in range(360):
|
self.orbit_vbo = OrbitVBO(self.orbit)
|
||||||
x, z, y = self.orbit.orbit(theta)
|
self.orbit_vao = VAO()
|
||||||
glVertex3f(x, y, z)
|
|
||||||
glEnd()
|
with self.orbit_vao:
|
||||||
glEndList()
|
glBindBuffer(GL_ARRAY_BUFFER, self.orbit_vbo.vbo)
|
||||||
|
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 id
|
return self.orbit_vbo, self.orbit_vao
|
||||||
|
|
||||||
def update(self):
|
def _draw_orbits(self, distance):
|
||||||
super(Body, self).update() # Notice how the parent class is skipped
|
shader = self.world.activate_shader('line')
|
||||||
|
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
|
||||||
roll = (self.initial_roll + self.world.tick * self.rotation_angle) % 360
|
pitch = world.evaluate(info['ring'].get('pitch', pitch))
|
||||||
self.rotation = pitch, yaw, roll
|
yaw = world.evaluate(info['ring'].get('yaw', yaw))
|
||||||
|
roll = world.evaluate(info['ring'].get('roll', roll))
|
||||||
|
self.ring_rotation = pitch, yaw, roll
|
||||||
|
|
||||||
self.parent.update()
|
self.ring_texture = load_texture_1d(info['ring'].get('texture'), clamp=True)
|
||||||
px, py, pz = self.parent.location
|
self.ring = Disk(distance, distance + size, 30)
|
||||||
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)
|
||||||
|
|
|
@ -1,499 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
from operator import attrgetter
|
|
||||||
from math import hypot, sqrt, atan2, degrees
|
|
||||||
from time import clock
|
|
||||||
import time
|
|
||||||
import random
|
|
||||||
import os
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from punyverse.camera import Camera
|
|
||||||
from punyverse.world import 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
|
|
||||||
|
|
||||||
try:
|
|
||||||
from itertools import zip_longest
|
|
||||||
except ImportError:
|
|
||||||
from itertools import izip_longest as zip_longest
|
|
||||||
|
|
||||||
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):
|
|
||||||
self.world_options = kwargs.pop('world_options', {})
|
|
||||||
|
|
||||||
super(Applet, self).__init__(*args, **kwargs)
|
|
||||||
texture.init()
|
|
||||||
|
|
||||||
if hasattr(self.config, '_attribute_names'):
|
|
||||||
info = [' %-22s %s' % (key + ':', getattr(self.config, key))
|
|
||||||
for key in self.config._attribute_names]
|
|
||||||
info = ['%-30s %-30s' % group for group in
|
|
||||||
zip_longest(info[::2], info[1::2], fillvalue='')]
|
|
||||||
info = 'OpenGL configuration:\n' + '\n'.join(info)
|
|
||||||
else:
|
|
||||||
info = 'Unknown OpenGL configuration'
|
|
||||||
|
|
||||||
self.loaded = False
|
|
||||||
self.__load_started = False
|
|
||||||
self._loading_phase = pyglet.text.Label(
|
|
||||||
font_name='Consolas', font_size=20, x=10, y=self.height - 80,
|
|
||||||
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='Consolas', font_size=16, x=10, y=self.height - 150,
|
|
||||||
color=(255, 255, 255, 255), width=self.width - 20, align='center',
|
|
||||||
multiline=True
|
|
||||||
)
|
|
||||||
self._info_label = pyglet.text.Label(
|
|
||||||
font_name='Consolas', font_size=13, x=10, y=self.height - 250,
|
|
||||||
color=(255, 255, 255, 255), width=self.width - 20,
|
|
||||||
multiline=True, text=info
|
|
||||||
)
|
|
||||||
pyglet.clock.schedule_once(self.load, 0)
|
|
||||||
|
|
||||||
def load(self, *args, **kwargs):
|
|
||||||
if self.loaded or self.__load_started:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.__load_started = True
|
|
||||||
|
|
||||||
def callback(phase, message, progress):
|
|
||||||
self.draw_loading(phase, message, progress)
|
|
||||||
self.flip()
|
|
||||||
self.dispatch_events()
|
|
||||||
|
|
||||||
start = clock()
|
|
||||||
self.fps = 0
|
|
||||||
self.world = World('world.json', callback, self.world_options)
|
|
||||||
phase = 'Initializing game...'
|
|
||||||
print(phase)
|
|
||||||
callback(phase, '', 0)
|
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
phase = 'Loading asteroids...'
|
|
||||||
print(phase)
|
|
||||||
|
|
||||||
def load_asteroids(files):
|
|
||||||
for id, file in enumerate(files):
|
|
||||||
callback(phase, 'Loading %s...' % file, float(id) / len(files))
|
|
||||||
yield model_list(load_model(file), 5, 5, 5, (0, 0, 0))
|
|
||||||
|
|
||||||
self.asteroid_ids = list(load_asteroids([r'asteroids/01.obj', r'asteroids/02.obj', r'asteroids/03.obj']))
|
|
||||||
|
|
||||||
c = self.cam
|
|
||||||
c.x, c.y, c.z = self.world.start
|
|
||||||
c.pitch, c.yaw, c.roll = self.world.direction
|
|
||||||
|
|
||||||
phase = 'Updating entities...'
|
|
||||||
print(phase)
|
|
||||||
callback(phase, '', 0)
|
|
||||||
for entity in self.world.tracker:
|
|
||||||
entity.update()
|
|
||||||
|
|
||||||
print('Loaded in %s seconds.' % (clock() - start))
|
|
||||||
self.loaded = True
|
|
||||||
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, cdll
|
|
||||||
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(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):
|
|
||||||
self.modifiers = modifiers
|
|
||||||
if not self.loaded:
|
|
||||||
return
|
|
||||||
|
|
||||||
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 not self.loaded:
|
|
||||||
return
|
|
||||||
|
|
||||||
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):
|
|
||||||
self.modifiers = modifiers
|
|
||||||
if symbol == key.Q:
|
|
||||||
self.screenshot()
|
|
||||||
if not self.loaded:
|
|
||||||
return
|
|
||||||
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 not self.loaded:
|
|
||||||
return
|
|
||||||
|
|
||||||
if symbol in self.keys:
|
|
||||||
self.keys.remove(symbol)
|
|
||||||
|
|
||||||
def on_resize(self, width, height):
|
|
||||||
if not self.loaded:
|
|
||||||
return super(Applet, self).on_resize(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):
|
|
||||||
if not self.loaded:
|
|
||||||
return
|
|
||||||
|
|
||||||
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()
|
|
||||||
collision = entity.collides(c.x, c.y, c.z)
|
|
||||||
if collision:
|
|
||||||
self.speed *= -1
|
|
||||||
c.move(self.speed * 12 * dt)
|
|
||||||
else:
|
|
||||||
self.__time_accumulate += delta
|
|
||||||
|
|
||||||
def draw_loading(self, phase=None, message=None, progress=None):
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT)
|
|
||||||
glLoadIdentity()
|
|
||||||
if phase is not None:
|
|
||||||
self._loading_phase.text = phase
|
|
||||||
if message is not None:
|
|
||||||
self._loading_label.text = message
|
|
||||||
self._loading_phase.draw()
|
|
||||||
self._loading_label.draw()
|
|
||||||
if progress is not None:
|
|
||||||
progress_bar(10, self.height - 170, self.width - 20, 50, progress)
|
|
||||||
self._info_label.draw()
|
|
||||||
|
|
||||||
def on_draw(self, glMatrixBuffer=GLfloat * 16):
|
|
||||||
if not self.loaded:
|
|
||||||
return self.draw_loading()
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
with glMatrix(), glRestore(GL_CURRENT_BIT):
|
|
||||||
if entity.background:
|
|
||||||
glTranslatef(c.x, c.y, c.z)
|
|
||||||
else:
|
|
||||||
glTranslatef(x, y, z)
|
|
||||||
glRotatef(pitch, 1, 0, 0)
|
|
||||||
glRotatef(yaw, 0, 1, 0)
|
|
||||||
glRotatef(roll, 0, 0, 1)
|
|
||||||
glCallList(entity.id)
|
|
||||||
if self.debug:
|
|
||||||
with glMatrix(), glRestore(GL_ENABLE_BIT | GL_POLYGON_BIT | GL_LINE_BIT):
|
|
||||||
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)
|
|
||||||
|
|
||||||
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):
|
|
||||||
with glMatrix(), glRestore(GL_ENABLE_BIT):
|
|
||||||
x0, y0, z0 = entity.location
|
|
||||||
glTranslatef(x0, y0, z0)
|
|
||||||
matrix = glMatrixBuffer()
|
|
||||||
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)
|
|
||||||
glEnable(GL_BLEND)
|
|
||||||
if has_atmosphere:
|
|
||||||
glCallList(entity.atmosphere)
|
|
||||||
if has_corona:
|
|
||||||
x, y, z = c.direction()
|
|
||||||
glTranslatef(-x, -y, -z)
|
|
||||||
glCallList(entity.corona)
|
|
||||||
|
|
||||||
if self.cloud and hasattr(entity, 'cloudmap') and entity.cloudmap:
|
|
||||||
with glMatrix(), glRestore(GL_ENABLE_BIT):
|
|
||||||
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)
|
|
||||||
|
|
||||||
if self.orbit and hasattr(entity, 'get_orbit') and hasattr(entity, 'parent'):
|
|
||||||
parent = entity.parent
|
|
||||||
distance = get_distance(parent)
|
|
||||||
if distance < parent.orbit_show:
|
|
||||||
with glMatrix(), glRestore(GL_ENABLE_BIT | GL_LINE_BIT | GL_CURRENT_BIT):
|
|
||||||
glTranslatef(*entity.parent.location)
|
|
||||||
glDisable(GL_LIGHTING)
|
|
||||||
solid = distance < parent.orbit_opaque
|
|
||||||
glColor4f(1, 1, 1, 1 if solid else
|
|
||||||
(1 - (distance - parent.orbit_opaque) / parent.orbit_blend))
|
|
||||||
if not solid:
|
|
||||||
glEnable(GL_BLEND)
|
|
||||||
glLineWidth(1)
|
|
||||||
glCallList(entity.get_orbit())
|
|
||||||
|
|
||||||
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()
|
|
||||||
with glRestore(GL_CURRENT_BIT | GL_LINE_BIT):
|
|
||||||
glLineWidth(2)
|
|
||||||
cx, cy = width / 2, height / 2
|
|
||||||
glColor4f(0, 1, 0, 1)
|
|
||||||
circle(10, 20, (cx, cy))
|
|
||||||
frustrum()
|
|
|
@ -1,29 +0,0 @@
|
||||||
@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
|
|
|
@ -1,344 +1,349 @@
|
||||||
|
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 random import random, gauss, choice
|
||||||
|
|
||||||
from six.moves import range
|
|
||||||
from pyglet.gl import *
|
from pyglet.gl import *
|
||||||
from pyglet.gl.gl_info import have_extension
|
# noinspection PyUnresolvedReferences
|
||||||
|
from six.moves import range
|
||||||
|
|
||||||
TWOPI = pi * 2
|
TWOPI = pi * 2
|
||||||
|
|
||||||
__all__ = ['compile', 'ortho', 'frustrum', 'crosshair', 'circle', 'disk', 'sphere', 'colourball', 'torus', 'belt',
|
__all__ = ['FontEngine', 'Matrix4f', 'Disk', 'OrbitVBO', 'SimpleSphere',
|
||||||
'flare', 'normal_sphere', 'glSection', 'glMatrix', 'glRestore', 'progress_bar']
|
'TangentSphere', 'Cube', 'Circle', 'BeltVBO', 'VAO']
|
||||||
|
|
||||||
|
|
||||||
class glSection(object):
|
def array_to_ctypes(arr):
|
||||||
def __init__(self, type):
|
return cast(arr.buffer_info()[0], POINTER({
|
||||||
self.type = type
|
'f': c_float,
|
||||||
|
'i': c_int,
|
||||||
def __enter__(self):
|
'I': c_uint,
|
||||||
glBegin(self.type)
|
'h': c_short,
|
||||||
|
'H': c_ushort,
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
}[arr.typecode]))
|
||||||
glEnd()
|
|
||||||
|
|
||||||
|
|
||||||
class glRestore(object):
|
def array_to_gl_buffer(buffer):
|
||||||
def __init__(self, flags):
|
vbo = c_uint()
|
||||||
self.flags = flags
|
glGenBuffers(1, byref(vbo))
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vbo.value)
|
||||||
def __enter__(self):
|
glBufferData(GL_ARRAY_BUFFER, buffer.itemsize * len(buffer), array_to_ctypes(buffer), GL_STATIC_DRAW)
|
||||||
glPushAttrib(self.flags)
|
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||||
|
return vbo.value
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
glPopAttrib()
|
|
||||||
|
|
||||||
|
|
||||||
class glMatrix(object):
|
def list_to_gl_buffer(buffer, array_type='f'):
|
||||||
def __enter__(self):
|
return array_to_gl_buffer(array(array_type, buffer))
|
||||||
glPushMatrix()
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
glPopMatrix()
|
|
||||||
|
|
||||||
|
|
||||||
def compile(pointer, *args, **kwargs):
|
class Matrix4f(object):
|
||||||
display = glGenLists(1)
|
def __init__(self, matrix):
|
||||||
glNewList(display, GL_COMPILE)
|
self.matrix = array('f', matrix)
|
||||||
pointer(*args, **kwargs)
|
assert len(self.matrix) == 16
|
||||||
glEndList()
|
|
||||||
return display
|
@classmethod
|
||||||
|
def from_angles(cls, location=(0, 0, 0), rotation=(0, 0, 0), view=False):
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
def ortho(width, height):
|
class Circle(object):
|
||||||
glDisable(GL_LIGHTING)
|
type = GL_FLOAT
|
||||||
glDisable(GL_DEPTH_TEST)
|
stride = 2 * 4
|
||||||
glMatrixMode(GL_PROJECTION)
|
position_offset = 0
|
||||||
glPushMatrix()
|
position_size = 2
|
||||||
glLoadIdentity()
|
|
||||||
glOrtho(0, width, 0, height, -1, 1)
|
def __init__(self, r, segs, shader):
|
||||||
glMatrixMode(GL_MODELVIEW)
|
self.vertex_count = segs
|
||||||
glPushMatrix()
|
buffer = segs * 2 * [0]
|
||||||
glLoadIdentity()
|
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)
|
||||||
|
|
||||||
|
|
||||||
def frustrum():
|
class Disk(object):
|
||||||
glMatrixMode(GL_PROJECTION)
|
type = GL_FLOAT
|
||||||
glPopMatrix()
|
stride = 3 * 4
|
||||||
glMatrixMode(GL_MODELVIEW)
|
position_offset = 0
|
||||||
glPopMatrix()
|
position_size = 2
|
||||||
glEnable(GL_LIGHTING)
|
u_offset = position_size * 4
|
||||||
glEnable(GL_DEPTH_TEST)
|
u_size = 1
|
||||||
|
|
||||||
|
def __init__(self, rinner, router, segs):
|
||||||
def crosshair(size, coords):
|
|
||||||
cx, cy = coords
|
|
||||||
with glSection(GL_LINES):
|
|
||||||
glVertex2f(cx - size, cy)
|
|
||||||
glVertex2f(cx + size, cy)
|
|
||||||
glVertex2f(cx, cy - size)
|
|
||||||
glVertex2f(cx, cy + size)
|
|
||||||
|
|
||||||
|
|
||||||
def circle(r, seg, coords):
|
|
||||||
cx, cy = coords
|
|
||||||
with glSection(GL_LINE_LOOP):
|
|
||||||
for i in range(seg):
|
|
||||||
theta = TWOPI * i / seg
|
|
||||||
glVertex2f(cx + cos(theta) * r, cy + sin(theta) * r)
|
|
||||||
|
|
||||||
|
|
||||||
def disk(rinner, router, segs, tex):
|
|
||||||
with glRestore(GL_ENABLE_BIT):
|
|
||||||
glEnable(GL_TEXTURE_2D)
|
|
||||||
glEnable(GL_BLEND)
|
|
||||||
glDisable(GL_LIGHTING)
|
|
||||||
glDisable(GL_CULL_FACE)
|
|
||||||
glBindTexture(GL_TEXTURE_2D, tex)
|
|
||||||
res = segs * 5
|
res = segs * 5
|
||||||
|
delta = 2 * pi / res
|
||||||
with glSection(GL_TRIANGLE_STRIP):
|
self.vertex_count = (res + 1) * 2
|
||||||
factor = TWOPI / res
|
# Need padding to make the last vertex render correctly... why?
|
||||||
theta = 0
|
buffer = self.vertex_count * 3 * [0]
|
||||||
for n in range(res + 1):
|
for i in range(res):
|
||||||
theta += factor
|
theta = delta * i
|
||||||
x = cos(theta)
|
x, y = cos(theta), sin(theta)
|
||||||
y = sin(theta)
|
buffer[6*i:6*i+6] = [rinner * x, rinner * y, 0, router * x, router * y, 1]
|
||||||
glTexCoord2f(0, 0)
|
buffer[6*res:6*res+6] = buffer[:6]
|
||||||
glVertex2f(rinner * x, rinner * y)
|
self.vbo = list_to_gl_buffer(buffer)
|
||||||
glTexCoord2f(1, 0)
|
|
||||||
glVertex2f(router * x, router * y)
|
|
||||||
|
|
||||||
|
|
||||||
def flare(rinner, router, res, prob, tex):
|
class SimpleSphere(object):
|
||||||
with glRestore(GL_ENABLE_BIT):
|
type = GL_FLOAT
|
||||||
glEnable(GL_TEXTURE_2D)
|
stride = 5 * 4
|
||||||
glDisable(GL_CULL_FACE)
|
direction_offset = 0
|
||||||
glDisable(GL_LIGHTING)
|
direction_size = 3
|
||||||
glBindTexture(GL_TEXTURE_2D, tex)
|
uv_offset = direction_size * 4
|
||||||
last_x = 1
|
uv_size = 2
|
||||||
last_y = 0
|
|
||||||
last_theta = 0
|
def __init__(self, lats, longs):
|
||||||
factor = TWOPI / res
|
tau = pi * 2
|
||||||
rdelta = (router - rinner)
|
phi_div = tau / longs
|
||||||
with glSection(GL_QUADS):
|
theta_div = pi / lats
|
||||||
for i in range(res + 1):
|
|
||||||
theta = last_theta + factor
|
self.vertex_count = (lats + 1) * (longs + 1) * 2
|
||||||
x = cos(theta)
|
buffer = self.vertex_count * 5 * [0]
|
||||||
y = sin(theta)
|
index = 0
|
||||||
if random() > prob:
|
reverse = False
|
||||||
distance = rinner + rdelta * random()
|
for i in range(longs + 1):
|
||||||
avg_theta = (last_theta + theta) / 2
|
phi1, phi2 = i * phi_div, (i + 1) * phi_div
|
||||||
x0, y0 = rinner * last_x, rinner * last_y
|
if reverse:
|
||||||
x1, y1 = rinner * x, rinner * y
|
phi1, phi2 = phi2, phi1
|
||||||
x2, y2 = distance * cos(avg_theta), distance * sin(avg_theta)
|
for j in range(lats + 1):
|
||||||
glTexCoord2f(0, 0)
|
theta = j * theta_div
|
||||||
glVertex2f(x0, y0)
|
if reverse:
|
||||||
glTexCoord2f(0, 1)
|
theta = pi - theta
|
||||||
glVertex2f(x1, y1)
|
sine = sin(theta)
|
||||||
glTexCoord2f(1, 0)
|
dz = cos(theta)
|
||||||
glVertex2f(x2, y2)
|
t = 1 - theta / pi
|
||||||
glTexCoord2f(1, 1)
|
buffer[index:index + 10] = [sine * cos(phi2), sine * sin(phi2), dz, phi2 / tau, t,
|
||||||
glVertex2f(x2, y2)
|
sine * cos(phi1), sine * sin(phi1), dz, phi1 / tau, t]
|
||||||
last_theta = theta
|
index += 10
|
||||||
last_x = x
|
reverse ^= True
|
||||||
last_y = y
|
|
||||||
|
self.vbo = list_to_gl_buffer(buffer)
|
||||||
|
|
||||||
|
|
||||||
def sphere(r, lats, longs, tex, lighting=True, inside=False, fv4=GLfloat * 4):
|
class TangentSphere(object):
|
||||||
with glRestore(GL_ENABLE_BIT | GL_TEXTURE_BIT):
|
type = GL_FLOAT
|
||||||
sphere = gluNewQuadric()
|
stride = 7 * 4
|
||||||
gluQuadricDrawStyle(sphere, GLU_FILL)
|
direction_offset = 0
|
||||||
gluQuadricTexture(sphere, True)
|
direction_size = 3
|
||||||
if lighting:
|
tangent_offset = direction_size * 4
|
||||||
gluQuadricNormals(sphere, GLU_SMOOTH)
|
tangent_size = 2
|
||||||
|
uv_offset = tangent_offset + tangent_size * 4
|
||||||
|
uv_size = 2
|
||||||
|
|
||||||
glEnable(GL_CULL_FACE)
|
def __init__(self, lats, longs):
|
||||||
glCullFace(GL_FRONT if inside else GL_BACK)
|
tau = pi * 2
|
||||||
glEnable(GL_TEXTURE_2D)
|
phi_div = tau / longs
|
||||||
if lighting:
|
theta_div = pi / lats
|
||||||
glDisable(GL_BLEND)
|
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fv4(1, 1, 1, 0))
|
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(1, 1, 1, 0))
|
|
||||||
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 125)
|
|
||||||
else:
|
|
||||||
glDisable(GL_LIGHTING)
|
|
||||||
glBindTexture(GL_TEXTURE_2D, tex)
|
|
||||||
|
|
||||||
gluSphere(sphere, r, lats, longs)
|
self.vertex_count = (lats + 1) * (longs + 1) * 2
|
||||||
|
buffer = self.vertex_count * 8 * [0]
|
||||||
gluDeleteQuadric(sphere)
|
index = 0
|
||||||
|
reverse = False
|
||||||
|
for i in range(longs + 1):
|
||||||
|
phi1, phi2 = i * phi_div, (i + 1) * phi_div
|
||||||
|
if reverse:
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
def colourball(r, lats, longs, colour, fv4=GLfloat * 4):
|
class Cube(object):
|
||||||
"""
|
type = GL_SHORT
|
||||||
Sphere function from the OpenGL red book.
|
stride = 3 * 2
|
||||||
"""
|
direction_offset = 0
|
||||||
with glRestore(GL_ENABLE_BIT):
|
direction_size = 3
|
||||||
sphere = gluNewQuadric()
|
vertex_count = 36
|
||||||
|
|
||||||
glDisable(GL_BLEND)
|
def __init__(self):
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fv4(*colour))
|
self.vbo = list_to_gl_buffer([
|
||||||
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,
|
||||||
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,
|
||||||
glEnable(GL_CULL_FACE)
|
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,
|
||||||
glCullFace(GL_BACK)
|
-1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1
|
||||||
|
], 'h')
|
||||||
gluSphere(sphere, r, lats, longs)
|
|
||||||
|
|
||||||
glEnable(GL_BLEND)
|
|
||||||
gluDeleteQuadric(sphere)
|
|
||||||
|
|
||||||
|
|
||||||
def normal_sphere(r, divide, tex, normal, lighting=True, inside=False, fv4=GLfloat * 4):
|
class OrbitVBO(object):
|
||||||
if (not have_extension('GL_ARB_multitexture') or not
|
type = GL_FLOAT
|
||||||
have_extension('GL_ARB_texture_env_combine') or not
|
stride = 3 * 4
|
||||||
have_extension('GL_EXT_texture_env_dot3')):
|
position_offset = 0
|
||||||
print('No hardware normal mapping support. No bumping for you.')
|
position_size = 3
|
||||||
return sphere(r, divide, divide, tex, lighting, inside)
|
vertex_count = 360
|
||||||
|
|
||||||
from .texture import load_texture
|
def __init__(self, orbit):
|
||||||
normal = load_texture(normal)
|
buffer = 360 * 3 * [0]
|
||||||
|
for theta in range(360):
|
||||||
|
x, z, y = orbit.orbit(theta)
|
||||||
|
buffer[3*theta:3*theta+3] = [x, y, z]
|
||||||
|
|
||||||
with glRestore(GL_ENABLE_BIT | GL_TEXTURE_BIT):
|
self.vbo = list_to_gl_buffer(buffer)
|
||||||
glEnable(GL_CULL_FACE)
|
|
||||||
glCullFace(GL_FRONT if inside else GL_BACK)
|
|
||||||
|
|
||||||
glActiveTextureARB(GL_TEXTURE0_ARB)
|
def close(self):
|
||||||
glBindTexture(GL_TEXTURE_2D, normal)
|
if self.vbo is not None:
|
||||||
glEnable(GL_TEXTURE_2D)
|
vbo = c_uint(self.vbo)
|
||||||
|
glDeleteBuffers(1, byref(vbo))
|
||||||
|
self.vbo = None
|
||||||
|
|
||||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB)
|
def __del__(self):
|
||||||
glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGBA_ARB)
|
self.close()
|
||||||
|
|
||||||
glActiveTextureARB(GL_TEXTURE1_ARB)
|
|
||||||
glBindTexture(GL_TEXTURE_2D, tex)
|
|
||||||
glEnable(GL_TEXTURE_2D)
|
|
||||||
|
|
||||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB)
|
|
||||||
glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
twopi_divide = TWOPI / divide
|
|
||||||
pi_divide = pi / divide
|
|
||||||
with glSection(GL_TRIANGLE_STRIP):
|
|
||||||
for j in range(divide + 1):
|
|
||||||
phi1 = j * twopi_divide
|
|
||||||
phi2 = (j + 1) * twopi_divide
|
|
||||||
|
|
||||||
for i in range(divide + 1):
|
|
||||||
theta = i * pi_divide
|
|
||||||
|
|
||||||
s = phi2 / TWOPI
|
|
||||||
t = theta / pi
|
|
||||||
dx, dy, dz = sin(theta) * cos(phi2), sin(theta) * sin(phi2), cos(theta)
|
|
||||||
glNormal3f(dx, dy, dz)
|
|
||||||
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, s, 1 - t)
|
|
||||||
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, s, 1 - t)
|
|
||||||
glVertex3f(r * dx, r * dy, r * dz)
|
|
||||||
|
|
||||||
s = phi1 / TWOPI # x
|
|
||||||
dx, dy = sin(theta) * cos(phi1), sin(theta) * sin(phi1)
|
|
||||||
glNormal3f(dx, dy, dz)
|
|
||||||
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, s, 1 - t)
|
|
||||||
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, s, 1 - t)
|
|
||||||
glVertex3f(r * dx, r * dy, r * dz)
|
|
||||||
|
|
||||||
|
|
||||||
def belt(radius, cross, object, count):
|
class FontEngine(object):
|
||||||
for i in range(count):
|
type = GL_SHORT
|
||||||
theta = TWOPI * random()
|
stride = 4 * 2
|
||||||
r = gauss(radius, cross)
|
position_offset = 0
|
||||||
x, y, z = cos(theta) * r, gauss(0, cross), sin(theta) * r
|
position_size = 2
|
||||||
|
tex_offset = position_size * 2
|
||||||
|
tex_size = 2
|
||||||
|
|
||||||
with glMatrix():
|
def __init__(self, shader, max_length=256):
|
||||||
glTranslatef(x, y, z)
|
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()
|
||||||
|
r = gauss(radius, cross)
|
||||||
|
x, y, z = cos(theta) * r, gauss(0, cross), sin(theta) * r
|
||||||
scale = gauss(1, 0.5)
|
scale = gauss(1, 0.5)
|
||||||
if scale < 0:
|
if scale < 0:
|
||||||
scale = 1
|
scale = 1
|
||||||
glScalef(scale, scale, scale)
|
choice(arrays).extend((x, y, z, scale))
|
||||||
glCallList(choice(object))
|
|
||||||
|
self.vbo = []
|
||||||
|
self.sizes = []
|
||||||
|
for a in arrays:
|
||||||
|
self.vbo.append(array_to_gl_buffer(a))
|
||||||
|
self.sizes.append(len(a) // 4)
|
||||||
|
|
||||||
|
|
||||||
try:
|
class VAO(object):
|
||||||
from _glgeom import torus
|
def __init__(self):
|
||||||
except ImportError:
|
buffer = GLuint()
|
||||||
def torus(major_radius, minor_radius, n_major, n_minor, material, shininess=125, fv4=GLfloat * 4):
|
glGenVertexArrays(1, byref(buffer))
|
||||||
"""
|
self.vao = buffer
|
||||||
Torus function from the OpenGL red book.
|
|
||||||
"""
|
|
||||||
with glRestore(GL_CURRENT_BIT):
|
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fv4(*material))
|
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(1, 1, 1, 1))
|
|
||||||
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
|
|
||||||
|
|
||||||
major_s = TWOPI / n_major
|
def __enter__(self):
|
||||||
minor_s = TWOPI / n_minor
|
glBindVertexArray(self.vao)
|
||||||
|
|
||||||
def n(x, y, z):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
m = 1.0 / sqrt(x * x + y * y + z * z)
|
glBindVertexArray(0)
|
||||||
return x * m, y * m, z * m
|
|
||||||
|
|
||||||
for i in range(n_major):
|
|
||||||
a0 = i * major_s
|
|
||||||
a1 = a0 + major_s
|
|
||||||
x0 = cos(a0)
|
|
||||||
y0 = sin(a0)
|
|
||||||
x1 = cos(a1)
|
|
||||||
y1 = sin(a1)
|
|
||||||
|
|
||||||
with glSection(GL_TRIANGLE_STRIP):
|
|
||||||
for j in range(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)
|
|
||||||
|
|
||||||
|
|
||||||
def progress_bar(x, y, width, height, filled):
|
|
||||||
with glRestore(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)
|
|
||||||
|
|
9
punyverse/glwrapper.h
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
# include <OpenGL/gl.h>
|
||||||
|
#else
|
||||||
|
# include <GL/gl.h>
|
||||||
|
#endif
|
28
punyverse/launcher.c
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#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;
|
||||||
|
}
|
31
punyverse/launcher.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
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()
|
190
punyverse/loader.py
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
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()
|
87
punyverse/main.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
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()
|
|
@ -1,13 +1,15 @@
|
||||||
from uuid import uuid4
|
|
||||||
import os
|
|
||||||
import gzip
|
|
||||||
import bz2
|
import bz2
|
||||||
|
import gzip
|
||||||
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
import six
|
import six
|
||||||
from six.moves import range
|
|
||||||
from pyglet.gl import *
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,22 +24,19 @@ openers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FACE_TRIANGLES = 0
|
|
||||||
FACE_QUADS = 1
|
|
||||||
|
|
||||||
|
|
||||||
class Face(object):
|
class Face(object):
|
||||||
def __init__(self, type, verts, norms, texs, vertices, normals, textures):
|
__slots__ = ('verts', 'norms', 'texs', 'size')
|
||||||
self.type = type
|
|
||||||
|
def __init__(self, verts, norms, texs):
|
||||||
self.verts = verts
|
self.verts = verts
|
||||||
self.norms = norms
|
self.norms = norms
|
||||||
self.texs = texs
|
self.texs = texs
|
||||||
self.vertices = vertices
|
self.size = len(verts)
|
||||||
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
|
||||||
|
@ -48,28 +47,11 @@ class Material(object):
|
||||||
|
|
||||||
|
|
||||||
class Group(object):
|
class Group(object):
|
||||||
def __init__(self, name=None):
|
__slots__ = ('material', 'faces')
|
||||||
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):
|
def __init__(self, material=None, faces=None):
|
||||||
min_x, min_y, min_z = 0, 0, 0
|
self.material = material
|
||||||
for face in self.faces:
|
self.faces = faces or []
|
||||||
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):
|
||||||
|
@ -113,31 +95,18 @@ class WavefrontObject(object):
|
||||||
|
|
||||||
def texture(self, words):
|
def texture(self, words):
|
||||||
l = len(words)
|
l = len(words)
|
||||||
x, y, z = 0, 0, 0
|
u, v = 0, 0
|
||||||
if l >= 2:
|
if l >= 2:
|
||||||
x = float(words[1])
|
u = float(words[1])
|
||||||
if l >= 3:
|
if l >= 3:
|
||||||
# OBJ origin is at upper left, OpenGL origin is at lower left
|
# OBJ origin is at upper left, OpenGL origin is at lower left
|
||||||
y = 1 - float(words[2])
|
v = 1 - float(words[2])
|
||||||
if l >= 4:
|
self.textures.append((u, v))
|
||||||
z = float(words[3])
|
|
||||||
self.textures.append((x, y, z))
|
|
||||||
|
|
||||||
def face(self, words):
|
def face(self, words):
|
||||||
l = len(words)
|
l = len(words)
|
||||||
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 = []
|
||||||
|
@ -146,21 +115,17 @@ class WavefrontObject(object):
|
||||||
raw_faces = words[i].split(b'/')
|
raw_faces = words[i].split(b'/')
|
||||||
l = len(raw_faces)
|
l = len(raw_faces)
|
||||||
|
|
||||||
current_value = int(raw_faces[0])
|
vindices.append(int(raw_faces[0]) - 1)
|
||||||
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]:
|
||||||
current_value = int(raw_faces[1])
|
tindices.append(int(raw_faces[1]) - 1)
|
||||||
if current_value <= texture_len:
|
else:
|
||||||
tindices.append(current_value - 1)
|
tindices.append(None)
|
||||||
face_textures.append(self.textures[current_value - 1])
|
|
||||||
if l >= 3 and raw_faces[2]:
|
if l >= 3 and raw_faces[2]:
|
||||||
current_value = int(raw_faces[2])
|
nindices.append(int(raw_faces[2]) - 1)
|
||||||
nindices.append(current_value - 1)
|
else:
|
||||||
face_normals.append(self.normals[current_value - 1])
|
nindices.append(None)
|
||||||
|
|
||||||
if self.current_group is None:
|
if self.current_group is None:
|
||||||
self.current_group = group = Group()
|
self.current_group = group = Group()
|
||||||
|
@ -168,14 +133,7 @@ class WavefrontObject(object):
|
||||||
else:
|
else:
|
||||||
group = self.current_group
|
group = self.current_group
|
||||||
|
|
||||||
group.vertices += face_vertices
|
group.faces.append(Face(vindices, nindices, tindices))
|
||||||
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].decode('utf-8')))
|
||||||
|
@ -190,11 +148,7 @@ class WavefrontObject(object):
|
||||||
print("Warning: no group")
|
print("Warning: no group")
|
||||||
|
|
||||||
def group(self, words):
|
def group(self, words):
|
||||||
name = words[1].decode('utf-8')
|
group = Group()
|
||||||
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
|
||||||
|
|
||||||
|
@ -230,11 +184,8 @@ class WavefrontObject(object):
|
||||||
dispatcher.get(type, default)(words)
|
dispatcher.get(type, default)(words)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
import sys
|
|
||||||
if hasattr(sys, 'frozen'):
|
model_base = os.path.join(os.path.dirname(__file__), 'assets', 'models')
|
||||||
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):
|
||||||
|
@ -245,83 +196,151 @@ def load_model(path):
|
||||||
return WavefrontObject(path)
|
return WavefrontObject(path)
|
||||||
|
|
||||||
|
|
||||||
def model_list(model, sx=1, sy=1, sz=1, rotation=(0, 0, 0)):
|
class ModelVBO(object):
|
||||||
for m, text in six.iteritems(model.materials):
|
__slots__ = ('has_normal', 'has_texture', 'data_buf', 'index_buf', 'offset_type', 'vertex_count', 'vao')
|
||||||
if text.texture:
|
|
||||||
load_texture(os.path.join(model.root, text.texture))
|
|
||||||
|
|
||||||
display = glGenLists(1)
|
def __init__(self):
|
||||||
|
self.vao = VAO()
|
||||||
|
|
||||||
glNewList(display, GL_COMPILE)
|
def build_vao(self, shader):
|
||||||
glPushMatrix()
|
stride = (3 + self.has_normal * 3 + self.has_texture * 2) * 4
|
||||||
glPushAttrib(GL_CURRENT_BIT)
|
|
||||||
|
|
||||||
pitch, yaw, roll = rotation
|
with self.vao:
|
||||||
glPushAttrib(GL_TRANSFORM_BIT)
|
glBindBuffer(GL_ARRAY_BUFFER, self.data_buf)
|
||||||
glRotatef(pitch, 1, 0, 0)
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.index_buf)
|
||||||
glRotatef(yaw, 0, 1, 0)
|
|
||||||
glRotatef(roll, 0, 0, 1)
|
|
||||||
glPopAttrib()
|
|
||||||
|
|
||||||
vertices = model.vertices
|
shader.vertex_attribute('a_position', 3, GL_FLOAT, GL_FALSE, stride, 0)
|
||||||
textures = model.textures
|
if self.has_normal:
|
||||||
normals = model.normals
|
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)
|
||||||
|
|
||||||
for g in model.groups:
|
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||||
material = g.material
|
|
||||||
|
|
||||||
tex_id = load_texture(os.path.join(model.root, material.texture)) if (material and material.texture) else 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 tex_id:
|
if not self.has_texture:
|
||||||
glEnable(GL_TEXTURE_2D)
|
shader.vertex_attribute_vec2('a_uv', 0, 0)
|
||||||
glBindTexture(GL_TEXTURE_2D, tex_id)
|
if instances:
|
||||||
else:
|
glDrawElementsInstanced(GL_TRIANGLES, self.vertex_count, self.offset_type, 0, instances)
|
||||||
glBindTexture(GL_TEXTURE_2D, 0)
|
else:
|
||||||
glDisable(GL_TEXTURE_2D)
|
glDrawElements(GL_TRIANGLES, self.vertex_count, self.offset_type, 0)
|
||||||
|
|
||||||
if material:
|
|
||||||
fv4 = GLfloat * 4
|
|
||||||
|
|
||||||
if material.Ka:
|
class WavefrontVBO(object):
|
||||||
kx, ky, kz = material.Ka
|
def __init__(self, model, shader, sx=1, sy=1, sz=1):
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, fv4(kx, ky, kz, 1))
|
self._tex_cache = {}
|
||||||
if material.Kd:
|
self.vbos = []
|
||||||
kx, ky, kz = material.Kd
|
self.scale = (sx, sy, sz)
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, fv4(kx, ky, kz, 1))
|
|
||||||
if material.Ks:
|
|
||||||
kx, ky, kz = material.Ks
|
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(kx, ky, kz, 1))
|
|
||||||
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material.shininess)
|
|
||||||
|
|
||||||
type = -1
|
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
|
||||||
|
textures = model.textures
|
||||||
|
normals = model.normals
|
||||||
|
|
||||||
|
for group in self.merge_groups(model):
|
||||||
|
processed = self.process_group(group, vertices, normals, textures)
|
||||||
|
self.vbos.append((group.material, processed))
|
||||||
|
processed.build_vao(shader)
|
||||||
|
|
||||||
|
def additional_attributes(self, callback):
|
||||||
|
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
|
||||||
|
|
||||||
def point(f, vertices, normals, textures, n):
|
|
||||||
if f.norms:
|
|
||||||
glNormal3f(*normals[f.norms[n]])
|
|
||||||
if tex_id:
|
if tex_id:
|
||||||
glTexCoord2f(*textures[f.texs[n]][:2])
|
glBindTexture(GL_TEXTURE_2D, tex_id)
|
||||||
|
shader.uniform_bool('u_material.hasDiffuse', True)
|
||||||
|
shader.uniform_texture('u_material.diffuseMap', 0)
|
||||||
|
else:
|
||||||
|
shader.uniform_bool('u_material.hasDiffuse', False)
|
||||||
|
|
||||||
x, y, z = vertices[f.verts[n]]
|
if mat and mat.Ka:
|
||||||
glVertex3f(x * sx, y * sy, z * sz)
|
shader.uniform_vec3('u_material.ambient', *mat.Ka)
|
||||||
|
else:
|
||||||
|
shader.uniform_vec3('u_material.ambient', 0.2, 0.2, 0.2)
|
||||||
|
|
||||||
glBegin(GL_TRIANGLES)
|
if mat and mat.Kd:
|
||||||
for f in g.faces:
|
shader.uniform_vec3('u_material.diffuse', *mat.Kd)
|
||||||
point(f, vertices, normals, textures, 0)
|
else:
|
||||||
point(f, vertices, normals, textures, 1)
|
shader.uniform_vec3('u_material.diffuse', 0.8, 0.8, 0.8)
|
||||||
point(f, vertices, normals, textures, 2)
|
|
||||||
|
|
||||||
if f.type == FACE_QUADS:
|
if mat and mat.Ks:
|
||||||
point(f, vertices, normals, textures, 2)
|
shader.uniform_vec3('u_material.specular', *mat.Ks)
|
||||||
point(f, vertices, normals, textures, 3)
|
else:
|
||||||
point(f, vertices, normals, textures, 0)
|
shader.uniform_vec3('u_material.specular', 0, 0, 0)
|
||||||
glEnd()
|
|
||||||
|
|
||||||
if tex_id:
|
if mat:
|
||||||
glBindTexture(GL_TEXTURE_2D, 0)
|
shader.uniform_float('u_material.shininess', mat.shininess)
|
||||||
glDisable(GL_TEXTURE_2D)
|
else:
|
||||||
|
shader.uniform_float('u_material.shininess', 0)
|
||||||
|
|
||||||
glPopAttrib()
|
vbo.draw(shader, instances=instances)
|
||||||
glPopMatrix()
|
|
||||||
|
|
||||||
glEndList()
|
def merge_groups(self, model):
|
||||||
return display
|
by_mat = defaultdict(list)
|
||||||
|
for g in model.groups:
|
||||||
|
if g.faces:
|
||||||
|
by_mat[g.material].append(g)
|
||||||
|
|
||||||
|
groups = []
|
||||||
|
for mat, gs in six.iteritems(by_mat):
|
||||||
|
faces = []
|
||||||
|
for g in gs:
|
||||||
|
faces += g.faces
|
||||||
|
groups.append(Group(mat, faces))
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def process_group(self, group, vertices, normals, textures):
|
||||||
|
sx, sy, sz = self.scale
|
||||||
|
max_texture = len(textures)
|
||||||
|
has_texture = bool(textures) and any(any(n is not None for n in f.texs) for f in group.faces)
|
||||||
|
has_normal = bool(normals) and any(any(n is not None for n in f.norms) for f in group.faces)
|
||||||
|
buffer = []
|
||||||
|
indices = []
|
||||||
|
offsets = {}
|
||||||
|
|
||||||
|
for f in group.faces:
|
||||||
|
verts = []
|
||||||
|
for v, n, t in zip(f.verts, f.norms, f.texs):
|
||||||
|
# Blender defines texture coordinates on faces even without textures.
|
||||||
|
if t is not None and t >= max_texture:
|
||||||
|
t = None
|
||||||
|
if (v, n, t) in offsets:
|
||||||
|
verts.append(offsets[v, n, t])
|
||||||
|
else:
|
||||||
|
index = len(offsets)
|
||||||
|
verts.append(index)
|
||||||
|
x, y, z = vertices[v]
|
||||||
|
item = [sx * x, sy * y, sz * z]
|
||||||
|
if has_normal:
|
||||||
|
item += [0, 0, 0] if n is None else list(normals[n])
|
||||||
|
if has_texture:
|
||||||
|
item += [0, 0] if t is None else list(textures[t])
|
||||||
|
offsets[v, n, t] = index
|
||||||
|
buffer += item
|
||||||
|
|
||||||
|
for a, b in zip(verts[1:], verts[2:]):
|
||||||
|
indices += [verts[0], a, b]
|
||||||
|
|
||||||
|
result = ModelVBO()
|
||||||
|
result.has_normal = has_normal
|
||||||
|
result.has_texture = has_texture
|
||||||
|
result.offset_type = GL_UNSIGNED_SHORT if len(offsets) < 65536 else GL_UNSIGNED_INT
|
||||||
|
result.data_buf = 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
|
||||||
|
|
|
@ -66,10 +66,11 @@ 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 = mean_anomaly
|
e1 = 0
|
||||||
e2 = mean_anomaly + self.eccentricity * sin(e1)
|
e2 = mean_anomaly
|
||||||
while abs(e1 - e2) > 0.000001:
|
while abs(e1 - e2) > 0.000001:
|
||||||
e1, e2 = e2, mean_anomaly + self.eccentricity * sin(e2)
|
e1, e2 = e2, 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):
|
||||||
|
|
127
punyverse/shader.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
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
|
12
punyverse/shaders/atmosphere.fragment.glsl
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#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);
|
||||||
|
}
|
13
punyverse/shaders/atmosphere.vertex.glsl
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#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;
|
||||||
|
}
|
28
punyverse/shaders/belt.vertex.glsl
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#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;
|
||||||
|
}
|
19
punyverse/shaders/clouds.fragment.glsl
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#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);
|
||||||
|
}
|
20
punyverse/shaders/clouds.vertex.glsl
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#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);
|
||||||
|
}
|
8
punyverse/shaders/line.fragment.glsl
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
out vec4 o_fragColor;
|
||||||
|
uniform vec4 u_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
o_fragColor = u_color;
|
||||||
|
}
|
8
punyverse/shaders/line.vertex.glsl
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
in vec3 a_position;
|
||||||
|
uniform mat4 u_mvpMatrix;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_mvpMatrix * vec4(a_position, 1);
|
||||||
|
}
|
43
punyverse/shaders/model.fragment.glsl
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#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);
|
||||||
|
}
|
22
punyverse/shaders/model.vertex.glsl
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#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;
|
||||||
|
}
|
55
punyverse/shaders/planet.fragment.glsl
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#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);
|
||||||
|
}
|
30
punyverse/shaders/planet.vertex.glsl
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#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);
|
||||||
|
}
|
22
punyverse/shaders/ring.fragment.glsl
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#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;
|
||||||
|
}
|
16
punyverse/shaders/ring.vertex.glsl
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#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;
|
||||||
|
}
|
13
punyverse/shaders/sky.fragment.glsl
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#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);
|
||||||
|
}
|
10
punyverse/shaders/sky.vertex.glsl
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#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;
|
||||||
|
}
|
9
punyverse/shaders/star.fragment.glsl
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#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);
|
||||||
|
}
|
14
punyverse/shaders/star.vertex.glsl
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#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;
|
||||||
|
}
|
12
punyverse/shaders/text.fragment.glsl
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#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);
|
||||||
|
}
|
14
punyverse/shaders/text.vertex.glsl
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#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);
|
||||||
|
}
|
|
@ -1,83 +1,57 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function, division
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from pyglet.gl import GLint, glGetIntegerv, GL_MAX_TEXTURE_SIZE
|
from PIL import Image
|
||||||
from ctypes import byref
|
|
||||||
|
|
||||||
buf = GLint()
|
from punyverse.texture import max_texture_size
|
||||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, byref(buf))
|
|
||||||
max_texture = buf.value
|
max_texture = max_texture_size()
|
||||||
|
|
||||||
del byref, GLint, glGetIntegerv, GL_MAX_TEXTURE_SIZE, buf
|
|
||||||
|
|
||||||
def resize(width, height, target):
|
def resize(width, height, target):
|
||||||
factor = (target + .0) / max(width, height)
|
factor = target / max(width, height)
|
||||||
return int(width * factor), int(height * factor)
|
return int(width * factor), int(height * factor)
|
||||||
|
|
||||||
|
|
||||||
def fits(width, height):
|
def fits(width, height):
|
||||||
return width < max_texture and height < max_texture
|
return width <= max_texture and height <= max_texture
|
||||||
|
|
||||||
|
|
||||||
def make_name(image, suffix):
|
def make_name(image, suffix):
|
||||||
name, ext = os.path.splitext(image)
|
name, ext = os.path.splitext(image)
|
||||||
return '%s_%s%s' % (name, suffix, ext)
|
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:
|
|
||||||
from PIL 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):
|
def shrink(file):
|
||||||
image = get_image(file)
|
image = Image.open(file)
|
||||||
width, height = get_size(image)
|
width, height = image.size
|
||||||
|
|
||||||
if fits(width, height):
|
if fits(width, height):
|
||||||
print('no need')
|
print('no need')
|
||||||
return
|
return
|
||||||
width, height = resize(width, height, 2048)
|
|
||||||
if fits(width, height):
|
for attempt, new_size in [(4096, 'large'), (2048, 'medium')]:
|
||||||
size = 'medium'
|
width, height = resize(width, height, attempt)
|
||||||
|
if fits(width, height):
|
||||||
|
size = new_size
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
width, height = resize(width, height, 1024) # 1024 is minimum
|
width, height = resize(width, height, 1024) # 1024 is minimum
|
||||||
size = 'small'
|
size = 'small'
|
||||||
|
|
||||||
print('size %s, %dx%d...' % (size, width, height), end=' ')
|
print('size %s, %dx%d...' % (size, width, height), end=' ')
|
||||||
name = make_name(file, size)
|
name = make_name(file, size)
|
||||||
if not os.path.exists(name):
|
if not os.path.exists(name):
|
||||||
image = 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))
|
||||||
|
image.resize((width, height), Image.ANTIALIAS).save(name)
|
||||||
print('saved to:', os.path.basename(name))
|
print('saved to:', os.path.basename(name))
|
||||||
save(image, name)
|
|
||||||
else:
|
else:
|
||||||
print('alrady there')
|
print('already there')
|
||||||
|
|
||||||
|
|
||||||
textures = [
|
textures = [
|
||||||
'mercury.jpg',
|
'mercury.jpg',
|
||||||
|
@ -99,6 +73,7 @@ textures = [
|
||||||
'moons/mimas.jpg',
|
'moons/mimas.jpg',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
punyverse = os.path.dirname(__file__)
|
punyverse = os.path.dirname(__file__)
|
||||||
try:
|
try:
|
||||||
|
@ -115,5 +90,6 @@ def main():
|
||||||
else:
|
else:
|
||||||
print('exists not')
|
print('exists not')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,31 +1,21 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from pyglet import image
|
|
||||||
from pyglet.gl import *
|
|
||||||
from ctypes import c_int, byref, c_uint
|
|
||||||
import os.path
|
import os.path
|
||||||
import struct
|
import struct
|
||||||
import itertools
|
from ctypes import c_int, byref
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import six
|
import six
|
||||||
from six.moves import zip
|
from pyglet import image
|
||||||
|
from pyglet.gl import *
|
||||||
|
from six.moves import range
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from punyverse._glgeom import bgr_to_rgb, flip_vertical
|
from ._glgeom import bgr_to_rgb, flip_vertical
|
||||||
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
|
|
||||||
|
|
||||||
from six.moves import range
|
|
||||||
|
|
||||||
def bgr_to_rgb(source, width, height, alpha=False):
|
def bgr_to_rgb(source, width, height, alpha=False):
|
||||||
length = len(source)
|
length = len(source)
|
||||||
|
@ -37,88 +27,65 @@ except ImportError:
|
||||||
for x in range(width):
|
for x in range(width):
|
||||||
offset = y * row + x * depth
|
offset = y * row + x * depth
|
||||||
for i in range(depth2):
|
for i in range(depth2):
|
||||||
result[offset+i] = source[offset+depth2-i-1]
|
result[offset + i] = source[offset + depth2 - i - 1]
|
||||||
if alpha:
|
if alpha:
|
||||||
result[offset+depth2] = source[offset+depth2]
|
result[offset + depth2] = source[offset + depth2]
|
||||||
return six.binary_type(result)
|
return six.binary_type(result)
|
||||||
|
|
||||||
|
|
||||||
def flip_vertical(source, width, height):
|
def flip_vertical(source, width, height):
|
||||||
length = len(source)
|
length = len(source)
|
||||||
row = length // height
|
row = length // height
|
||||||
result = bytearray(length)
|
result = bytearray(length)
|
||||||
for y1 in range(height):
|
for y1 in range(height):
|
||||||
y2 = height - y1 - 1
|
y2 = height - y1 - 1
|
||||||
result[y1*row:y1*row+row] = source[y2*row:y2*row+row]
|
result[y1 * row:y1 * row + row] = source[y2 * row:y2 * row + row]
|
||||||
return six.binary_type(result)
|
return six.binary_type(result)
|
||||||
else:
|
|
||||||
magick = False
|
|
||||||
|
|
||||||
__all__ = ['load_texture', 'load_clouds', 'load_image', 'pil_load']
|
__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):
|
||||||
def init():
|
return num != 0 and ((num & (num - 1)) == 0)
|
||||||
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')
|
|
||||||
|
|
||||||
#bgra = gl_info.have_extension('GL_EXT_bgra')
|
|
||||||
|
|
||||||
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 = str(data)
|
data = six.binary_type(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 ('GIF87a', 'GIF89a'):
|
if size >= 10 and data[:6] in (b'GIF87a', b'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('\211PNG\r\n\032\n')
|
elif size >= 24 and data.startswith(b'\211PNG\r\n\032\n') and data[12:16] == b'IHDR':
|
||||||
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('\211PNG\r\n\032\n'):
|
elif size >= 16 and data.startswith(b'\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('\377\330'):
|
elif size >= 2 and data.startswith(b'\377\330'):
|
||||||
content_type = 'image/jpeg'
|
content_type = 'image/jpeg'
|
||||||
jpeg = BytesIO(data)
|
jpeg = BytesIO(data)
|
||||||
jpeg.read(2)
|
jpeg.read(2)
|
||||||
|
@ -131,13 +98,11 @@ 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)
|
||||||
h, w = struct.unpack(">HH", jpeg.read(4))
|
height, width = 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:
|
||||||
|
@ -146,15 +111,26 @@ 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):
|
||||||
init()
|
max_texture = max_texture_size()
|
||||||
|
|
||||||
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):
|
||||||
|
@ -163,115 +139,181 @@ def load_image(file, path):
|
||||||
try:
|
try:
|
||||||
file = open(path, 'rb')
|
file = open(path, 'rb')
|
||||||
except IOError:
|
except IOError:
|
||||||
print('exists not')
|
print('does not exist')
|
||||||
raise ValueError('Texture exists not')
|
raise ValueError('Texture does not exist')
|
||||||
|
|
||||||
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:
|
try:
|
||||||
file.close()
|
raw = image.load(path, file=file)
|
||||||
file = Image(path.encode('mbcs' if os.name == 'nt' else 'utf8'))
|
except Exception:
|
||||||
geo = file.size()
|
print('cannot be loaded')
|
||||||
check_size(geo.width(), geo.height())
|
raise ValueError('cannot be loaded')
|
||||||
print()
|
|
||||||
blob = Blob()
|
|
||||||
file.flip()
|
|
||||||
file.write(blob, 'RGBA')
|
|
||||||
texture = blob.data
|
|
||||||
mode = GL_RGBA
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
raw = image.load(path, file=file)
|
|
||||||
except IOError:
|
|
||||||
print('exists not')
|
|
||||||
raise ValueError('Texture exists not')
|
|
||||||
|
|
||||||
width, height = raw.width, raw.height
|
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
|
|
||||||
if raw.format in ('BGR', 'BGRA'):
|
# Flip from BGR to RGB
|
||||||
if bgra:
|
if raw.format in ('BGR', 'BGRA'):
|
||||||
mode = {GL_RGBA: GL_BGRA, GL_RGB: GL_BGR}[mode]
|
if gl_info.have_extension('GL_EXT_bgra'):
|
||||||
texture = raw.data
|
mode = GL_BGRA if 'A' in raw.format else GL_BGR
|
||||||
else:
|
|
||||||
texture = bgr_to_rgb(raw.data, width, height, 'A' in raw.format)
|
|
||||||
elif raw.format in ('RGB', 'RGBA'):
|
|
||||||
texture = raw.data
|
texture = raw.data
|
||||||
else:
|
else:
|
||||||
texture = raw.get_data('RGBA', width * 4)
|
texture = bgr_to_rgb(raw.data, width, height, 'A' in raw.format)
|
||||||
|
elif raw.format in ('RGB', 'RGBA'):
|
||||||
|
texture = raw.data
|
||||||
|
else:
|
||||||
|
texture = raw.get_data('RGBA', width * 4)
|
||||||
|
|
||||||
return path, width, height, len(raw.format), mode, flip_vertical(texture, width, height)
|
return path, width, height, len(raw.format), mode, flip_vertical(texture, width, height)
|
||||||
|
|
||||||
|
|
||||||
def load_texture(file):
|
def get_file_path(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
|
||||||
|
|
||||||
if path in cache:
|
|
||||||
return cache[path]
|
|
||||||
|
|
||||||
path, width, height, depth, mode, texture = load_image(file, path)
|
def create_texture():
|
||||||
|
buffer = GLuint()
|
||||||
buffer = c_uint()
|
|
||||||
glGenTextures(1, byref(buffer))
|
glGenTextures(1, byref(buffer))
|
||||||
id = buffer.value
|
id = buffer.value
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, id)
|
|
||||||
|
|
||||||
if badcard:
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, mode, width, height, 0, mode, GL_UNSIGNED_BYTE, texture)
|
|
||||||
else:
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
|
|
||||||
gluBuild2DMipmaps(GL_TEXTURE_2D, depth, width, height, mode, GL_UNSIGNED_BYTE, texture)
|
|
||||||
|
|
||||||
cache[path] = id
|
|
||||||
|
|
||||||
return id
|
return id
|
||||||
|
|
||||||
|
|
||||||
def pil_load(file):
|
def delete_texture(id):
|
||||||
from PIL import Image
|
buffer = GLuint(id)
|
||||||
return Image.open(os.path.join(os.path.dirname(__file__), 'assets', 'textures', file))
|
glDeleteTextures(1, byref(buffer))
|
||||||
|
|
||||||
|
|
||||||
def load_clouds(file):
|
def get_internal_mode(mode):
|
||||||
if os.path.isabs(file):
|
return {
|
||||||
path = file
|
GL_RGB: GL_RGB8,
|
||||||
file = os.path.basename(path)
|
GL_BGR: GL_RGB8,
|
||||||
else:
|
GL_RGBA: GL_RGBA8,
|
||||||
path = os.path.join(os.path.dirname(__file__), 'assets', 'textures', file)
|
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)
|
||||||
|
|
||||||
buffer = c_uint()
|
id = create_texture()
|
||||||
glGenTextures(1, byref(buffer))
|
|
||||||
id = buffer.value
|
|
||||||
|
|
||||||
pixels = bytearray(len(texture) * 4)
|
|
||||||
white = b'\xff'[0]
|
|
||||||
pixels[:] = itertools.chain.from_iterable(zip(itertools.repeat(white), itertools.repeat(white),
|
|
||||||
itertools.repeat(white),
|
|
||||||
itertools.islice(texture, 0, None, depth)))
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, id)
|
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_MAG_FILTER, GL_LINEAR)
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, six.binary_type(pixels))
|
|
||||||
|
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
|
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
|
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))
|
||||||
|
id = buffer.value
|
||||||
|
|
||||||
|
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)
|
||||||
|
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))
|
||||||
|
|
||||||
|
return id
|
||||||
|
|
||||||
|
|
||||||
|
def get_cube_map(files, callback=None):
|
||||||
|
assert len(files) == 6
|
||||||
|
callback = callback or (lambda index, file: None)
|
||||||
|
|
||||||
|
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):
|
||||||
|
if isinstance(info, list):
|
||||||
|
for item in info:
|
||||||
|
try:
|
||||||
|
return loader(item, **kwargs)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return loader(info, **kwargs)
|
||||||
|
if not optional:
|
||||||
|
raise ValueError('No texture found')
|
||||||
|
|
279
punyverse/ui.py
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
#!/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)
|
18
punyverse/utils.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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
|
|
@ -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. a list means a colour",
|
"texture": "a group of texture to use, tried in that order",
|
||||||
"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,23 +15,22 @@
|
||||||
"length": 63.7,
|
"length": 63.7,
|
||||||
"bodies": {
|
"bodies": {
|
||||||
"sun": {
|
"sun": {
|
||||||
"texture": ["sun.jpg", [0.99, 0.97, 0.66, 1]],
|
"texture": ["sun.jpg"],
|
||||||
"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": {
|
||||||
"corona_texture": "sun_corona.png",
|
"glow_color": [0.92, 0.92, 0.82],
|
||||||
"corona_size": 1500,
|
"glow_texture": "glow.png",
|
||||||
"corona_division": 100,
|
"glow_size": 300
|
||||||
"corona_prob": 0.5,
|
|
||||||
"diffuse_texture": "sun_diffuse.png",
|
|
||||||
"diffuse_size": 300
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mercury": {
|
"mercury": {
|
||||||
"texture": ["mercury.jpg", "mercury_small.jpg", [0.44, 0.43, 0.43, 1]],
|
"texture": ["mercury.jpg", "mercury_small.jpg"],
|
||||||
"radius": 2439.7,
|
"radius": 2439.7,
|
||||||
"z": "0.466697 * AU",
|
"z": "0.466697 * AU",
|
||||||
"pitch": -90,
|
"pitch": -90,
|
||||||
|
@ -40,7 +39,7 @@
|
||||||
"rotation": 5067014
|
"rotation": 5067014
|
||||||
},
|
},
|
||||||
"venus": {
|
"venus": {
|
||||||
"texture": ["venus.jpg", [0.655, 0.38, 0.1, 1]],
|
"texture": ["venus.jpg"],
|
||||||
"radius": 6051.8,
|
"radius": 6051.8,
|
||||||
"z": "0.723327 * AU",
|
"z": "0.723327 * AU",
|
||||||
"pitch": -90,
|
"pitch": -90,
|
||||||
|
@ -49,7 +48,7 @@
|
||||||
"rotation": -20996798
|
"rotation": -20996798
|
||||||
},
|
},
|
||||||
"earth": {
|
"earth": {
|
||||||
"texture": ["earth.jpg", "earth_medium.jpg", "earth_small.jpg", [0.11, 0.32, 0.43, 1]],
|
"texture": ["earth.jpg", "earth_large.jpg", "earth_medium.jpg", "earth_small.jpg"],
|
||||||
"radius": 6378.1,
|
"radius": 6378.1,
|
||||||
"z": "AU",
|
"z": "AU",
|
||||||
"pitch": -90,
|
"pitch": -90,
|
||||||
|
@ -58,16 +57,19 @@
|
||||||
"mass": 5.97219e+24,
|
"mass": 5.97219e+24,
|
||||||
"rotation": 86400,
|
"rotation": 86400,
|
||||||
"division": 90,
|
"division": 90,
|
||||||
"normal": "earth_normal.jpg",
|
"normal_map": ["earth_normal.jpg", "earth_normal_small.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"],
|
||||||
"diffuse_texture": "atmosphere_earth.png",
|
"glow_color": [0.11, 0.32, 0.43],
|
||||||
"diffuse_size": 30
|
"glow_texture": "glow.png",
|
||||||
|
"glow_size": 30
|
||||||
},
|
},
|
||||||
"orbit_distance": "AU",
|
"orbit_distance": "AU",
|
||||||
"satellites": {
|
"satellites": {
|
||||||
"moon": {
|
"moon": {
|
||||||
"texture": ["moon.jpg", "moon_medium.jpg", "moon_small.jpg", [0.53, 0.53, 0.53, 1]],
|
"texture": ["moon.jpg", "moon_medium.jpg", "moon_small.jpg"],
|
||||||
"radius": 1738.14,
|
"radius": 1738.14,
|
||||||
"distance": 38439,
|
"distance": 38439,
|
||||||
"sma": 384399,
|
"sma": 384399,
|
||||||
|
@ -94,7 +96,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mars": {
|
"mars": {
|
||||||
"texture": ["mars.jpg", "mars_small.jpg", "mars_medium.jpg", [0.85, 0.47, 0.2, 1]],
|
"texture": ["mars.jpg", "mars_large.jpg", "mars_medium.jpg", "mars_small.jpg"],
|
||||||
"radius": 3396.2,
|
"radius": 3396.2,
|
||||||
"z": "1.524 * AU",
|
"z": "1.524 * AU",
|
||||||
"pitch": -90,
|
"pitch": -90,
|
||||||
|
@ -113,7 +115,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jupiter": {
|
"jupiter": {
|
||||||
"texture": ["jupiter.jpg", "jupiter_medium.jpg", "jupiter_small.jpg", [0.65, 0.36, 0.19, 1]],
|
"texture": ["jupiter.jpg", "jupiter_medium.jpg", "jupiter_small.jpg"],
|
||||||
"radius": 71492,
|
"radius": 71492,
|
||||||
"mass": 1.8986e+27,
|
"mass": 1.8986e+27,
|
||||||
"z": "5.2 * AU",
|
"z": "5.2 * AU",
|
||||||
|
@ -124,7 +126,7 @@
|
||||||
"orbit_distance": "3 * AU",
|
"orbit_distance": "3 * AU",
|
||||||
"satellites": {
|
"satellites": {
|
||||||
"io": {
|
"io": {
|
||||||
"texture": ["moons/io.jpg", "moons/io_small.jpg", [0.62, 0.56, 0.35, 1]],
|
"texture": ["moons/io.jpg", "moons/io_small.jpg"],
|
||||||
"radius": "1821.3 * 5",
|
"radius": "1821.3 * 5",
|
||||||
"distance": 126510,
|
"distance": 126510,
|
||||||
"sma": 421700,
|
"sma": 421700,
|
||||||
|
@ -134,7 +136,7 @@
|
||||||
"eccentricity": 0.0041
|
"eccentricity": 0.0041
|
||||||
},
|
},
|
||||||
"europa": {
|
"europa": {
|
||||||
"texture": ["moons/europa.jpg", "moons/europa_small.jpg", [0.77, 0.74, 0.65, 1]],
|
"texture": ["moons/europa.jpg", "moons/europa_small.jpg"],
|
||||||
"radius": "1560.8 * 5",
|
"radius": "1560.8 * 5",
|
||||||
"distance": 201270,
|
"distance": 201270,
|
||||||
"sma": 670900,
|
"sma": 670900,
|
||||||
|
@ -144,7 +146,7 @@
|
||||||
"eccentricity": 0.009
|
"eccentricity": 0.009
|
||||||
},
|
},
|
||||||
"ganymede": {
|
"ganymede": {
|
||||||
"texture": ["moons/ganymede.jpg", "moons/ganymede_small.jpg", [0.52, 0.47, 0.46, 1]],
|
"texture": ["moons/ganymede.jpg", "moons/ganymede_small.jpg"],
|
||||||
"radius": "2634.1 * 5",
|
"radius": "2634.1 * 5",
|
||||||
"distance": 321120,
|
"distance": 321120,
|
||||||
"sma": 1070400,
|
"sma": 1070400,
|
||||||
|
@ -154,7 +156,7 @@
|
||||||
"eccentricity": 0.0013
|
"eccentricity": 0.0013
|
||||||
},
|
},
|
||||||
"callisto": {
|
"callisto": {
|
||||||
"texture": ["moons/callisto.jpg", "moons/callisto_small.jpg", [0.49, 0.43, 0.34, 1]],
|
"texture": ["moons/callisto.jpg", "moons/callisto_small.jpg"],
|
||||||
"radius": "2410.3 * 5",
|
"radius": "2410.3 * 5",
|
||||||
"distance": 564810,
|
"distance": 564810,
|
||||||
"sma": 1882700,
|
"sma": 1882700,
|
||||||
|
@ -166,7 +168,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"saturn": {
|
"saturn": {
|
||||||
"texture": ["saturn.jpg", "saturn_medium.jpg", "saturn_small.jpg", [0.9, 0.8, 0.64, 1]],
|
"texture": ["saturn.jpg", "saturn_medium.jpg", "saturn_small.jpg"],
|
||||||
"radius": 60268,
|
"radius": 60268,
|
||||||
"mass": 5.6846e+26,
|
"mass": 5.6846e+26,
|
||||||
"z": "9.58 * AU",
|
"z": "9.58 * AU",
|
||||||
|
@ -181,7 +183,7 @@
|
||||||
"orbit_distance": "4 * AU",
|
"orbit_distance": "4 * AU",
|
||||||
"satellites": {
|
"satellites": {
|
||||||
"titan": {
|
"titan": {
|
||||||
"texture": ["moons/titan.jpg", "moons/titan_small.jpg", [0.52, 0.39, 0.23, 1]],
|
"texture": ["moons/titan.jpg", "moons/titan_small.jpg"],
|
||||||
"radius": "2576 * 10",
|
"radius": "2576 * 10",
|
||||||
"distance": "1221870 / 3 + 200000",
|
"distance": "1221870 / 3 + 200000",
|
||||||
"sma": 1221870,
|
"sma": 1221870,
|
||||||
|
@ -191,7 +193,7 @@
|
||||||
"eccentricity": 0.0288
|
"eccentricity": 0.0288
|
||||||
},
|
},
|
||||||
"rhea": {
|
"rhea": {
|
||||||
"texture": ["moons/rhea.jpg", "moons/rhea_small.jpg", [0.62, 0.60, 0.59, 1]],
|
"texture": ["moons/rhea.jpg", "moons/rhea_small.jpg"],
|
||||||
"radius": "763.8 * 10",
|
"radius": "763.8 * 10",
|
||||||
"distance": "527108 / 3 + 200000",
|
"distance": "527108 / 3 + 200000",
|
||||||
"sma": 527108,
|
"sma": 527108,
|
||||||
|
@ -201,7 +203,7 @@
|
||||||
"eccentricity": 0.0012583
|
"eccentricity": 0.0012583
|
||||||
},
|
},
|
||||||
"iapetus": {
|
"iapetus": {
|
||||||
"texture": ["moons/iapetus.jpg", "moons/iapetus_small.jpg", [0.62, 0.60, 0.59, 1]],
|
"texture": ["moons/iapetus.jpg", "moons/iapetus_small.jpg"],
|
||||||
"radius": "734.5 * 10",
|
"radius": "734.5 * 10",
|
||||||
"distance": "3560820 / 3 + 200000",
|
"distance": "3560820 / 3 + 200000",
|
||||||
"sma": 3560820,
|
"sma": 3560820,
|
||||||
|
@ -211,7 +213,7 @@
|
||||||
"eccentricity": 0.0286125
|
"eccentricity": 0.0286125
|
||||||
},
|
},
|
||||||
"dione": {
|
"dione": {
|
||||||
"texture": ["moons/dione.jpg", "moons/dione_small.jpg", [0.46, 0.46, 0.46, 1]],
|
"texture": ["moons/dione.jpg", "moons/dione_small.jpg"],
|
||||||
"radius": "561.4 * 10",
|
"radius": "561.4 * 10",
|
||||||
"distance": "377396 / 3 + 200000",
|
"distance": "377396 / 3 + 200000",
|
||||||
"sma": 377396,
|
"sma": 377396,
|
||||||
|
@ -221,7 +223,7 @@
|
||||||
"eccentricity": 0.0022
|
"eccentricity": 0.0022
|
||||||
},
|
},
|
||||||
"tethys": {
|
"tethys": {
|
||||||
"texture": ["moons/tethys.jpg", "moons/tethys_small.jpg", [0.68, 0.68, 0.66, 1]],
|
"texture": ["moons/tethys.jpg", "moons/tethys_small.jpg"],
|
||||||
"radius": "531.1 * 10",
|
"radius": "531.1 * 10",
|
||||||
"distance": "294619 / 3 + 200000",
|
"distance": "294619 / 3 + 200000",
|
||||||
"sma": 294619,
|
"sma": 294619,
|
||||||
|
@ -231,7 +233,7 @@
|
||||||
"eccentricity": 0.0001
|
"eccentricity": 0.0001
|
||||||
},
|
},
|
||||||
"enceladus": {
|
"enceladus": {
|
||||||
"texture": ["moons/enceladus.jpg", "moons/enceladus_small.jpg", [0.74, 0.74, 0.74, 1]],
|
"texture": ["moons/enceladus.jpg", "moons/enceladus_small.jpg"],
|
||||||
"radius": "252.1 * 10",
|
"radius": "252.1 * 10",
|
||||||
"distance": "237948 / 3 + 200000",
|
"distance": "237948 / 3 + 200000",
|
||||||
"sma": 237948,
|
"sma": 237948,
|
||||||
|
@ -241,7 +243,7 @@
|
||||||
"eccentricity": 0.0047
|
"eccentricity": 0.0047
|
||||||
},
|
},
|
||||||
"mimas": {
|
"mimas": {
|
||||||
"texture": ["moons/mimas.jpg", "moons/mimas_small.jpg", [0.47, 0.47, 0.47, 1]],
|
"texture": ["moons/mimas.jpg", "moons/mimas_small.jpg"],
|
||||||
"radius": "198.2 * 10",
|
"radius": "198.2 * 10",
|
||||||
"distance": "181902 / 3 + 200000",
|
"distance": "181902 / 3 + 200000",
|
||||||
"sma": 181902,
|
"sma": 181902,
|
||||||
|
@ -261,7 +263,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uranus": {
|
"uranus": {
|
||||||
"texture": ["uranus.jpg", [0, 0.53, 0.84, 1]],
|
"texture": ["uranus.jpg"],
|
||||||
"radius": 25559,
|
"radius": 25559,
|
||||||
"mass": 8.6810e+25,
|
"mass": 8.6810e+25,
|
||||||
"z": "19.23 * AU",
|
"z": "19.23 * AU",
|
||||||
|
@ -278,7 +280,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"neptune": {
|
"neptune": {
|
||||||
"texture": ["neptune.jpg", [0.31, 0.49, 0.59, 1]],
|
"texture": ["neptune.jpg"],
|
||||||
"radius": 24764,
|
"radius": 24764,
|
||||||
"mass": 1.0243e+26,
|
"mass": 1.0243e+26,
|
||||||
"z": "30.5 * AU",
|
"z": "30.5 * AU",
|
||||||
|
@ -286,19 +288,6 @@
|
||||||
"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": {
|
||||||
|
@ -307,10 +296,25 @@
|
||||||
"radius": "2.362 * AU",
|
"radius": "2.362 * AU",
|
||||||
"cross": 1000,
|
"cross": 1000,
|
||||||
"scale": 30,
|
"scale": 30,
|
||||||
"count": 1024,
|
"count": 4096,
|
||||||
"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
|
||||||
|
|
|
@ -1,64 +1,15 @@
|
||||||
from __future__ import print_function
|
from __future__ import division
|
||||||
|
|
||||||
from collections import OrderedDict
|
import json
|
||||||
import os
|
import os
|
||||||
|
from collections import OrderedDict
|
||||||
try:
|
|
||||||
import json
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
raise SystemExit('No JSON module found')
|
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
try:
|
|
||||||
from punyverse._model import model_list, load_model
|
|
||||||
except ImportError:
|
|
||||||
from punyverse.model import model_list, load_model
|
|
||||||
|
|
||||||
from punyverse.glgeom import *
|
|
||||||
from punyverse.entity import *
|
|
||||||
from punyverse.texture import *
|
|
||||||
from punyverse import texture
|
from punyverse import texture
|
||||||
|
from punyverse.camera import Camera
|
||||||
from math import pi, sqrt
|
from punyverse.entity import *
|
||||||
|
from punyverse.shader import Program
|
||||||
G = 6.67384e-11 # Gravitation Constant
|
|
||||||
|
|
||||||
|
|
||||||
def get_best_texture(info, optional=False, loader=load_texture):
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def load_world(file, callback=lambda message, completion: None):
|
def load_world(file, callback=lambda message, completion: None):
|
||||||
|
@ -66,35 +17,85 @@ def load_world(file, callback=lambda message, completion: None):
|
||||||
|
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
def __init__(self, file, callback, options=None):
|
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.tracker = []
|
||||||
self.start = (0, 0, 0)
|
|
||||||
self.direction = (0, 0, 0)
|
|
||||||
self.x = None
|
self.x = None
|
||||||
self.y = None
|
self.y = None
|
||||||
self.z = None
|
self.z = None
|
||||||
self.tick_length = 1
|
self.tick_length = 0
|
||||||
self.tick = 0
|
self.tick = 0
|
||||||
|
self.asteroids = AsteroidManager(self)
|
||||||
|
self.cam = Camera()
|
||||||
|
|
||||||
|
self._sky = sky
|
||||||
|
|
||||||
|
self._program = None
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.options = options or {}
|
self.programs = self._load_programs()
|
||||||
self._phase = 'Parsing configuration...'
|
|
||||||
self._parse(file)
|
self._parse(file)
|
||||||
del self.callback # So it can't be used after loading finishes
|
|
||||||
|
|
||||||
def _eval(self, value):
|
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)
|
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):
|
def _parse(self, file):
|
||||||
self.callback(self._phase, 'Loading configuration file...', 0)
|
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._au = root.get('au', 2000)
|
||||||
self._length = root.get('length', 4320)
|
self._length = root.get('length', 4320)
|
||||||
self._context = {'AU': self._au, 'TEXTURE': texture.max_texture, 'KM': 1.0 / self._length}
|
self._context = {'AU': self._au, 'TEXTURE': texture.max_texture_size(), 'KM': 1.0 / self._length}
|
||||||
|
|
||||||
tick = root.get('tick', 4320) # How many second is a tick?
|
self.tick_length = root.get('tick', 4320) # How many second is a tick?
|
||||||
self.tick_length = tick
|
|
||||||
|
|
||||||
# Need to know how many objects are being loaded
|
# Need to know how many objects are being loaded
|
||||||
self._objects = 0
|
self._objects = 0
|
||||||
|
@ -105,173 +106,113 @@ class World(object):
|
||||||
self._objects += 1
|
self._objects += 1
|
||||||
count_objects(body.get('satellites', {}))
|
count_objects(body.get('satellites', {}))
|
||||||
count_objects(root['bodies'])
|
count_objects(root['bodies'])
|
||||||
print(self._objects, 'objects to be loaded...')
|
|
||||||
|
|
||||||
if 'start' in root:
|
if 'start' in root:
|
||||||
info = root['start']
|
info = root['start']
|
||||||
x = self._eval(info.get('x', 0))
|
self.cam.x = self.evaluate(info.get('x', 0))
|
||||||
y = self._eval(info.get('y', 0))
|
self.cam.y = self.evaluate(info.get('y', 0))
|
||||||
z = self._eval(info.get('z', 0))
|
self.cam.z = self.evaluate(info.get('z', 0))
|
||||||
pitch = self._eval(info.get('pitch', 0))
|
self.cam.pitch = self.evaluate(info.get('pitch', 0))
|
||||||
yaw = self._eval(info.get('yaw', 0))
|
self.cam.yaw = self.evaluate(info.get('yaw', 0))
|
||||||
roll = self._eval(info.get('roll', 0))
|
self.cam.roll = self.evaluate(info.get('roll', 0))
|
||||||
self.start = (x, y, z)
|
|
||||||
self.direction = (pitch, yaw, roll)
|
|
||||||
|
|
||||||
for planet, info in six.iteritems(root['bodies']):
|
for planet, info in six.iteritems(root['bodies']):
|
||||||
message = 'Loading %s.' % planet
|
|
||||||
print(message)
|
|
||||||
self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects),
|
self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects),
|
||||||
message, float(self._current_object) / self._objects)
|
'Loading %s.' % planet, self._current_object / self._objects)
|
||||||
self._body(planet, info)
|
self._body(planet, info)
|
||||||
self._current_object += 1
|
self._current_object += 1
|
||||||
|
|
||||||
if 'belts' in root:
|
if 'belts' in root:
|
||||||
self._phase = 'Loading belts...'
|
belt_count = len(root['belts'])
|
||||||
self._current_object = 0
|
for i, (name, info) in enumerate(six.iteritems(root['belts']), 1):
|
||||||
for name, info in six.iteritems(root['belts']):
|
self.callback('Loading belts (%d of %d)...' % (i, belt_count),
|
||||||
message = 'Loading %s.' % name
|
'Loading %s.' % name, i / belt_count)
|
||||||
print(message)
|
self.tracker.append(Belt(name, self, info))
|
||||||
self.callback(self._phase, message, float(self._current_object) / len(root['belts']))
|
|
||||||
self._belt(name, info)
|
|
||||||
|
|
||||||
def _belt(self, name, info):
|
if 'sky' in root and self._sky:
|
||||||
x = self._eval(info.get('x', 0))
|
def callback(index, file):
|
||||||
y = self._eval(info.get('y', 0))
|
self.callback('Loading sky...', 'Loading %s.' % file, index / 6)
|
||||||
z = self._eval(info.get('z', 0))
|
self.tracker.append(Sky(self, root['sky'], callback))
|
||||||
radius = self._eval(info.get('radius', 0))
|
|
||||||
cross = self._eval(info.get('cross', 0))
|
|
||||||
count = int(self._eval(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 'asteroids' in root:
|
||||||
if not isinstance(models, list):
|
asteroids = root['asteroids']
|
||||||
models = [models]
|
for i, file in enumerate(asteroids):
|
||||||
objects = []
|
self.callback('Loading asteroids...', 'Loading %s...' % file, i / len(asteroids))
|
||||||
for model in models:
|
self.asteroids.load(file)
|
||||||
objects.append(model_list(load_model(model), info.get('sx', scale), info.get('sy', scale),
|
|
||||||
info.get('sz', scale), (0, 0, 0)))
|
|
||||||
|
|
||||||
self.tracker.append(Belt(compile(belt, radius, cross, objects, count),
|
self.font_tex = load_alpha_mask(root['font'], clamp=True)
|
||||||
(x, y, z), (inclination, longitude, argument),
|
|
||||||
rotation_angle=theta, world=self))
|
|
||||||
|
|
||||||
def _body(self, name, info, parent=None):
|
def _body(self, name, info, parent=None):
|
||||||
lighting = info.get('lighting', True)
|
|
||||||
x = self._eval(info.get('x', 0))
|
|
||||||
y = self._eval(info.get('y', 0))
|
|
||||||
z = self._eval(info.get('z', 0))
|
|
||||||
pitch = self._eval(info.get('pitch', 0))
|
|
||||||
yaw = self._eval(info.get('yaw', 0))
|
|
||||||
roll = self._eval(info.get('roll', 0))
|
|
||||||
rotation = self._eval(info.get('rotation', 86400))
|
|
||||||
radius = self._eval(info.get('radius', self._length)) / self._length
|
|
||||||
background = info.get('background', False)
|
|
||||||
orbit_distance = self._eval(info.get('orbit_distance', self._au))
|
|
||||||
division = info.get('division', max(min(int(radius / 8), 60), 10))
|
|
||||||
|
|
||||||
if 'texture' in info:
|
if 'texture' in info:
|
||||||
cheap, skip, texture = get_best_texture(info['texture'], optional=info.get('optional', False))
|
body = SphericalBody(name, self, info, parent)
|
||||||
if skip:
|
|
||||||
return
|
|
||||||
if cheap:
|
|
||||||
object_id = compile(colourball, radius, division, division, texture)
|
|
||||||
else:
|
|
||||||
if self.options.get('normal', False) and 'normal' in info:
|
|
||||||
object_id = compile(normal_sphere, radius, division, texture,
|
|
||||||
info['normal'], lighting=lighting, inside=background)
|
|
||||||
else:
|
|
||||||
object_id = compile(sphere, radius, division, division, texture,
|
|
||||||
lighting=lighting, inside=background)
|
|
||||||
elif 'model' in info:
|
elif 'model' in info:
|
||||||
scale = info.get('scale', 1)
|
body = ModelBody(name, self, info, parent)
|
||||||
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:
|
||||||
print('Nothing to load for %s.' % name)
|
raise ValueError('Nothing to load for %s.' % name)
|
||||||
return
|
|
||||||
|
|
||||||
params = {'world': self, 'orbit_distance': orbit_distance, 'radius': None if background else radius}
|
if parent:
|
||||||
if parent is None:
|
parent.satellites.append(body)
|
||||||
type = Body
|
|
||||||
else:
|
else:
|
||||||
x, y, z = parent.location
|
self.tracker.append(body)
|
||||||
distance = self._eval(info.get('distance', 100)) # Semi-major axis when actually displayed in virtual space
|
|
||||||
sma = self._eval(info.get('sma', distance)) # Semi-major axis used to calculate orbital speed
|
|
||||||
if hasattr(parent, 'mass') and parent.mass is not None:
|
|
||||||
period = 2 * pi * sqrt((sma * 1000) ** 3 / (G * parent.mass))
|
|
||||||
speed = 360 / (period + .0)
|
|
||||||
if not rotation: # Rotation = 0 assumes tidal lock
|
|
||||||
rotation = period
|
|
||||||
else:
|
|
||||||
speed = info.get('orbit_speed', 1)
|
|
||||||
type = Satellite
|
|
||||||
params.update(parent=parent, orbit_speed=speed,
|
|
||||||
distance=distance / self._length, eccentricity=info.get('eccentricity', 0),
|
|
||||||
inclination=info.get('inclination', 0), longitude=info.get('longitude', 0),
|
|
||||||
argument=info.get('argument', 0))
|
|
||||||
|
|
||||||
if 'mass' in info:
|
|
||||||
params['mass'] = info['mass']
|
|
||||||
|
|
||||||
atmosphere_id = 0
|
|
||||||
cloudmap_id = 0
|
|
||||||
corona_id = 0
|
|
||||||
if 'atmosphere' in info:
|
|
||||||
atmosphere_data = info['atmosphere']
|
|
||||||
atm_size = self._eval(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)
|
|
||||||
|
|
||||||
if atm_texture is not None:
|
|
||||||
cheap, _, atm_texture = get_best_texture(atm_texture)
|
|
||||||
if not cheap:
|
|
||||||
atmosphere_id = compile(disk, radius, radius + atm_size, 30, atm_texture)
|
|
||||||
|
|
||||||
theta = 360.0 / rotation if rotation else 0
|
|
||||||
object = type(object_id, (x, y, z), (pitch, yaw, roll), rotation_angle=theta,
|
|
||||||
atmosphere=atmosphere_id, cloudmap=cloudmap_id, background=background,
|
|
||||||
corona=corona_id, **params)
|
|
||||||
self.tracker.append(object)
|
|
||||||
|
|
||||||
if 'ring' in info:
|
|
||||||
ring_data = info['ring']
|
|
||||||
texture = ring_data.get('texture', None)
|
|
||||||
distance = self._eval(ring_data.get('distance', radius * 1.2))
|
|
||||||
size = self._eval(ring_data.get('size', radius / 2))
|
|
||||||
pitch = self._eval(ring_data.get('pitch', pitch))
|
|
||||||
yaw = self._eval(ring_data.get('yaw', yaw))
|
|
||||||
roll = self._eval(ring_data.get('roll', roll))
|
|
||||||
|
|
||||||
cheap, _, texture = get_best_texture(texture)
|
|
||||||
if not cheap:
|
|
||||||
self.tracker.append(
|
|
||||||
type(compile(disk, distance, distance + size, 30, texture), (x, y, z),
|
|
||||||
(pitch, yaw, roll), **params))
|
|
||||||
|
|
||||||
for satellite, info in six.iteritems(info.get('satellites', {})):
|
for satellite, info in six.iteritems(info.get('satellites', {})):
|
||||||
message = 'Loading %s, satellite of %s.' % (satellite, name)
|
|
||||||
print(message)
|
|
||||||
self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects),
|
self.callback('Loading objects (%d of %d)...' % (self._current_object, self._objects),
|
||||||
message, float(self._current_object) / self._objects)
|
'Loading %s, satellite of %s.' % (satellite, name), self._current_object / self._objects)
|
||||||
self._body(satellite, info, object)
|
self._body(satellite, info, body)
|
||||||
self._current_object += 1
|
self._current_object += 1
|
||||||
|
|
||||||
|
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:
|
||||||
|
self._time_accumulate += delta
|
||||||
|
|
||||||
|
def view_matrix(self):
|
||||||
|
return self.cam.view_matrix
|
||||||
|
|
||||||
|
def projection_matrix(self):
|
||||||
|
return self._projection_matrix
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def vp_matrix(self):
|
||||||
|
return self._projection_matrix * self.cam.view_matrix
|
||||||
|
|
||||||
|
def resize(self, width, height):
|
||||||
|
self.cam.aspect = width / max(height, 1)
|
||||||
|
self._projection_matrix = self.cam.projection_matrix()
|
||||||
|
self.vp_matrix = None
|
||||||
|
|
||||||
|
def activate_shader(self, name):
|
||||||
|
program = None
|
||||||
|
if self._program != name:
|
||||||
|
if name is None:
|
||||||
|
glUseProgram(0)
|
||||||
|
else:
|
||||||
|
program = self.programs[name]
|
||||||
|
glUseProgram(program.program)
|
||||||
|
self._program = name
|
||||||
|
elif self._program is not None:
|
||||||
|
program = self.programs[self._program]
|
||||||
|
return program
|
||||||
|
|
109
setup.py
|
@ -1,8 +1,11 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from setuptools import setup, Extension
|
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'))
|
has_pyx = os.path.exists(os.path.join(os.path.dirname(__file__), 'punyverse', '_glgeom.pyx'))
|
||||||
|
|
||||||
|
@ -23,19 +26,98 @@ else:
|
||||||
|
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
gl_libs = ['opengl32']
|
gl_libs = ['opengl32']
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
gl_libs = []
|
||||||
else:
|
else:
|
||||||
gl_libs = ['GL']
|
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:
|
with open(os.path.join(os.path.dirname(__file__), 'README.md')) as f:
|
||||||
long_description = f.read()
|
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(
|
setup(
|
||||||
name='punyverse',
|
name='punyverse',
|
||||||
version='0.4',
|
version='1.2',
|
||||||
packages=['punyverse'],
|
packages=['punyverse'],
|
||||||
package_data={
|
package_data={
|
||||||
'punyverse': [
|
'punyverse': [
|
||||||
'world.json',
|
'world.json',
|
||||||
|
'shaders/*.glsl',
|
||||||
'assets/textures.txt',
|
'assets/textures.txt',
|
||||||
'assets/textures/*.jpg',
|
'assets/textures/*.jpg',
|
||||||
'assets/textures/*.png',
|
'assets/textures/*.png',
|
||||||
|
@ -49,21 +131,23 @@ setup(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ext_modules=cythonize([
|
ext_modules=cythonize([
|
||||||
Extension('punyverse._glgeom', sources=[pyx_path('punyverse/_glgeom.pyx')], libraries=gl_libs),
|
Extension('punyverse._glgeom', sources=[pyx_path('punyverse/_glgeom.pyx')], libraries=gl_libs,
|
||||||
Extension('punyverse._model', sources=[pyx_path('punyverse/_model.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={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'punyverse = punyverse.__main__:main',
|
'punyverse = punyverse.main:main',
|
||||||
|
'punyverse_make_launcher = punyverse.launcher:main',
|
||||||
'punyverse_small_images = punyverse.small_images:main',
|
'punyverse_small_images = punyverse.small_images:main',
|
||||||
],
|
],
|
||||||
'gui_scripts': [
|
'gui_scripts': [
|
||||||
'punyversew = punyverse.__main__:main'
|
'punyversew = punyverse.main:main'
|
||||||
]
|
]
|
||||||
|
|
||||||
},
|
},
|
||||||
install_requires=['pyglet', 'Pillow', 'six'],
|
install_requires=['pyglet<1.4', 'Pillow', 'six'],
|
||||||
|
|
||||||
author='quantum',
|
author='quantum',
|
||||||
author_email='quantum2048@gmail.com',
|
author_email='quantum2048@gmail.com',
|
||||||
|
@ -75,10 +159,19 @@ setup(
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
'Development Status :: 4 - Beta',
|
||||||
'Environment :: Win32 (MS Windows)',
|
'Environment :: Win32 (MS Windows)',
|
||||||
|
'Environment :: X11 Applications',
|
||||||
'Intended Audience :: End Users/Desktop',
|
'Intended Audience :: End Users/Desktop',
|
||||||
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
|
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
|
||||||
'Operating System :: Microsoft :: Windows',
|
'Operating System :: Microsoft :: Windows',
|
||||||
|
'Operating System :: POSIX :: Linux',
|
||||||
'Programming Language :: Python',
|
'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 :: Games/Entertainment :: Simulation',
|
||||||
'Topic :: Multimedia :: Graphics :: 3D Rendering',
|
'Topic :: Multimedia :: Graphics :: 3D Rendering',
|
||||||
'Topic :: Scientific/Engineering :: Visualization',
|
'Topic :: Scientific/Engineering :: Visualization',
|
||||||
|
|