Living Fluid Particles. One last step to take

BlitzMax Forums/BlitzMax Programming/Living Fluid Particles. One last step to take

So I've been tinkering with a fluid dynamics screensaver for a number of days now. I just love just sitting and watching it and I think other
people I know might enjoy it too. I want to release it as a screensaver.

I know you can simply change .exe to .scr and it will work as a screensaver. But it doesn't work flawlessly; it doesn't preview properly
in the windows screensaver selection screen.

Is there a way to turn this exe into a proper screensaver executable file? And can I give the file a proper icon? A blank window for an icon
looks very unprofessional.

I encourage you guys to try it and tell me what you think. Here is an exe
http://www.andysuderman.com/1/AndysGelblobScreensaver.exe

and here is the code:
SuperStrict
AppTitle = "Move mouse to Exit"
Global particleNum:Int = 6900

SeedRnd MilliSecs()
Graphics 800,600, 32

Global debug:Int = False

Global poof:Timage = drawpoof()
Global colourVolitility:Int = 2
Global initialWaited:Int = False

Type vectorType
	Field x:Float, y:Float
EndType

Type lazyParticleSpyType
	'This code follows a single particle(#10) around and checks if it is staying still for too long.
	'If it is, an explosion is created at the location of the spy particle.  This makes the dormant blobs
	'explode sometimes, which looks cool.
	Field movementPoints:Int
	Method update()
		If particles[10].x1< 1 And particles[10].y1<1 Then 
			movementPoints:-1
		Else
			movementPoints:+1
			If movementPoints>100 Then movementPoints = 100
		EndIf
		If movementPoints<0 Then explode()
	EndMethod
	Method explode()
		Local angle:Int, power:Float
		For Local a:Int = 0 To particleNum
			If within(particles[10].x, particles[10].y, particles[a].x, particles[a].y, 40, 40) Then
				angle = Rand(360)
				power = RndFloat()*10
				If Rand(4)=2 Then power:*2
				particles[a].x1=Sin(angle)*power
				particles[a].y1=Cos(angle)*power
			EndIf
		Next
		movementPoints = 100
		particles[10].x:+400
		If particles[10].x>800 Then particles[10].x:-800
	EndMethod
	Method New()
		movementPoints = 180
	EndMethod
EndType

Global lazyParticleSpy:lazyParticleSpyType = New lazyParticleSpyType


Type gridType
	Field show:Int
	Field width:Int = 800, height:Int = 600
	Field resolution:Int = 30
	Field xPoints:Int  = width/resolution+3
	Field yPoints:Int = height/resolution+2
	Field vector:vectorType[xPoints+1,yPoints+1]
	Field xCenter:Int = 400-width*.5, yCenter:Int = 300-height*.5
	Method New()
		For Local yy:Int = 0 To yPoints
			For Local xx:Int = 0 To xPoints
				vector[xx,yy] = New vectorType
			Next
		Next
	EndMethod
	Method calmDown()
		For Local y:Int = 0 To yPoints
			For Local x:Int = 0 To xPoints
				grid.vector[x,y].x:+(RndFloat()-.5)*.02
				grid.vector[x,y].y:+(RndFloat()-.5)*.02
				grid.vector[x,y].x:*.999
				grid.vector[x,y].y:*.999
			Next
		Next
	EndMethod
	Method speedLimit()
		For Local yy:Int = 0 To yPoints
			For Local xx:Int = 0 To xPoints
				If Abs(vector[xx, yy].x) > 9 vector[xx, yy].x:*.6
				If Abs(vector[xx, yy].y) > 9 vector[xx, yy].y:*.6
			Next
		Next
	EndMethod
	Method circles(event:Int=2)
		Local angle:Int, centerXpixels:Int = 400, centerYpixels:Int = 300, radius:Int = 300
		Local xGrid:Int, yGrid:Int, centerXgrid:Int, centerYGrid:Int, r:Int, times:Int = 1
		
		Select event
			Case 1
				centerXPixels = Rand(800)
				centerYPixels = Rand(600)
			Case 2
				times = 20
				centerXPixels = Rand(800)
				centerYPixels = Rand(600)
				radius = 90+Rand(140)
			Default
				'change nothing. Keep default values
		EndSelect
		For Local b:Int = 1 To times
			r = 0
			Repeat
				angle = 0
				Repeat
					xGrid = (centerXpixels+r*Sin(angle))/resolution
					yGrid = (centerYpixels+r*Cos(angle))/resolution
					If xGrid > xPoints Then xGrid:-xPoints
					If yGrid > yPoints Then yGrid:-yPoints
					If xGrid < 0 Then xGrid:+xPoints
					If yGrid < 0 Then yGrid:+yPoints
					grid.vector[xGrid,yGrid].x = Sin(angle+90)
					grid.vector[xGrid,yGrid].y = Cos(angle+90)
					angle:+10
				Until angle>360
				r:+10
			Until r > radius
			angle = 0
			Repeat
				xGrid = (centerXpixels+radius*Sin(angle))/resolution
				yGrid = (centerYpixels+radius*Cos(angle))/resolution
				If xGrid > xPoints Then xGrid:-xPoints
				If yGrid > yPoints Then yGrid:-yPoints
				If xGrid < 0 Then xGrid:+xPoints
				If yGrid < 0 Then yGrid:+yPoints
				grid.vector[xGrid,yGrid].x = Sin(angle+140)*2
				grid.vector[xGrid,yGrid].y = Cos(angle+140)*2
				angle:+10
			Until angle> 360
			centerXPixels = Rand(800)
			centerYPixels = Rand(600)
			radius = 90+Rand(140)
		Next
	EndMethod
	Method waterSmear(whichOne:Int =0)
		If Not whichone Then whichone = Rand(2)

		Select whichOne
			Case 1
				For Local y:Int = 0 To yPoints
					For Local x:Int = 0 To xPoints
						grid.vector[x,y].x = .2
						grid.vector[x,y].y = Sin(x*10)+1
					Next
				Next
			Case 2
				Local b:Float, d:Int, angleOffset:Int
				Local c:Float = RndFloat()*.6+.2
				angleOffset = Rnd(100)-30
				For Local y:Int = 0 To yPoints
					For Local x:Int = 0 To xPoints
						grid.vector[x,y].x = -.4*Cos(angleOffset)
						d=0
						If y > (yPoints*c) Then d=180'b = -b
						b = Sin(x*(10-x)+(d+angleOffset))
						grid.vector[x,y].y = b
					Next
				Next
			Default
		EndSelect
	EndMethod
	Method explode()
		For Local y:Int = 0 To yPoints
			For Local x:Int = 0 To xPoints
				If Rnd()<.2
					grid.vector[x,y].x:+(RndFloat()-.5)*15
					grid.vector[x,y].y:+(RndFloat()-.5)*15
				EndIf
			Next
		Next
	EndMethod
	Method kill()
		For Local y:Int = 0 To yPoints
			For Local x:Int = 0 To xPoints
				grid.vector[x,y].x=0
				grid.vector[x,y].y=0
			Next
		Next
	EndMethod
	Method drawGradient()
		glbegin Gl_Quads
			glcolor3f	 0.9, 0.4, 0.1
			glvertex3f 	2, 2, 0
			glvertex3f  80, 2, 0
			glvertex3f  80, 72, 0
			glvertex3f  2, 52, 0
		glend 
	EndMethod
	Method drawVectors()
		Local xPos:Int, yPos:Int
		For Local yarray:Int = 0 To yPoints-2
			For Local xarray:Int = 0 To xPoints-2
				xPos = xCenter + xArray * resolution
				yPos = yCenter + yArray * resolution
				SetColor 250,0,0
				Drawl xPos,yPos, vector[xArray, yArray].x*20, vector[xArray, yArray].y*20,1,LINE
			Next
		Next
	EndMethod
EndType
Global grid:gridType = New gridType

Type timerType
	Field t50Flag:Int, t50:Int
	Field t22000Flag:Int, t22000:Int
	Method update()
		t50Flag = 0
		t22000Flag = 0 
		If MilliSecs()-t50 >50 t50Flag = 1; t50 = MilliSecs()
		If MilliSecs()-t22000>22000 t22000Flag = 1; t22000 = MilliSecs()
	EndMethod
	Method New()
		t50 = MilliSecs()
		t22000 = MilliSecs()-10000
	EndMethod
EndType

Type colourSchemeType
	'colour[which particle, which overall scheme]
	Field redCls:Float, greenCls:Float, blueCls:Float
	Field redClsTarget:Float, greenClsTarget:Float, blueClsTarget:Float
	Field r:Int[4,6], g:Int[4,6], b:Int[4,6]
	Field current:Int, totalSchemes:Int = 5
	Method update()
		If timer.t22000Flag Then
			current:+1
			If current> totalSchemes Then current = 0
			changecolour()
			Select Rand(6)
				Case 1
					grid.waterSmear(1)
				Case 2
					grid.waterSmear(2)
				Case 3,4
					grid.kill()
					For Local a:Int = 0 To particleNum -1 Step 2 		'this makes half of the particles have reverse gravity
						particles[a].gravity=.08
					Next
				Default
					grid.circles()
					If initialWaited = True And Rand(2) = 1 Then particleType.reset(4)
			EndSelect
			If initialWaited = False grid.circles()
			initialWaited = True
		EndIf
		If timer.t50Flag Then
			
			lazyParticleSpy.update()
			
			If redCls<redClsTarget redCls:+colourVolitility*.2
			If redCls>redClsTarget redCls:-colourVolitility*.2

			If GreenCls<GreenClsTarget GreenCls:+colourVolitility*.2
			If GreenCls>GreenClsTarget GreenCls:-colourVolitility*.2

			If BlueCls<BlueClsTarget BlueCls:+colourVolitility*.2
			If BlueCls>BlueClsTarget BlueCls:-colourVolitility*.2
			
			SetClsColor redCls, greenCls, blueCls
		EndIf
	EndMethod
	Method changeColour()
		redClsTarget=r[2, current]*.6
		greenClsTarget=g[2, current]*.6
		blueClsTarget=b[2, current]*.6
		
		For Local a:Int = 0 To 3
			particles[a].r = r[a, current]
			particles[a].g = g[a, current]
			particles[a].b = b[a, current]
		Next
	EndMethod
	Method debug()
		For Local y:Int = 0 To totalSchemes
			For Local x: Int = 0 To 3
				SetColor r[x, y], g[x, y], b[x, y]
				DrawRect 40+x*60, 40+y*70, 57, 57
			Next
		Next
		Flip
		WaitKey
		End
	EndMethod
	
	Method New()
		'Colour Scheme 0
		r[0,0]=000
		g[0,0]=55
		b[0,0]=000
		
		r[1,0]=140
		g[1,0]=196
		b[1,0]=100

		r[2,0]=158
		g[2,0]=000
		b[2,0]=001
		
		r[3,0]=146
		g[3,0]=155
		b[3,0]=000
		
	
		'Colour Scheme 1
		r[0,1]=239
		g[0,1]=186
		b[0,1]=132
		
		r[1,1]=165
		g[1,1]=038
		b[1,1]=039
		
		r[2,1]=255
		g[2,1]=95
		b[2,1]=80
		
		r[3,1]=155
		g[3,1]=103
		b[3,1]=043
		
		'Colour Scheme 2
		r[0,2]=010
		g[0,2]=104
		b[0,2]=000
		
		r[1,2]=000
		g[1,2]=150
		b[1,2]=122
		
		r[2,2]=040
		g[2,2]=190
		b[2,2]=000
		
		r[3,2]=033
		g[3,2]=033
		b[3,2]=165

		'Colour Scheme 3
		r[0,3]=130
		g[0,3]=030
		b[0,3]=150
		
		r[1,3]=066
		g[1,3]=025
		b[1,3]=165
		
		r[2,3]=145
		g[2,3]=060
		b[2,3]=040
		
		r[3,3]=000
		g[3,3]=111
		b[3,3]=155
		
		'Colour Scheme 4
		r[0,4]=037
		g[0,4]=185
		b[0,4]=126

		r[1,4]=255
		g[1,4]=186
		b[1,4]=156
		
		r[2,4]=255
		g[2,4]=121
		b[2,4]=066
		
		r[3,4]=055
		g[3,4]=095
		b[3,4]=020
		
		'Colour Scheme 5
		r[0,5]=000
		g[0,5]=005
		b[0,5]=080
		
		r[1,5]=132
		g[1,5]=130
		b[1,5]=198
		
		r[2,5]=074
		g[2,5]=024
		b[2,5]=239
		
		r[3,5]=074
		g[3,5]=060
		b[3,5]=000

	EndMethod
