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
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