Files
2014-02-05 10:44:04 +05:30

1303 lines
38 KiB
Python

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