EndType

Global colourScheme:colourSchemeType = New colourSchemeType
colourScheme.current = Rand(colourScheme.totalSchemes)
'colourScheme.debug()


Type particleType
	Field x:Float, y:Float
	Field x1:Float, y1:Float
	Field r:Int, g:Int, b:Int
	Field xGrid:Int, yGrid:Int
	Field gravity:Float
	Method do(ID:Int)
		If timer.t50Flag Then
			If ID > 3 Then
				For Local a:Int = 0 To 3
					If within(x, y, particles[a].x, particles[a].y, 50,50) Then
						If r < particles[a].r Then 
							r:+colourVolitility
							If r>255 Then r = 255
						EndIf
						If g < particles[a].g Then 
							g:+colourVolitility
							If g>255 Then g = 255
						EndIf
						If b < particles[a].b Then 
							b:+colourVolitility
							If b>255 Then b = 255
						EndIf

						If r > particles[a].r Then 
							r:-colourVolitility
							If r<0 Then r = 0
						EndIf
						If g > particles[a].g Then 
							g:-colourVolitility
							If g<0 Then g = 0
						EndIf
						If b > particles[a].b Then 
							b:-colourVolitility
							If b<0 Then b = 0
						EndIf
					EndIf
				Next
			ElseIf initialWaited Then
				For Local a:Int = 0 To 3
					If ID <> a Then
						If within (x, y, particles[a].x, particles[a].y, 60, 60) Then
							x1:+(x-particles[a].x)*.05
							y1:+(y-particles[a].y)*.05
						EndIf
					EndIf
				Next
			EndIf
		EndIf
		x1:+(RndFloat()-.5)*.1
		y1:+(RndFloat()-.5)*.1
		x:+x1
		y:+y1
		y1:+gravity
		x1:*.9
		y1:*.9
		If x> grid.width+4 x = -4
		If x< -4 x = grid.width+4
		If y> grid.height+4 y = -4
		If y< -4 y = grid.height+4
		xGrid = x/grid.resolution
		yGrid = y/grid.resolution
		x1:+(grid.vector[xGrid, yGrid].x+grid.vector[xGrid+1, yGrid].x)*.5
		y1:+(grid.vector[xGrid, yGrid].y+grid.vector[xGrid, yGrid+1].y)*.5
		If ID = 10 And debug Then 
			SetColor 200, 200, 200
			drawl(x-25,y-25, 50, 50, 1, CORNERS)
			drawl(x-25,y-30, lazyParticleSpy.movementPoints*.5, 5,1, FILL)
			SetAlpha .2
			DrawText "Keys: 1-reposition, 2-kill movement, 3-rand movement, 4-smear movement, 5-radial paths, 6-next", 5, 5
			SetAlpha 1
		EndIf
		SetColor r,g,b
		DrawImage poof,x+grid.xCenter, y+grid.yCenter
	EndMethod
	Function reset(which:Int = 0)
		If Not which Then which = Rand(4)
		Select which
		Case 1
			For Local a:Int = 0 To particles.length-1
				particles[a].x = grid.width*.5
				particles[a].y = grid.height*.8
			Next
		Case 2
		For Local a:Int = 0 To particles.length-1
			Local aa:Int = Rnd(360)
			particles[a].x = grid.width*.5+Rnd(grid.width*.4)*Sin(aa)
			particles[a].y = grid.width*.5+Rnd(grid.height*.4)*Cos(aa)
		Next
		Case 3, 4
			For Local a:Int = 0 To particles.length-1
				particles[a].x = Rnd(grid.width)
				particles[a].y = Rnd(grid.height)
			Next
		EndSelect
	EndFunction
	Method New()
		x = grid.width*.5
		y = grid.height*.9
		colourVolitility = 30
		r = 55
		g = 55
		b = 55
		gravity=-.06
	EndMethod
