Created
July 26, 2010 17:01
-
-
Save kovrov/490839 to your computer and use it in GitHub Desktop.
2dshadows.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Dynamic 2D Shadows, quick prototype | |
See http://www.gamedev.net/reference/articles/article2032.asp""" | |
from PySFML import sf | |
from OpenGL.GL import * | |
from OpenGL.GLU import * | |
import math | |
class Light: | |
"""A Light source""" | |
def __init__( self, x, y, depth, size, range, intensity ): | |
"""Create a new light source | |
Size is ignored for now (will be used for soft shadows)""" | |
assert intensity > 0.0 and intensity <= 1.0 | |
self.x, self.y = x, y | |
self.depth = depth | |
self.size, self.range = size, range | |
self.intensity = intensity | |
def draw( self ): | |
"""Draw the light source halo""" | |
num_subdivisions = 32 | |
glBegin( GL_TRIANGLE_FAN ) | |
glColor4f( 1.0, 1.0, 1.0, self.intensity ) | |
glVertex3f( self.x, self.y, self.depth ) | |
glColor4f( 1.0, 1.0, 1.0, 0.0 ) | |
for i in range( num_subdivisions + 1 ): | |
angle = math.pi * 2 * i / num_subdivisions | |
glVertex3f( self.range * math.cos( angle ) + self.x, | |
self.range * math.sin( angle ) + self.y, self.depth ) | |
glEnd() | |
class Geom: | |
"""Some geometry that casts shadows""" | |
# Constants for tagging edges with regards to the current light | |
# see Geom.render_shadow | |
BACK_FACING_EDGE = 0 | |
FRONT_FACING_EDGE = 1 | |
# FIXME: this should be depending on the camera zoom | |
SHADOW_LENGTH = 800 | |
def __init__( self, vertices, depth ): | |
self.vertices = vertices | |
self.depth = depth | |
def render( self ): | |
"""Render the Geom""" | |
glBegin( GL_POLYGON ) | |
for vertex in self.vertices: | |
glVertex3f( vertex[0], vertex[1], self.depth ) | |
glEnd() | |
def render_shadow( self, light ): | |
"""Render the Geom's shadow with regards to the specified light""" | |
# The shadow's starting and ending vertex indices in the geometry | |
shadow_start_index = None | |
shadow_end_index = None | |
# Iterate over all vertices and decide whether the associated edges | |
# face the light or not | |
edge_facings = [] | |
prev_vertex = self.vertices[-1] | |
for vertex_index, vertex in enumerate( self.vertices ): | |
nx = vertex[1] - prev_vertex[1] | |
ny = prev_vertex[0] - vertex[0] | |
dot = nx * ( light.x - prev_vertex[0] ) + ny * ( light.y - prev_vertex[1] ) | |
if dot > 0: | |
# the edge is front facing | |
edge_facings.append( Geom.BACK_FACING_EDGE ) | |
else: | |
# the edge is back facing | |
edge_facings.append( Geom.FRONT_FACING_EDGE ) | |
prev_vertex = vertex | |
# Find the first and last back facing edges | |
# to extrude the shadows out of them | |
prev_front_edge = edge_facings[-1] | |
for edge_index, front_edge in enumerate( edge_facings ): | |
if not front_edge and prev_front_edge: | |
shadow_end_index = edge_index - 1 | |
elif front_edge and not prev_front_edge: | |
shadow_start_index = edge_index - 1 | |
prev_front_edge = front_edge | |
# Warp around edge indices | |
if shadow_start_index == -1: shadow_start_index = len( self.vertices ) - 1 | |
if shadow_end_index == -1: shadow_end_index = len( self.vertices ) - 1 | |
# The shadow start and end indices might be None if the light is inside | |
# the geometry. If so, do not draw any shadow | |
if shadow_start_index is None or shadow_end_index is None: | |
return | |
# Draw the shadow as a triangle strip | |
glBegin( GL_TRIANGLE_STRIP ) | |
vertex_index = shadow_start_index | |
while vertex_index != shadow_end_index: | |
next_vertex_index = ( vertex_index + 1 ) % len( self.vertices ) | |
cur_vertex = self.vertices[ vertex_index ] | |
next_vertex = self.vertices[ next_vertex_index ] | |
# Compute normalized direction vector from light to current vertex | |
from_light_x, from_light_y = cur_vertex[0] - light.x, cur_vertex[1] - light.y | |
from_light_len = math.sqrt( from_light_x ** 2 + from_light_y ** 2 ) | |
from_light_x /= from_light_len | |
from_light_y /= from_light_len | |
# Compute normalized direction vector from light to next vertex | |
next_from_light_x, next_from_light_y = next_vertex[0] - light.x, next_vertex[1] - light.y | |
next_from_light_len = math.sqrt( next_from_light_x ** 2 + next_from_light_y ** 2 ) | |
next_from_light_x /= next_from_light_len | |
next_from_light_y /= next_from_light_len | |
# Append two triangles to the triangle strip | |
glVertex3f( cur_vertex[0], cur_vertex[1], light.depth ) | |
glVertex3f( cur_vertex[0] + from_light_x * Geom.SHADOW_LENGTH, cur_vertex[1] + from_light_y * Geom.SHADOW_LENGTH, light.depth ) | |
glVertex3f( next_vertex[0], next_vertex[1], light.depth ) | |
glVertex3f( next_vertex[0] + next_from_light_x * Geom.SHADOW_LENGTH, next_vertex[1] + next_from_light_y * Geom.SHADOW_LENGTH, light.depth ) | |
vertex_index = next_vertex_index | |
glEnd() | |
class Game: | |
def __init__( self ): | |
self.lights = [] | |
self.geoms = [] | |
self.geoms.append( Geom( [ (120.0, 120.0), (200.0, 120.0), (200.0, 200.0), (120.0, 200.0) ], 0.7 ) ) | |
self.geoms.append( Geom( [ (300.0, 20.0), (400.0, 50.0), (400.0, 120.0), (250.0, 100.0) ], 0.7 ) ) | |
self.geoms.append( Geom( [ (500.0, 300.0), (700.0, 400.0), (500.0, 480.0), (450.0, 400.0) ], 0.7 ) ) | |
self.lights.append( Light( 0.0, 120.0, 0.0, 1.0, 500.0, 0.5 ) ) | |
# FIXME: when enabling second light, the shadows do not merge properly | |
#self.lights.append( Light( 0.0, 120.0, 0.0, 1.0, 500.0, 0.5 ) ) | |
def render( self ): | |
glClearDepth( 1.0 ) | |
glClearColor( 0.0, 0.0, 0.0, 0.0 ) | |
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ) | |
#glEnable( GL_DEPTH_TEST ) | |
glEnable( GL_BLEND ) | |
glDisable( GL_CULL_FACE ) | |
glMatrixMode( GL_PROJECTION ) | |
glLoadIdentity() | |
glOrtho( 0, 800, 600, 0, -1.0, 1.0) | |
glMatrixMode( GL_MODELVIEW ) | |
glLoadIdentity() | |
glMatrixMode( GL_TEXTURE ) | |
glLoadIdentity() | |
# Fill z-buffer | |
glDepthMask( True ) | |
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ) | |
glColor( 1.0, 1.0, 1.0, 1.0 ) | |
for geom in self.geoms: | |
geom.render() | |
glDepthMask( False ) | |
# Draw geometry with each light | |
for light in self.lights: | |
# Fill alpha with light | |
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ) | |
glBlendFunc( GL_SRC_ALPHA, GL_ONE ) | |
light.draw() | |
# Shadows | |
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ) | |
glBlendFunc( GL_ONE, GL_ZERO ) | |
glColor( 0.0, 0.0, 0.0, 0.0 ) | |
for geom in self.geoms: | |
geom.render_shadow( light ) | |
glDepthMask( True ) | |
# Draw geometry as shaded by current light | |
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ) | |
glBlendFunc( GL_DST_ALPHA, GL_ONE ) | |
glColor( 1.0, 1.0, 0.0, 1.0 ) | |
for geom in self.geoms: | |
geom.render() | |
# Ambient | |
glDisable( GL_BLEND ) | |
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ) | |
glBlendFunc( GL_DST_ALPHA, GL_ONE ) | |
glColor( 1.0, 1.0, 0.0, 1.0 ) | |
for geom in self.geoms: | |
geom.render() | |
def loop( self ): | |
"""Main program""" | |
# Create window | |
self.window = sf.RenderWindow( sf.VideoMode( 800, 600 ), "Softshad", sf.Style.Close ) | |
self.window.SetFramerateLimit( 60 ) | |
self.window.UseVerticalSync( True ) | |
light_speed = 10 | |
# Main loop | |
while True: | |
# Events | |
event = sf.Event() | |
while self.window.GetEvent( event ): | |
if event.Type == sf.Event.Closed: | |
return | |
elif event.Type == sf.Event.KeyPressed: | |
if event.Key.Code == sf.Key.Escape: | |
return | |
# Logic | |
self.lights[0].x = self.window.GetInput().GetMouseX() | |
self.lights[0].y = self.window.GetInput().GetMouseY() | |
# NOTE: This is for controlling light using arrow keys | |
""" | |
if self.window.GetInput().IsKeyDown( sf.Key.Left ): | |
self.lights[0].x -= light_speed | |
if self.window.GetInput().IsKeyDown( sf.Key.Right ): | |
self.lights[0].x += light_speed | |
if self.window.GetInput().IsKeyDown( sf.Key.Up ): | |
self.lights[0].y -= light_speed | |
if self.window.GetInput().IsKeyDown( sf.Key.Down ): | |
self.lights[0].y += light_speed | |
""" | |
# NOTE: This is for controlling a second light with the keyboard | |
""" | |
if self.window.GetInput().IsKeyDown( sf.Key.Q ): | |
self.lights[1].x -= light_speed | |
if self.window.GetInput().IsKeyDown( sf.Key.D ): | |
self.lights[1].x += light_speed | |
if self.window.GetInput().IsKeyDown( sf.Key.Z ): | |
self.lights[1].y -= light_speed | |
if self.window.GetInput().IsKeyDown( sf.Key.S ): | |
self.lights[1].y += light_speed | |
""" | |
# Rendering | |
self.render() | |
self.window.Display() | |
if __name__ == '__main__': | |
game = Game() | |
game.loop() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment