Skip to content

Instantly share code, notes, and snippets.

@kovrov
Created July 26, 2010 17:01
Show Gist options
  • Save kovrov/490839 to your computer and use it in GitHub Desktop.
Save kovrov/490839 to your computer and use it in GitHub Desktop.
2dshadows.py
"""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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment