Fluid/Path-building thingy-jig

Blitz3D Forums/Blitz3D Programming/Fluid/Path-building thingy-jig

This is something I've been messing around with for the past hour or so, it's based on something I saw on bit-101.com and I decided to give it a go myself.

Imagine a grid of signposts; when a particle reaches a signpost it turns slightly in the direction of the signpost and the signpost turns slightly to match the previous direction of the particle. The emergent behaviour is quite interesting as after a few moments "gulleys" begin to form which the particles flow down. When you see it, it'll be a little clearer!

It could do with a bit of optimisation, and being 2D in Blitz3D it'll be a little slower anyway. It's not too bad on my computer (1000 particles, 40x30 grid), your mileage may vary!

Anyway, the code:
Graphics 800, 600, 32, 1

SetBuffer BackBuffer()




Global MAXX	= GraphicsWidth()
Global MAXY	= GraphicsHeight()

Const GRIDX			= 40
Const GRIDY			= 30
Const CELL			= 20
Const MIDCELL		= 10

Const NUM_PARTICLES = 1000
Const PARTICLE_TURN = 15
Const PARTICLE_SPEED= 5
Const GRID_TURN 	= 1


Dim grid(GRIDX, GRIDY)

For y = 0 To GRIDY - 1: For x = 0 To GRIDX - 1
	grid(x, y) = Rand(360)
Next: Next


Type p_dat
	Field x#, y#
	Field ang
End Type

Global p.p_dat

For n=0 To NUM_PARTICLES - 1
	p.p_dat = New p_dat
		p\x = Rnd(MAXX)
		p\y = Rnd(MAXY)
		p\ang = Rnd(360)
Next


Repeat
	Cls

	count = MilliSecs()

	; Move particles
	For p.p_dat = Each p_dat
		gx = Int(p\x / CELL)
		gy = Int(p\y / CELL)
		
		If p\ang > grid(gx, gy) Then p\ang = p\ang - PARTICLE_TURN: grid(gx, gy) = grid(gx, gy) + GRID_TURN
		If p\ang < grid(gx, gy) Then p\ang = p\ang + PARTICLE_TURN: grid(gx, gy) = grid(gx, gy) - GRID_TURN
		
		
		If p\ang < 0 Then p\ang = p\ang + 360
		If p\ang > 359 Then p\ang = p\ang - 360

		
		h# = Sin(p\ang) * PARTICLE_SPEED
		v# = Cos(p\ang) * PARTICLE_SPEED
		
		p\x = p\x + h#
		p\y = p\y + v#
		
		If p\x < 0 Then p\x = p\x + MAXX
		If p\x > MAXX Then p\x = p\x - MAXX
		If p\y < 0 Then p\y = p\y + MAXY
		If p\y > MAXY Then p\y = p\y - MAXY
	Next

	
	If KeyHit(57) Then
		For y = 0 To GRIDY - 1: For x=0 To GRIDX - 1
			grid(x, y) = turn * 90
		Next: Next
		turn = turn + 1			
	End If

	If KeyHit(19) Then
		For y = 0 To GRIDY - 1: For x=0 To GRIDX - 1
			grid(x, y) = Rnd(360)
		Next: Next
	End If

	; Draw particles
	Color 255, 255, 255
	For p.p_dat = Each p_dat
		Plot p\x, p\y
	Next

	; Draw grid
	count = MilliSecs()

	Color 32, 32, 32
	For y = 0 To GRIDY - 1: For x = 0 To GRIDX - 1
		x1 = x * CELL + MIDCELL
		y1 = y * CELL + MIDCELL
		
		x2 = x1 + Sin(grid(x, y)) * MIDCELL
		y2 = y1 + Cos(grid(x, y)) * MIDCELL
	
		Line x1, y1, x2, y2
		
		If grid(x, y) < 0 Then grid(x, y) = grid(x, y) + 360
		If grid(x, y) > 359 Then grid(x, y) = grid(x, y) - 360
	Next: Next
	
	count = MilliSecs() - count
	
	Color 255, 255, 255
	Text 10, 10, count	
	Text 10, MAXY - 26, "R - randomise all squares; Space - Flip direction of squares NSEW; Esc - quit"
	
	Flip

Until KeyHit(1)

End


That's very interesting. Nice technique. Im sure it has implications although I'm just not certain of what just yet.

