Code archives/3D Graphics - Effects/Dynamic Water

This code has been declared by its author to be Public Domain code.

Download source code

Dynamic Water by Danny2004
These functions can easily be used to have your characters footsteps cause ripples in the water.
This Demo shows a boucning ball between two liquid surfaces.
Not fully finished yet, but should get you going.
Hit the left-mouse button to create random splashes...

Heavily 'inspired' on previous samples by Reda Borchardt and Rob Cummings.

Have fun,-
Danny

Sample screen shot:
; fxWater Module by Danny van der Ark - dendanny@xs4all.nl
;
; (Heavily) inpired by samples by Reda Borchardt and Rob Cummings.
; use as you like - send me a sample would be cool.
;
; april '04
;

;
; Usage:
;
; 1. Create a water surface using: fxWater_Create( name$, xsize, zsize, dampening#, parent )
;    This function returns the 'type-handle' to the water surface wich you will need later.
;
; 2. Obtain the entity handle (to apply color, texture, alpha, etc) using the function
;	 fxWater_get_entity( typehandle) -> typehandle is the value returned by fxWater_Create()
;
; 3. Then simply call 'fxWater_Update()' every frame during your main loop.
;
;
; 4. To create a splah in the water use the fxWater_Dimple() function. The function needs the
;	 type handle that was returned by 'fxWater_Create', the coordinates of the splash, the
;	 force/strength of the splash and the 'range' or size of the splash.
;
;	 fxWater_Dimple( hand, x,z, force#=1.0, range#=1.0)
;
; 5. Freeing stuff not done yet because I want to alter it to use memory banks in stead of
;	 a large static array to store the vertex data - wich should give a more efficient use
;	 of memory...
;
; You can create as many surfaces of any size as you wish. If you wish surfaces larger than 100x100
; then adjust the fxwater_max_depth/width globals. If you want more than 5 surfaces at the same time
; adjust the fxwater_max accordingly. 
; 
; It is necesary To 'UpdateNormals' every frame to ensure the highlights are re-calculated every
; frame. Without this the ripples are there, but just not visible. This slows things down
; dramatically ofcourse. Any other ideas welcome....
;
; He ho, enjoy!
;
; ooo
; (_) Danny v.d. Ark



Global fxWATER_MAX = 5				;max number of active surfaces/effects at one time
Global fxWATER_NUM = 0				;current number of active water effects.

Global FXWATER_MAX_WIDTH = 100		;max sub-division for water meshes
Global FXWATER_MAX_DEPTH = 100		;max sub-division for water meshes

Global FXWATER_MAX_BANKS = fxWATER_MAX * 2
Global FXWATER_NUM_BANKS = 0


Global DEMO_WATER_WIDTH	 = 40 	;--> Adjust these for different plane sizes / mesh resolution
Global DEMO_WATER_DEPTH  = 40


;reserve memory banks for vertice altitudes
Dim fxWaterBank#(FXWATER_MAX_BANKS, FXWATER_MAX_WIDTH, FXWATER_MAX_DEPTH )


Type fxWaterWave

	Field id
	Field name$
	
	Field active		;if not ripples then fx is not actiave
	
	Field parent		;parent entity (if any)
	Field entity		;water mesh
	Field surface		;water surface

	Field bank1			;pointers to array for mem storage
	Field bank2

	Field width			;width of plane / surface
	Field depth			;depth of plane / surface
	Field dampening#	;water dynamics

End Type

;Demo options
Global OPT_WIREFRAME = 0
Global OPT_REFRSH 	 = 0
Global OPT_BALLFREEZE= 0
Global OPT_CEILING	 = 1

;--------------------------------------------------------------------------------------------------

;Graphics3D 640,480,32,1
Graphics3D 640,480,0,2

; Camera + Light
Global campivot = CreatePivot()
Global camera = CreateCamera(campivot)

PositionEntity camera, 0, 0, -35
PointEntity camera, campivot

light = CreateLight(2)
PositionEntity light,-60,0,100
LightColor light,240,240,210
PointEntity light, campivot

light = CreateLight(2)
PositionEntity light,50,0,-50
LightColor light,150,150,180
PointEntity light, campivot

AmbientLight 40,40,40

;load texture
;tex = LoadTexture ("water.tga",1+64)
tex = create_noise_map()

;create water planes
Global w1 = fxWater_Create( "floor",  DEMO_WATER_WIDTH, DEMO_WATER_DEPTH, 0.025 )
water = fxWater_get_entity( w1 )
PositionEntity water, 0, -10, 0
EntityColor water, 30,50,200
EntityShininess water, 0.1
EntityTexture water,tex

Global w2 = fxWater_Create( "ceiling",  DEMO_WATER_WIDTH, DEMO_WATER_DEPTH, 0.025 )
water = fxWater_get_entity( w2 )
FlipMesh water ; flip it because the camera is underneath it
PositionEntity water, 0, 10, 0
EntityColor water, 250,100,100
EntityColor water, 140,130,20
EntityShininess water, 0.1
EntityTexture water,tex

;force refresh
fxWater_dimple( w1, 1,1, 0.001, 0.001)
fxWater_dimple( w2, 1,1, 0.001, 0.001)

;set random ball direction/speed
Global bx#=  0.2
Global by#= -1.75
Global bz#= -0.25

Global bmaxx# =  DEMO_WATER_WIDTH * 0.5	;half the width of the water plane
Global bmaxy# = 10.0
Global bmaxz# =  DEMO_WATER_DEPTH * 0.5	;half the depth of the water plane

;create ball
ball = CreateSphere(6)
EntityColor ball, 0,0,0
EntityShininess ball, 1.0
EntityTexture ball, tex

SeedRnd MilliSecs()

; Main Loop
tstart = MilliSecs() + 1000
frame = 0

Global lastx# = 0
Global lasty# = 0
Global lastz# = 0

tim = CreateTimer(50)

l$ = "WavyWaterFx by Danny van der Ark"

While Not KeyHit(1)

	;update bouncing ball
	update_ball( ball )
	
	;update fxWater
	fxWater_update()
	
	;render
    UpdateWorld
    RenderWorld

	;debug
	Color 000,000,000
	Text 2,1, l$
	Color 255,255,255
	Text 2,0, l$
	Color 100,100,100
	Text 2,12, "based on samples from Reda Borchardt And Rob Cummings."
	Color 20,140,255
	Text 2,454, "[ESC] to quit - [D] for fast but dirty refresh(" + OPT_REFRESH + ") - [W] To toggle WireFrame (" + OPT_WIREFRAME + ")"
	Text 2,466, "[P] To pause  - [B] to pause/reset ball - [C] to toggle ceiling"
	Color 250,250,220
	Text 10,220,"FPS  " + fps
	Text 10,232,"TRIS " + TrisRendered()

	;orbit camera
	;TurnEntity campivot, 0, 0.25, 0

	;check fps
	frame = frame + 1
	If MilliSecs() >= tstart Then
		fps = frame
		frame = 0
		tstart = MilliSecs() + 1000
	EndIf

	;toggle ceiling [C]
	If KeyHit(46) Then
		OPT_CEILING = 1 - OPT_CEILING
		If OPT_CEILING Then
			water = fxWater_Get_Entity(w2)
			ShowEntity water
		Else
			water = fxWater_Get_Entity(w2)
			HideEntity water
		EndIf
	EndIf
	
	;pause all [P]
	If KeyHit(25) Then
		FlushKeys
		While Not KeyHit(25) Wend
	EndIf

	;pause ball [B]
	If KeyHit(48) Then
		OPT_BALLFREEZE = 1 - OPT_BALLFREEZE
		If OPT_BALLFREEZE Then
			PositionEntity ball, 0,0,0
			bx# = 0
			by# = 0
			bz# = 0
		Else
			bx#=Rnd#(-1,1)
			by#=Rnd#(-2,2)
			bz#=Rnd#(-1,1)
		EndIf
	EndIf

	;wireframe [W]
	If KeyHit(17) Then
		OPT_WIREFRAME = 1 - OPT_WIREFRAME
		WireFrame OPT_WIREFRAME
	EndIf
	
	;Dirty refresh [D]
	If KeyHit(32) Then
		OPT_REFRESH = 1 - OPT_REFRESH
	EndIf	

	;check refresh mode
	If OPT_REFRESH Then
		;fast and dirty (pot noodle style)
	    Flip False
	Else
		;slow but clean
		Flip True
		WaitTimer(tim)
	EndIf

	If MouseHit(1) Then
		x = Rnd(1,DEMO_WATER_WIDTH)
		z = Rnd(1,DEMO_WATER_DEPTH)
		fxWater_Dimple( w1, x, z, 1, 5)
	EndIf
Wend

End

;--------------------------------------------------------------------------------------------------

Function update_ball( ent )

	;move the ball
	TranslateEntity ent, bx,by,bz

	DoDimple = False
	
	;check left/right walls
	If EntityX#(ent) >  bmaxx-2 Then bx# = bx# * -1
	If EntityX#(ent) < -bmaxx+2 Then bx# = bx# * -1

	;check ceiling bounce
	If EntityY#(ent) >  bmaxy-2 Then
		;reverse vertical direction
		by# = by# * -1.0
		;create dimple
		fxWater_Dimple(w2, EntityX(ent)+bmaxx,EntityZ(ent)+bmaxz, -1.0, 5.0)
		;slightly alter direction
		bx# = bx# + Rnd#(-0.25, 0.25)
		bz# = bz# + Rnd#(-0.25, 0.25)
	EndIf

	;check floor bounce
	If EntityY#(ent) < -bmaxy+2 Then
		;reverse vertical direction
		by# = by# * -1.0
		;create dimple
		fxWater_Dimple(w1, EntityX(ent)+bmaxx,EntityZ(ent)+bmaxz, 1.0, 5.0)
		;slightly alter direction
		bx# = bx# + Rnd#(-0.25, 0.25)
		bz# = bz# + Rnd#(-0.25, 0.25)
	EndIf

	;check front/back walls
	If EntityZ#(ent) >  bmaxz-2 Then bz# = bz# * -1
	If EntityZ#(ent) < -bmaxz+2 Then bz# = bz# * -1

End Function

;--------------------------------------------------------------------------------------------------

Function fxWater_Create( name$="", width=1, depth=1, damp#=0.01, parent=0 )


	;create new Wavy water effect plane
	w.fxWaterWave = New fxWaterWave

	w\id		= 1
	w\name$		= name$
	
	w\active	= True
	
	;create rectangular grid mesh
	w\width		= width
	w\depth		= depth
	w\dampening#= 1 - damp#
	
	w\parent	= parent
	w\entity	= create_mesh_plane( w\width, w\depth, False, parent )
	w\surface	= GetSurface(w\entity,1)
	
	;store handle for quick retrieval during collision
	NameEntity w\entity, Handle(w)
	
	;reserve memory banks to hold vertex energy
	w\bank1		= fxWater_Create_Buffer()
	w\bank2		= fxWater_Create_Buffer()
	
	;return mesh handle
	Return Handle(w)
	
End Function

;--------------------------------------------------------------------------------------------------

Function fxWater_update()

	For w.fxWaterWave = Each fxWaterWave

		;if the surface is perfectly flat then this value remains 0
		dyna# = 0
		
		;process water
	    For x = 1 To w\width-1
	        For z = 1 To w\depth-1
				fxWaterBank#(w\bank2,x,z) = (fxWaterBank#(w\bank1,x-1,z) + fxWaterBank#(w\bank1,x+1,z) + fxWaterBank#(w\bank1,x,z+1) + fxWaterBank#(w\bank1,x,z-1)) / 2.1-fxWaterBank#(w\bank2,x,z) 
				fxWaterBank#(w\bank2,x,z) = fxWaterBank#(w\bank2,x,z) * w\dampening#
				dyna# = dyna# + fxWaterBank#(w\bank2,x,z)
	        Next
	    Next

		;Only deform patch if necesary
		If dyna# <> 0 Then
		    ;PatchTransform
			k=0
			For i = 0 To w\depth
		        For j = 0 To w\width
					VertexCoords(w\surface,k,VertexX(w\surface,k),fxWaterBank#(w\bank2,j,i),VertexZ(w\surface,k))
		            k=k+1
		        Next
		    Next
		EndIf

		;should be optional - depending on type of texture (slows down seriously!)
		UpdateNormals w\entity
		
	    ;SwapWaterBuffer
		tmp = w\bank1
		w\bank1 = w\bank2
		w\bank2 = tmp

	Next

End Function

;--------------------------------------------------------------------------------------------------

Function fxWater_Dimple( hand, x,z, force#=1.0, range#=1.0)
	
	w.fxWaterWave = Object.fxWaterWave(hand)

	For xg = x - range# * 0.5 To x+range# * 0.5
		For zg = z - range# * 0.5 To z+range# * 0.5
			If xg> 0 And xg < w\width And zg>0 And zg<w\depth Then
				fxWaterBank#(w\bank2, xg,zg) = force#
			EndIf
		Next
	Next

End Function

;--------------------------------------------------------------------------------------------------

Function fxWater_get_surface( hand )

	w.fxWaterWave = Object.fxWaterWave( hand )
	Return w\surface

End Function

;--------------------------------------------------------------------------------------------------

Function fxWater_get_entity( hand )

	w.fxWaterWave = Object.fxWaterWave( hand )
	Return w\entity

End Function

;--------------------------------------------------------------------------------------------------

Function fxWater_Create_Buffer() ; xsize=1, zsize=1 )

	If FXWATER_NUM_BANKS >= FXWATER_MAX_BANKS Then 
		RuntimeError "[fxWater::Create_buffer] Max amount of fxWater memory banks reached!"
	Else
		;create a new memory bank and resize it to fit
		FXWATER_NUM_BANKS = FXWATER_NUM_BANKS + 1
		;NOTE: convert array into memory bank
	EndIf

	Return FXWATER_NUM_BANKS
	
End Function

;--------------------------------------------------------------------------------------------------

Function fxWater_Free_Buffers( )

;| Frees all buffers. Call as a part of when scene/level is removed from memory.


	;NOTE: resize memory banks to 0 (once implemented).

	Return 0
	
End Function

;--------------------------------------------------------------------------------------------------

;Creates a flat grid mesh
Function create_mesh_plane(width=1,depth=1,doublesided=False,parent=0)

	tot = width + (depth*width)
	mix#= (width+depth) * 0.5
	
	mesh=CreateMesh( parent )
	surf=CreateSurface( mesh )
	
	stx#=-.5
	sty#=stx
	stp#=Float(1)/Float(mix#)
	y#=sty#
	
	For a=0 To depth
		x#=stx
		v#=a/Float(depth)
		
		For b=0 To width
			u#=b/Float(width)
			AddVertex(surf,x,0,y,u,v)
			x=x+stp
		Next
		y=y+stp
	Next
	
	For a=0 To depth-1
		For b=0 To width-1
			v0=a*(width+1)+b:v1=v0+1
			v2=(a+1)*(width+1)+b+1:v3=v2-1
			AddTriangle( surf,v0,v2,v1 )
			AddTriangle( surf,v0,v3,v2 )
		Next
	Next
	
	UpdateNormals mesh

	If doublesided=True Then EntityFX mesh,16
	
	FitMesh mesh, -width*0.5, 0, -depth*0.5, width, 1, depth
	
	Return mesh

	
End Function

;--------------------------------------------------------------------------------------------------

Function create_noise_map()

; creates a noise map to be used as a generic reflection map

	sq = 128

	tex = CreateTexture(sq,sq,65,1)
	tbuf = TextureBuffer(tex)
	SetBuffer(tbuf)
	
	For x = 0 To sq-1
		For y = 0 To sq-1
			r = Rnd(100,120)
			g = Rnd(100,130)
			b = Rnd(190,240)
			Color r,g,b
			Rect x,y,1,1
		Next
	Next	
	
	SetBuffer(BackBuffer())
	
	Return tex

End Function


;--------------------------------------------------------------------------------------------------
;[end of stuff]

Comments

churchaxe2004
looks very nice!


DJWoodgate2004
Yes, very good. You shoud be able to make it a bit faster by using that Calculate_normal routine SSwift put in the archives a little while ago. You will need to add a Local statement to his routine though as I think it uses some variables you use as globals. Comment out the bit where he normalises the normals as well, not required in this case. You may think of other ways to optimise it for use here.

Another option I suppose is to fake something that looks somewhat convincing but runs pretty fast. Not so easy to get something that looks convincing though as you can see from my rather poor effort :(
;Only deform patch if necesary
If Abs(dyna#) > 0.2 Then
    ;PatchTransform
    k=0
    For i = 0 To w\depth
       For j = 0 To w\width
	  h#=fxwaterbank(w\bank2,j,i)		
	  VertexCoords(w\surface,k,VertexX(w\surface,k),h,VertexZ(w\surface,k))
	  VertexNormal(w\surface,k,-0.8-h*0.25,h*0.1,0.2-h*0.25)
	  k=k+1
	Next
    Next
EndIf



Ziltch2004
Thanks


DJWoodgate2004
I guess you could also try adding some noise into the mix. This uses Rnd() but I expect there are better ways.
VertexNormal(w\surface,k, -0.5-h*0.25+Rnd(0.1), 0.5+h*0.1, 0.2-h*0.25+Rnd(0.1))



Danny2004
Cheers David,

Yes I noticed I made a mistake in the part where it's "not" supposed to update the mesh when nothing is happening. I'll check out that 'vertex normal' stuff you're talking about, looks like good stuff. Also I want to have the 'vertex buffers' stored in variable-sized memory banks in steda of static (memory munching) arrays.
I'll reposted when I get there..

ta
Danny,-


explosive2004
Very cool stuff.

Thanks a lot Danny and all the others.

yeah, and I'm gonna send you a sample, if I got something cool


Clyde2004
That is amazing, welldone dude!


puki2004
Now I have my new super-compuer, it actually runs very quick.


mrjh2006
Does this work on animated meshes?


AJirenius2008
[edit]


Code Archives Forum