Fluid/Path-building thingy-jig

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

AdsAdamJ(Posted 2006) [#1]
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



_PJ_(Posted 2006) [#2]
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...


AdsAdamJ(Posted 2006) [#3]
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.


BlackJumper(Posted 2006) [#4]
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 !


Steven Noyce(Posted 2006) [#5]
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!


_PJ_(Posted 2006) [#6]
I also thought of a swarm of ants.....


Curtastic(Posted 2006) [#7]
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.



AdsAdamJ(Posted 2006) [#8]
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!


Curtastic(Posted 2006) [#9]
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.


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



AdsAdamJ(Posted 2006) [#10]
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



_PJ_(Posted 2006) [#11]
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.


AdsAdamJ(Posted 2006) [#12]
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.