I notice the movement of the particles eventually aligns the randomised barriers. If these aprticles gained/lost energy through their collisions I wonder what the results would be like then...

Yeah, eventually a stable pattern is produced and nothing much else seems to happen. Although, all it takes is one particle to nudge a barrier, and it can flip the whole situation over in a matter of a few minutes. Gotta love chaos theory!

I'm going to experiement with this a bit more, and try and optimise it as well.

Cool. All it takes is a random delay between Flips to keep the pattern from stabilising - would make an excellent screensaver.

... actually - just tried that and what you need is a burst of consecutive Flips followed by a settling down period.


First Sswift's orbiting balls, now AdamJ's Shoals of the Canyons (?) .... looks like an emergent behaviour renaissance for Blitz coders !

This is realy cool!
It looks like fluid dynamics.
With a little optimization, I think that it could be used to make fire, smoke, and other particle effects!

I also thought of a swarm of ants.....

hey this is cool, thanks.
I changed and added some things. The angle turning wasn't being calculated correctly, so I added a function that makes an angle turn toward another angle by turning whichever way is closest.
I also made it preserve gulleys by not turning grid angles that are already close to the particle's angle.

I also added a water effect. Set TRAILS to 1 to see it. its cool.
SeedRnd MilliSecs()
Graphics 800, 600, 0, 1

Const TRAILS=0

If Not trails Then SetBuffer BackBuffer()




Global MAXX	= GraphicsWidth()
Global MAXY	= GraphicsHeight()

Const GRIDX			= 40
Const GRIDY			= 30
Const CELL			= 20
Const MIDCELL		= 10

Const NUM_PARTICLES = 1000
Const PARTICLE_TURN = 15
Const PARTICLE_SPEED= 5
Const GRID_TURN 	= 1

Const RECT_SIZE     = 50
Const RECT_SPEED    = 2


Dim grid(GRIDX, GRIDY)

For y = 0 To GRIDY - 1: For x = 0 To GRIDX - 1
	grid(x, y) = Rand(360)
Next: Next


Type p_dat
	Field x#, y#
	Field ang
	Field b
End Type

Global p.p_dat

For n=0 To NUM_PARTICLES - 1
	p.p_dat = New p_dat
		p\x = Rnd(0,MAXX-1)
		p\y = Rnd(0,MAXY-1)
		p\ang = Rnd(360)
		p\b=Rnd(30,255)
Next

Repeat
	If Not trails Then Flip:Cls


	; Move particles
	For p.p_dat = Each p_dat
		gx = Int(p\x / CELL)
		gy = Int(p\y / CELL)
		
		g=grid(gx, gy)
		p\ang=anglemoveto(p\ang,g,PARTICLE_TURN)
		
		;only change the grid angle if it is at least 30 degrees different from the particle's angle
		If Abs(angledif(g,p\ang))>30 Then
			grid(gx, gy)=anglemoveto(g,p\ang,GRID_TURN)
		EndIf

		
		h# = Sin(p\ang) * PARTICLE_SPEED
		v# = Cos(p\ang) * PARTICLE_SPEED
		
		p\x = p\x + h#
		p\y = p\y + v#
		
		If p\x < 0 Then
			p\x = p\x + MAXX
		ElseIf p\x >= MAXX Then
			p\x = p\x - MAXX
		EndIf
		If p\y < 0 Then
			p\y = p\y + MAXY
		ElseIf p\y >= MAXY Then
			p\y = p\y - MAXY
		EndIf
	Next

	
	If KeyHit(57) Then
		Cls
		For y = 0 To GRIDY - 1: For x=0 To GRIDX - 1
			grid(x, y) = turn * 90
		Next: Next
		turn = turn + 1
		If turn>3 Then turn=0
	End If

	If KeyHit(19) Then
		Cls
		For y = 0 To GRIDY - 1: For x=0 To GRIDX - 1
			grid(x, y) = Rnd(360)
		Next: Next
	End If

	If trails Then
		LockBuffer BackBuffer()
		For p = Each p_dat
			WritePixel p\x, p\y, p\b
		Next
		UnlockBuffer BackBuffer()

		For p = Each p_dat
			movex=Sin(p\ang)*Rnd(1,RECT_SPEED)
			movey=Cos(p\ang)*Rnd(1,RECT_SPEED)
			size=Rand(2,rect_size)
			x=p\x-size/2
			y=p\y-size/2
			CopyRect x,y,size,size,x+movex,y+movey
		Next
	Else
		count = MilliSecs()
		
		LockBuffer BackBuffer()

		; Draw particles
		For p = Each p_dat
			WritePixel p\x, p\y, $FFFFFF
		Next
		
		; Draw grid
		Color 44, 44, 44
		For y = 0 To GRIDY - 1: For x = 0 To GRIDX - 1
			x1 = x * CELL + MIDCELL
			y1 = y * CELL + MIDCELL
			
			x2 = x1 + Sin(grid(x, y)) * MIDCELL
			y2 = y1 + Cos(grid(x, y)) * MIDCELL
		
			Line x1, y1, x2, y2
			
		Next: Next
		
		UnlockBuffer BackBuffer()

		count = MilliSecs() - count
		
		Color 255, 255, 255
		Text 10, 10, count	
		Text 10, MAXY - 26, "R - randomise all squares; Space - Flip direction of squares NSEW; Esc - quit"
	EndIf
	
