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
|