EndType
Global particles:particleType[particleNum+1]
Global timer:timerType = New timerType
Global whichParticle:Int
For Local a:Int = 0 To particleNum
	whichParticle = a
	particles[a] = New particleType
Next
Type mouseType
	Field x:Int, y:Int
	Field x1:Int, y1:Int
	Method update()
		x1 = x-MouseX()
		y1 = y-MouseY()
		If Abs(x1)>5 Or Abs(y1)> 5	Then
			If Abs(x1)< 50 And Abs(y1)<50 Then End
		EndIf
		x = MouseX()
		y = MouseY()
	EndMethod
	Method New()
		x=MouseX()
		y=MouseY()
		x1=0
		y1=0
	EndMethod
EndType
Global mouse:mouseType = New mouseType
mouse.update()

HideMouse()
Repeat
	timer.update()
	colourScheme.update()
	grid.drawGradient()
	If grid.show grid.drawVectors()
	grid.calmDown()
	For Local a:Int = 0 To particles.length-1
		particles[a].do(a)
	Next
	mouse.update()
	If KeyHit(key_1) particleType.reset()
	If KeyHit(key_2) grid.kill()
	If KeyHit(key_3) grid.explode()
	If KeyHit(key_4) grid.waterSmear()
	If KeyHit(key_5) grid.circles()
	If KeyHit(key_6) timer.t22000 = 0
	
	SetColor 100, 100, 100
	SetAlpha .05
	SetColor 255, 255, 255
	DrawText "2009 'Dubious Drewski'", 620, 585
	SetAlpha 1
	Flip;Cls
Until KeyDown(key_escape) Or AppTerminate() Or AppSuspended()
End

Function drawPoof:Timage(xsize:Int=32, ysize:Int=32)
	Local image:timage = CreateImage (xsize,ysize,,DYNAMICIMAGE)
	Local argb:Int,a:Int, r:Int, g:Int, b:Int
	Local xmid:Int = image.width*.5
	Local yMid:Int = image.height*.5
	Local temp:TPixmap = LockImage(image)
	For Local y:Int = 0 To image.height-1
		For Local x:Int = 0 To image.width-1
			Local scale:Float = 255.0/xMid
			argb=0
			Local dist:Float = Sqr((x-xMid)^2+(y-yMid)^2)
			a=(Xmid-dist)*scale
			argb:|a
			a = a Shl 8
			argb:|a
			a = a Shl 8
			argb:|a
			a = a Shl 8
			argb:|a
			If dist> xMid argb=0
			WritePixel(temp,x,y,argb)
		Next
	Next
	UnlockImage(image)
	SetBlend lightblend
	MidHandleImage(image)
'	DrawImage(image, 40, 40);Flip;WaitKey';End
	Return image
