BattleTanks II Beta Release

Miscellaneous Forums/Blitz Showcase/BattleTanks II Beta Release

blitznerd: Here you go:
;Terrain Shadow Library
;Copyright (C) 2006 John Judnich

;NOTE: Any shadow may not span more than SHADOW_TILES terrain tiles

;TODO:
;+	Ignore update of shadows behind camera
;+	Add frame-skip optimization with interpolated fill-in
;	 Add automatic shadow contour rendering

Const SHADOW_TILES = 256
Const Y_OFFSET# = .7
Const EDGE_Y_OFFSET# = .3

Global ShadowTerrain
Global ShadowTerrainScale#
Global ShadowPivot
Global ShadowFrameSkip = 1
Global FrameCount = 0

Type TerrainShadow
	Field Entity
	Field LastX#, LastY#, LastZ#
	Field LastPitch#, LastYaw#, LastRoll#
	
	Field Size#
	
	Field ShadowTexture
	Field ShadowMesh
	Field ShadowSurface
	
	Field ViewDist#
	Field Visible
	Field Hidden
	
	Field Alpha#
	Field Scale#
End Type


;This initializes the shadow system, making it ready for use. Basically, this just
;sets a variable to a terrain so the shadow system knows what to cast the shadows on.
;Optionally, you can set FrameSkip to adjust how many frames must elapse before the
;shadow mesh is regenerated to fit the terrain's shape. 0 will keep the shadow up to
;date at all times, while higher values may cause some visual "glitches" (mostly on
;mountainous terrains where the shape under the shadow caster changes constantly),
;although the shadow system will run much faster. The default value for FrameSkip is
;1, which works well for almost any terrains.
Function ShadowsInit(Terrain, TerrainScale#, FrameSkip = 1)
	ShadowTerrain = Terrain
	ShadowTerrainScale# = TerrainScale
	ShadowFrameSkip = FrameSkip
	FrameCount = FrameSkip
	If ShadowPivot = 0 Then ShadowPivot = CreatePivot()
End Function


;Use this function to turn on shadows for a specified entity. After calling this function,
;you should see it's shadow on your terrain (as long as you be sure to call UpdateShadows()
;before RenderWorld. "ShadowTexure" provides the option to specify your own shadow, instead
;of letting the shadow system automatically render it. Specifying your own shadow can improve
;loading time with many many entities. Note: When specifying a custom shadow image, you only
;need to do it once - after you do, you can omit the ShadowTexture parameter.
Function ShowShadow(Entity, ShadowTexture = 0)
	objnum = Int(EntityName$(Entity))
	obj.TerrainShadow = Object.TerrainShadow(objnum)
	
	If obj = Null Or ShadowTexture <> 0 Then ;If there is no shadow for Entity, or it's texture is being changed, then make a new one
		;If the shadow texture is being changed, delete the shadow and start over
		If obj <> Null Then DeleteShadow(Entity)
		
		;Create a new shadow
		obj.TerrainShadow = New TerrainShadow
		NameEntity Entity, Handle(obj)
		obj\Entity = Entity
		
		obj\Size = MeshWidth(Entity)
		depth# = MeshDepth(Entity)
		If depth > obj\Size Then obj\Size = depth
		
		obj\ShadowMesh = CreateMesh()
		EntityFX obj\ShadowMesh, 1+32
		EntityBlend obj\ShadowMesh, 1
		obj\ShadowSurface = CreateSurface(obj\ShadowMesh)
		
		HideEntity obj\ShadowMesh

		obj\Alpha = .7
		obj\ViewDist = 0
		obj\Scale = 1
		obj\Hidden = False
		obj\Visible = False
				
		;Render the shadow if a texture is not specified, otherwise use the given texture
		If ShadowTexture = 0 Then
			;TODO: Render soft shadow to texture
		Else
			obj\ShadowTexture = ShadowTexture
		End If
		
		;Apply texture
		EntityTexture obj\ShadowMesh, obj\ShadowTexture
	Else
		If obj\Hidden = True Then
			;Un-hide the shadow if it already exists
			ShowEntity obj\ShadowMesh
			obj\Hidden = False
		End If
	End If
End Function


;This can be used to temporarily turn off a shadow for an object. If you do not plan to show the
;shadow again, DeleteShadow() can be used instead, which deletes it from memory.
Function HideShadow(Entity)
	objnum = Int(EntityName$(Entity))
	obj.TerrainShadow = Object.TerrainShadow(objnum)
	
	If obj <> Null Then
		If obj\Hidden = False Then
			;Hide the shadow if it exists
			HideEntity obj\ShadowMesh
			obj\Hidden = True
		End If
	End If
End Function


;This turns off an object's shadow, deleting the shadow from memory. You must also be sure to call
;this function before deleting a shadowed entity, because the shadow system may crash, trying to
;access a deleted object when rendering the shadow.
Function DeleteShadow(Entity)
	objnum = Int(EntityName$(Entity))
	obj.TerrainShadow = Object.TerrainShadow(objnum)
	
	If obj <> Null Then
		;Delete the shadow if it exists
		FreeEntity obj\ShadowMesh
		Delete obj
	Else
		DebugLog "Warning: Tried to delete a non-existant shadow"
	End If
End Function


;This sets the view distance for a shadow. This can be used to increase performance when you have a
;very large amount of objects casting shadows. To disable the view distance limit, set the view
;distance value to 0. Note: When an object reaches the end of it's view distance, the shadow is not
;instantly hidden, the shadow's visibility varies with the object's distance from the camera. Default: 0
Function ShadowViewDistance(Entity, ViewDistance#)
	objnum = Int(EntityName$(Entity))
	obj.TerrainShadow = Object.TerrainShadow(objnum)
	
	obj\ViewDist = ViewDistance	
End Function


;This sets the alpha (transparency) value of a shadow. Values near 0 produce shadows that are barely visible,
;while values near 1 produce shadows almost completely opaque. Default: 0.6
Function ShadowAlpha(Entity, Alpha#)
	objnum = Int(EntityName$(Entity))
	obj.TerrainShadow = Object.TerrainShadow(objnum)
	
	obj\Alpha = Alpha
End Function


;You can use this function to adjust the size of an object's shadow. Default: 1.0
Function ShadowScale(Entity, Scale#)
	objnum = Int(EntityName$(Entity))
	obj.TerrainShadow = Object.TerrainShadow(objnum)
	
	obj\Scale = Scale 
End Function


;This is a completely optional function which you can use to pre-load shadows for an image, without
;actually showing them yet. You can use this during your loading screen if you are displaying shadows
;for a very large number of objects, and you are using the shadow system's built-in shadow render
;(which can be slow when preparing many objects' shadows). In most cases, however, this function is
;completely unnecessary and you don't need to use it, since the shadow system will automatically prepare
;an object's shadow the first time you call ShowShadow() for an entity.
Function PrepareShadow(Entity, ShadowTexture)
	objnum = Int(EntityName$(Entity))
	obj.TerrainShadow = Object.TerrainShadow(objnum)
	
	If obj = Null Then
		ShowShadow(Entity, ShadowTexture)
		HideShadow(Entity)
	End If
End Function


;This function updates all object's shadows so they appear correctly on your terrain. It is very important that
;you call this function before RenderWorld each loop or shadows may appear incorrectly. "Cam" must be set to
;your game's main camera for this to operate properly.
Function UpdateShadows(cam)
	FrameCount = FrameCount + 1
	If FrameCount > ShadowFrameSkip Then FrameCount = 0

	For shadow.TerrainShadow = Each TerrainShadow
		;Only consider shadows the user did not hide
		If shadow\Hidden = False Then
		
			x# = EntityX(shadow\Entity, True)
			y# = EntityY(shadow\Entity, True)
			z# = EntityZ(shadow\Entity, True)
			pitch# = EntityPitch(shadow\Entity, True)
			yaw# = EntityYaw(shadow\Entity, True)
			roll# = EntityRoll(shadow\Entity, True)
			
			;Calculate shadow alpha
			If shadow\ViewDist <> 0 Then
				xd# = EntityX(cam, True) - x
				yd# = EntityY(cam, True) - y
				zd# = EntityZ(cam, True) - z
				dist# = Sqr(xd*xd + yd*yd + zd*zd)
				
				alpha# = (shadow\ViewDist - dist) / shadow\ViewDist
			Else
				alpha# = 1
			End If
			alpha = alpha * shadow\Alpha
			If alpha < 0 Then alpha = 0
			
			;Calculate the visibility status of the shadow
			If alpha <> 0 Then
				;Check if the shadow is behind the camera or not
				TerrY# = TerrainY(ShadowTerrain, x, EntityY(ShadowTerrain), z)
				If PointVisible(cam, x, TerrY, z) Then
					Visible = True
				Else
					Visible = False
				End If
			Else
				Visible = False
			End If
			
			;If the shadow is visible, update it
			If Visible Then
				;Only update the shadow mesh if it's entity has moved/rotated
				If shadow\Visible <> Visible Or x <> shadow\LastX Or y <> shadow\LastY Or z <> shadow\LastZ Or pitch <> shadow\LastPitch Or yaw <> shadow\LastYaw Or roll <> shadow\LastRoll Then 
					If FrameCount = 0 Then
						;Regenerate the shadow mesh
						CreateTerrainDecal(shadow\ShadowMesh, shadow\ShadowSurface, x-shadow\Size*.5*shadow\Scale*1.4142, z-shadow\Size*.5*shadow\Scale*1.4142, x+shadow\Size*.5*shadow\Scale*1.4142, z+shadow\Size*.5*shadow\Scale*1.4142, pitch#, yaw#, roll#)
					Else
						;"Fake" mesh regeneration
						TranslateEntity shadow\ShadowMesh, x - shadow\LastX, y - shadow\LastY, z - shadow\LastZ
						TurnEntity shadow\ShadowMesh, 0, yaw - shadow\LastYaw, 0
					End If
				End If
				;Update the alpha
				EntityAlpha shadow\ShadowMesh, alpha
			End If
			
			;Hide/show the entity as necessary
			If shadow\Visible <> Visible Then
				If Visible Then
					ShowEntity shadow\ShadowMesh
				Else
					HideEntity shadow\ShadowMesh
				End If
				shadow\Visible = Visible
			End If
			
			shadow\LastX = x
			shadow\LastY = y
			shadow\LastZ = z
			shadow\LastPitch = pitch
			shadow\LastYaw = yaw
			shadow\LastRoll = roll
		End If
	Next
End Function


;****************** Internal Functions *******************
Dim VertGrid(SHADOW_TILES, SHADOW_TILES)
Function CreateTerrainDecal(mesh, surface, x1#, z1#, x2#, z2#, pitch#, yaw#, roll#)
	ClearSurface surface
	
	midx# = (x2 + x1) * .5
	midz# = (z2 + z1) * .5

	PositionEntity mesh, midx, 0, midz
	RotateEntity mesh, 0, 0, 0

	x1grid = Floor(x1 / ShadowTerrainScale)
	z1grid = Floor(z1 / ShadowTerrainScale)
	x2grid = Ceil(x2 / ShadowTerrainScale)
	z2grid = Ceil(z2 / ShadowTerrainScale)
	
	For xg = x1grid To x2grid
	For zg = z1grid To z2grid
		edge = False
	
		vertx# = xg * ShadowTerrainScale
		vertz# = zg * ShadowTerrainScale
		If xg = x1grid Then vertx = x1: edge = True
		If xg = x2grid Then vertx = x2: edge = True
		If zg = z1grid Then vertz = z1: edge = True
		If zg = z2grid Then vertz = z2: edge = True
		
		verty# = TerrainY(ShadowTerrain, vertx + EntityX(ShadowTerrain), EntityY(ShadowTerrain), vertz + EntityZ(ShadowTerrain))
		
		vertx# = vertx# + EntityX(ShadowTerrain)
		vertz# = vertz# + EntityZ(ShadowTerrain)
		If edge = False Then verty# = verty# + Y_OFFSET Else verty# = verty# + EDGE_Y_OFFSET#
		
		u# = (vertx-x1) / (x2-x1)
		v# = (vertz-z1) / (z2-z1)
		
		RotateEntity ShadowPivot, 0, -(yaw+180), 0
		ScaleEntity ShadowPivot, 1.4142, 1.4142, 1.4142
		TFormVector u - .5, 0, v - .5, ShadowPivot, 0
		u# = TFormedX() + .5
		v# = TFormedZ() + .5
		
		VertGrid(xg-x1grid, zg-z1grid) = AddVertex(surface, vertx - midx, verty, vertz - midz, u, v)
	Next
	Next
		
	For xg = 1 To (x2grid-x1grid)
	For zg = 1 To (z2grid-z1grid)
		AddTriangle surface, VertGrid(xg, zg), VertGrid(xg, zg-1), VertGrid(xg-1, zg-1)
		AddTriangle surface, VertGrid(xg-1, zg), VertGrid(xg, zg), VertGrid(xg-1, zg-1)
	Next
	Next
End Function

Function PointVisible(Viewer, x#, y#, z#)
	TFormPoint x, y, z, 0, Viewer
	If TFormedZ() <= 0 Then Return False Else Return True
End Function


When calling ShowShadow(), be sure to specify a shadow texture (which is applied to the terrain), because the current system can't render it for you yet.

CreateTerrainDecal(mesh, surface, x1#, z1#, x2#, z2#, pitch#, yaw#, roll#) is also useful for applying other decals to the terrain, like the shell craters.


Grisu: Thanks for the suggestions :) I hope to add some of those in the future. For now, you can download some nice mods others have made for BattleTanks II (add-on tanks and maps) here:
[a http://www.bundysoft.com/dokuwiki/doku.php?id=projects:johnj#battletanks_ii]BattleTanks II Expansion Packs[/a]
(scroll down to "Expansion Packs & Add-Ons")

If you want to create your own maps for BattleTanks II, read this:
[a http://www.bundysoft.com/dokuwiki/doku.php?id=projects:johnj:expanding_battletanks_ii]BattleTanks II Map-Making[/a]