Until KeyHit(1)
End




;makes the angle a little closer to the target angle and returns it
Function AngleMoveTo#(anglenow#,angletar#,amount#)

	amount=amount*Sgn(angletar-anglenow)

	If Abs(angletar-anglenow)>180 Then
		anglenow=anglenow-amount
	Else
		anglenow=anglenow+amount
	EndIf
	
	If anglenow<0 Then
		anglenow=anglenow+360
	ElseIf anglenow>360 Then
		anglenow=anglenow-360
	EndIf

	Return anglenow
End Function



;how far away two angles are, assuming they can skip over the end of anglemax to get to 0
;gives positive and negative return values
Function AngleDif#(anglenow#,angletar#,anglemax=360)
	Local temp#

	temp#=angletar-anglenow
	If temp>+anglemax/2 Then Return temp-anglemax
	If temp<-anglemax/2 Then Return temp+anglemax
	Return temp
End Function


Ah! I thought the angles weren't being calculated correctly, but I couldn't work out what. (this was 2am though!) Thanks for fixing it. And also the lockbuffer stuff as well, I forgot you could do that. Much faster! Glad you like it anyway, I'm wondering what else I could do with this. I'm tempted to make it in 3D mode instead and use a spherical gradiant sprite to make it look more like realistically flowing water. I could even make it flow in 3 dimensions thinking about it, more like smoke.

I'll post it when I'm done!

EDIT: Excellent, you can bump up the number of particles now as well, I have 10,000 running quite smoothly...

EDIT: Just tested the water effect as well, that's really quite impressive well done!

thanks.
this is so awesome for texture generation!
Press T to make a texture. You can fiddle with the constants too... for a long long time.
SeedRnd MilliSecs()
Graphics 800, 600, 0, 1


Global MAXX	= GraphicsWidth()
Global MAXY	= GraphicsHeight()

Const GRIDX			= 40
Const GRIDY			= 30
Const CELL			= 20
Const MIDCELL		= 10

Const NUM_PARTICLES = 4000
Const PARTICLE_TURN = 15
Const PARTICLE_SPEED= 3
Const GRID_TURN 	= 1

Const COLOR_ADD     = 30
Const TEX_SIZE      = 128


Dim grid(GRIDX, GRIDY)

For y = 0 To GRIDY - 1
For x = 0 To GRIDX - 1
	grid(x, y) = Rand(360)
Next
Next


Type p_dat
	Field x#, y#
	Field ang
End Type

Local p.p_dat

For n=0 To NUM_PARTICLES - 1
	p = New p_dat
	p\x = Rnd(0,MAXX-1)
	p\y = Rnd(0,MAXY-1)
	p\ang = Rnd(360)
Next

