[bb] Ulysses Foliage System by N [ 1+ years ago ]

Started by BlitzBot, June 29, 2017, 00:28:39

Previous topic - Next topic

BlitzBot

Title : Ulysses Foliage System
Author : N
Posted : 1+ years ago

Description : This is a single-surface foliage system.  What it does is places foliage and grass sprites (not actual sprites but virtual ones) across a terrain in a fashion similar to this:

[img]www.noelcower.com/pathway.html">

It's very simple and easy to use, the functions are as follows:

InitGrass(Camera, Y-Trace Range, Spawning Radius)
  Initializes the grass system.  Camera is the only mandatory argument.

KillGrass()
  Kills the grass system.

LoadGrassTexture(Path,Columns,Rows,No Mip Mapping)
  Loads a texture useable by the grass system.  Columns specifies how many textures there are horizontally, and Rows specifies how many textures there are vertically.  Refer to grass3.tga to get a visual example of how the textures need to be laid out.

CreateGrassSpawner(Texture,Tile,Size,Mesh,Grass Added Per Update,Maximum Amount of Grass,Field Of View)
  Creates a grass spawner, the Texture argument is mandatory.

StartGrassSpawner(Spawner)
  Tells a grass spawner to attempt to spawn grass quads.

StopGrassSpawner(Spawner)
  Tells a grass spawner to not attempt to spawn any grass quads.

ToggleGrassSpawner(Spawner)
  Toggles whether or not a grass spawner spawns grass quads.

SetWind(Spawner,Affects,WindSpeed,WindDirection,WindPower)
  Sets the wind speed a spawner uses.

WindAffects(Spawner,Affects)
  Sets whether or not a spawner is affected by wind.

SetSpawner(Spawner, BunchRange, Quads per Bunch)
  Use this function to fine-tune the amount of quads per grass-bunch and the distance between them.

FreeGrassSpawner(Spawner)
  Frees a grass spawner.

gQuickSort(..)
  Internal function, disregard it.

UpdateGrass()
  Updates the grass objects.

AddGrassObject(Entity,Surface,Color Texture Path)
  Specifies that an object is a viable attach-point for grass quads.  This returns an integer handle that can be used to remove the object later.  If you would like to limit it to a single-surface within the mesh you can pass a surface to the Surface argument (as displayed in the example).  This function will build a node-list so that grass will be spawned from positions on a precompiled grid of positions, so you will want to call this outside of a main loop, otherwise you could end up waiting a bit.  Passing a path to a texture to the last argument of this function will make it load the texture and proceed to color nodes according to positions on the texture.  For reference, the texture is read like this: negative X is 0, negatize Z is 0, positive X is 1, positive Z is 1.  Think of that as if it were UV coordinates.

RemoveGrassObject(GrassObject)
  Removes an object from the list of objects useable by grass quads.

CreateGrassBlock(X,Z,Width,Length)
  Contrary to what this function's name suggests it does, this creates a rectangle within-which grass cannot be spawned.

RemoveGrassBlock(Block)
  Removes a grass block from memory.

