Plaing a grass-mesh on the map

Blitz3D Forums/Blitz3D Programming/Plaing a grass-mesh on the map

Okay, here's some sample code that does what I was talking about. It is very tweakable. Here's the run down:

1) You'll need a grass model. I agree with the above poster, you could just use two sprites. But for me the model is a cleaner solution (since I can potentially do more with it). It is easy enough to change if you want to go with sprites. You also need some texture for the ground. For this demo I went with mossybrick01.jpg, which I believe came from one of Mark's samples (the castle one?).

2) This isn't trying to be super efficient at the low-level. It is trying to be efficient at the high level. The purpose of the sample code was to demo the algorithm, so stay focused on that.

3) You can very easily control the density, distance, etc. of the grass. I should have put in most consts at the top, but I didn't.

4) The point of this code was to draw grass around the moving player, but never having to store more than just what the player can see. Yet always look good, never pop. It is not designed to allow you to custom craft where the grass goes. This is SeriousSam2 style grass.

5) Yes we could animate the grass if you wanted.

6) Yes you could have multiple grass models.

7) It is designed so you only need the GrassSystem type, the grass functions (GrassSystemXXXX) and theGrassSystem global singleton. You just have to edit GrassSystemGetHeightAtPoint() to return the height of your terrain at the specified point. Blitz built in terrain can do this in one function. If you use your own mesh, just calculate it (for testing, don't worry about interpolation). On a side note, for those of you evaluating terrain engines, pay attention to whether or not the engine supplies a means of getting the height at an arbitrary point.

I feel sure I'm forgetting something...
Ken



;---------------------------------------------------
; GrassTest -- simple test app to demonstrate a grass
;              algorithm.  Send questions/bugs to
;              harward@...
;
; Depends on a Grass.b3d file.  Use any old .b3d as a
; placeholder if need be.
;----------------------------------------------------

; Constants
Const KEY_W								= 17
Const KEY_A								= 30
Const KEY_S								= 31
Const KEY_D								= 32
Const KEY_ESC							= 1
Const KEY_ARROW_LEFT					= 205
Const KEY_ARROW_RIGHT					= 203
Const PLAYER_ROTATION_SPEED#			= 1.0
Const PLAYER_MOVEMENT_SPEED#			= 1.0
Const GRASS_MAX_GRASS					= 500
Const GRASS_FADE_NEAR_DISTANCE#			= 200.0
Const GRASS_FADE_FAR_DISTANCE#			= 400.0
Const GRASS_MAX_DISTANCE#				= 500.0
Const GRASS_RANGE_DISTANCE#				= GRASS_MAX_DISTANCE - GRASS_FADE_FAR_DISTANCE


; Grass System data
Type GrassSystem
	Field		grassFilename$
	Field		grassModel
	Field		grassDensity#
	Field		grassScaleFactor#
	Field		grassCount%
	Field		grassPlaced%
	Field		lastPlayerPivot%
	Field		seeded%
	Field		grass%[ GRASS_MAX_GRASS ]
End Type


; Player data
Type Player
	Field		turnLeft%
	Field		turnRight%
	Field		moveForward%
	Field		moveLeft%
	Field		moveBackward%
	Field		moveRight%
	Field		camera
End Type


; Globals
Global theGrassSystem.GrassSystem		= Null
Global thePlayer.Player					= Null

main()
End


; start of program
Function main()
	startUp()
	mainLoop()
	shutDown()
End Function

; Sets the game up
Function startUp()
	Graphics3D( 1024, 768, 32, 0 )
	SetBuffer( BackBuffer() )
	SeedRnd( MilliSecs() )
	
	Local plane = CreatePlane()
	Local texture = LoadTexture( "mossybrick01.jpg" )
	ScaleTexture( texture, 256.0, 256.0 )
	EntityTexture( plane, texture )
	EntityFX( plane, 1 )
	
	GrassSystemConstructor( "grass01.b3d", 0.2 )
	PlayerConstructor()
End Function


; The main loop
Function mainLoop()
	Local playing = True
	While ( playing )		
		playing = getInput()
		update()
		draw()
	Wend
End Function

; shuts down the game
Function shutDown()
	PlayerDestructor()
	GrassSystemDestructor()
;	TerrainSystemDestructor()
End Function

; Updates the world
Function update()
	PlayerUpdate()
	GrassSystemUpdate( EntityX( thePlayer\camera ), EntityY( thePlayer\camera ), EntityZ( thePlayer\camera ) )
	UpdateWorld()
End Function

; Draws the world
Function draw()
	RenderWorld()
	Text( 20, 20, "Grass count: " + theGrassSystem\grassCount )
	Text( 20, 40, "Grass added: " + theGrassSystem\grassPlaced )
	Flip()
End Function

; Moves the player
Function getInput%()
	
	thePlayer\moveForward		= False
	thePlayer\moveBackward		= False
	thePlayer\moveLeft			= False
	thePlayer\moveRight			= False
	thePlayer\turnLeft			= False
	thePlayer\turnRight			= False
	
	If ( KeyDown( KEY_W ) )
		thePlayer\moveForward	= True
	EndIf
	If ( KeyDown( KEY_A ) )
		thePlayer\moveLeft		= True
	EndIf
	If ( KeyDown( KEY_S ) )
		thePlayer\moveBackward	= True
	EndIf
	If ( KeyDown( KEY_D ) )
		thePlayer\moveRight		= True
	EndIf
	If ( KeyDown( KEY_ARROW_LEFT ) )
		thePlayer\turnLeft		= True
	EndIf
	If ( KeyDown( KEY_ARROW_RIGHT ) )
		thePlayer\turnRight		= True
	EndIf
	
	If ( KeyDown( KEY_ESC ) )	Return False
		
	Return True
End Function



;--------------------------------------------------------
;               G R A S S   F U N C T I O N S
;--------------------------------------------------------
; Constructor
Function GrassSystemConstructor.GrassSystem( filename$, scaleFactor#  )
	If ( theGrassSystem <> Null )	RuntimeError( "GrassSystem already instantiated" )
		
	theGrassSystem					= New GrassSystem
	theGrassSystem\grassScaleFactor	= scaleFactor
	theGrassSystem\grassFilename	= filename
	theGrassSystem\grassModel		= LoadMesh( theGrassSystem\grassFilename )
	theGrassSystem\lastPlayerPivot	= CreatePivot()
	theGrassSystem\grassCount		= 0
	theGrassSystem\grassPlaced		= 0
	theGrassSystem\seeded			= False
	
	For grassIndex = 0 To GRASS_MAX_GRASS
		theGrassSystem\grass[ grassIndex ] = 0 
	Next
	
	ScaleEntity( theGrassSystem\grassModel, scaleFactor, scaleFactor, scaleFactor )
	EntityAutoFade( theGrassSystem\grassModel, GRASS_FADE_NEAR_DISTANCE, GRASS_FADE_FAR_DISTANCE )
	HideEntity( theGrassSystem\grassModel )
End Function

; Destructor
Function GrassSystemDestructor()
	If ( theGrassSystem\grassModel )
		FreeEntity( theGrassSystem\grassModel )
		theGrassSystem\grassModel	= 0
	EndIf
	
	Delete theGrassSystem
	theGrassSystem	= Null
End Function


; Places grass if player moved (or we haven't seeded yet )
Function GrassSystemUpdate( playerX#, playerY#, playerZ# )
	Local playerPivot		= CreatePivot()
	PositionEntity( playerPivot, playerX, playerY, playerZ )
	
	If ( theGrassSystem\seeded = False )
		PositionEntity( theGrassSystem\lastPlayerPivot, playerX, playerY, playerZ )
		GrassSystemAddGrass( 0, GRASS_MAX_DISTANCE )
		theGrassSystem\seeded	= True
		Return
	EndIf
	
	; check to see if we need to add more grass
	If ( EntityDistance( playerPivot, theGrassSystem\lastPlayerPivot ) > GRASS_RANGE_DISTANCE )
		
		; destroy out of range grass
		For grassIndex = 0 To GRASS_MAX_GRASS
			Local grass = theGrassSystem\grass[ grassIndex ]
			
			If ( grass <> 0 )
				If ( EntityDistance( grass, playerPivot ) > GRASS_MAX_DISTANCE )
					FreeEntity( grass )
					theGrassSystem\grass[ grassIndex ]	= 0
					theGrassSystem\grassCount			= theGrassSystem\grassCount - 1
				EndIf
			EndIf
		Next
		
		; add new grass
		PositionEntity( theGrassSystem\lastPlayerPivot, playerX, playerY, playerZ )
		GrassSystemAddGrass( GRASS_FADE_FAR_DISTANCE, GRASS_MAX_DISTANCE )
	EndIf
	
	FreeEntity( playerPivot )
End Function


; Places grass in a ring around the player, typically outside viewing range
Function GrassSystemAddGrass( minRadius#, maxRadius# )
	Local playerX#				= EntityX( theGrassSystem\lastPlayerPivot )
	Local playerZ#				= EntityZ( theGrassSystem\lastPlayerPivot )
	Local grassToPlace			= GRASS_MAX_GRASS - theGrassSystem\grassCount	
	
	theGrassSystem\grassPlaced	= 0
	For grassIndex = 0 To grassToPlace-1
		Local radius#			= Rnd( minRadius, maxRadius )
		Local degrees#			= Rnd( 0.0, 359.0 )
		Local x#				= radius * Cos( degrees ) + playerX
		Local z#				= radius * Sin( degrees ) + playerZ
		Local y#				= GrassSystemGetHeightAtPoint( x, z )
		If ( GrassSystemPlaceGrass( x, y, z ) = False )
			Return ; we cannot place anymore grass right now
		EndIf
	Next
End Function


; Add grass entity
Function GrassSystemPlaceGrass%( x#, y#, z# )
	Local grassEntity = CopyEntity( theGrassSystem\grassModel )
	PositionEntity( grassEntity, x, y, z )
	EntityAutoFade( grassEntity, GRASS_FADE_NEAR_DISTANCE, GRASS_FADE_FAR_DISTANCE )

	For grassIndex = 0 To GRASS_MAX_GRASS
		If ( theGrassSystem\grass[ grassIndex ] = 0 )
			theGrassSystem\grass[ grassIndex ]	= grassEntity
			theGrassSystem\grassCount			= theGrassSystem\grassCount + 1
			theGrassSystem\grassPlaced			= theGrassSystem\grassPlaced + 1
			Return True
		EndIf
	Next
	Return False
End Function

; This is the only function that you need to edit.  Just
; have it return the height (y-value) of your terrain at
; the specified point.
Function GrassSystemGetHeightAtPoint#( x#, z# )
	Return 0
End Function


;--------------------------------------------------------
;                P L A Y E R   F U N C T I O N S
;--------------------------------------------------------
; Constructor
Function PlayerConstructor()
	thePlayer				= New Player
	thePlayer\camera		= CreateCamera() 
	CameraFogMode( thePlayer\camera, 1 )
	CameraFogColor( thePlayer\camera, 255.0, 200.0, 180.0 )
	CameraClsColor( thePlayer\camera, 150.0, 200.0, 255.0 )
	PositionEntity( thePlayer\camera, 1, 1, 1 ) 
End Function

; Destructor
Function PlayerDestructor()
	Delete thePlayer
	thePlayer = Null
End Function



; Update
Function PlayerUpdate()

	Local xRotationDelta#		= 0.0
	Local yRotationDelta#		= 0.0
	Local zRotationDelta#		= 0.0
	Local xMovementDelta#		= 0.0
	Local yMovementDetta#		= 0.0
	Local zMovementDelta#		= 0.0
	
	If ( thePlayer\turnLeft )		yRotationDelta	= -PLAYER_ROTATION_SPEED
	If ( thePlayer\turnRight )		yRotationDelta	=  PLAYER_ROTATION_SPEED
	If ( thePlayer\moveLeft	)		xMovementDelta	= -PLAYER_MOVEMENT_SPEED
	If ( thePlayer\moveRight )		xMovementDelta	=  PLAYER_MOVEMENT_SPEED
	If ( thePlayer\moveForward )	zMovementDelta	=  PLAYER_MOVEMENT_SPEED
	If ( thePlayer\moveBackward )	zMovementDelta	= -PLAYER_MOVEMENT_SPEED

	; update based on player input
	MoveEntity( thePlayer\camera, xMovementDelta, yMovementDelta, zMovementDelta )
	TurnEntity( thePlayer\camera, xRotationDelta, yRotationDelta, zRotationDelta )
	
	; adjust for map height
	Local x#					= EntityX( thePlayer\camera )
	Local z#					= EntityZ( thePlayer\camera )
	Local y#					= GrassSystemGetHeightAtPoint( x, z )
	PositionEntity( thePlayer\camera, x, y+20.0, z )
End Function