From 2348e1d2751815ccce94efc6419d066340876f62 Mon Sep 17 00:00:00 2001 From: Quantum Date: Sun, 2 Feb 2014 18:22:45 -0500 Subject: [PATCH] Added hardware normal mapping, usable with -n/--normal option. --- punyverse/__main__.py | 8 ++- punyverse/game.py | 4 +- punyverse/glgeom.py | 112 ++++++++++++++++++++---------------------- punyverse/world.json | 1 + punyverse/world.py | 5 +- 5 files changed, 67 insertions(+), 63 deletions(-) diff --git a/punyverse/__main__.py b/punyverse/__main__.py index 26ac613..7e1861c 100644 --- a/punyverse/__main__.py +++ b/punyverse/__main__.py @@ -16,6 +16,8 @@ def main(): 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 @@ -38,10 +40,14 @@ def main(): 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) + config=config, world_options=world_options) pyglet.app.run() diff --git a/punyverse/game.py b/punyverse/game.py index 0e06f34..637ceb3 100644 --- a/punyverse/game.py +++ b/punyverse/game.py @@ -41,6 +41,8 @@ def entity_distance(x0, y0, z0): 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() @@ -79,7 +81,7 @@ class Applet(pyglet.window.Window): start = clock() self.fps = 0 - self.world = World('world.json', callback) + self.world = World('world.json', callback, self.world_options) phase = 'Initializing game...' print phase callback(phase, '', 0) diff --git a/punyverse/glgeom.py b/punyverse/glgeom.py index 97f644b..6a09ad2 100644 --- a/punyverse/glgeom.py +++ b/punyverse/glgeom.py @@ -1,6 +1,7 @@ from math import * from pyglet.gl import * from random import random, gauss, choice +from pyglet.gl.gl_info import have_extension TWOPI = pi * 2 @@ -181,71 +182,64 @@ def colourball(r, lats, longs, colour, fv4=GLfloat * 4): gluDeleteQuadric(sphere) -try: - from _glgeom import normal_sphere -except ImportError: - import warnings - warnings.warn('Large sphere drawing in Python is slow') +def normal_sphere(r, divide, tex, normal, lighting=True, fv4=GLfloat * 4): + if (not have_extension('GL_ARB_multitexture') or not + have_extension('GL_ARB_texture_env_combine') or not + have_extension('GL_EXT_texture_env_dot3')): + print 'No hardware normal mapping support. No bumping for you.' + return sphere(r, divide, divide, tex, lighting) - def normal_sphere(r, divide, tex, normal, lighting=True, fv4=GLfloat * 4): - from texture import pil_load - print 'Loading normal map: %s...' % normal, - normal_map = pil_load(normal) - normal = normal_map.load() - print - width, height = normal_map.size - gray_scale = len(normal[0, 0]) == 1 + from texture import load_texture + normal = load_texture(normal) - with glRestore(GL_ENABLE_BIT | GL_TEXTURE_BIT): - glEnable(GL_TEXTURE_2D) - if lighting: - glDisable(GL_BLEND) - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fv4(1, 1, 1, 0)) - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(1, 1, 1, 0)) - glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 125) - else: - glDisable(GL_LIGHTING) - glBindTexture(GL_TEXTURE_2D, tex) + with glRestore(GL_ENABLE_BIT | GL_TEXTURE_BIT): + glActiveTextureARB(GL_TEXTURE0_ARB) + glBindTexture(GL_TEXTURE_2D, normal) + glEnable(GL_TEXTURE_2D) - twopi_divide = TWOPI / divide - pi_divide = pi / divide - with glSection(GL_TRIANGLE_STRIP): - for j in xrange(divide + 1): - phi1 = j * twopi_divide - phi2 = (j + 1) * twopi_divide + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB) + glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGBA_ARB) - for i in xrange(divide + 1): - theta = i * pi_divide + glActiveTextureARB(GL_TEXTURE1_ARB) + glBindTexture(GL_TEXTURE_2D, tex) + glEnable(GL_TEXTURE_2D) - s = phi2 / TWOPI - u = min(int(s * width), width - 1) - t = theta / pi - v = min(int(t * height), height - 1) - if gray_scale: - x = y = z = normal[u, v] - else: - x, y, z = normal[u, v] - dx, dy, dz = sin(theta) * cos(phi2), sin(theta) * sin(phi2), cos(theta) - nx, ny, nz = x / 127.5 - 1, y / 127.5 - 1, z / 127.5 - 1 # Make into [-1, 1] - nx, nz = cos(theta) * nx + sin(theta) * nz, -sin(theta) * nx + cos(theta) * nz - nx, ny = cos(phi2) * nx - sin(phi2) * ny, sin(phi2) * nx + cos(phi2) * ny - glNormal3f(nx, ny, nz) - glTexCoord2f(s, 1 - t) # GL is bottom up - glVertex3f(r * dx, r * dy, r * dz) + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB) + glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE) - s = phi1 / TWOPI # x - u = min(int(s * width), width - 1) - if gray_scale: - x = y = z = normal[u, v] - else: - x, y, z = normal[u, v] - dx, dy = sin(theta) * cos(phi1), sin(theta) * sin(phi1) - nx, ny, nz = x / 127.5 - 1, y / 127.5 - 1, z / 127.5 - 1 - nx, nz = cos(theta) * nx + sin(theta) * nz, -sin(theta) * nx + cos(theta) * nz - nx, ny = cos(phi1) * nx - sin(phi1) * ny, sin(phi1) * nx + cos(phi1) * ny - glNormal3f(nx, ny, nz) - glTexCoord2f(s, 1 - t) - glVertex3f(r * dx, r * dy, r * dz) + 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 xrange(divide + 1): + phi1 = j * twopi_divide + phi2 = (j + 1) * twopi_divide + + for i in xrange(divide + 1): + theta = i * pi_divide + + s = phi2 / TWOPI + 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): diff --git a/punyverse/world.json b/punyverse/world.json index a61fa17..36ecee7 100644 --- a/punyverse/world.json +++ b/punyverse/world.json @@ -58,6 +58,7 @@ "mass": 5.97219e+24, "rotation": 86400, "division": 90, + "normal": "earth_normal.jpg", "atmosphere": { "cloud_texture": ["cloudmap.jpg", "cloudmap_small.jpg"], "diffuse_texture": "atmosphere_earth.png", diff --git a/punyverse/world.py b/punyverse/world.py index c912819..ef388ff 100644 --- a/punyverse/world.py +++ b/punyverse/world.py @@ -62,7 +62,7 @@ def load_world(file, callback=lambda message, completion: None): class World(object): - def __init__(self, file, callback): + def __init__(self, file, callback, options=None): self.tracker = [] self.start = (0, 0, 0) self.direction = (0, 0, 0) @@ -73,6 +73,7 @@ class World(object): self.tick = 0 self.callback = callback + self.options = options or {} self._phase = 'Parsing configuration...' self._parse(file) del self.callback # So it can't be used after loading finishes @@ -177,7 +178,7 @@ class World(object): if cheap: object_id = compile(colourball, radius, division, division, texture) else: - if 'normal' in info: + if self.options.get('normal', False) and 'normal' in info: object_id = compile(normal_sphere, radius, division, texture, info['normal'], lighting=lighting) else: object_id = compile(sphere, radius, division, division, texture, lighting=lighting)