Shadows

Blitz3D Forums/Blitz3D Programming/Shadows

This code will project shadows straight down.

It was designed for circular shadows. However, since you can use any texture you want you can generate a new texture each frame. So write a little code that makes an appropriate shadow texture from the specified mesh and voila, nice shadows which actually represent the shapes of stuff.

The shadows will wrap around the speficied reciver mesh. The caster is the object which determines the location of the shadow in space.

The shadows will taper off in brightness as the angle of the surface they are relative to gets steeper, so shadows will never be seen on perfectly vertical walls.

It shouldn't be too hard to change the code to allow the shadows to be projected in any direction. What would be required is finding out where the vertcies of the model are in space after being rotated in such a way that their location relative to the shadow is below it. Well that's not a great explanation... basically you need to rotate the world so that the line of sight of the "light" casting the shadow points downward. That way you can use the cheap equations to find what polygons are inside the shadow volume.

Oh, and right now the code is set up so that the object shadows ar ebeing cast onto can't be rotated in any way, and it must be at 0,0,0 so the locations of the vertices within it are correct relative to the shadow. But fixing that is also trivial. At least the movement bit. The rotation is a little more costly to fix. Basicically this is good for casting shadows onto levels.

Oh and if you want to use this in a commercial game, send $10 via paypal, to sswift@.... I need the money. You can use it in shareware for free.

; -------------------------------------------------------------------------------------------------------------------
; Swift Shadow System 1.0 - Copyright 2002 Shawn C. Swift
; -------------------------------------------------------------------------------------------------------------------

	
Type Shadow_Caster
	Field Caster
	Field Receiver
	Field Texture
	Field Radius#
End Type
	
		
Type Shadow
	Field Mesh
End Type	


