;BlitzGrass
;Copyright (C) John Judnich

;Basic grass type - contains a brush with the grass image
Type BG_GrassType
	Field Brush
	Field Texture, File$
End Type

;Grass layer - defines a type of grass to be applied to the terrain
Type BG_GrassLayer
	Field GrassType.BG_GrassType
	
	Field Xscale#, Yscale#
	Field Density
	
	Field GrassMap
End Type

;Grass block - contains many single-surface grass props
Type BG_GrassBlock
	Field GrassType.BG_GrassType
	Field Mesh, Surf
	Field Alpha#, Active, oldActive
	Field TimeOut
End Type

Global BG_Terrain
Global BG_TerrainSize#

Global BG_ViewRange#
Global BG_DensityFactor#
Global BG_BlockSize#

Global BG_oldMilliSecs

Global BG_Gpivot

;Initializes BlitzGrass and sets the terrain which grass will be placed on (optional). Set TerrainScale#
;to the value you scaled the terrain along the X and Z axis.
Function BG_Init(Terrain = 0, TerrainScale# = 1)
	BG_Terrain = Terrain
	If Terrain Then BG_TerrainSize = TerrainSize(BG_Terrain) * TerrainScale
	
	BG_SetLOD(70, 1)
	If BG_Gpivot = 0 Then BG_Gpivot = CreatePivot()
End Function

;Adjusts the maximum view range, and density factor for grass. The density factor should range
;from 0 to 1, where 0 is no grass at all, and 1 is the full density for each grass layer.
Function BG_SetLOD(ViewRange#, DensityFactor#)
	BG_ViewRange = ViewRange
	BG_DensityFactor = DensityFactor
	BG_BlockSize = BG_ViewRange / 3
End Function

;Updates the grass system. Call this every loop before RenderWorld.
Function BG_Update(Camera)
	timestep = MilliSecs() - BG_oldMilliSecs
	BG_oldMilliSecs = MilliSecs()
	If timestep > 1000 Then timestep = 1000	;Not real-time
	
	;Update grass blocks
	For grs.BG_GrassBlock = Each BG_GrassBlock
		;Fade grass blocks in/out
		dx# = EntityX(Camera) - EntityX(grs\Mesh)
		dz# = EntityZ(Camera) - EntityZ(grs\Mesh)
		Dist# = Sqr(dx*dx + dz*dz)
		
		grs\Alpha = 1.0 - (Dist / BG_ViewRange)
		
		If grs\Alpha < 0 Then grs\Alpha = 0 Else If grs\Alpha > 1 Then grs\Alpha = 1
		
		If Dist > BG_ViewRange + BG_BlockSize Then grs\Active = False Else grs\Active = True
		
		EntityAlpha grs\Mesh, grs\Alpha
		
		If grs\Active <> grs\oldActive Then
			grs\oldActive = grs\Active
			If grs\Active Then ShowEntity grs\Mesh Else HideEntity grs\Mesh: grs\TimeOut = 0
		EndIf
		
		;Delete old grass blocks no longer active
		If grs\Active = False Then
			grs\TimeOut = grs\TimeOut + timestep
			
			;If the grass block has been inactive for over 3 seconds, delete it
			If grs\TimeOut > 3000 Then
				FreeEntity grs\Mesh
				Delete grs
			End If
		EndIf
	Next
	
	;Create grass blocks where needed
	ViewRange# = BG_ViewRange + BG_BlockSize
	BlockSize2# = BG_BlockSize * BG_BlockSize
	ViewRange2# = ViewRange * ViewRange
	Grids# = ViewRange / BG_BlockSize
	
	For xi# = -Grids To Grids
	For zi# = -Grids To Grids
		xd# = xi * BG_BlockSize
		zd# = zi * BG_BlockSize
		dist2# = xd*xd + zd*zd
		
		If dist2# < ViewRange2 Then
			x# = EntityX(Camera) + xd
			z# = EntityZ(Camera) + zd
			
			;First check if this section of 3D space is already occupied by a grass block
			Occupied = False
			For grs.BG_GrassBlock = Each BG_GrassBlock 
				If grs\Active Then
					dx# = x - EntityX(grs\Mesh)
					dz# = z - EntityZ(grs\Mesh)
					dist2# = dx*dx + dz*dz
					If dist2 <= BlockSize2 Then
						Occupied = True
						Exit
					End If
				End If
			Next
			
			;If not, then create a new grass block here
			If Not Occupied Then
				For em.BG_GrassLayer = Each BG_GrassLayer
					BG_sysCreateBlock(em, x, z)
				Next
			EndIf
		End If
	Next
	Next

End Function

;Loads a grass layer. GrassImage$ should be a flat image of a grass billboard (usually masked or with alpha
;transparency). By default, this grass layer will show up everywhere in your world (an infinite amount
;of grass). To set regions you want the grass to appear or not, see BG_SetGrassMap().
Function BG_LoadGrass(GrassImage$, Flags = 1+2+16+32)
	em.BG_GrassLayer = New BG_GrassLayer
	
	;Find if the given grass type already exists
	GrassImage = Lower(BG_sysGlobalPath(GrassImage))
	For typ.BG_GrassType = Each BG_GrassType
		If typ\File = GrassImage Then
			em\GrassType = typ
			Exit
		EndIf
	Next
	
	;Load the grass type if it does not exist
	If em\GrassType = Null Then
		typ.BG_GrassType = New BG_GrassType
		typ\Texture = 0: typ\File = GrassImage
		typ\Brush = LoadBrush(typ\File, Flags)
		If typ\Brush = 0 Then
			Delete typ
			Delete em
			Return 0
		End If
		em\GrassType = typ
	End If
	
	;GrassMap array
	em\GrassMap = BG_sysArrayDim(256, 256)
	For x = 0 To 256
	For y = 0 To 256
		BG_sysArraySet(em\GrassMap, x, y, 255)
	Next
	Next
	
	;Default values
	em\Xscale = 1: em\Yscale = 1
	em\Density = 500
	
	Return Handle(em)
End Function

;Scales a layer's grass billboard originally loaded with BG_LoadGrass()
Function BG_SetScale(Layer, x#, y#)
	em.BG_GrassLayer = Object.BG_GrassLayer(Layer)
	
	em\Xscale = x: em\Yscale = y
End Function

;Get's the grass billboard's width scale
Function BG_GetScaleX#(Layer)
	em.BG_GrassLayer = Object.BG_GrassLayer(Layer)
	
	Return em\Xscale
End Function

;Get's the grass billboard's height scale
Function BG_GetScaleY#(Layer)
	em.BG_GrassLayer = Object.BG_GrassLayer(Layer)
	
	Return em\Yscale
End Function

;Specify an image which will be used to determine where grass will be placed on the terrain. Black pixels
;will result in no grass at that location, while white pixels will result in full grass density.
Function BG_SetGrassMap(Layer, MapFile$)
	em.BG_GrassLayer = Object.BG_GrassLayer(Layer)
	Img = LoadImage(MapFile)
	Width = ImageWidth(Img)-1
	Height = ImageHeight(Img)-1
	Buff = ImageBuffer(Img)
	
	LockBuffer Buff
	
	For x = 0 To 255
	For y = 0 To 255
		rgb = ReadPixelFast(255*x/Width, Height-(255*y/Height), Buff)
		density = (rgb Shr 8) And $FF
		BG_sysArraySet(em\GrassMap, x, y, density)
	Next
	Next
	
	UnlockBuffer Buff
End Function

;Sets the relative density for the layer.
Function BG_SetDensity(Layer, Density)
	em.BG_GrassLayer = Object.BG_GrassLayer(Layer)
	
	em\Density = Density
End Function

;Returns the relative density for the layer.
Function BG_GetDensity(Layer)
	em.BG_GrassLayer = Object.BG_GrassLayer(Layer)
	
	Return em\Density
End Function

;-----------------------------------------------------------------------------------------
;These functions (BG_sys.. function) are functions which should be called only by BlitzGrass
;internally. Attempting to call any of these functions may cause unexpected errors.

Function BG_sysCreateBlock.BG_GrassBlock(em.BG_GrassLayer, x#, z#)
	grs.BG_GrassBlock = New BG_GrassBlock
	
	grs\Mesh = CreateMesh()
	grs\Surf = CreateSurface(grs\Mesh, em\GrassType\Brush)
	PositionEntity grs\Mesh, x, 0, z
	EntityFX grs\Mesh, 1+16
	
	MeshX# = EntityX(grs\Mesh)
	MeshZ# = EntityZ(grs\Mesh)
	
	BillboardCount = BG_DensityFactor * em\Density * BG_BlockSize * BG_BlockSize / 544
	scl# = 256 / BG_TerrainSize
	
	For i = 1 To BillboardCount
		xp# = Rnd(x - BG_BlockSize, x + BG_BlockSize)
		zp# = Rnd(z - BG_BlockSize, z + BG_BlockSize)
		
		Density = BG_sysArrayGet(em\GrassMap, scl*xp, scl*zp)
		If Rand(0, 255) < Density Then
			PositionEntity BG_Gpivot, xp, 0, zp
			RotateEntity BG_Gpivot, 0, Rnd(-180, 180), 0
			sc# = Rnd(.8, 1.2)
			ScaleEntity BG_Gpivot, sc*em\Xscale, sc*em\Yscale, 1;sc*em\Zscale
			
			;TFormPoint 0, -.707, 0, BG_Gpivot, grs\Mesh
			;v1 = AddVertex(grs\Surf, TFormedX(), TFormedY()+TerrainY(BG_Terrain, TFormedX()+MeshX, 0, TFormedZ()+MeshZ), TFormedZ(), .5,1.7)
			;TFormPoint -1.2, 1, 0, BG_Gpivot, grs\Mesh
			;v2 = AddVertex(grs\Surf, TFormedX(), TFormedY()+TerrainY(BG_Terrain, TFormedX()+MeshX, 0, TFormedZ()+MeshZ), TFormedZ(), -.65,0)
			;TFormPoint 1.2, 1, 0, BG_Gpivot, grs\Mesh
			;v3 = AddVertex(grs\Surf, TFormedX(), TFormedY()+TerrainY(BG_Terrain, TFormedX()+MeshX, 0, TFormedZ()+MeshZ), TFormedZ(), 1.65,0)
			
			;AddTriangle grs\Surf, v1, v2, v3
			
			If TerrainY(BG_Terrain, TFormedX()+MeshX, 0, TFormedZ()+MeshZ)>0 Then
			
			If BG_Terrain <> 0 Then
				TFormPoint -.5, 1, 0, BG_Gpivot, grs\Mesh
				v1 = AddVertex(grs\Surf, TFormedX(), TFormedY()+TerrainY(BG_Terrain, TFormedX()+MeshX, 0, TFormedZ()+MeshZ), TFormedZ(), 0, 0)
				TFormPoint .5, 1, 0, BG_Gpivot, grs\Mesh
				v2 = AddVertex(grs\Surf, TFormedX(), TFormedY()+TerrainY(BG_Terrain, TFormedX()+MeshX, 0, TFormedZ()+MeshZ), TFormedZ(), 1, 0)
				TFormPoint .5, 0, 0, BG_Gpivot, grs\Mesh
				v3 = AddVertex(grs\Surf, TFormedX(), TFormedY()+TerrainY(BG_Terrain, TFormedX()+MeshX, 0, TFormedZ()+MeshZ), TFormedZ(), 1, 1)
				TFormPoint -.5, 0, 0, BG_Gpivot, grs\Mesh
				v4 = AddVertex(grs\Surf, TFormedX(), TFormedY()+TerrainY(BG_Terrain, TFormedX()+MeshX, 0, TFormedZ()+MeshZ), TFormedZ(), 0, 1)
			Else
				TFormPoint -.5, 1, 0, BG_Gpivot, grs\Mesh
				v1 = AddVertex(grs\Surf, TFormedX(), TFormedY(), TFormedZ(), 0, 0)
				TFormPoint .5, 1, 0, BG_Gpivot, grs\Mesh
				v2 = AddVertex(grs\Surf, TFormedX(), TFormedY(), TFormedZ(), 1, 0)
				TFormPoint .5, 0, 0, BG_Gpivot, grs\Mesh
				v3 = AddVertex(grs\Surf, TFormedX(), TFormedY(), TFormedZ(), 1, 1)
				TFormPoint -.5, 0, 0, BG_Gpivot, grs\Mesh
				v4 = AddVertex(grs\Surf, TFormedX(), TFormedY(), TFormedZ(), 0, 1)
			End If
			
			AddTriangle grs\Surf, v1, v2, v3
			AddTriangle grs\Surf, v1, v3, v4
			
			End If
		End If
	Next
	
	grs\Alpha = 0
	grs\Active = False
	grs\oldActive = False
	EntityAlpha grs\Mesh, 0
	HideEntity grs\Mesh
	
	Return grs
End Function

Function BG_sysGlobalPath$(path$)
	If FileType(CurrentDir() + path) = 1 Then
		Return CurrentDir() + path
	Else
		Return path
	End If
End Function

Function BG_sysArrayDim(xsize, ysize)
	arraybank = CreateBank((xsize+1)*(ysize+1)*1 + 8)
	PokeInt arraybank, 0, xsize+1
	PokeInt arraybank, 4, ysize+1
	Return arraybank
End Function

Function BG_sysArraySet(arraybank, x, y, value#)
	If x > PeekInt(arraybank, 0) Then RuntimeError "Grass position out of bounds!"
	If y > PeekInt(arraybank, 4) Then RuntimeError "Grass position out of bounds!"
	el = (y*PeekInt(arraybank,0) + x) + 8
	PokeInt arraybank, el, value
End Function

Function BG_sysArrayGet(arraybank, x, y)
	;If x > PeekInt(arraybank, 0) Then RuntimeError "Grass position out of bounds!"
	;If y > PeekInt(arraybank, 4) Then RuntimeError "Grass position out of bounds!"
	el = (y*PeekInt(arraybank,0) + x) + 8
	Return PeekByte(arraybank, el)
End Function

Function BG_sysArrayFree(arraybank)
	FreeBank arraybank
End Function