Repeat

	; Move particles
	For p.p_dat = Each p_dat
		gx = Int(p\x / CELL)
		gy = Int(p\y / CELL)
		
		g=grid(gx, gy)
		p\ang=anglemoveto(p\ang,g,PARTICLE_TURN)
		grid(gx, gy)=anglemoveto(g,p\ang,GRID_TURN)
		
		
		h# = Sin(p\ang) * PARTICLE_SPEED
		v# = Cos(p\ang) * PARTICLE_SPEED
		
		p\x = p\x + h#
		p\y = p\y + v#
		
		If Int(p\x) < 0 Then
			p\x = p\x + MAXX
		ElseIf Int(p\x) >= MAXX Then
			p\x = p\x - MAXX
		EndIf
		If Int(p\y) < 0 Then
			p\y = p\y + MAXY
		ElseIf Int(p\y) >= MAXY Then
			p\y = p\y - MAXY
		EndIf
	Next
	
	If KeyHit(28) Then
		Cls
	EndIf

	
	If KeyHit(57) Then
		For y = 0 To GRIDY - 1
		For x = 0 To GRIDX - 1
			grid(x, y) = turn * 90
		Next
		Next
		turn = turn + 1
		If turn>3 Then turn=0
	End If

	If KeyHit(19) Then
		Cls
		For y = 0 To GRIDY - 1
		For x = 0 To GRIDX - 1
			grid(x, y) = Rnd(360)
		Next
		Next
	End If
	
	;make texture
	If KeyHit(20) Then
		image=CreateImage(maxx,maxy)
		GrabImage image,0,0
		ResizeImage image,TEX_SIZE,TEX_SIZE
		Cls
		For y=0 To maxy/TEX_SIZE
			For x=0 To maxx/TEX_SIZE
				DrawImage image,x*TEX_SIZE,y*TEX_SIZE
			Next
		Next
		FlushKeys
		WaitKey
		FlushKeys
		Cls
	EndIf
	
	;draw
	LockBuffer
	For p = Each p_dat
		b=ReadPixel(p\x,p\y) And $FF
		If b<255-COLOR_ADD Then
			WritePixel p\x, p\y, b+COLOR_ADD
		EndIf
	Next
	UnlockBuffer
	
Until KeyHit(1)
End




;makes the angle a little closer to the targetangle and returns it
Function AngleMoveTo#(anglenow#,angletar#,amount#)

	amount=amount*Sgn(angletar-anglenow)

	If Abs(angletar-anglenow)>180 Then
		anglenow=anglenow-amount
	Else
		anglenow=anglenow+amount
	EndIf
	
	If anglenow<0 Then
		anglenow=anglenow+360
	ElseIf anglenow>360 Then
		anglenow=anglenow-360
	EndIf

	Return anglenow
End Function


Heres some textures I made. Isn't the top one freaky as hell? It looks like a zombie


I love the bottom texture, looks like some kind of wood grain or rope.

I've done a little altering of the code. Nothing major, just something so you can turn the grid on/off and fiddled with the particle color code.

SeedRnd MilliSecs()
Graphics 800, 600, 0, 1

Const TRAILS=0

If Not trails Then SetBuffer BackBuffer()




Global MAXX	= GraphicsWidth()
Global MAXY	= GraphicsHeight()

Const GRIDX			= 40
Const GRIDY			= 30
Const CELL			= 20
Const MIDCELL		= 10

Const NUM_PARTICLES = 5000
Const PARTICLE_TURN = 15
Const PARTICLE_SPEED= 2
Const GRID_TURN 	= 1

Const RECT_SIZE     = 50
Const RECT_SPEED    = 2


Dim grid(GRIDX, GRIDY)

For y = 0 To GRIDY - 1: For x = 0 To GRIDX - 1
	grid(x, y) = Rand(360)
Next: Next


Type p_dat
	Field x#, y#
	Field ang
	Field r, g, b, dec_col
End Type

Global p.p_dat

For n=0 To NUM_PARTICLES - 1
	p.p_dat = New p_dat
		p\x = Rnd(0,MAXX-1)
		p\y = Rnd(0,MAXY-1)
		p\ang = Rnd(360)
		p\r = 64
		p\g = 64
		p\b = Rnd(64) + 192
		p\dec_col = (p\r * $FF00) + (p\g * $FF) + p\b
Next