; -------------------------------------------------------------------------------------------------------------------
; This function sets up a new shadow caster/receiver pair.
;
; Caster   = Mesh to cast shadows.
; Receiver = Mesh to receive shadows.
; Texture  = Texture to use for shadow.  (Must have UV clamping enabled for proper operation!)
; Radius#  = Radius of the shadow.
; -------------------------------------------------------------------------------------------------------------------
Function Cast_Shadow(Caster, Receiver, Texture, Radius#)

	ThisCaster.Shadow_Caster = New Shadow_Caster	
	
	ThisCaster\Caster   = Caster
	ThisCaster\Receiver = Receiver
	ThisCaster\Texture  = Texture
	ThisCaster\Radius#  = Radius#

End Function


; -------------------------------------------------------------------------------------------------------------------
; This function deletes all old shadows and creates a shadow mesh for each shadow casting object.
;
; Note:
; Shadows only need to be repositioned and recreated each time the scene is rendered, 
; not every time the physics are updated.
; -------------------------------------------------------------------------------------------------------------------
Function Update_Shadows()

	Delete_Shadows()

	For ThisCaster.Shadow_Caster = Each Shadow_Caster
		 
		Shadow_Center_X# = EntityX#(ThisCaster\Caster, True)
		Shadow_Center_Y# = EntityY#(ThisCaster\Caster, True)
		Shadow_Center_Z# = EntityZ#(ThisCaster\Caster, True)
		Shadow_Radius#   = ThisCaster\Radius#		
		
		ThisShadow.Shadow = New Shadow
		ThisShadow\Mesh = Create_Shadow(ThisCaster\Receiver, Shadow_Center_X#-Radius#, Shadow_Center_Z#+Radius#, Shadow_Center_X#+Radius#, Shadow_Center_Z#-Radius#, Shadow_Center_Y#)
	
		EntityFX ThisShadow\Mesh, 1+2+8
		EntityBlend ThisShadow\Mesh, 2
		EntityTexture ThisShadow\Mesh, ThisCaster\Texture
		
	Next	
		
End Function


; -------------------------------------------------------------------------------------------------------------------
; This function deletes all active shadows.
; -------------------------------------------------------------------------------------------------------------------
Function Delete_Shadows()

	For ThisShadow.Shadow = Each Shadow
		FreeEntity ThisShadow\Mesh
	Next	

End Function


; -------------------------------------------------------------------------------------------------------------------
; This function deletes all shadow casters/recievers which reference the specified entity.
;
; When you delete an entity which is casting or receiving a shadow, you must call this function so that the game
; does not crash from trying to reference an entity which does not exist.
; -------------------------------------------------------------------------------------------------------------------
Function Delete_Shadow_Caster(Entity)

	For ThisCaster.Shadow_Caster = Each Shadow_Caster

		If (ThisCaster\Caster = Entity) Or (ThisCaster\Receiver = Entity) 
			Delete ThisCaster
		EndIf	

	Next	

End Function


; -------------------------------------------------------------------------------------------------------------------
; This function creates a shadow mesh and returns it's handle.
;
; Reciever = The entity to recieve the shadow.
; -------------------------------------------------------------------------------------------------------------------
Function Create_Shadow(Receiver, Shadow_X1#, Shadow_Z1#, Shadow_X2#, Shadow_Z2#, Shadow_Y#)

	; Get the cordinates of the lower left hand corner of the shadow.
	Shadow_Corner_X# = Shadow_X1#
	Shadow_Corner_Z# = Shadow_Z2# 

	Shadow_Scale# = Sqr(Shadow_X1#^2 - Shadow_X2#^2)

	ShadowTris = 0

	; Create the mesh for the shadow, and give it a surface to add polygons to.
	MESH_Shadow = CreateMesh()
	SURFACE_Shadow = CreateSurface(MESH_Shadow)

	; Loop through all triangles in all surfaces of the reciever.
	Surfaces = CountSurfaces(Receiver)
	For LOOP_Surface = 1 To Surfaces

		Surface_Handle = GetSurface(Receiver, LOOP_Surface)

		Tris = CountTriangles(Surface_Handle)
		For LOOP_Tris = 0 To Tris-1

			Vertex_0 = TriangleVertex(Surface_Handle, LOOP_Tris, 0)
			Vertex_1 = TriangleVertex(Surface_Handle, LOOP_Tris, 1)
			Vertex_2 = TriangleVertex(Surface_Handle, LOOP_Tris, 2)
									
			; Check to see if the triangle is inside the shadow's bounding rectangle.
			;
			; This test works by seeing if all of a triangle's points are on a specific side of each side of the
			; rectangle.  The test is not 100% accurate... a very few triangles will pass the test but actually be
			; outside the region.  But we are concerned only with making sure we find all the triangles which ARE
			; in the region and cull the vast majority outside the region, so a little sloppiness in the test is
			; okay if that means it's really fast.
			Shadow = True

			Z0# = VertexZ#(Surface_Handle, Vertex_0)
			Z1# = VertexZ#(Surface_Handle, Vertex_1)
			Z2# = VertexZ#(Surface_Handle, Vertex_2)

			; Is polygon to north of shadow's bounding rectangle?
			If (Z0# > Shadow_Z1#) And (Z1# > Shadow_Z1#) And (Z2# > Shadow_Z1#) 
				Shadow = False 
			Else	

				; Is polygon to south of shadow's bounding rectangle?
				If (Z0# < Shadow_Z2#) And (Z1# < Shadow_Z2#) And (Z2# < Shadow_Z2#) 
					Shadow = False	
				Else
					
					X0# = VertexX#(Surface_Handle, Vertex_0)
					X1# = VertexX#(Surface_Handle, Vertex_1)
					X2# = VertexX#(Surface_Handle, Vertex_2)

					; Is polygon to west of shadow's bounding rectangle?
					If (X0# < Shadow_X1#) And (X1# < Shadow_X1#) And (X2# < Shadow_X1#) 
						Shadow = False 
					Else

						; Is polygon to east of shadow's bounding rectangle?
						If (X0# > Shadow_X2#) And (X1# > Shadow_X2#) And (X2# > Shadow_X2#) 
							Shadow = False 
						Else

							Y0# = VertexY#(Surface_Handle, Vertex_0)
							Y1# = VertexY#(Surface_Handle, Vertex_1)
							Y2# = VertexY#(Surface_Handle, Vertex_2)

							; Is shadow below the polygon?
							If (Shadow_Y# < Y0#) And (Shadow_Y# < Y1#) And (Shadow_Y# < Y2#)
								Shadow = False
							Else
								
								NY0# = VertexNY#(Surface_Handle, Vertex_0) 
								NY1# = VertexNY#(Surface_Handle, Vertex_1) 
								NY2# = VertexNY#(Surface_Handle, Vertex_2)

								FaceNY# = NY0# + NY1# + NY2#								

								; Is polygon facing downwards?
								If (FaceNY# < 0) Then
									Shadow = False
								Else
								
									If (FaceNY# = 0)
										Shadow = False
									EndIf	
									
								EndIf
							EndIf
						EndIf
					EndIf
				EndIf
			EndIf	
			
			; If the triangle is inside the bounding box then add it to the mesh.
			; Note that shadow texture will be flipped top to bottom because of how UV coordinates are assigned to the vertices from the world coordinates.
			If Shadow = True

				Shadow_Vertex_0 = AddVertex(SURFACE_Shadow, X0#, Y0#, Z0#, (X0#-Shadow_Corner_X#)/Shadow_Scale#, (Z0#-Shadow_Corner_Z#)/Shadow_Scale#)
				Shadow_Vertex_1 = AddVertex(SURFACE_Shadow, X1#, Y1#, Z1#, (X1#-Shadow_Corner_X#)/Shadow_Scale#, (Z1#-Shadow_Corner_Z#)/Shadow_Scale#)
				Shadow_Vertex_2 = AddVertex(SURFACE_Shadow, X2#, Y2#, Z2#, (X2#-Shadow_Corner_X#)/Shadow_Scale#, (Z2#-Shadow_Corner_Z#)/Shadow_Scale#)

				; Adjust shadow brightness at each vertex according to vertex normal.				
					VC = (1.0-NY0#)^3*255.0
					If (VC > 0) Then VertexColor SURFACE_Shadow, Shadow_Vertex_0, VC, VC, VC
	
					VC = (1.0-NY1#)^3*255.0
					If (VC > 0) Then VertexColor SURFACE_Shadow, Shadow_Vertex_1, VC, VC, VC
				
					VC = (1.0-NY2#)^3*255.0
					If (VC > 0) Then VertexColor SURFACE_Shadow, Shadow_Vertex_2, VC, VC, VC

				; Add the triangle to the shadow mesh.
					AddTriangle(SURFACE_Shadow, Shadow_Vertex_0, Shadow_Vertex_1, Shadow_Vertex_2)

				ShadowTris = ShadowTris + 1

			EndIf
			
		Next			
	
	Next

	Return MESH_Shadow

End Function