EndFunction
Function within:Int(x1:Float,y1:Float,x2:Float,y2:Float,xsize:Int,ysize:Int)
	If x1>x2-xSize And x1< x2+xSize Then
		If y1>y2-ySize And y1< y2+ySize Return True
	EndIf
	Return False
EndFunction

Const NORMAL:Int = 0
Const DOTTED:Int = 1
Function drawEllipse(x:Int,y:Int,xSize:Int,ySize:Int,StepSize:Int = 15, width:Int=1, style:Int = NORMAL)
	Local oldlinewidth:Int = GetLineWidth(), dot:Int = 1
	SetLineWidth width
	Local a:Int = stepSize
	Repeat
		If dot DrawLine x+xSize*Sin(a), y+ySize*Cos(a),x+xSize*Sin(a-stepSize), y+ySize*Cos(a-stepSize)
		a:+stepSize
		If style dot = 1-dot
	Until a> 360
	SetLineWidth oldlinewidth
EndFunction



Const FILL:Int = 3
Const LINE:Int = 2
Const CORNERS:Int = 1
Const BOX:Int = 0

Function drawl(x1:Int,y1:Int,x2:Int,y2:Int,width:Int=1,style:Int = BOX)
	Local oldlinewidth:Int = GetLineWidth()
	SetLineWidth width
	Select style
	Case 3
		DrawRect x1,y1,x2,y2
	Case 2
		x2:+x1
		y2:+y1
		DrawLine x1,y1,x2,y2
	Case 1
		x2:+x1
		y2:+y1
		DrawLine (x1,y1,x1+(x2-x1)*.1,y1)
		DrawLine (x1,y1,x1,y1+(y2-y1)*.1)
		DrawLine (x1,y2-(y2-y1)*.1,x1,y2)
		DrawLine (x1,y2,x1+(x2-x1)*.1,y2)
		
		DrawLine (x2,y1,x2-(x2-x1)*.1,y1)
		DrawLine (x2,y1,x2,y1+(y2-y1)*.1)
		DrawLine (x2,y2-(y2-y1)*.1,x2,y2)
		DrawLine (x2,y2,x2-(x2-x1)*.1,y2)
	Default
		x2:+x1
		y2:+y1
		DrawLine (x1,y1,x2,y1)
		DrawLine (x1,y1,x1,y2)
		DrawLine (x1,y2,x2,y2)
		DrawLine (x2,y1,x2,y2)
	EndSelect
	SetLineWidth oldlinewidth
End Function



It looks great :D. anyway, the code crashes with debug mode on with array out of bounds, and the graphics sentence is trying to use a 1 bit color graphics? It should be something like Graphics(800,600,32) instead of Graphics (800,600,1). Just in case you want to release :D
Anyway, really nice looking effect.

wow, that is really nice!

By the way, in the definitions of the colour schemes, you're using array indices from 1 to 6, when you should use 0 to 5. I got an array out of bounds error with debug turned on.

Previews and settings windows are activated by command-line parameters - see this VB example.

There are lots of icon editors around, if you google. I don't know which ones are good, but it's definitely something you can do quite easily.

Graphics line fixed and out of bounds bug fixed. Thanks guys. I'm going to go to bed and find out about adding those command line parameters tomorrow, because I've never done anything like that before.

very nice!

there are proper screensaver functions for bmax in the code archives.

Dayumm! This is quite awesome!

..Just a note, it ran reallly slow on DirectX for me, switched it to OpenGL and its all snappy now.

Thanks for pointing that out. I hate this OpenGL/DirectX issue. I wish it would just work! So if I add this

SetGraphicsDriver GLMax2DDriver()

to the beginning of my code, I get one heck of a funky printed halftone look in all of my gradients. Everything looks quite cool and not like your average pixelation, but it's still a complete surprise and not something I want.

to the beginning of my code, I get one heck of a funky printed halftone look in all of my gradients. Everything looks quite cool and not like your average pixelation, but it's still a complete surprise and not something I want.
Not for me!