ExportNodes(Path$)
  This exports the current set of nodes to a file.  If you have the bz2 wrapper DLL by elias_t then Ulysses will also compress the file (since 512^2 nodes can be a tad large- 7.5mb in some cases, either way using over 64^2 nodes can be extremely slow if you're not being careful).  Do not pass a file extension to 'path', or you will result in something along the lines of 'file.your_ext.unl'.

ImportNodes(Path$)
  Imports a set of nodes from a file.  If you have elias_t's bz2 wrapper DLL it will attempt to decompress the node file.

Obviously, the commands are simple.

<a href="http://www.noelcower.com/Ulysses.zip" target="_blank">Media Pack</a>
This is a small (by small I do mean small- four grass sprites, two flat textures, and a terrain mesh) media pack that contains a few grass sprites for your use.


Code :
Code (blitzbasic) Select
;; ************************ READ ME **************************
;; If you do not have the BZ2 user-lib made by elias_t then this function is necessary, otherwise **REMOVE IT**.
;; ************************ READ ME **************************
;Function bz2(compress,level,cfrom$,cto$)
; CopyFile cfrom,cto
;End Function
;; Commented out by default because I have the bz2 userlib and because it's included in Ulysses.zip.



Type GrassTile
Field Surface
Field US#,VS#
Field Columns,Rows
End Type

Type GrassSet
Field Mesh
Field Radius#
Field Range#
Field W,H
Field GSin#[359]
Field GCos#[359]
Field TPivot
Field Camera
Field OX#,OY#,OZ#,OYaw#,OPitch#
Field TPivotB
End Type

Type GTex
Field Index
Field FOV
Field ID
Field Count
Field Size#
Field Rate%
Field Tex.GrassTile
Field MaxCount%
Field Frame
Field On
Field WindDirection%
Field WindSpeed#
Field WindPower#
Field Mesh
Field WindAffects
Field BunchRange#
Field BunchCount% ;; you can go from 1 to GrassBunchCount-1
End Type

Type GrassObject
Field Entity
Field Surface
Field Nodes.GNode[NODE_SEG^2 - 1]
Field Rows.NodeRow[NODE_SEG-1]
End Type

Type GNode
Field X#,Y#,Z#
Field Width#,Depth#
Field NX#,NY#,NZ#
Field U% ;; Useable
Field Occupied[OccupiedIndices]
Field R#,G#,B#,A#
End Type

Type NodeRow
Field X#,Width#
Field Z#,Depth#
Field Nodes.GNode[NODE_SEG-1]
End Type

Type GSprite
Field TimeOut
Field X#,Y#,Z#
Field NX#,NY#,NZ#
Field N.GNode
Field Parent.GTex
Field IDelete
Field Quads.GQuad[cGrassBunchCount-1]
Field Range#
End Type

Type GQuad
Field X#,Y#,Z#
Field VX#[3]
Field VY#[3]
Field VZ#[3]
Field CAlpha#
Field DAlpha#
Field Angle#
Field gx#,gy#,gz#,rx#,ry#,rz#
Field Size#
Field Reverse%
Field R%,G%,B%
End Type

Type GBlock
Field X#,Y#,Z#,W#,L#,H#
End Type

;; Internal array
Dim SortArray#(0,0)

;; The amount of grass per node
Const cGrassBunchCount = 10

;; Function Arguments
Const cGrassCount = 500
Const cGrassRate = 1

;; The amount of segments per precalculated node array
;; Keep in mind, this value should be low, even if you export the node list and import it later to save time, Blitz will still slow down when you go through NODE_SEG^2 amount of nodes
Const NODE_SEG = 48

;; The amount of larger blocks of nodes to check against when finding the node to use
Const CONT_SEG = 8

;; Various Function Modified Variables
Global IntersectedX#,IntersectedY#,IntersectedZ#,IntersectedTriangle%,IntersectedSurface%

;; The time it takes to call the UpdateGrass function
Global GrassUpdateTime% = 0

;; The width/length of the grass bunch range
Global cGrassBunchRange# = 3

;; Grass system data and information about the graphics mode
Global gGrassSet.GrassSet = Null

;; Grass sprites in existence (not quads, rather the nodes that contain n amount of quads)
Global cCurrentGrassCount = 0

;; Amount of different kinds of grass spawners a node can 'carry'
Const OccupiedIndices = 16

;; Amount of spawners in existence, never modify this variable
Global ExistingSpawners = 0

;; Amount of nodes in existence
Global ExistingNodes

;; The amount of grass quads drawn
Global GrassQuadsDrawn = 0

;; Minimum Y normal of grass angles
Global YNormal# = .8

;; The alpha-level of grass pieces.
Global GrassAlpha# = 1

;; The speed at which the grass fades in.  0 is not at all, 100 is instantly (as in no interpolation).
Global AlphaFadeSpeed# = 2.5

;; Wind speed defaults
Const WindAffects = 1
Const WindDirection = 45 ;; This is intentionally an integer so I can use it with the sin/cos lookup tables (refer to type GrassSet)
Const WindPower# = .6 ;; Set this to something like .4 or something, otherwise it won't work out too well...
Const WindSpeed# = .06 ;; Decrease to speed up, increase to slow down

;; Kill off-screen grass quads
Global KillOffScreen = 0

;; The amount of time when a grass bunch isn't visible before it gets removed
Global GrassTimeout = 400

;; Call this before the main loop- this is mandatory
Function InitGrass.GrassSet(Camera,CheckRange#=64,GrassRadius#=80)
g.GrassSet = New GrassSet
gCamera = Camera
gRange = CheckRange ;; store checking settings
gRadius = GrassRadius
gMesh = CreateMesh() ;; create the mesh that all grass sprites are placed in
EntityFX gMesh,1+2+16+32
gW = GraphicsWidth() ;; store graphics width and height
gH = GraphicsHeight()
For A = 0 To 359 ;; build sincos lookup tables
gGSin[A] = Sin(A)
gGCos[A] = Cos(A)
Next
gTPivot = CreatePivot()
gTPivotB = CreatePivot()
gGrassSet = g
Return g
End Function

;; frees the grass system from memory
Function KillGrass()
FreeEntity gGrassSetTPivot
FreeEntity gGrassSetTPivotB
FreeEntity gGrassSetMesh
Delete Each GTex
Delete Each GSprite
Delete Each GrassTile
Delete Each GrassSet
Delete Each GQuad
Delete Each GrassObject
Delete Each GBlock
Delete Each GNode
End Function

;; Loads a texture useable by Ulysses
Function LoadGrassTexture(Path$,Columns=1,Rows=1,NoMips=False)
If NoMips Then ClearTextureFilters
T = LoadTexture(Path,1+2+16+32)
If NoMips Then TextureFilter "",1+8
If Not T Then Return False
g.GrassTile = New GrassTile
gSurface = CreateSurface(gGrassSetMesh)
B = CreateBrush()
BrushTexture B,T,0,0
PaintSurface gSurface,B
FreeBrush B
gUS = (Float(TextureWidth(T))/Columns)/TextureWidth(T)
gVS = (Float(TextureHeight(T))/Rows)/TextureHeight(T)
gRows = Rows
gColumns = Columns
FreeTexture T
Return Handle(g)
End Function

;; Pass your grass sprite texture to Texture
Function CreateGrassSpawner(Texture,Frame=0,Size#=2,Mesh=0,GrowthRate%=cGrassRate,MaxAmount%=cGrassCount,FOV% = 90,On=1)
If Texture = 0 Then Return False

g.GTex = New GTex
gID = Handle(g)
gFOV = FOV
gSize = Size
gMaxCount = MaxAmount
gFrame = Frame
gRate = GrowthRate
gTex = Object.GrassTile(Texture)
gOn = On
gIndex = ExistingSpawners
gWindDirection = WindDirection
gWindPower = WindPower
gWindSpeed = WindSpeed
gWindAffects = WindAffects
gBunchCount = cGrassBunchCount
gBunchRange = cGrassBunchRange
Local MinY# = 999999
If Mesh <> 0 Then
HideEntity Mesh
For n = 1 To CountSurfaces(Mesh)
S = GetSurface(Mesh,n)
For V = 0 To CountVertices(S)-1
y# = VertexY(S,V)
If y < MinY Then MinY = y
Next
Next
PositionMesh Mesh,0,-MinY,0
EndIf
gMesh = Mesh
ExistingSpawners = ExistingSpawners + 1
Return gID
End Function

;; Tells a spawner to create grass sprites
Function StartGrassSpawner(Spawner)
g.GTex = Object.GTex(Spawner)
If g = Null Then Return False
gOn = 1
Return True
End Function

;; Toggles whether or not a spawner creates grass sprites
Function ToggleGrassSpawner(Spawner)
g.GTex = Object.GTex(Spawner)
If g = Null Then Return False
gOn = Not gOn
Return True
End Function

;; Sets the wind speeds for a specific spawner.  Normally you shouldn't have to use this, but in the case you want to disable the effects of wind on a mesh, for example, you can.
Function SetWind(Spawner,Affects=WindAffects,WindSpeed#=WindSpeed,WindDirection=WindDirection,WindPower#=WindPower#)
g.GTex = Object.GTex(Spawner)
If g = Null Then Return False
gWindSpeed = WindSpeed
gWindDirection = WindDirection
gWindPower = WindPower
gWindAffects = Affects
Return True
End Function

;; Sets whether or not a spawner is affected by wind speeds
Function WindAffects(Spawner,Affects=WindAffects)
g.GTex = Object.GTex(Spawner)
If g = Null Then Return False
gWindAffects = Affects
Return True
End Function

;; Tells a spawner to not create grass sprites
Function StopGrassSpawner(Spawner)
g.GTex = Object.GTex(Spawner)
If g = Null Then Return False
gOn = 0
Return True
End Function

Function SetSpawner(Spawner,Range=2.3,Amount=cGrassBunchCount)
g.GTex = Object.GTex(Spawner)
If g = Null Then Return False
gBunchRange = Range
gBunchCount = Amount
Return True
End Function

;; Frees a spawner from memory
Function FreeGrassSpawner(Spawner)
FreeEntity gGrassSetMesh
gGrassSetMesh = CreateMesh()
S.GTex = Object.GTex(Spawner)
If S = Null Then Return False
SIndex = SIndex

For n.GNode = Each GNode
For f = SIndex To OccupiedIndices
If f < OccupiedIndices Then
nOccupied[f] = nOccupied[f+1]
EndIf

If f >= ExistingSpawners Then
nOccupied[f] = 0
EndIf
Next
Next

For g.GTex = Each GTex
If gIndex > SIndex Then
gIndex = gIndex - 1
EndIf
If gID = Spawner Then
For i.GSprite = Each GSprite
If iParent = g Then
iNOccupied[iParentIndex] = 0
For k = 0 To cGrassBunchCount-1
Delete iQuads[k]
Next
Delete i
EndIf
Next
Delete g
Return True
EndIf
Next
Return False
End Function

;; Creates a virtual block (top-down) that prevents grass from being grown within a specified area.  This allows somewhat finer-precision control of the grass system.
Function CreateGrassBlock(X#,Y#,Z#,Width#,Height#,Depth#)
B.GBlock = New GBlock
If Width < 0 Then
X = X + Width
Width = -Width
EndIf

If Height < 0 Then
Y = Y + Height
Height = -Height
EndIf

If Depth < 0 Then
Z = Z + Depth
Depth = -Depth
EndIf

BX = X
BY = Y
B = Z

BW = Width
BH = Height
BL = Depth
Return Handle(B)
End Function

Function RemoveGrassBlock(Block)
B.GBlock = Object.GBlock(Block)
If B <> Null Then Delete B Return 1
Return 0
End Function

;; Internal function
Function gQuickSort( L, R, RandomPivot = True )
Local A, B, SwapA#, SwapB#, Middle#
A = L
B = R

If RandomPivot Then
Middle = SortArray( Rand(L, R), 0 )
Else
Middle = SortArray( (L+R)/2, 0 )
EndIf

While True

While SortArray( A, 0 ) < Middle
A = A + 1
If A > R Then Exit
Wend

While  Middle < SortArray( B, 0 )
B = B - 1
If B < 0 Then Exit
Wend

If A > B Then Exit

SwapA = SortArray( A, 0 )
SwapB = SortArray( A, 1 )
SortArray( A, 0 ) = SortArray( B, 0 )
SortArray( A, 1 ) = SortArray( B, 1 )
SortArray( B, 0 ) = SwapA
SortArray( B, 1 ) = SwapB

A = A + 1
B = B - 1

If B < 0 Then Exit

Wend

If L < B Then gQuickSort( L, B )
If A < R Then gQuickSort( A, R )
End Function

;; Updates the grass system
Function UpdateGrass()
If gGrassSet = Null Then Return False
For t.GrassTile = Each GrassTile
ClearSurface tSurface
Next
GrassQuadsDrawn = 0

For i.GSprite = Each GSprite

CameraProject gGrassSetCamera,iX,iY,i
PX = ProjectedX()
PY = ProjectedY()
PositionEntity gGrassSetTPivot,iX,iY,i
D# = EntityDistance(gGrassSetCamera,gGrassSetTPivot)

If (ProjectedZ() <= 0) Or ((PX < -100 Or PX > gGrassSetW+100) And KillOffScreen)  Or D > iRange*1.5 Then
iTimeOut = iTimeOut + 1
If iTimeOut > GrassTimeout
iIDelete = 1
For n = 0 To iParentBunchCount-1
iQuads[n]DAlpha = 0
Next
EndIf
Else
iTimeOut = 0
iIDelete = 0
For n = 0 To iParentBunchCount-1
iQuads[n]DAlpha = GrassAlpha
Next
EndIf

If iIDelete = 1 Then
If iQuads[0]CAlpha <= .1 Then iIDelete = 2
EndIf

If iIDelete = 2 Then
iParentCount = iParentCount - 1
cCurrentGrassCount = cCurrentGrassCount - 1
For n = 0 To iParentBunchCount-1
Delete iQuads[n]
Next
iNOccupied[iParentIndex] = 0
Delete i
EndIf
Next

CX# = EntityX(gGrassSetCamera,1)
CY# = EntityY(gGrassSetCamera,1)
CZ# = EntityZ(gGrassSetCamera,1)

TFormPoint gGrassSetOX,gGrassSetOY,gGrassSetOZ,0,gGrassSetCamera

Backward# = 1
If TFormedZ() > 0 Then Backward# = TFormedZ()*.4

CYaw# = EntityYaw(gGrassSetCamera,1)
TurnYaw = (gGrassSetOYaw-CYaw)
CPitch# = EntityPitch(gGrassSetCamera,1)
TPivot = gGrassSetTPivot
TPivotB = gGrassSetTPivotB
For g.GTex = Each GTex
If gOn = 1 Then
If CX <> gGrassSetOX Or CY <> gGrassSetOY Or CZ <> gGrassSetOZ Or CYaw <> gGrassSetOYaw Or CPitch <> gGrassSetOPitch Then
Count = gMaxCount-gCount
If Count > gRate Then Count = gRate
For j = 1 To Count
A = (CYaw+Rnd(-gFOV/2,gFOV/2)+90-TurnYaw*1.5) Mod 359
If A > 359 Then A = A - 359
If A < 0 Then A = A + 359
R# = Rnd(.5,gGrassSetRadius)*Backward;*Cos(CPitch)
X# = CX + gGrassSetGCos[Int A] * R
Z# = CZ + gGrassSetGSin[Int A] * R
PositionEntity gGrassSetTPivot,X,0,Z
Closest.GNode = Null
ClD# = 5000000
For nr.NodeRow = Each NodeRow
If X > nrX And X < nrX+nrWidth Then
If Z > nr And Z < nr+nrDepth Then
For n = 0 To NODE_SEG-1
If X > nrNodes[n]X And X < nrNodes[n]X + nrNodes[n]Width Then
If Z > nrNodes[n] And Z < nrNodes[n]+nrDepth Then
Closest = nrNodes[n]
EndIf
EndIf
Next
EndIf
EndIf
Next

Con = 1

If Closest <> Null Then
X = ClosestX : Y = ClosestY : Z = Closest
For b.GBlock = Each GBlock
If X > bX And X < bX+bW And Z > b And Z <b+bL And Y > bY And Y < bY + bH  Then Con = 0 Exit
Next
Else
Con = 0
EndIf

If Con Then
If ClosestOccupied[gIndex] = 0 And ClosestU Then
i.GSprite = New GSprite
iParent = g
iX = ClosestX
i = Closest
iY = ClosestY
iNX# =ClosestNX
iNY# = ClosestNY
iNZ# = ClosestNZ
iN = Closest
ClosestOccupied[iParentIndex] = 1
iRange = R*Rnd(1,2)

PositionEntity TPivotB,iX,iY,i
RotateEntity TPivotB,0,0,0

For n = 0 To gBunchCount-1
PositionEntity TPivot,iX,iY,i
iQuads[n] = New GQuad
rX# = Rnd(-iParentBunchRange,iParentBunchRange)
rZ# = Rnd(-iParentBunchRange,iParentBunchRange)
iQuads[n]X = RX
iQuads[n] = rZ
iQuads[n]Size = Rnd(gSize*.7,gSize*2)
iQuads[n]Angle = Rnd(359)
iQuads[n]DAlpha = GrassAlpha
iQuads[n]Reverse = Rand(0,1)
iQuads[n]R = ClosestR*255
iQuads[n]G = ClosestG*255
iQuads[n]B = ClosestB*255

RotateEntity TPivot,0,iQuads[n]Angle,0
AlignToVector TPivot,iNX,iNY,iNZ,2,1
MoveEntity TPivot,rX,0,rZ
AlignToVector TPivot,iNX,3.6,iNZ,2,1

If iParentMesh = 0 Then
TFormPoint -iQuads[n]Size,iQuads[n]Size*2,0,TPivot,TPivotB
iQuads[n]VX[0] = TFormedX()
iQuads[n]VY[0] = TFormedY()
iQuads[n]VZ[0] = TFormedZ()

TFormPoint iQuads[n]Size,iQuads[n]Size*2,0,TPivot,TPivotB
iQuads[n]VX[1] = TFormedX()
iQuads[n]VY[1] = TFormedY()
iQuads[n]VZ[1] = TFormedZ()

TFormPoint iQuads[n]Size,0,0,TPivot,TPivotB
iQuads[n]VX[2] = TFormedX()
iQuads[n]VY[2] = TFormedY()
iQuads[n]VZ[2] = TFormedZ()

TFormPoint -iQuads[n]Size,0,0,TPivot,TPivotB
iQuads[n]VX[3] = TFormedX()
iQuads[n]VY[3] = TFormedY()
iQuads[n]VZ[3] = TFormedZ()
EndIf

iQuads[n]x = EntityPitch(TPivot,1)
iQuads[n]y = EntityYaw(TPivot,1)
iQuads[n]z = EntityRoll(TPivot,1)
iQuads[n]gx = EntityX(TPivot,1)
iQuads[n]gy = EntityY(TPivot,1)
iQuads[n]gz = EntityZ(TPivot,1)
Next
gCount = gCount + 1
cCurrentGrassCount = cCurrentGrassCount + 1
EndIf
EndIf
Next
EndIf
EndIf
Next

gGrassSetOX = CX
gGrassSetOY = CY
gGrassSetOZ = CZ
gGrassSetOYaw = CYaw
gGrassSetOPitch = CPitch

TPivot = gGrassSetTPivot

Dim SortArray(cCurrentGrassCount-1,2)
n = 0
For i.GSprite = Each GSprite
PositionEntity TPivot,iX,iY,i
SortArray(n,0) = -EntityDistance(TPivot,gGrassSetCamera)+iParentIndex
SortArray(n,1) = Handle(i)
n = n + 1
Next

If cCurrentGrassCount > 12 Then gQuickSort(0,cCurrentGrassCount-2,1)

ASpeed# = AlphaFadeSpeed#/100.0

TimeTaken = MilliSecs()
WTime = MilliSecs()
For p = 0 To cCurrentGrassCount-1
i.GSprite = Object.GSprite(SortArray(p,1))
If i <> Null Then
For n = 0 To iParentBunchCount-1
iQuads[n]CAlpha = iQuads[n]CAlpha+(iQuads[n]DAlpha-iQuads[n]CAlpha)*ASpeed#

If iQuads[n]CAlpha*iNA > .01 Then
Col = (iParentFrame) Mod iParentTexColumns
Row = (iParentFrame-Col)/iParentTexColumns
US# = iParentTexUS
VS# = iParentTexVS
U# = US * Col
V# = VS * Row

If iParentWindAffects Then
WA = ((WindDirection)+iX*2) Mod 359
WB = Int(WTime*WindSpeed + iX) Mod 359
If WA > 359 Then WA = WA - 359
If WA < 0 Then WA = WA + 359
If WB > 359 Then WB = WB - 359
If WB < 0 Then WB = WB + 359
windX# = gGrassSetGSin[WA] * WindPower*gGrassSetGSin[WB]
windZ# = gGrassSetGCos[WA] * WindPower*gGrassSetGCos[WB]
Else
windX = 0
windZ = 0
EndIf

If iQuads[n]Y = 0 Then iQuads[n]Y = EntityY(TPivot)-iY

If iIDelete = 0 Then
RX# = 0 : RY# = 0 : RZ# = 0

For beef = 0 To 3
RX = RX + iQuads[n]VX[beef]
RY = RY + iQuads[n]VY[beef]
RZ = RZ + iQuads[n]VZ[beef]
Next

RX = RX / 3
RY = RY / 3
RZ = RZ / 3

CameraProject gGrassSetCamera,iQuads[n]gx,iQuads[n]gy,iQuads[n]gz

iQuads[n]DAlpha = GrassAlpha

If ProjectedZ() <= 0 Then
iQuads[n]DAlpha = 0
ElseIf iParentMesh = 0
PX = ProjectedX()
If PX <= -16 Or PX > gGrassSetW+16 Then
iQuads[n]DAlpha = 0
Else
PY = ProjectedY()
If PY <= -16 Or PY > gGrassSetH + 16 Then iQuads[n]DAlpha = 0
EndIf
EndIf
Else
iQuads[n]DAlpha = 0
EndIf

GrassQuadsDrawn = GrassQuadsDrawn + 1
S = iParentTexSurface

If iParentMesh = 0 Then
eV = AddVertex(S,iX+iQuads[n]VX[0]+windX,iY+iQuads[n]VY[0],i+iQuads[n]VZ[0]+windZ,U+iQuads[n]Reverse*US,V)

AddVertex(S,iX+iQuads[n]VX[1]+windX,iY+iQuads[n]VY[1],i+iQuads[n]VZ[1]+windZ,U+US-iQuads[n]Reverse*US,V)

AddVertex(S,iX+iQuads[n]VX[2],iY+iQuads[n]VY[2],i+iQuads[n]VZ[2],U+US-iQuads[n]Reverse*US,V+VS)

AddVertex(S,iX+iQuads[n]VX[3],iY+iQuads[n]VY[3],i+iQuads[n]VZ[3],U+iQuads[n]Reverse*US,V+VS)

AddTriangle S,eV,eV+1,eV+2
AddTriangle S,eV+2,eV+3,eV

For o = 0 To 3
VertexColor S,eV+o,iQuads[n]R,iQuads[n]G,iQuads[n]B,iQuads[n]CAlpha
Next
Else
Mesh = iParentMesh
For in = 1 To CountSurfaces(Mesh)
Surface = GetSurface(Mesh,1)
lv = CountVertices(S)
PositionEntity Mesh,iQuads[n]gx,iQuads[n]gy,iQuads[n]gz,1
RotateEntity Mesh,iQuads[n]x+windX*3,iQuads[n]y,iQuads[n]z+windZ*3,1
If iParentWindAffects Then AlignToVector Mesh,windX/2,3.6,windZ/2,2,.3
For iv = 0 To CountVertices(Surface)-1
TFormPoint VertexX(Surface,iv)*iQuads[n]Size,VertexY(Surface,iv)*iQuads[n]Size,VertexZ(Surface,iv)*iQuads[n]Size,Mesh,0
nv = AddVertex(S,TFormedX(),TFormedY(),TFormedZ(),U+VertexU(Surface,iv)*US,V+VertexV(Surface,iv)*VS,VertexW(Surface,iv))
VertexColor S,nv,iQuads[n]R,iQuads[n]G,iQuads[n]B,iQuads[n]CAlpha
TFormNormal VertexNX(Surface,iv),VertexNY(Surface,iv),VertexNZ(Surface,iv),Mesh,0
VertexNormal S,nv,TFormedX(),TFormedY(),TFormedZ()
Next
For iv = 0 To CountTriangles(Surface)-1
AddTriangle S,lv+TriangleVertex(Surface,iv,0),lv+TriangleVertex(Surface,iv,1),lv+TriangleVertex(Surface,iv,2)
Next
Next
Surface = 0
EndIf
EndIf
Next
EndIf
Next
GrassUpdateTime = MilliSecs()-TimeTaken
End Function

;; Specify a mesh, if you want to specify a specific surface in the mesh then you can do that as well via the Surface argument
Function AddGrassObject(GObject,Surface=0,Lightmap$="")
g.GrassObject = New GrassObject
gEntity = GObject
gSurface = Surface

Local Mi#[2]
Local Ma#[2]

If Lightmap <> "" Then
Tex = LoadTexture(Lightmap,1+2)
If Tex Then
T = TextureBuffer(Tex)
W = TextureWidth(Tex)
H = TextureHeight(TeX)
EndIf
EndIf

If Surface <> 0 Then
For n = 0 To CountVertices(Surface)-1
TFormPoint VertexX(Surface,n),VertexY(Surface,n),VertexZ(Surface,n),GObject,0
TX# = TFormedX()
TY# = TFormedY()
TZ# = TFormedZ()
If TX < Mi[0] Then Mi[0] = TX
If TY < Mi[1] Then Mi[1] = TY
If TZ < Mi[2] Then Mi[2] = TZ

If TX > Ma[0] Then Ma[0] = TX
If TY > Ma[1] Then Ma[1] = TY
If TZ > Ma[2] Then Ma[2] = TZ
Next
Else
For k = 1 To CountSurfaces(GObject)
Surface = GetSurface(GObject,k)
For n = 0 To CountVertices(Surface)-1
TFormPoint VertexX(Surface,n),VertexY(Surface,n),VertexZ(Surface,n),GObject,0
TX# = TFormedX()
TY# = TFormedY()
TZ# = TFormedZ()
If TX < Mi[0] Then Mi[0] = TX
If TY < Mi[1] Then Mi[1] = TY
If TZ < Mi[2] Then Mi[2] = TZ

If TX > Ma[0] Then Ma[0] = TX
If TY > Ma[1] Then Ma[1] = TY
If TZ > Ma[2] Then Ma[2] = TZ
Next
Next
Surface = 0
EndIf
If Tex Then LockBuffer(T)

NodeWidth# = (Ma[0]-Mi[0])/NODE_SEG
NodeDepth# = (Ma[2]-Mi[2])/NODE_SEG
RowWidth# = (Ma[0]-Mi[0])

For v# = 0 To NODE_SEG-1
r.NodeRow = New NodeRow
rX = Mi[0]
r = Mi[2]+(Ma[2]-Mi[2])*(v/NODE_SEG)
rWidth = RowWidth
rDepth = NodeDepth
For u# = 0 To NODE_SEG-1
If Surface <> 0 Then
RI = Ray_Intersect_Surface(GObject,Surface,Mi[0]+(Ma[0]-Mi[0])*(u/NODE_SEG),Ma[1],Mi[2]+(Ma[2]-Mi[2])*(v/NODE_SEG),.01,Mi[1]-Ma[1],.01,0,1)
Else
RI = Ray_Intersect_Mesh(GObject,Mi[0]+(Ma[0]-Mi[0])*(u/NODE_SEG),Ma[1]+1,Mi[2]+(Ma[2]-Mi[2])*(v/NODE_SEG),.01,Mi[1]-Ma[1]-2,.01,0,1)
EndIf

Node = v*NODE_SEG+u

gNodes[Node] = New GNode
rNodes[u] = gNodes[Node]
gNodes[Node]U = RI

If Tex Then
X = (u/NODE_SEG)*W
Y = (v/NODE_SEG)*H
Pixel = ReadPixelFast(X,Y,T)
cA# = Pixel Shr 24 And 255
cR# = Pixel Shr 16 And 255
cG# = Pixel Shr 8 And 255
cB# = Pixel And 255
gNodes[Node]R = cR
gNodes[Node]G = cG
gNodes[Node]B = cB
gNodes[Node]A = cA/255
Else
gNodes[Node]R = 255
gNodes[Node]G = 255
gNodes[Node]B = 255
gNodes[Node]A = 1
EndIf

If RI Then
gNodes[Node]X = IntersectedX
gNodes[Node]Y = IntersectedY
gNodes[Node] = IntersectedZ
gNodes[Node]NX = TriangleNX(IntersectedSurface,IntersectedTriangle)
gNodes[Node]NY = TriangleNY(IntersectedSurface,IntersectedTriangle)
gNodes[Node]NZ = TriangleNZ(IntersectedSurface,IntersectedTriangle)
gNodes[Node]Width = NodeWidth
gNodes[Node]Depth = NodeDepth

If gNodes[Node]NY < YNormal Then
gNodes[Node]U = 0
EndIf
EndIf
Next
Next
If Tex Then
UnlockBuffer(T)
FreeTexture(Tex)
EndIf

Return Handle(g)
End Function

;; Export a nodeset
Function ExportNodes(Path$)
FOut = WriteFile(Path$+".unltemp")
If Not FOut Then Return False

WriteString FOut,"NODESET_"

For r.NodeRow = Each NodeRow
WriteFloat FOut,rX
WriteFloat FOut,r
WriteFloat FOut,rWidth
WriteFloat FOut,rDepth
For n = 0 To NODE_SEG-1
i.GNode = rNodes[n]
WriteFloat FOut,iX
WriteFloat FOut,iY
WriteFloat FOut,i
WriteFloat FOut,iWidth
WriteFloat FOut,iDepth
WriteFloat FOut,iNX
WriteFloat FOut,iNY
WriteFloat FOut,iNZ
WriteByte FOut,iR
WriteByte FOut,iG
WriteByte FOut,iB
WriteByte FOut,iA*255
WriteByte FOut,iU
Next
Next

CloseFile FOut

bz2( 0, 9, Path$+".unltemp", Path$+".unl" )
DeleteFile Path$+".unltemp"

Return True
End Function

;; Imports a nodeset.  Nodesets should be rebuilt after -any- changes to anything grass is placed upon
Function ImportNodes(Path$)
If FileType(Path+".unl") <> 1 Then Return False

bz2( 1, 0, Path+".unl", Path+".unltemp" )

FIn = ReadFile(Path$+".unltemp")
If Not FIn Then Return False

ID$ = ReadString(FIn)
If ID$ <> "NODESET_" Then
CloseFile FIn
Return False
EndIf

While Not Eof(FIn)
r.NodeRow = New NodeRow
rX = ReadFloat(FIn)
r = ReadFloat(FIn)
rWidth = ReadFloat(FIn)
rDepth = ReadFloat(FIn)
For n = 0 To NODE_SEG-1
i.GNode = New GNode
iX = ReadFloat(FIn)
iY = ReadFloat(FIn)
i = ReadFloat(FIn)
iWidth = ReadFloat(FIn)
iDepth = ReadFloat(FIn)
iNX = ReadFloat(FIn)
iNY = ReadFloat(FIn)
iNZ = ReadFloat(FIn)
iR = ReadByte(FIn)
iG = ReadByte(FIn)
iB = ReadByte(FIn)
iA = Float(ReadByte(FIn))/255
iU = ReadByte(FIn)
rNodes[n] = i
Next
Wend

DeleteFile Path+".unltemp"

CloseFile FIn
Return True
End Function

;; Removes an object useable by the grass system
Function RemoveGrassObject(GObject)
g.GrassObject = Object.GrassObject(GObject)
If g = Null Then Return False
For n = 0 To NODE_SEG-1
For i = 0 To NODE_SEG-1
Delete gRows[n]Nodes[i]
Next
Delete gRows[n]
Next
Delete g
Return True
End Function

;; sswift's ray-triangle intersection code, butchered by me
Function Ray_Intersect_Triangle(Px#, Py#, Pz#, Dx#, Dy#, Dz#, V0x#, V0y#, V0z#, V1x#, V1y#, V1z#, V2x#, V2y#, V2z#, Extend_To_Infinity=True, Cull_Backfaces=False)
E1x# = V2x# - V0x#
E1y# = V2y# - V0y#
E1z# = V2z# - V0z#

E2x# = V1x# - V0x#
E2y# = V1y# - V0y#
E2z# = V1z# - V0z#

Hx# = (Dy# * E2z#) - (E2y# * Dz#)
Hy# = (Dz# * E2x#) - (E2z# * Dx#)
Hz# = (Dx# * E2y#) - (E2x# * Dy#)

A# = (E1x# * Hx#) + (E1y# * Hy#) + (E1z# * Hz#)

If (Cull_Backfaces = True) And (A# >= 0) Then Return False

If (A# > -0.00001) And (A# < 0.00001) Then Return False

F# = 1.0 / A#

Sx# = Px# - V0x#
Sy# = Py# - V0y#
Sz# = Pz# - V0z#

U# = F# * ((Sx# * Hx#) + (Sy# * Hy#) + (Sz# * Hz#))

If (U# < 0.0) Or (U# > 1.0) Return False

Qx# = (Sy# * E1z#) - (E1y# * Sz#)
Qy# = (Sz# * E1x#) - (E1z# * Sx#)
Qz# = (Sx# * E1y#) - (E1x# * Sy#)

V# = F# * ((Dx# * Qx#) + (Dy# * Qy#) + (Dz# * Qz#))

If (V# < 0.0) Or ((U# + V#) > 1.0) Return False

T# = F# * ((E2x# * Qx#) + (E2y# * Qy#) + (E2z# * Qz#))
IntersectedX# = Px+Dx*T
IntersectedY# = Py+Dy*T
IntersectedZ# = Pz+Dz*T

If (T# < 0) Then Return False

If (Extend_To_Infinity = False) And (T# > 1) Return False

Return True
End Function

Function Ray_Intersect_Mesh(Mesh, Px#, Py#, Pz#, Dx#, Dy#, Dz#, Extend_To_Infinity=True, Cull_Backfaces=False)
Surfaces = CountSurfaces(Mesh)
If Surfaces > 0
For SurfaceLoop = 1 To Surfaces
Surface = GetSurface(Mesh, SurfaceLoop)

Tris  = CountTriangles(Surface)
For TriLoop = 0 To Tris-1

V0 = TriangleVertex(Surface, TriLoop, 0)
V1 = TriangleVertex(Surface, TriLoop, 1)
V2 = TriangleVertex(Surface, TriLoop, 2)

V0x# = VertexX#(Surface, V0)
V0y# = VertexY#(Surface, V0)
V0z# = VertexZ#(Surface, V0)

V1x# = VertexX#(Surface, V1)
V1y# = VertexY#(Surface, V1)
V1z# = VertexZ#(Surface, V1)

V2x# = VertexX#(Surface, V2)
V2y# = VertexY#(Surface, V2)
V2z# = VertexZ#(Surface, V2)

TFormPoint V0x#, V0y#, V0z#, Mesh, 0
V0x# = TFormedX#()
V0y# = TFormedY#()
V0z# = TFormedZ#()

TFormPoint V1x#, V1y#, V1z#, Mesh, 0
V1x# = TFormedX#()
V1y# = TFormedY#()
V1z# = TFormedZ#()

TFormPoint V2x#, V2y#, V2z#, Mesh, 0
V2x# = TFormedX#()
V2y# = TFormedY#()
V2z# = TFormedZ#()

Intersected = Ray_Intersect_Triangle(Px#, Py#, Pz#, Dx#, Dy#, Dz#, V0x#, V0y#, V0z#, V1x#, V1y#, V1z#, V2x#, V2y#, V2z#, Extend_To_Infinity, Cull_Backfaces)
If Intersected Then
IntersectedTriangle = TriLoop
IntersectedSurface = Surface
Return True
EndIf
Next
Next
EndIf

IntersectedTriangle = -1
IntersectedSurface = -1

Return False
End Function

;; hacked the Ray_Intersect_Mesh function so it can be used on surfaces as well
Function Ray_Intersect_Surface(Mesh, Surface, Px#, Py#, Pz#, Dx#, Dy#, Dz#, Extend_To_Infinity=True, Cull_Backfaces=False)
Tris  = CountTriangles(Surface)
For TriLoop = 0 To Tris-1
V0 = TriangleVertex(Surface, TriLoop, 0)
V1 = TriangleVertex(Surface, TriLoop, 1)
V2 = TriangleVertex(Surface, TriLoop, 2)

V0x# = VertexX#(Surface, V0)
V0y# = VertexY#(Surface, V0)
V0z# = VertexZ#(Surface, V0)

V1x# = VertexX#(Surface, V1)
V1y# = VertexY#(Surface, V1)
V1z# = VertexZ#(Surface, V1)

V2x# = VertexX#(Surface, V2)
V2y# = VertexY#(Surface, V2)
V2z# = VertexZ#(Surface, V2)

TFormPoint V0x#, V0y#, V0z#, Mesh, 0
V0x# = TFormedX#()
V0y# = TFormedY#()
V0z# = TFormedZ#()

TFormPoint V1x#, V1y#, V1z#, Mesh, 0
V1x# = TFormedX#()
V1y# = TFormedY#()
V1z# = TFormedZ#()

TFormPoint V2x#, V2y#, V2z#, Mesh, 0
V2x# = TFormedX#()
V2y# = TFormedY#()
V2z# = TFormedZ#()

Intersected = Ray_Intersect_Triangle(Px#, Py#, Pz#, Dx#, Dy#, Dz#, V0x#, V0y#, V0z#, V1x#, V1y#, V1z#, V2x#, V2y#, V2z#, Extend_To_Infinity, Cull_Backfaces)
If Intersected Then
IntersectedTriangle = TriLoop
IntersectedSurface = Surface
Return True
EndIf
Next

IntersectedTriangle = -1
IntersectedSurface = -1

Return False
End Function

;; some functions of my own...
;; just to note, these don't return the actual normals of a triangle, they only return the averaged normals of the vertices.  in most cases- when it comes to terrain- this is enough
Function TriangleNX#(Surface,Triangle)
Local NX#
For N = 0 To 2
NX# = NX# + VertexNX(Surface,TriangleVertex(Surface,Triangle,N))
Next
Return NX / 3
End Function

Function TriangleNY#(Surface,Triangle)
Local NY#
For N = 0 To 2
NY# = NY# + VertexNY(Surface,TriangleVertex(Surface,Triangle,N))
Next
Return NY / 3
End Function

Function TriangleNZ#(Surface,Triangle)
Local NZ#
For N = 0 To 2
NZ# = NZ# + VertexNZ(Surface,TriangleVertex(Surface,Triangle,N))
Next
Return NZ / 3
End Function



;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; EXAMPLE CODE ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; FPS
Global FPS,LastCheck,Frames
Function GetFPS()
Frames = Frames + 1

If MilliSecs() > LastCheck+1000 Then
LastCheck = MilliSecs()
FPS = Frames
Frames = 0
EndIf
Return FPS
End Function

;; Graphics Mode
Graphics 320,240,32,2
If FileType("grass3.tga") <> 1 Then
bz2 1,0,"grass3.tga.bz2","grass3.tga"
Delay 20
EndIf
ClsColor 255,255,255
Cls
Color 0,0,0
Graphics3D 800,600,32,Int(Replace(Lower(Input$("Windowed? Y/N        ")),"y","1"))+1
;; Camera
C = CreateCamera()

;; Terrain - objects no longer need pick-modes
T = LoadMesh("terrain.3ds")
ScaleEntity T,40*.4,35*.4,40*.4
TranslateEntity T,0,-70,0

;; for some reason whenever i add or remove something from this the surface indices change, so now i have to check for vertex count to know which one i need
If CountVertices(GetSurface(t,1)) > CountVertices(GetSurface(t,2)) Then
s1 = 1
s2 = 2
Else
s1 = 2
s2 = 1
EndIf

B = LoadBrush("detail.png")
GetBrushTexture(B)
PaintSurface GetSurface(T,s1),B
FreeBrush B
B = LoadBrush("path.png")
PaintSurface GetSurface(T,s2),B
FreeBrush B

;; Lighting
L = CreateLight(2)
PositionEntity L,0,500,0
LightRange L,200
LightColor L,255,255,255
AmbientLight 96,96,96

;; Grass
InitGrass(C,50,180)
Tex = LoadGrassTexture("grass3.tga",2,4)

SetSpawner(CreateGrassSpawner(Tex,0,.9/1.5,0,1,200),3,2)
SetSpawner(CreateGrassSpawner(Tex,1,1.65/1.5,0,3,200),3,7)
SetSpawner(CreateGrassSpawner(Tex,2,.8/1.5,0,1,200),2.3,3)
;SetSpawner(CreateGrassSpawner(Tex,3,1/1.5,0,1,200),2.3,1)
;SetSpawner(CreateGrassSpawner(Tex,4,1/1.5,0,1,200),2.3,1)
SetSpawner(CreateGrassSpawner(Tex,5,1.65/1.5,0,4,200),2,1)

If FileType("terrain.unl") Then
Print "Rebuild object nodeset?  Y/N"
Repeat
k = Asc(Lower(Chr(WaitKey())))
Until k = Asc("y") Or k = Asc("n")
If k = Asc("y") Then
Print "Rebuilding..."
DeleteFile "terrain.unl"
TerrainObject = AddGrassObject(T,GetSurface(T,s1))
ExportNodes("terrain")
Else
Print "Loading nodeset..."
ImportNodes("terrain")
EndIf
Print "Done."
Else
TerrainObject = AddGrassObject(T,GetSurface(T,s1))
ExportNodes("terrain")
EndIf

;; Tweening
MFPS = 60
Period = 1000/MFPS
Time = MilliSecs()-Period

S = CreateSprite()
ScaleSprite S,10,10
EntityFX S,1+16
EntityBlend S,3
EntityOrder S,-5
PositionEntity S,0,0,2
EntityColor S,255,255,255
EntityParent S,C,1

Alpha# = 1.4

CameraClsColor C,191,223,245

Repeat
Repeat
Elapsed = MilliSecs() - Time
Until Elapsed

Ticks = Elapsed / Period
Tween# = Float(Elapsed Mod Period) / Float(Period)

For U = 0 To Ticks
Time = Time + Period

If U = Ticks Then
CaptureWorld
EndIf

If Alpha < .01 Then
Alpha = 0
Else
Alpha# = Alpha# * .98
EndIf
EntityAlpha S,Alpha

;; Camera
PitchAccel# = MouseYSpeed()*.05
PanAccel# = -MouseXSpeed()*.05

Pitch# = Pitch + PitchAccel
Pan# = Pan + PanAccel
If Pitch > 80 Then Pitch = 80
If Pitch < -80 Then Pitch = -80
RotateEntity C,Pitch,Pan,0
MoveMouse GraphicsWidth()/2,GraphicsHeight()/2
MoveV# =(KeyDown(200)-KeyDown(208))*.7
MoveEntity C,0,0,MoveV

;; Grass
UpdateGrass()

;; Graphics
UpdateWorld
Next

;; Graphics
RenderWorld Tween

;; FPS
GetFPS()
Text 2,2,"FPS : "+FPS

Flip 0
Until KeyHit(1)

;; Grass
KillGrass()

;; Graphics
ClearWorld
End


Comments :


Rook Zimbabwe(Posted 1+ years ago)

 Looks GREAT! but can it NOT put grass where it senses a MODEL like a building??? That would be a great tool too!


N(Posted 1+ years ago)

 Rook: Refer to the AddGrassObject command.That function basically tells it where it can and can't put grass.


Doggie(Posted 1+ years ago)

 There's a bug in this linewindX# = gGrassSetGSin[(WA+iX*2) Mod 359] * WindPower*gGrassSetGSin[Int(MilliSecs()*WindSpeed) Mod 359]If my camera goes below 30(in the y direction) then I get a "Blitz Array Index Out of Bounds" error.


N(Posted 1+ years ago)

 That doesn't make sense, since that line has no relation to the Y position of the camera.  I'd say it's just trying to access part of the array above 359 or below 0.  I'll look into it.


Rimmsy(Posted 1+ years ago)

 verrrrrry nice.


N(Posted 1+ years ago)

 Added two new functions and sortof improved (in my opinion) the way grass is spawned.I'm trying to find a way to avoid the line-picking to get the positions and normals of the grass bunches, but so far every idea I've had has been shot down due to me forgetting one detail or another.


mrtricks(Posted 1+ years ago)

 Very nice but gets some nasty slowdown on mine...


N(Posted 1+ years ago)

 mrtricks: I'd say the vertex lighting and 10,000-detail Blitz terrain in the example code is mostly to blame for that.  But some of the blame lies with my system as well for using line picks for every 5 or so (it's a variable amount specified by a constant) grass bunches.Anyhow, I've implemented a texture system that allows you to reduce the surface count to one surface per 4 to 8 grass sprite textures.  I'm still working on the line-picking problem though, since that's slow as hell if the growth rate is high.


N(Posted 1+ years ago)

 The new texture system is implemented, and I've removed the line-picking and switched to using sswift's triangle intersect functions.


Kornflex(Posted 1+ years ago)

 Fantastic and thanks you for that !


Picklesworth(Posted 1+ years ago)

 This is very nice Noel, as always!I hope you keep working on this :DWhen accompanied with a nice grass texture for the ground the effect is even better.


N(Posted 1+ years ago)

 <ego>I am such a nice person -_-</ego>But in all seriousness, without sswift's ray intersect code, this would be a hell of a lot slower than it is now.I've got plans for pre-compiling node arrays now, too, in order to speed stuff up.


Picklesworth(Posted 1+ years ago)

 oh, ray intersect code I need that badly! thanks for telling me there's a such thing :DIt's odd, but I can't see any grass in the newest version :(


N(Posted 1+ years ago)

 Not sure why not.  Also, it no longer works on Blitz terrains (because of the ray intersect code- you could, as an example, generate a mesh that is based off of a loaded Blitz terrain to make it work again though), so if you're using one that might be the problem.


Picklesworth(Posted 1+ years ago)

 I'm just using your demo code alone along with the media packEdit:Decided not to add any more posts...When I copy and paste the data written on this page rather than download the .bb file, there is a small region that has grass on it. It takes a while to notice though... Is it supposed to be just one small segment?


N(Posted 1+ years ago)

 Hm.. well I made sure to test the example code that came in the zip, and it worked fine.  I'll take another look at it to see if there's anything I've left out.


N(Posted 1+ years ago)

 The idea I had for precompiling grass nodes is now implemented, along with some minor optimizations and changes to make it more efficient at times.


Picklesworth(Posted 1+ years ago)

 Ooh, works much better now. Thanks noel!Lots of variables to play with too :)This is probably the most useful grass system I've seen in blitz yet. I can't wait to try it out with some nice sprites :D


N(Posted 1+ years ago)

 Updates:-Vertex positions are precalculated, this is to save time on redrawing them per-update.-Nodes used to apply to backfacing triangles (top-down, remember), so if you, for example, had an arch over a section of terrain, it would have created grass on the underside of it: not pretty.  This is 'fixed'.-You can now have a color-map applied to grass nodes when you add an object to the list.  It's an extra argument to AddGrassObject().  Full RGBA support (meaning you control red, green, blue, and alpha of areas).Bugs:-Sometimes grass spawns in the air.  This bug is obviously due to some error (of mine, likely, since the ray intersect code works fine in normal cases) when nodes are being generated.  I have no idea what's causing it yet, but I know where it is and as such it's isolated.<div class="quote"> This is probably the most useful grass system </div>Well, it's only one of three I think.  In any case, I feel this is a really useful module of sorts for adding a great deal of depth to your environments.


Picklesworth(Posted 1+ years ago)

 hm, that's the problem.It seems that when I run this in debug mode, or with a complete compilation in a folder other than the original folder of the .bb file, it will scatter the trail texture everywhere and the grass doesn't show up except in the valley.


N(Posted 1+ years ago)

 Updated- Added support for meshes (this is single-surface support, so even if the mesh you're referencing is using multiple surfaces it will put it all in the same surface).- Various bug fixes.- Importing and exporting node sets.- Per-spawner wind settings.You need to be careful with the mesh spawners, because if you use a lot of polygons then you will probably end up with a pretty damn slow game.  I'm working on some ideas to make this faster, but at the moment you'll have to live with this.


N(Posted 1+ years ago)

 Updated- No more distance checks when determining which node a grass bunch gets placed on- now it checks rows and when the position is determined to be inside of the row it places the quad inside of the node it's inside of.- Added the function SetSpawner() which allows you to choose some ranges.  You might or might not need to use this, it depends on your scene.


Picklesworth(Posted 1+ years ago)

 This newer version seems a bit slower to me... Still great nontheless! And nicer :)


Mustang(Posted 1+ years ago)

 Wooo!


Kissme(Posted 1+ years ago)

 Damned !!! veyr veyr nice stuff ! TYKiss.


N(Posted 1+ years ago)

 I just noticed that starting at my last comment, the time between comments has incremented by two days each.Mr. Pickles commented after two days;Mustang after four;KissKool after six.


morduun(Posted 1+ years ago)

 Very, very slow (6-10fps) with obvious and noticeable pop-in on my system: p3/1G, ge4ti/4400, 256MB ram, WinXP Pro.


Ion-Storm(Posted 1+ years ago)

 Make sure DEBUG is turned off in the editor, ELSE it will slow down..RegardsIon.


morduun(Posted 1+ years ago)

 This goes without saying.  But it also doesn't help the framerate on my rig, either, as I wasn't using it with debug in the first place.


FireballStarfish(Posted 1+ years ago)

 The image doesn't work, and neither does the link :(


N(Posted 1+ years ago)

 It's 3 years old.  Cheers.


YellBellzDotCom(Posted 1+ years ago)

 Does anyone have this available? I sure would like to take a gander! If so, please email to hilbily001@...Thanks!!


N(Posted 1+ years ago)

 Well, you got the source code up there.