## Gloss ## Copyright (C) 2009 Paul Hudson (http://www.tuxradar.com/gloss) ## Gloss is free software; you can redistribute it and/or ## modify it under the terms of the GNU Lesser General Public License ## as published by the Free Software Foundation; either version 3 ## of the License, or (at your option) any later version. ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## You should have received a copy of the GNU Lesser General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from __future__ import division import math import os import pygame import random import signal import sys import threading import time import traceback import weakref from OpenGL.GL import * OpenGL.ERROR_CHECKING = False from OpenGL.GL.EXT.framebuffer_object import * from OpenGL.GLU import * from pygame.locals import * class GlossGame(object): def __init__(self, name): Gloss.active_game = self Gloss.game_name = name Gloss.screen_resolution = 1280,720 Gloss.full_screen = False Gloss.enable_multisampling = False # set empty event handlers self.on_mouse_motion = None self.on_mouse_down = None self.on_mouse_up = None self.on_key_down = None self.on_key_up = None self.on_quit = None self.on_joy_axis_motion = None self.on_joy_ball_motion = None self.on_joy_button_down = None self.on_joy_button_up = None self.on_joy_hat_motion = None def preload_content(self): pass def load_content(self): pass def draw_loading_screen(self): pass def draw(self): pass def update(self): pass def gloss_initialise(self): os.environ['SDL_VIDEO_CENTERED'] = '1' pygame.mixer.pre_init(44100, 16, 2, 4096) pygame.init() pygame.joystick.init() for i in range(pygame.joystick.get_count()): joystick = pygame.joystick.Joystick(i) joystick.init() Gloss.joysticks.append(joystick) Gloss.game_clock = pygame.time.Clock() pygame.display.set_caption(Gloss.game_name) if Gloss.enable_multisampling: pygame.display.gl_set_attribute(pygame.locals.GL_MULTISAMPLEBUFFERS, 1) pygame.display.gl_set_attribute(pygame.locals.GL_MULTISAMPLESAMPLES, 4) if Gloss.full_screen: pygame.display.set_mode(Gloss.screen_resolution, pygame.OPENGL | pygame.DOUBLEBUF | pygame.FULLSCREEN) else: pygame.display.set_mode(Gloss.screen_resolution, pygame.OPENGL | pygame.DOUBLEBUF) if Gloss.screen_resolution == (0, 0): scr = pygame.display.get_surface() Gloss.screen_resolution = scr.get_size() Gloss.MaxTextureSize = glGetInteger(GL_MAX_TEXTURE_SIZE); Gloss.clear(Color.CORNFLOWER_BLUE) pygame.display.flip() Gloss.clear(Color.CORNFLOWER_BLUE) glEnable(GL_CULL_FACE) glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) glEnable(GL_LINE_SMOOTH) glEnable(GL_TEXTURE_2D) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glEnable(GL_BLEND) glEnable(GL_LIGHTING) glColorMaterial (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE) glEnable (GL_COLOR_MATERIAL) Gloss.set_scene_tint(Color.WHITE) glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(0, Gloss.screen_resolution[0], Gloss.screen_resolution[1], 0, -100, 100) Gloss.viewport_size = Gloss.screen_resolution glMatrixMode(GL_MODELVIEW) glLoadIdentity() self.preload_content() self.draw_loading_screen() pygame.display.flip() Gloss.tick_count = 0 self.load_content() def gloss_internal_update(self): for particlesystem in reversed(Gloss.auto_particle_systems): # don't update this particle system if it's frozen if particlesystem.frozen: continue if particlesystem.update() is False: Gloss.auto_particle_systems.remove(particlesystem) particlesystem.alive = False if particlesystem.on_finish is not None: particlesystem.on_finish(particlesystem) def gloss_internal_draw(self): # if we take a screenshot after picking, we get the picking view rather than the real view # to fix this, save_screenshot() looks for Gloss.redraw_needed, and, if it's true, redraws the screen if Gloss.picking: Gloss.redraw_needed = True else: Gloss.redraw_needed = False self.draw() def run(self): self.gloss_initialise() Gloss.game_is_running = True while Gloss.game_is_running: Gloss.elapsed_seconds = Gloss.game_clock.tick(60) / 1000 Gloss.total_seconds += Gloss.elapsed_seconds if Gloss.elapsed_seconds > 0.02: Gloss.running_slowly = True else: Gloss.running_slowly = False # tick_count is used in lots of places, so read it only once per update Gloss.tick_count = pygame.time.get_ticks() for event in pygame.event.get(): if event.type == MOUSEMOTION: if self.on_mouse_down is not None: self.on_mouse_motion(event) if event.type == MOUSEBUTTONDOWN: if self.on_mouse_down is not None: self.on_mouse_down(event) if event.type == MOUSEBUTTONUP: if self.on_mouse_up is not None: self.on_mouse_up(event) if Gloss.sprite_click_tracking: Gloss.select_object(event.pos) if event.type == KEYDOWN: if event.mod & KMOD_LCTRL == KMOD_LCTRL: if event.key == K_ESCAPE: # Left Ctrl+Escape held - quit Gloss.game_is_running = False if self.on_key_down is not None: self.on_key_down(event) if event.type == KEYUP: if self.on_key_up is not None: self.on_key_up(event) if event.type == JOYAXISMOTION: if self.on_joy_axis_motion is not None: self.on_joy_axis_motion(event) if event.type == JOYBALLMOTION: if self.on_joy_ball_motion is not None: self.on_joy_ball_motion(event) if event.type == JOYHATMOTION: if self.on_joy_hat_motion is not None: self.on_joy_hat_motion(event) if event.type == JOYBUTTONDOWN: if self.on_joy_button_down is not None: self.on_joy_button_down(event) if event.type == JOYBUTTONUP: if self.on_joy_button_up is not None: self.on_joy_button_up(event) if event.type == QUIT: Gloss.game_is_running = False # this all has to be done after the above events so that any changes from events # take place as soon as possible self.gloss_internal_update() self.update() self.gloss_internal_draw() pygame.display.flip() if self.on_quit is not None: self.on_quit() pygame.quit() def quit(self): Gloss.game_is_running = False class Color(object): def __init__(self, r, g, b, a = 1.0): self.r = r self.g = g self.b = b self.a = a @staticmethod def from_bytes(r, g, b, a = 255): return Color(r / 255.0, g / 255.0, b / 255.0, a / 255.0) @staticmethod def from_html(col): pygamecol = pygame.color.Color(col) return Color.from_pygame(pygamecol) @staticmethod def from_pygame(col): return Color(col[0] / 255, col[1] / 255, col[2] / 255, col[3] / 255) @staticmethod def lerp(colorfrom, colorto, amount): return Color(Gloss.lerp(colorfrom.r, colorto.r, amount), Gloss.lerp(colorfrom.g, colorto.g, amount), Gloss.lerp(colorfrom.b, colorto.b, amount), Gloss.lerp(colorfrom.a, colorto.a, amount)) @staticmethod def lerp2(colorfrom, colorto, amount): return Color(Gloss.lerp2(colorfrom.r, colorto.r, amount), Gloss.lerp2(colorfrom.g, colorto.g, amount), Gloss.lerp2(colorfrom.b, colorto.b, amount), Gloss.lerp2(colorfrom.a, colorto.a, amount)) @staticmethod def multi_lerp(values, amount): result = Gloss.clamp(amount, 0.0, 1.0) if result == 1.0: return values[len(values) - 1] num_items = len(values) position = int(math.floor(amount * (num_items - 1))) # what was the minimum item we were up to? individual_lerp = 1.0 / (num_items - 1) # how much is each step? current_lerp = amount - position * individual_lerp # how far through the current step are we? lerp_amount = current_lerp / individual_lerp # how much is that compared to a full step return Color.lerp(values[position], values[position + 1], lerp_amount) @staticmethod def smooth_step(colorfrom, colorto, amount): return Color(Gloss.smooth_step(colorfrom.r, colorto.r, amount), Gloss.smooth_step(colorfrom.g, colorto.g, amount), Gloss.smooth_step(colorfrom.b, colorto.b, amount), Gloss.smooth_step(colorfrom.a, colorto.a, amount)) @staticmethod def smooth_step2(colorfrom, colorto, amount): return Color(Gloss.smooth_step2(colorfrom.r, colorto.r, amount), Gloss.smooth_step2(colorfrom.g, colorto.g, amount), Gloss.smooth_step2(colorfrom.b, colorto.b, amount), Gloss.smooth_step2(colorfrom.a, colorto.a, amount)) class Point(object): @staticmethod def add(point1, point2): return (point1[0] + point2[0], point1[1] + point2[1]) def bounce_both(point1, point2, amount, overshoot = 20): return (Gloss.bounce_both(point1[0], point2[0], amount, overshoot), Gloss.bounce_both(point1[1], point2[1], amount, overshoot)) def bounce_in(point1, point2, amount, overshoot = 20): return (Gloss.bounce_in(point1[0], point2[0], amount, overshoot), Gloss.bounce_in(point1[1], point2[1], amount, overshoot)) def bounce_out(point1, point2, amount, overshoot = 20): return (Gloss.bounce_out(point1[0], point2[0], amount, overshoot), Gloss.bounce_out(point1[1], point2[1], amount, overshoot)) @staticmethod def distance(point1, point2): return math.sqrt(Point.distance_squared(point1, point2)) @staticmethod def distance_squared(point1, point2): return (point1[0] - point2[0]) * (point1[0] - point2[0]) + (point1[1] - point2[1]) * (point1[1] - point2[1]) @staticmethod def length(point): return math.sqrt(Point.length_squared(point)) @staticmethod def length_squared(point): return (point[0] * point[0]) + (point[1] * point[1]) @staticmethod def lerp(point1, point2, amount): return (Gloss.lerp(point1[0], point2[0], amount), Gloss.lerp(point1[1], point2[1], amount)) @staticmethod def lerp2(point1, point2, amount): return (Gloss.lerp2(point1[0], point2[0], amount), Gloss.lerp2(point1[1], point2[1], amount)) @staticmethod def multi_lerp(values, amount): result = Gloss.clamp(amount, 0.0, 1.0) if result == 1.0: return values[len(values) - 1] num_items = len(values) position = int(math.floor(amount * (num_items - 1))) # what was the minimum item we were up to? individual_lerp = 1.0 / (num_items - 1) # how much is each step? current_lerp = amount - position * individual_lerp # how far through the current step are we? lerp_amount = current_lerp / individual_lerp # how much is that compared to a full step return Point.lerp(values[position], values[position + 1], lerp_amount) @staticmethod def multiply(point1, to): if to.__class__.__name__ == "tuple": return (point1[0] * to[0], point1[1] * to[1]) else: return (point1[0] * to, point1[1] * to) @staticmethod def normalize(point): length = self.length() factor = 1.0 / length return (point[0] * factor, point[1] * factor) @staticmethod def smooth_step(point1, point2, amount): return (Gloss.smooth_step(point1[0], point2[0], amount), Gloss.smooth_step(point1[1], point2[1], amount)) @staticmethod def smooth_step2(point1, point2, amount): return (Gloss.smooth_step2(point1[0], point2[0], amount), Gloss.smooth_step2(point1[1], point2[1], amount)) @staticmethod def subtract(point1, point2): return (point1[0] - point2[0], point1[1] - point2[1]) class Gloss(object): VERSION = 0.9 PI_OVER_2 = math.pi / 2.0 PI_OVER_4 = math.pi / 4.0 TWO_PI = math.pi * 2.0 Color.WHITE = Color(1, 1, 1, 1) Color.RED = Color(1, 0, 0, 1) Color.GREEN = Color(0, 1, 0, 1) Color.BLUE = Color(0, 0, 1, 1) Color.BLACK = Color(0, 0, 0, 1) Color.CORNFLOWER_BLUE = Color(0.392156863, 0.584313725, 0.929411765, 1) # used for clearing the screen to a safe value Color.PURPLE = Color(0.5, 0, 0.5, 1) # used for clearing rendertargets to a safe value Color.TRANSPARENT_WHITE = Color(1, 1, 1, 0) Point.ZERO = (0, 0) picking = False enable_texturing = True running_slowly = False total_seconds = 0 sprite_click_tracking = False # set to True when any sprite has an OnClick method attached last_clicked_sprite = None # the sprite that was clicked most recently joysticks = [] auto_particle_systems = [] sprites = [] @staticmethod def bounce_both(value1, value2, amount, overshoot = 20): overshoot *= 0.25949 value2 -= value1 amount /= 0.5 if (amount < 1): return value2 / 2 * (amount * amount * ((overshoot + 1) * amount - overshoot)) + value1 else: amount -= 2 return value2 / 2 * (amount * amount * ((overshoot + 1) * amount + overshoot) + 2) + value1 @staticmethod def bounce_in(value1, value2, amount, overshoot = 20): overshoot *= 0.170158 value2 -= value1 return value2 * amount * amount * ((overshoot + 1) * amount - overshoot) + value1; @staticmethod def bounce_out(value1, value2, amount, overshoot = 20): overshoot *= 0.170158 amount = amount / 1.0 - 1 value2 -= value1 return value2 * (amount * amount * ((overshoot + 1) * amount + overshoot) + 1) + value1; @staticmethod def draw_box(position = (0, 0), width = 128, height = 128, rotation = 0.0, origin = (0, 0), scale = 1, color = Color.WHITE): glPushMatrix() glColor4f(color.r, color.g, color.b, color.a) glDisable(GL_TEXTURE_2D) boxwidth = width * scale boxheight = height * scale if origin is None: originx = boxwidth / 2 originy = boxheight / 2 else: originx = origin[0] * scale originy = origin[1] * scale glTranslatef(position[0], position[1], 0) if rotation is not 0.0: glRotatef(rotation, 0, 0, 1) glBegin(GL_QUADS) glVertex2f(-originx, boxheight - originy) glVertex2f(boxwidth - originx, boxheight - originy) glVertex2f(boxwidth - originx, -originy) glVertex2f(-originx, -originy) glEnd() glEnable(GL_TEXTURE_2D) glPopMatrix() @staticmethod def draw_line(start, finish, color = Color.WHITE, width = 1.0): glPushMatrix() glColor4f(color.r, color.g, color.b, color.a) glLineWidth(width) glDisable(GL_TEXTURE_2D) glBegin(GL_LINES) glVertex2f(start[0], start[1]) glVertex2f(finish[0], finish[1]) glEnd() glEnable(GL_TEXTURE_2D) glPopMatrix() @staticmethod def draw_lines(lines, color = Color.WHITE, width = 1.0, join = False): glPushMatrix() glColor4f(color.r, color.g, color.b, color.a) glLineWidth(width) glDisable(GL_TEXTURE_2D) if join: glBegin(GL_LINE_LOOP) else: glBegin(GL_LINE_STRIP) for line in lines: glVertex2f(line[0], line[1]) glEnd() glEnable(GL_TEXTURE_2D) glPopMatrix() @staticmethod def draw_triangle(points = [(0, 0), (-50, 100), (50, 100)], position = (0,0), rotation = 0.0, origin = (0, 0), scale = 1, color = Color.WHITE): glPushMatrix() glColor4f(color.r, color.g, color.b, color.a) glDisable(GL_TEXTURE_2D) glDisable(GL_CULL_FACE) # don't give people the hassle of thinking about clockwise vs anti-clockwise if origin is None: x1 = points[0][0] x2 = points[1][0] x3 = points[2][0] y1 = points[0][1] y2 = points[1][1] y3 = points[2][1] originx = (x1 + x2 + x3) / 3.0 originy = (y1 + y2 + y3) / 3.0 else: originx = origin[0] originy = origin[1] glTranslatef(position[0], position[1], 0) if rotation is not 0.0: glRotatef(rotation, 0, 0, 1) glBegin(GL_TRIANGLES) glVertex2f((points[0][0] - originx) * scale, (points[0][1] - originy) * scale) glVertex2f((points[1][0] - originx) * scale, (points[1][1] - originy) * scale) glVertex2f((points[2][0] - originx) * scale, (points[2][1] - originy) * scale) glEnd() glEnable(GL_TEXTURE_2D) glEnable(GL_CULL_FACE) glPopMatrix() @staticmethod def fill(texture = None, color = Color.WHITE, top = Color.CORNFLOWER_BLUE, bottom = Color.CORNFLOWER_BLUE, vertical = True): # never draw backgrounds when picking if Gloss.picking: return if texture is not None: width = Gloss.viewport_size[0] height = Gloss.viewport_size[1] glPushMatrix() glColor4f(color.r, color.g, color.b, color.a) glBindTexture(GL_TEXTURE_2D, texture.surface) glBegin(GL_TRIANGLE_STRIP) glTexCoord2f(0, texture.height_ratio); glVertex2f(0, height) glTexCoord2f(texture.width_ratio, texture.height_ratio); glVertex2f(width, height) glTexCoord2f(0, 1); glVertex2f(0, 0) glTexCoord2f(texture.width_ratio, 1); glVertex2f(width, 0) glEnd() glPopMatrix() else: # if we're still here, draw the polygon without texture using gradient colors if Gloss.enable_texturing: glDisable(GL_TEXTURE_2D) width = Gloss.viewport_size[0] height = Gloss.viewport_size[1] glPushMatrix() if vertical: glBegin(GL_TRIANGLE_STRIP) glColor4f(bottom.r, bottom.g, bottom.b, bottom.a); glVertex2f(0, height) glColor4f(bottom.r, bottom.g, bottom.b, bottom.a); glVertex2f(width, height) glColor4f(top.r, top.g, top.b, top.a); glVertex2f(0, 0) glColor4f(top.r, top.g, top.b, top.a); glVertex2f(width, 0) glEnd() else: glBegin(GL_TRIANGLE_STRIP) glColor4f(top.r, top.g, top.b, top.a); glVertex2f(0, height) glColor4f(bottom.r, bottom.g, bottom.b, bottom.a); glVertex2f(width, height) glColor4f(top.r, top.g, top.b, top.a); glVertex2f(0, 0) glColor4f(bottom.r, bottom.g, bottom.b, bottom.a); glVertex2f(width, 0) glEnd() if Gloss.enable_texturing: glEnable(GL_TEXTURE_2D) glPopMatrix() @staticmethod def clamp(value, minval, maxval): if value > maxval: value = maxval elif value < minval: value = minval return value @staticmethod def clear(color = Color.CORNFLOWER_BLUE): glClearColor(color.r, color.g, color.b, color.a) glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) @staticmethod def hermite(value1, tangent1, value2, tangent2, amount): AmountSquared = amount * amount AmountCubed = AmountSquared * amount if amount is 0.0: result = value1 elif amount is 1.0: result = value2 else: result = (2 * value1 - 2 * value2 + tangent2 + tangent1) * AmountCubed + (3 * value2 - 3 * value1 - 2 * tangent1 - tangent2) * AmountSquared + tangent1 * amount + value1 return result @staticmethod def lerp(value1, value2, amount): result = Gloss.clamp(amount, 0.0, 1.0) return value1 + (value2 - value1) * result @staticmethod def lerp2(value1, value2, amount): result = Gloss.clamp(amount, 0.0, 1.0) if result > 0.5: return value2 + (value1 - value2) * ((result - 0.5) * 2) else: return value1 + (value2 - value1) * (result * 2) @staticmethod def multi_lerp(values, amount): # this method lets people lerp through multiple numbers, eg (0, 1, 2) with # lerp amount 0.5 would return 1. It works by figuring out where in the values list # we're up to, seeing how far we've moved towards the next position. result = Gloss.clamp(amount, 0.0, 1.0) if result == 1.0: return values[len(values) - 1] num_items = len(values) position = int(math.floor(amount * (num_items - 1))) # what was the minimum item we were up to? individual_lerp = 1.0 / (num_items - 1) # how much is each step? current_lerp = amount - position * individual_lerp # how far through the current step are we? lerp_amount = current_lerp / individual_lerp # how much is that compared to a full step return Gloss.lerp(values[position], values[position + 1], lerp_amount) @staticmethod def next_po2(value): logbase2 = (math.log(value) / math.log(2)) return int(round(pow(2, math.ceil(logbase2)))) @staticmethod def smooth_step(value1, value2, amount): result = Gloss.clamp(amount, 0.0, 1.0) result = Gloss.hermite(value1, 0.0, value2, 0.0, result) return result @staticmethod def smooth_step2(value1, value2, amount): result = Gloss.clamp(amount, 0.0, 1.0) if result > 0.5: result = Gloss.hermite(value2, 0.0, value1, 0.0, (result - 0.5) * 2) else: result = Gloss.hermite(value1, 0.0, value2, 0.0, result * 2) return result @staticmethod def rand_float(value1, value2): return random.random() * (value2 - value1) + value1 @staticmethod def save_screenshot(filename): if Gloss.redraw_needed: Gloss.active_game.gloss_internal_draw() glFinish() glPixelStorei(GL_PACK_ALIGNMENT, 4) glPixelStorei(GL_PACK_ROW_LENGTH, 0) glPixelStorei(GL_PACK_SKIP_ROWS, 0) glPixelStorei(GL_PACK_SKIP_PIXELS, 0) data = glReadPixels(0, 0, Gloss.viewport_size[0], Gloss.viewport_size[1], GL_RGBA, GL_UNSIGNED_BYTE) surface = pygame.image.fromstring(data, (Gloss.viewport_size[0], Gloss.viewport_size[1]), 'RGBA', 1) pygame.image.save(surface, filename) @staticmethod def set_scene_tint(color): glLightModelfv( GL_LIGHT_MODEL_AMBIENT, ((color.r, color.g, color.b, color.a)) ) @staticmethod def to_radians(degrees): return degrees * 0.017453292519943295769236907685 @staticmethod def enable_picking(): Gloss.picking = True glDisable(GL_LIGHTING) glDisable(GL_TEXTURE_2D) Gloss.enable_texturing = False @staticmethod def disable_picking(): Gloss.picking = False glEnable(GL_LIGHTING) glEnable(GL_TEXTURE_2D) Gloss.enable_texturing = True @staticmethod def select_object(pos): Gloss.enable_picking() Gloss.active_game.gloss_internal_draw() pixels = glReadPixelsub(pos[0], Gloss.viewport_size[1] - pos[1], 1, 1, GL_RGBA) r = pixels[0][0][0] g = pixels[0][0][1] b = pixels[0][0][2] Gloss.disable_picking() for sprite in Gloss.sprites: if (sprite.pick_color[0] == r) and sprite.pick_color[1] == g and sprite.pick_color[2] == b: if sprite._on_click is not None: # a function has been attached here - call it! sprite._on_click(sprite) Gloss.last_clicked_sprite = sprite return sprite return None class Texture(object): def __init__(self, source): self.surface = None if (source.__class__.__name__ == "str") or (source.__class__.__name__ == "unicode"): try: surface = pygame.image.load(source) except: print("Unable to load texture " + source + ".") sys.exit(1) else: surface = source width = surface.get_width() height = surface.get_height() self.width = width self.height = height self.half_width = self.width / 2 self.half_height = self.height / 2 po2width = Gloss.next_po2(width) po2height = Gloss.next_po2(height) if (po2width > Gloss.MaxTextureSize or po2height > Gloss.MaxTextureSize): print "Fatal error: texture at " + path + " is bigger than the maximum supported texture size of " + str(Gloss.MaxTextureSize) sys.exit(1) self.width_ratio = (self.width / po2width) self.height_ratio = 1 - (self.height / po2height) if (width != po2width or height != po2height): tmpsurface = pygame.Surface((po2width, po2height), SRCALPHA, 32) tmpsurface.blit(surface, (0,0)) surface = tmpsurface data = pygame.image.tostring(surface, "RGBA", 1) self.surface = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, self.surface) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, po2width, po2height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) def __del__(self): if self.surface is not None: # before we try freeing this memory, be careful: Python may have unloaded the OpenGL module by now, in which case just bail out if glDeleteTextures is not None: glDeleteTextures(self.surface) self.surface = None def draw(self, position = (0, 0), rotation = 0.0, origin = (0, 0), scale = 1, color = Color.WHITE): # never draw textures if picking if Gloss.picking: return texwidth = self.width * scale texheight = self.height * scale if origin is None: originx = texwidth / 2 originy = texheight / 2 else: originx = origin[0] * scale originy = origin[1] * scale glPushMatrix() glTranslatef(position[0], position[1], 0) if rotation is not 0.0: glRotatef(rotation, 0, 0, 1) glColor4f(color.r, color.g, color.b, color.a) glBindTexture(GL_TEXTURE_2D, self.surface) glBegin(GL_TRIANGLE_STRIP) glTexCoord2f(0, self.height_ratio); glVertex2f(-originx, texheight - originy) glTexCoord2f(self.width_ratio, self.height_ratio); glVertex2f(texwidth - originx, texheight - originy) glTexCoord2f(0, 1); glVertex2f(-originx, -originy) glTexCoord2f(self.width_ratio, 1); glVertex2f(texwidth - originx, -originy) glEnd() glPopMatrix() class Sprite(object): next_id = 1 pick_r = 0 pick_g = 0 pick_b = 0 def __init__(self, texture, position = None): self.id = str(Sprite.next_id) Sprite.next_id += 1 self.texture = texture if position is None: self.position = (0, 0) else: self.position = position Sprite.pick_r += 1 if Sprite.pick_r > 255: Sprite.pick_r = 0 Sprite.pick_g += 1 if Sprite.pick_g > 255: Sprite.pick_g = 0 Sprite.pick_b += 1 self.pick_color = (Sprite.pick_r, Sprite.pick_g, Sprite.pick_b, 255) self._on_click = None Gloss.sprites.append(weakref.ref(self)) def __del__(self): # remove any dead weak references from the sprite list for i in reversed(range(len(Gloss.sprites))): if Gloss.sprites[i]() is None: del Gloss.sprites[i] def get_on_click(self): return self._on_click def set_on_click(self, value): self._on_click = value Gloss.sprite_click_tracking = True on_click = property(get_on_click, set_on_click) def draw(self, position = None, rotation = 0.0, origin = (0, 0), scale = 1, color = Color.WHITE): texwidth = self.texture.width * scale texheight = self.texture.height * scale if origin is None: originx = texwidth / 2 originy = texheight / 2 else: originx = origin[0] * scale originy = origin[1] * scale if position is None: position = self.position glPushMatrix() glTranslatef(position[0], position[1], 0) if rotation is not 0.0: glRotatef(rotation, 0, 0, 1) if Gloss.picking is False: glColor4f(color.r, color.g, color.b, color.a) else: glColor3ub(self.pick_color[0], self.pick_color[1], self.pick_color[2]) glBindTexture(GL_TEXTURE_2D, self.texture.surface) glBegin(GL_TRIANGLE_STRIP) glTexCoord2f(0, self.texture.height_ratio); glVertex2f(-originx, texheight - originy) glTexCoord2f(self.texture.width_ratio, self.texture.height_ratio); glVertex2f(texwidth - originx, texheight - originy) glTexCoord2f(0, 1); glVertex2f(-originx, -originy) glTexCoord2f(self.texture.width_ratio, 1); glVertex2f(texwidth - originx, -originy) glEnd() glPopMatrix() def move(self, x, y): self.position = (self.position[0] + x, self.position[1] + y) def move_to(self, x = None, y = None): if x is None: if y is None: return self.position = (self.position[0], y) return if y is None: self.position = (x, self.position[1]) else: self.position = (x, y) class ParticleSystem(object): additive = False def __init__(self, texture, position = None, lifespan = -1, creationspeed = None, initialparticles = 50, particlelifespan = 1000, minspeed = 50, maxspeed = 250, minrotation = 0, maxrotation = 0, minscale = 1.0, maxscale = 1.0, growth = 0.0, wind = None, drag = None, startcolor = Color.WHITE, endcolor = Color.TRANSPARENT_WHITE, onfinish = None, name = ""): Gloss.auto_particle_systems.append(self) self.name = name self.on_finish = onfinish self.texture = texture if position is None: self.position = (0, 0) else: self.position = position self.created_time = Gloss.tick_count self.last_particle_created_time = Gloss.tick_count self.frozen = False # used to stop particle systems being automatically updated self.alive = True # set to be False when destroyed self.lifespan = lifespan self.creation_speed = creationspeed self.initial_particles = initialparticles self.particle_lifespan = particlelifespan self.start_color = startcolor self.end_color = endcolor self.particle_speed_min = minspeed self.particle_speed_max = maxspeed self.particle_rotation_min = minrotation self.particle_rotation_max = maxrotation self.particle_scale_min = minscale self.particle_scale_max = maxscale self.particle_growth = growth self.particle_drag = drag self.particle_wind = wind self.particles = [] for i in range(initialparticles): self.create_particle() def __del__(self): self.particles = [] # destroy all particles now def draw(self): if self.alive is False: return # never draw particle systems if picking if Gloss.picking: return if self.additive: glBlendFunc(GL_SRC_ALPHA, GL_ONE) for particle in self.particles: self.texture.draw(position = particle.position, rotation = particle.rotation, origin = None, scale = particle.scale + (particle.anim_pos * self.particle_growth), color = particle.color) if self.additive: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) def update(self): # kill off old particle systems if (self.lifespan == -1): # long-term particle systems also need to be auto-removed if they haven't done anything for a little while if len(self.particles) == 0: return False else: # if we have finished our life and all our particles are dead, return false so that the game can remove us from the list of particle systems if self.created_time + self.lifespan < Gloss.tick_count: return False # do we need to create a new particle? if (self.creation_speed is not None and self.last_particle_created_time + self.creation_speed < Gloss.tick_count): self.create_particle() #cache the current wind and drag amounts to save calculation in the particle loop if self.particle_wind is not None: current_wind = Point.multiply(self.particle_wind, Gloss.elapsed_seconds) if self.particle_drag is not None: current_drag = Gloss.elapsed_seconds * self.particle_drag for i in reversed(range(len(self.particles))): particle = self.particles[i] if particle.created_time + self.particle_lifespan < Gloss.tick_count: del self.particles[i] particle.position = Point.add(particle.position, Point.multiply(particle.velocity, Gloss.elapsed_seconds)) if self.particle_wind is not None: particle.position = Point.add(particle.position, current_wind) particle.anim_pos = Gloss.clamp((Gloss.tick_count - particle.created_time) / self.particle_lifespan, 0.0, 1.0) particle.color = Color.lerp(self.start_color, self.end_color, particle.anim_pos) if self.particle_drag is not None: particle.velocity = Point.subtract(particle.velocity, Point.multiply(particle.velocity, current_drag)) if (Point.length_squared(particle.velocity) <= 0): particle.velocity = Point.ZERO return True def create_particle(self): particle = Particle() particle.created_time = Gloss.tick_count particle.position = self.position particle.rotation = Gloss.rand_float(self.particle_rotation_min, self.particle_rotation_max) particle.scale = Gloss.rand_float(self.particle_scale_min, self.particle_scale_max) angle = random.random() * Gloss.TWO_PI particle.velocity = Point.multiply((math.cos(angle), math.sin(angle)), Gloss.rand_float(self.particle_speed_min, self.particle_speed_max)) self.last_particle_created_time = Gloss.tick_count self.particles.append(particle) class Particle: def __init__(self): self.anim_pos = 0.0 self.color = Color.WHITE class RenderTarget: def __init__(self, width = 512, height = 512): self.buffer = None self.surface = None self.width = width self.height = height self.half_width = width / 2.0 self.half_height = height / 2.0 # create the framebuffer temp = glGenFramebuffersEXT(1) self.buffer = temp glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self.buffer) # create a texture for rendering temp = glGenTextures(1) self.surface = temp glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, self.surface) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, width, height, 0, GL_RGBA, GL_FLOAT, None) glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, self.surface, 0) status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) if status != GL_FRAMEBUFFER_COMPLETE_EXT: return Gloss.clear(Color.PURPLE) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0) # we don't need this activated yet, so unbind it for now # start rendering to this framebuffer def activate(self): glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self.buffer) Gloss.viewport_size = self.width, self.height glViewport(0, 0, self.width, self.height) glMatrixMode(GL_PROJECTION) glPushMatrix() glLoadIdentity() glOrtho(0, self.width, self.height, 0, -100, 100) glMatrixMode(GL_MODELVIEW) glPushMatrix() glLoadIdentity() # stop rendering to this framebuffer def deactivate(self): glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0) Gloss.viewport_size = Gloss.screen_resolution glViewport(0, 0, Gloss.screen_resolution[0], Gloss.screen_resolution[1]) glMatrixMode(GL_PROJECTION) glPopMatrix() glMatrixMode(GL_MODELVIEW) glPopMatrix() # destroy this framebuffer and free allocated resources. def __del__(self): glDeleteFramebuffersEXT(1, [self.buffer]) glDeleteTextures(1, [self.surface]) self.buffer = None self.surface = None # draw the contents of the buffer to the screen def draw(self, position, width = None, height = None, rotation = 0.0, origin = (0, 0), scale = 1, color = Color.WHITE): # always draw this texture, even when picking glEnable(GL_TEXTURE_2D) if width is None: width = self.width if height is None: height = self.height texheight = height * scale texwidth = width * scale if origin is None: originx = texwidth / 2 originy = texheight / 2 else: originx = origin[0] * scale originy = origin[1] * scale glPushMatrix() glTranslatef(position[0], position[1], 0) if rotation is not 0.0: glRotatef(rotation, 0, 0, 1) if Gloss.picking is False: glColor4f(color.r, color.g, color.b, color.a) else: # when picking, use pure white so that all textures inside this are visible glColor4f(1, 1, 1, 1) glBindTexture(GL_TEXTURE_2D, self.surface) glBegin(GL_TRIANGLE_STRIP) glTexCoord2f(0, 0); glVertex2f(-originx, texheight - originy) glTexCoord2f(1, 0); glVertex2f(texwidth - originx, texheight - originy) glTexCoord2f(0, 1); glVertex2f(-originx, -originy) glTexCoord2f(1, 1); glVertex2f(texwidth - originx, -originy) glEnd() # disable texturing if we're picking if Gloss.enable_texturing is False: glDisable(GL_TEXTURE_2D) glPopMatrix() class SpriteFont(object): def __init__(self, filename, size = 18, bold = False, underline = False, startcharacter = 32, endcharacter = 126): self.font = pygame.font.Font(filename, size) self.font.set_underline(bold) self.font.set_bold(underline) self.characters = {} for letter in range(startcharacter, endcharacter + 1): self.characters[chr(letter)] = SpriteFontLetter(self.font, chr(letter)) self.line_height = self.characters["A"].height def draw(self, text = "Hello, Gloss!", position = (0, 0), rotation = 0.0, scale = 1.0, color = Color.WHITE, letterspacing = 0, linespacing = 0): # don't draw text when picking if Gloss.picking: return currentwidth = 0 # used to reset to the start of the line when breaking if position is None: position = self.position letterspacing *= scale linespacing *= scale glPushMatrix() glTranslatef(position[0], position[1], 0) if rotation is not 0.0: glRotatef(rotation, 0, 0, 1) glColor4f(color.r, color.g, color.b, color.a) lines = text.splitlines() for line in lines: for letter in line: lettertexture = self.characters[letter] texheight = lettertexture.height * scale texwidth = lettertexture.width * scale glBindTexture(GL_TEXTURE_2D, lettertexture.surface) glBegin(GL_TRIANGLE_STRIP) glTexCoord2f(0, lettertexture.height_ratio); glVertex2f(0, texheight) glTexCoord2f(lettertexture.width_ratio, lettertexture.height_ratio); glVertex2f(texwidth, texheight) glTexCoord2f(0, 1); glVertex2f(0, 0) glTexCoord2f(lettertexture.width_ratio, 1); glVertex2f(texwidth, 0) glEnd() glTranslatef(texwidth + letterspacing, 0, 0) currentwidth += texwidth + letterspacing # carriage return + line feed glTranslatef(-currentwidth, texheight + linespacing, 0) currentwidth = 0 glPopMatrix() def measure_string(self, text, scale = 1.0, letterspacing = 0, linespacing = 0): currentwidth = 0 # used to track the width of the current line maxwidth = 0 # used to track the width of the longest line maxheight = 0 # used to track the height of the longest line letterspacing *= scale linespacing *= scale lines = text.splitlines() for line in lines: for letter in line: lettertexture = self.characters[letter] texheight = lettertexture.height * scale texwidth = lettertexture.width * scale currentwidth += texwidth + letterspacing # carriage return + line feed if currentwidth > maxwidth: maxwidth = currentwidth currentwidth = 0 maxheight += texheight + linespacing return maxwidth,maxheight class SpriteFontLetter(object): def __init__(self, font, letter): surface = font.render(letter, True, (255,255,255)) width = surface.get_width() height = surface.get_height() self.width = width self.height = height self.half_width = self.width / 2 self.half_height = self.height / 2 po2width = Gloss.next_po2(width) po2height = Gloss.next_po2(height) if (po2width > Gloss.MaxTextureSize or po2height > Gloss.MaxTextureSize): print "Fatal error: texture at " + path + " is bigger than the maximum supported texture size of " + str(Gloss.MaxTextureSize) sys.exit(1) self.width_ratio = (self.width / po2width) self.height_ratio = 1 - (self.height / po2height) if (width != po2width or height != po2height): tmpsurface = pygame.Surface((po2width, po2height), SRCALPHA, 32) tmpsurface.blit(surface, (0,0)) surface = tmpsurface letter = pygame.image.tostring(surface, 'RGBA', 1) self.surface = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, self.surface) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, po2width, po2height, 0, GL_RGBA, GL_UNSIGNED_BYTE, letter)