Repeat
	If Not trails Then Flip:Cls


	; Move particles
	For p.p_dat = Each p_dat
		gx = Int(p\x / CELL)
		gy = Int(p\y / CELL)
		
		g=grid(gx, gy)
		p\ang=anglemoveto(p\ang,g,PARTICLE_TURN)
		
		;only change the grid angle if it is at least 30 degrees different from the particle's angle
		If Abs(angledif(g,p\ang))>30 Then
			grid(gx, gy)=anglemoveto(g,p\ang,GRID_TURN)
		EndIf

		
		h# = Sin(p\ang) * PARTICLE_SPEED
		v# = Cos(p\ang) * PARTICLE_SPEED
		
		p\x = p\x + h#
		p\y = p\y + v#
		
		If p\x < 0 Then
			p\x = p\x + MAXX
		ElseIf p\x >= MAXX Then
			p\x = p\x - MAXX
		EndIf
		If p\y < 0 Then
			p\y = p\y + MAXY
		ElseIf p\y >= MAXY Then
			p\y = p\y - MAXY
		EndIf
	Next



	If trails Then
		LockBuffer BackBuffer()
		For p = Each p_dat
			WritePixel p\x, p\y, p\dec_col
		Next
		UnlockBuffer BackBuffer()

		For p = Each p_dat
			movex=Sin(p\ang)*Rnd(1,RECT_SPEED)
			movey=Cos(p\ang)*Rnd(1,RECT_SPEED)
			size=Rand(2,rect_size)
			x=p\x-size/2
			y=p\y-size/2
			CopyRect x,y,size,size,x+movex,y+movey
		Next
		
	Else
		count = MilliSecs()
		
		LockBuffer BackBuffer()

		; Draw particles
		For p = Each p_dat
			WritePixel p\x, p\y, p\dec_col
		Next
		
		; Draw grid
		If grid_switch Then
			Color 44, 44, 44
			For y = 0 To GRIDY - 1: For x = 0 To GRIDX - 1
				x1 = x * CELL + MIDCELL
				y1 = y * CELL + MIDCELL
				
				x2 = x1 + Sin(grid(x, y)) * MIDCELL
				y2 = y1 + Cos(grid(x, y)) * MIDCELL
			
				Line x1, y1, x2, y2
			
			Next: Next
		End If
		
		UnlockBuffer BackBuffer()

		count = MilliSecs() - count




		
		Color 255, 255, 255

		If KeyHit(34) Then grid_switch = Not grid_switch

		If KeyHit(59) Then showhelp = Not showhelp

		If showhelp Then
			Text 10, 10, "Render time: " + count + " m/sec"
			Text 10, 30, "G   - Show/Hide Grid"
			Text 10, 46, "R   - Randomise all squares"
			Text 10, 62, "F   - Flip direction of grid"
			Text 10, 78, "Esc - quit"
		Else
			Text 10, 10, "F1 - help"
		End If
	
		If KeyHit(33) Then
			Cls
			For y = 0 To GRIDY - 1: For x=0 To GRIDX - 1
				grid(x, y) = turn * 90
			Next: Next
			turn = turn + 1
			If turn>3 Then turn=0
		End If

		If KeyHit(19) Then
			Cls
			For y = 0 To GRIDY - 1: For x=0 To GRIDX - 1
				grid(x, y) = Rnd(360)
			Next: Next
		End If	
	
	EndIf
	
Until KeyHit(1)
End




;makes the angle a little closer to the target angle and returns it
Function AngleMoveTo#(anglenow#,angletar#,amount#)

	amount=amount*Sgn(angletar-anglenow)

	If Abs(angletar-anglenow)>180 Then
		anglenow=anglenow-amount
	Else
		anglenow=anglenow+amount
	EndIf
	
	If anglenow<0 Then
		anglenow=anglenow+360
	ElseIf anglenow>360 Then
		anglenow=anglenow-360
	EndIf

	Return anglenow
End Function



;how far away two angles are, assuming they can skip over the end of anglemax to get to 0
;gives positive and negative return values
Function AngleDif#(anglenow#,angletar#,anglemax=360)
	Local temp#

	temp#=angletar-anglenow
	If temp>+anglemax/2 Then Return temp-anglemax
	If temp<-anglemax/2 Then Return temp+anglemax
	Return temp
End Function


As for the 3D effects for water, My thoughts on the best implementation are:

The 'dots' represent Positive Y Vertex points for triangles in a mesh with some soft-surface physics.

Bit techy for me to even attempt, but that's my thoughts anyway.

I've created a 3D version of this, but it's not working as how I imagined it. Rather than arranging itself into gulleys like the 2D version, there's no hint of any kind of order at all. If you look a little closer, you do notice that the particles loosely follow each other, but not very much. I don't think it's a problem with the code as such, more to do with the fact that in 3D the particles have more freedom of movement and won't be as inclined to follow each other so much. Perhaps if I left it running a bit longer something may emerge, but I'm doubting it. I'm going to keep looking to make sure I haven't made any mistakes and I'll post the source when I'm done.