After wasting too much time playing this : http://dan-ball.jp/en/javagame/dust/
I decided to try my hand at a blitzmax version...
Still a work in progress, but I'd be interested to hear what FPS people get (trying to cope with a lot of particles at once is a bit of a challenge). It's only using OpenGL at the moment as I don't currently have a working Windows box for directx dev. Physics are a bit fudged and the codes a bit of a mess but it's a start.
Updated Version 0.3 - 13th April 2008
Download v.0.3 Windows Exe http://minimono.net/downloads/elemental/elemental-windows/elemental.exe
Download v.0.3 Mac OS X (PPC) zip http://minimono.net/downloads/elemental/elemental-osx-ppc/elemental.zip
Download v.0.3 Max OS X (Intel) zip http://minimono.net/downloads/elemental/elemental-osx-intel/elemental.zip

Blitzmax Source
version 0.3
I decided to try my hand at a blitzmax version...
Still a work in progress, but I'd be interested to hear what FPS people get (trying to cope with a lot of particles at once is a bit of a challenge). It's only using OpenGL at the moment as I don't currently have a working Windows box for directx dev. Physics are a bit fudged and the codes a bit of a mess but it's a start.
Updated Version 0.3 - 13th April 2008
Download v.0.3 Windows Exe http://minimono.net/downloads/elemental/elemental-windows/elemental.exe
Download v.0.3 Mac OS X (PPC) zip http://minimono.net/downloads/elemental/elemental-osx-ppc/elemental.zip
Download v.0.3 Max OS X (Intel) zip http://minimono.net/downloads/elemental/elemental-osx-intel/elemental.zip

Blitzmax Source
version 0.3
' Elemental ' (c) Tesuji 2008 SuperStrict Framework BRL.blitz Import BRL.glmax2d 'Import BRL.D3D7Max2d Import BRL.max2d Import BRL.pixmap Import BRL.pngloader Import BRL.jpgloader Import BRL.retro SetGraphicsDriver GLMax2DDriver() 'Graphics 800,600,0 Graphics 800,600,32,0 SeedRnd MilliSecs() Global width:Int=512,height:Int=512 Global px:Particle[width+2,height+2] Global centerX:Int = GraphicsWidth()/2 Global centerY:Int = GraphicsHeight()/2 Global halfWidth:Int = width/2 Global halfHeight:Int = height/2 Global xOrigin:Int = centerX-halfWidth Global yOrigin:Int = centerY-halfHeight Global particles:TList = CreateList() Global menuItems:TList = CreateList() Const OUTPUT_DIRECTORY:String = "images" Const RENDERMODE_PIXEL:Int = 0 Const RENDERMODE_BLOB:Int = 1 Const RENDERMODE_BLOB2:Int = 2 Const RENDERMODE_IMAGE:Int = 3 Const MAX_RENDERMODES:Int = 4 Const DRAWMODE_PEN:Int = 0 Const DRAWMODE_LINE:Int = 1 Const DRAWMODE_CIRCLE:Int = 2 Const MAX_DRAWMODES:Int = 3 Const ITEMGROUP_ELEMENTS:Int = 0 Const ITEMGROUP_BRUSH:Int = 1 Const ITEMGROUP_CONTROL:Int = 2 Const MAX_FPS_HISTORY:Int = 60 Const ELEMENT_EMPTY:Int = 0 Const ELEMENT_WATER:Int = 1 Const ELEMENT_CONCRETE:Int = 2 Const ELEMENT_SALT:Int = 3 Const ELEMENT_MAGMA:Int = 4 Const ELEMENT_FIRE:Int = 5 Const ELEMENT_OIL:Int = 6 Const ELEMENT_SAND:Int = 7 Const ELEMENT_SMOKE:Int = 8 Const ELEMENT_GRAPHITE:Int = 9 Const ELEMENT_EMBER:Int = 10 Const ELEMENT_STEAM:Int = 11 Const ELEMENT_ACID:Int = 12 Const ELEMENT_WOOD:Int = 13 Const ELEMENT_SEED:Int = 14 Const ELEMENT_SPROUT:Int = 15 Const ELEMENT_GUNPOWDER:Int = 16 Const ELEMENT_MISSILE:Int = 17 Const ELEMENT_GRASS:Int = 18 Const MAX_ELEMENTS:Int = 19 ' 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Global elementLabels:String[] = [ "Air", "Water", "Concrete", "Salt", "Magma", "Fire", "Oil", "Sand", "Smoke", "Graphite", "Ember", "Steam", "Acid", "Wood", "Seed", "Sprout", "Gunpowder", "Missile", "Grass" ] Global elementCreatable:Int[] = [ True, True, True, True, True, True, True, True, False, True, False, False, True, True, True, False, True, False, False ] Global elementBitmask:Int[] = [ $00000000, $00000001, $00000002, $00000004, $00000008, $00000010, $00000020, $00000040, $00000080, $00000100, $00000200, $00000400, $00000800, $00001000, $00002000, $00004000, $00008000, $00010000, $00020000 ] Global lifespan:Int[] = [ -1, -1, -1, -1, -1, 128, -1, -1, 128, -1, 64, 255, -1, 128, -1, 300, -1, 1255, -1 ] Global elementAfterlife:Int[] = [ ELEMENT_EMPTY, ELEMENT_STEAM, ELEMENT_EMPTY, ELEMENT_EMPTY, ELEMENT_EMPTY, ELEMENT_SMOKE, ELEMENT_EMPTY, ELEMENT_EMPTY, ELEMENT_EMPTY, ELEMENT_EMPTY, ELEMENT_FIRE, ELEMENT_EMPTY, ELEMENT_EMPTY, ELEMENT_WOOD, ELEMENT_EMPTY, ELEMENT_SEED, ELEMENT_EMPTY, ELEMENT_EMPTY, ELEMENT_SEED ] Global elementImmovable:Int[] = [ False, False, True, False, False, False, False, False, False, True, True, False, False, True, False, False, False, False, True ] Global elementDrag:Float[] = [ 1.0, 0.95, 0.0, 0.1, 0.8, 0.9, 0.85, 0.5, 0.995, 0.0, 0.0, 0.395, 0.87, 0.0, 0.85, 0.85, 0.58, 0.0, 0.0 ] ' elementDrag Global elementMass:Float[] = [ 0.1, 0.3, 1.0, 1.8, 3.4, 0.52, 0.3, 3.2, 2.01, 1.0, 1.0, 2.01, 0.32, 1.0, 0.13, 0.013, 2.2, 0.5, 1.0 ] ' elementMass Global gravity:Float[] = [ 1.0, 2.0, 0.0, 2.0, 2.0, -2.0, 1.0, 2.0, -1.0, 0.0, 0.0, -1.0, 1.0, 1.0, 0.4, -0.01, 2.0, 0.1, 1.0 ] ' gravity Global slippyness:Float[] = [ 0.0, 1.0, 0.0, 0.9, 1.0, 0.7, 1.0, 0.9, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.995, 0.35, 0.9, 1.0, 0.0 ] Global airDamp:Float[] = [ 1.0, 0.0, 0.0, 0.65, 0.0, 0.5, 0.0, 0.35, 0.85, 0.0, 0.0, 0.59, 0.0, 0.0, 0.79, 0.93, 0.35, 0.99, 0.0 ] Global elementBounce:Float[] = [ 0.0, 0.0, 0.0, 0.5, 0.0, 0.75, 0.0, 0.35, 0.85, 0.0, 0.0, 0.59, 0.0, 0.0, 0.40, 0.93, 0.35, 0.99, 0.0 ] Global elementRed:Int[] = [ $00, $10, $80, $F0, $C0, $FF, $75, $BA, $C0, $30, $8F, $C0, $20, $65, $65, $75, $10, $F0, $45 ] Global elementGreen:Int[] = [ $00, $9A, $80, $F0, $20, $40, $2A, $A2, $C0, $30, $00, $C0, $B0, $3E, $7E, $9E, $10, $F0, $8E ] Global elementBlue:Int[] = [ $80, $CE, $80, $F0, $10, $10, $02, $30, $C0, $30, $00, $E0, $08, $22, $22, $22, $10, $D0, $12 ] Global elementInteractions:Interaction[MAX_ELEMENTS,MAX_ELEMENTS] ' element reacts with to form leaving behind with a probability of n in 100000 Interaction.Create(ELEMENT_WATER, ELEMENT_SALT, ELEMENT_WATER, ELEMENT_WATER, 185) Interaction.Create(ELEMENT_WATER, ELEMENT_MAGMA, ELEMENT_STEAM, ELEMENT_SAND, 2000) Interaction.Create(ELEMENT_WATER, ELEMENT_ACID, ELEMENT_STEAM, ELEMENT_WATER, 200) Interaction.Create(ELEMENT_WATER, ELEMENT_EMBER, ELEMENT_STEAM, ELEMENT_WATER, 10000) Interaction.Create(ELEMENT_WATER, ELEMENT_FIRE, ELEMENT_EMPTY, ELEMENT_STEAM, 5000) Interaction.Create(ELEMENT_WATER, ELEMENT_MISSILE, ELEMENT_STEAM, ELEMENT_STEAM, 50000) Interaction.Create(ELEMENT_SALT, ELEMENT_WATER, ELEMENT_SALT, ELEMENT_WATER, 105) Interaction.Create(ELEMENT_GRAPHITE, ELEMENT_FIRE, ELEMENT_FIRE, ELEMENT_EMBER, 500) Interaction.Create(ELEMENT_GRAPHITE, ELEMENT_MAGMA, ELEMENT_MAGMA, ELEMENT_FIRE, 2000) Interaction.Create(ELEMENT_GRAPHITE, ELEMENT_EMBER, ELEMENT_EMBER, ELEMENT_EMBER, 25000) Interaction.Create(ELEMENT_GRAPHITE, ELEMENT_MISSILE, ELEMENT_SMOKE, ELEMENT_EMBER, 25000) Interaction.Create(ELEMENT_EMBER, ELEMENT_GRAPHITE, ELEMENT_EMBER, ELEMENT_EMBER, 25000) Interaction.Create(ELEMENT_EMBER, ELEMENT_OIL, ELEMENT_FIRE, ELEMENT_SMOKE, 50000) Interaction.Create(ELEMENT_EMBER, ELEMENT_SAND, ELEMENT_MAGMA, ELEMENT_EMBER, 250) Interaction.Create(ELEMENT_EMBER, ELEMENT_WOOD, ELEMENT_FIRE, ELEMENT_EMBER, 2500) Interaction.Create(ELEMENT_EMBER, ELEMENT_SEED, ELEMENT_FIRE, ELEMENT_FIRE, 2500) Interaction.Create(ELEMENT_EMBER, ELEMENT_GRASS, ELEMENT_FIRE, ELEMENT_FIRE, 2500) Interaction.Create(ELEMENT_MAGMA, ELEMENT_WATER, ELEMENT_STEAM, ELEMENT_MAGMA, 10000) Interaction.Create(ELEMENT_MAGMA, ELEMENT_ACID, ELEMENT_EMBER, ELEMENT_MAGMA, 150) Interaction.Create(ELEMENT_MAGMA, ELEMENT_WOOD, ELEMENT_FIRE, ELEMENT_MAGMA, 2150) Interaction.Create(ELEMENT_MAGMA, ELEMENT_SEED, ELEMENT_FIRE, ELEMENT_MAGMA, 3150) Interaction.Create(ELEMENT_MAGMA, ELEMENT_GRASS, ELEMENT_FIRE, ELEMENT_MAGMA, 4150) Interaction.Create(ELEMENT_MAGMA, ELEMENT_GUNPOWDER,ELEMENT_FIRE, ELEMENT_MAGMA, 10000) Interaction.Create(ELEMENT_MAGMA, ELEMENT_MISSILE, ELEMENT_SMOKE, ELEMENT_MAGMA, 5000) Interaction.Create(ELEMENT_FIRE, ELEMENT_WATER, ELEMENT_EMPTY, ELEMENT_STEAM, 10000) Interaction.Create(ELEMENT_FIRE, ELEMENT_GRAPHITE, ELEMENT_EMBER, ELEMENT_SMOKE, 2500) Interaction.Create(ELEMENT_FIRE, ELEMENT_SAND, ELEMENT_MAGMA, ELEMENT_SMOKE, 150) Interaction.Create(ELEMENT_FIRE, ELEMENT_WOOD, ELEMENT_FIRE, ELEMENT_GRAPHITE, 2000) Interaction.Create(ELEMENT_FIRE, ELEMENT_GRASS, ELEMENT_FIRE, ELEMENT_FIRE, 1500) Interaction.Create(ELEMENT_FIRE, ELEMENT_SEED, ELEMENT_FIRE, ELEMENT_EMBER, 500) Interaction.Create(ELEMENT_FIRE, ELEMENT_GUNPOWDER,ELEMENT_FIRE, ELEMENT_FIRE, 15000) Interaction.Create(ELEMENT_OIL, ELEMENT_FIRE, ELEMENT_FIRE, ELEMENT_FIRE, 15000) Interaction.Create(ELEMENT_OIL, ELEMENT_MAGMA, ELEMENT_FIRE, ELEMENT_FIRE, 5000) Interaction.Create(ELEMENT_OIL, ELEMENT_MISSILE, ELEMENT_SMOKE, ELEMENT_FIRE, 25000) Interaction.Create(ELEMENT_ACID, ELEMENT_GRAPHITE, ELEMENT_STEAM, ELEMENT_ACID, 200) Interaction.Create(ELEMENT_ACID, ELEMENT_CONCRETE, ELEMENT_GRAPHITE, ELEMENT_ACID, 225) Interaction.Create(ELEMENT_ACID, ELEMENT_OIL, ELEMENT_STEAM, ELEMENT_ACID, 900) Interaction.Create(ELEMENT_ACID, ELEMENT_SAND, ELEMENT_STEAM, ELEMENT_ACID, 2500) Interaction.Create(ELEMENT_ACID, ELEMENT_SALT, ELEMENT_STEAM, ELEMENT_ACID, 8500) Interaction.Create(ELEMENT_ACID, ELEMENT_WATER, ELEMENT_STEAM, ELEMENT_ACID, 200) Interaction.Create(ELEMENT_ACID, ELEMENT_WOOD, ELEMENT_GRAPHITE, ELEMENT_ACID, 10000) Interaction.Create(ELEMENT_ACID, ELEMENT_MISSILE, ELEMENT_STEAM, ELEMENT_ACID, 40000) Interaction.Create(ELEMENT_ACID, ELEMENT_GUNPOWDER,ELEMENT_STEAM, ELEMENT_ACID, 400) Interaction.Create(ELEMENT_ACID, ELEMENT_GRASS, ELEMENT_STEAM, ELEMENT_ACID, 4000) Interaction.Create(ELEMENT_WOOD, ELEMENT_FIRE, ELEMENT_FIRE, ELEMENT_EMBER, 5000) Interaction.Create(ELEMENT_WOOD, ELEMENT_EMBER, ELEMENT_FIRE, ELEMENT_EMBER, 1000) Interaction.Create(ELEMENT_WOOD, ELEMENT_MISSILE, ELEMENT_FIRE, ELEMENT_EMBER, 1000) Interaction.Create(ELEMENT_GUNPOWDER, ELEMENT_FIRE, ELEMENT_FIRE, ELEMENT_FIRE, 5000) Interaction.Create(ELEMENT_GUNPOWDER, ELEMENT_MAGMA, ELEMENT_FIRE, ELEMENT_FIRE, 15000) Interaction.Create(ELEMENT_GUNPOWDER, ELEMENT_MISSILE, ELEMENT_FIRE, ELEMENT_FIRE, 50000) Global selectedElement:Int = ELEMENT_WATER Global renderMode:Int = RENDERMODE_BLOB ' todo - double sized pixels seem too much for the default windows OGL drivers in my old laptop. Need a dx fast plot really. '?Win32 'renderMode = RENDERMODE_PIXEL '? Global drawMode:Int = DRAWMODE_PEN Global frame:Int = 0 Global vSync:Int = 0 Global simulationRunning:Int = True Global displayBackground:Int = True Global loadSelectMode:Int = False Global saveSelectMode:Int = False Const MAX_PEN_SIZE:Int = 3 Global penSizes:Int[] = [1,3,5] Global selectedPenSize:Int = 1 Global alphaByte:Byte ' ----------------------------------------------------------- Global fpstime:Long Global fpsHistory:Int[MAX_FPS_HISTORY] Global randomNumbers1:Float[1000000] Global randomNumbersIndex1:Int = 0 Global randomNumbers2:Int[1000000] Global randomNumbersIndex2:Int = 0 Global randomNumbers3:Float[1000000] Global randomNumbersIndex3:Int = 0 For Local i:Int = 0 To 999999 randomNumbers1[i] = Rnd(-1.0,1.0) randomNumbers2[i] = Rand(0,100000) randomNumbers3[i] = Rnd(0.0,1.0) Next Global prevMouseX:Int, prevMouseY:Int Global drag:Int = False, dragFromX:Int, dragFromY:Int Global blobImage:TImage = createBlobImage() Global backgroundImage:TImage = createGradientImage(width,height) Global menuImage:TImage = createMenuImage() Global zoomImage:TImage = CreateImage(64,64,1,DYNAMICIMAGE + FILTEREDIMAGE) Global selectedImageIndex:Int = 0 Global savedGamePixmap:TPixmap createSaveGameDir() initParticles() reflectStateInMenu() ' ------------------------------------------------------------ ' mainloop ' ------------------------------------------------------------ While Not KeyHit(KEY_ESCAPE) Cls SetOrigin centerX, centerY If displayBackground = True SetColor 0,24,96 SetBlend SOLIDBLEND SetImageHandle backgroundImage,halfWidth,halfHeight DrawImage backgroundImage,0,-1 End If If loadSelectMode = True savedGamePixmap = selectSavedFile() If savedGamePixmap <> Null Particle.initFromPixmap(savedGamePixmap) loadSelectMode = False reflectStateInMenu() Else If saveSelectMode = True savedGamePixmap = Particle.toPixmap() savePixmap(savedGamePixmap) saveSelectMode = False selectedImageIndex = 0 reflectStateInMenu() Else ' process and render particles Particle.preRender() If simulationRunning = True For Local p:Particle = EachIn particles p.update() p.interact() p.render() Next Else For Local p:Particle = EachIn particles p.render() Next End If Particle.postRender() End If SetColor 255,255,255 SetOrigin 0,0 SetScale 1,1 SetBlend ALPHABLEND SetAlpha 1 DrawText "FPS: " + countFps(),16+(xOrigin),6 DrawText CountList(particles),(xOrigin)+width-64,6 userInput() updateMenu() If KeyDown(KEY_LCONTROL) Or simulationRunning = False Then displayZoom() If KeyDown(KEY_LCONTROL) And KeyHit(KEY_G) Then screenshot() Flip vSync frame = (frame + 1) Mod 50000 If randomNumbersIndex1 > 500000 randomNumbersIndex1 = 0 If randomNumbersIndex2 > 500000 randomNumbersIndex2 = 0 If randomNumbersIndex3 > 500000 randomNumbersIndex3 = 0 Delay 1 Wend End ' -------------------------------------------------------------------------------------- ' ------------------------------------------ ' Simple 2D Image Sprite with z scaling. ' ------------------------------------------ Type TSprite Global sprites:TList = CreateList() Field x:Float,y:Float,z:Float Field image:TImage Field reflection:Int = True Function Create:TSprite(image:TImage, x:Float=0,y:Float=0,z:Float=0) Local s:TSprite = New TSprite SetImageHandle image, ImageWidth(image)/2, ImageHeight(image)/2 s.image = image s.x = x s.y = y s.z = z ListAddLast sprites,s Return s End Function Function deleteAll() ClearList(sprites) End Function ' render all sprites in correct z order Function renderAll() SetOrigin GraphicsWidth()/2, GraphicsHeight()/2 sprites.sort() For Local s:TSprite = EachIn sprites s.render() Next SetOrigin 0,0 End Function ' sort by zOrder Method Compare:Int(other:Object) Local s:TSprite = TSprite(other) If z < s.z Return -1 If z = s.z Return 0 If z > s.z Return 1 End Method Method render() Local scale:Float = 2^z Local sx:Int = x*(GraphicsWidth()/2)*scale Local sy:Int = y*(GraphicsHeight()/2)*scale SetScale scale,scale DrawImage image,sx,sy If reflection Local alpha:Float = GetAlpha() Local blend:Int = GetBlend() SetAlpha alpha*.5 SetScale scale,-scale DrawImage image,sx,sy+((ImageHeight(image)*scale))+1 SetBlend SHADEBLEND SetAlpha 1.0 DrawImage backgroundImage, sx, sy+1+((ImageHeight(backgroundImage)*scale)) SetAlpha alpha SetBlend blend End If End Method Method remove() sprites.remove(Self) End Method End Type ' ----------------------------- ' Wrapper class for Element ' ----------------------------- Type TElement Field id:Int Function Create:TElement(id:Int) Local e:TElement = New TElement e.id = id Return e End Function End Type ' ------------------------------ ' File System object ' ------------------------------ Type TFile Field path:String Field filename:String Field modified:Int Field size:Int Field isLoaded:Int = False Function Create:TFile(filepath:String,filename:String,modified:Int,size:Int) Local file:TFile = New TFile file.path = filepath file.filename = filename file.modified = modified file.size = size Return file End Function Method toString:String() Return path +"/"+filename+" "+modified+" "+size+" "+isLoaded End Method ' sort by last modified timestamp Method Compare:Int(otherObject:Object) Local file:TFile = TFile(otherObject) Return file.modified - Self.modified End Method End Type ' ---------------------------------- ' Element Particle ' ---------------------------------- Type Particle Field element:Int Field x:Int,y:Int Field xp:Float, yp:Float, xv:Float, yv:Float Field mass:Float Field life:Int Field energy:Int Field link:TLink Field asleep:Int = 0 Function Create:Particle(element:Int, x:Int, y:Int, xv:Float=0.0, yv:Float=0.0) If element = ELEMENT_EMPTY clear(x,y) Else If x > 0 And x < width And y > 0 And y < height And px[x,y] = Null Local p:Particle = New Particle p.x = x p.y = y p.xp = x p.yp = y+.99 p.energy = 0 p.init(element,xv,yv) px[x,y] = p p.link = particles.AddLast(p) 'Print "created particle : " + p.toString() Return p End If End Function Function reset() For Local x:Int = 0 To width-1 For Local y:Int = 0 To height-1 If px[x,y] <> Null Particle.clear(x,y) Next Next ClearList particles End Function Function clear(x:Int,y:Int) If x >= 0 And x < width And y >= 0 And y < height Local p:Particle = px[x,y] If p <> Null px[x,y] = Null p.remove() End If End If End Function Function toPixmap:TPixmap() Local pixmap:TPixmap = CreatePixmap(width,height, PF_RGBA8888) ClearPixels(pixmap, $00000080) For Local y:Int = 0 To height-1 Local pb:Byte Ptr = PixmapPixelPtr(pixmap,0,y) Local alpha:Byte = 128+(y/4) For Local x:Int = 0 To width-1 pb[(x*4)+3] = alpha Next Next For Local p:Particle = EachIn particles Local pb:Byte Ptr = PixmapPixelPtr(pixmap,p.x,p.y) pb[0] = elementRed[p.element] pb[1] = elementGreen[p.element] pb[2] = elementBlue[p.element] pb[3] = 255 Next Return pixmap End Function Function initFromPixmap(pixmap:TPixmap) Local red:Byte Local green:Byte Local blue:Byte Local colorKey:String Local element:Int Local e:TElement ' create colour -> element lookup map Local colorToElementMap:TMap = CreateMap() For element = 0 To MAX_ELEMENTS-1 e = TElement.Create(element) colorKey = rgbToString(elementRed[element],elementGreen[element],elementBlue[element]) MapInsert(colorToElementMap, colorKey, e) Next Particle.reset() For Local y:Int = 0 To height-1 Local pb:Byte Ptr = PixmapPixelPtr(pixmap,0,y) For Local x:Int = 0 To width-1 red = pb[(x*4)+0] green = pb[(x*4)+1] blue = pb[(x*4)+2] colorKey = rgbToString(red,green,blue) e = TElement(MapValueForKey(colorToElementMap, colorKey)) 'Local approx:Int = (((red + green + blue) /3)/8) 'If approx < MAX_ELEMENTS Then Particle.Create(approx,x,y) If e <> Null And e.id <> ELEMENT_EMPTY Then Particle.Create(e.id,x,y) Next Next End Function Function preRender() Select renderMode Case RENDERMODE_PIXEL SetBlend ALPHABLEND alphaByte = 255 glDisable GL_TEXTURE_2D glPointSize 1.0 glBegin GL_POINTS Case RENDERMODE_BLOB SetBlend ALPHABLEND alphaByte = 128 glDisable GL_TEXTURE_2D glPointSize 2.0 glBegin GL_POINTS Case RENDERMODE_BLOB2 SetBlend ALPHABLEND alphaByte = 64 glDisable GL_TEXTURE_2D glPointSize 2.0 glBegin GL_POINTS Case RENDERMODE_IMAGE SetBlend LIGHTBLEND alphaByte = 64 SetScale .5,.5 End Select End Function Function postRender() Select renderMode Case RENDERMODE_IMAGE SetScale 1,1 Default glEnd glEnable GL_TEXTURE_2D End Select End Function Method init(targetElement:Int, xVelocity:Float=0.0, yVelocity:Float=0.0) element = targetElement life = lifespan[element] If life > 0 life = Rand(life/2,life) xv = xVelocity yv = yVelocity 'mass = Rnd(elementMass[element]*.5, elementMass[element]*2.0) mass = (elementMass[element]*.5) + (randomNumbers3[randomNumbersIndex3] * elementMass[element]*1.5) randomNumbersIndex3 :+ 1 End Method Method remove() link.remove() End Method Method toString:String() Return "[element:"+element+" life:"+life+" x:"+x+" y:"+y+"]" End Method Method airmove() ' debug If yv > 1.0 Then yv = 1.0 Else If yv < -1.0 Then yv = -1.0 End If If yv > 1.0 Then yp :+ 1.0 Else If yv < -1.0 Then yp :- 1.0 Else yp :+ yv End If If Int(yp) <> y px[x,y] = Null y = yp If y >= height Or y < 1 Then remove() Else px[x,y] = Self End If End If ' apply air friction yv :+ (mass * gravity[element])*.005 End Method Method move(neighbour:Particle) If elementImmovable[neighbour.element] yv = -yv * elementBounce[element] Else If neighbour.element <> element ' debug ' If yv > 1.0 Then ' yv = 1.0 ' Else If yv < -1.0 Then ' yv = -1.0 ' End If If yv > 1.0 Then yp :+ 1.0 Else If yv < -1.0 Then yp :- 1.0 Else yp :+ yv End If If Int(yp) <> y px[x,y] = neighbour neighbour.y = y neighbour.yp = y y = yp If y >= height Or y < 1 Then remove() Else px[x,y] = Self End If End If yv = (((mass * gravity[element])*.05) * elementDrag[neighbour.element]) Else neighbour.yv :+ (yv*.05) ' impart velocity to neighbour yv = yv * .95 ' slow down if next to same element End If End If End Method Method update() If life >= 0 Then life :- 1 If life <= 0 Then If elementAfterlife[element] = ELEMENT_EMPTY Particle.clear(x,y) Else If element <> elementAfterlife[element] changeElement(Self, elementAfterlife[element], xv, yv) End If Return End If End If Local neighbour:Particle Local nextTo:Particle Select element ' solids don't move Case ELEMENT_CONCRETE Case ELEMENT_GRAPHITE Case ELEMENT_EMBER Case ELEMENT_WOOD Case ELEMENT_GRASS Case ELEMENT_MISSILE Local oyp:Float = yp If yv > 1.0 Then yp :+ 1.0 Else If yv < -1.0 Then yp :- 1.0 Else yp :+ yv End If Local oxp:Float = xp If xv > 1.0 Then xp :+ 1.0 Else If xv < -1.0 Then xp :- 1.0 Else xp :+ xv End If If Int(yp) <> y If px[x,yp] = Null px[x,y] = Null If life < lifespan[element]/2 If life Mod 3 = 0 Then Particle.Create(ELEMENT_FIRE,oxp,oyp,-xv*.25,-yv*.25) Else Particle.Create(ELEMENT_FIRE,oxp,oyp,-xv*.25,-yv*.25) End If y = yp px[x,y] = Self Else yp = oyp 'yv = -yv End If End If If Int(xp) <> x If px[xp,y] = Null px[x,y] = Null 'Particle.Create(ELEMENT_EMBER,oxp,oyp,xv,yv) x = xp px[x,y] = Self Else xp = oxp 'xv = -xv End If End If yv :+ .005 ' friction xv :* .998 yv :* .998 Default If yv > 0 If px[x,y+1] = Null Then airmove() Else move(px[x,y+1]) End If Else If yv < 0 If px[x,y-1] = Null Then airmove() Else move(px[x,y-1]) End If Else yv :+ ((mass * gravity[element])*.005) End If If px[x,y+1] <> Null ' element below randomNumbersIndex2 :+1 If xv = 0 Or (slippyness[element] = 1.0 And randomNumbers2[randomNumbersIndex2] < 10000) randomNumbersIndex1 :+1 Local slip:Float = Sgn(randomNumbers1[randomNumbersIndex1]) If px[x+slip,y] = Null Then xv = slip End If xv :* slippyness[element] Else ' air below randomNumbersIndex1 :+1 xv :+ randomNumbers1[randomNumbersIndex1] * slippyness[element] xv :* airDamp[element] End If ' slide ' debug ' If xv > 1.0 Then ' xv = 1.0 ' Else If xv < -1.0 Then ' xv = -1.0 ' End If If xv > 1.0 Then xp :+ 1.0 If xv > 8.0 xv = 8.0 Else If xv < -1.0 Then xp :- 1.0 If xv < -8.0 xv = -8.0 Else xp :+ xv End If 'xp :+ xv If Int(xp) <> x nextTo = px[xp,y] If nextTo = Null px[x,y] = Null x = xp yp = y+.5 If px[x,y+1] = Null Then yv :+ slippyness[element] * Sgn gravity[element] * .15 End If If x > 1 And x < width-1 px[x,y] = Self Else remove() End If Else If nextTo.element <> element nextTo.xv :+ (xv*elementDrag[nextTo.element]*2.0) End If xv = -xv xp = x End If End If End Select End Method Method interact() Select element Case ELEMENT_WATER interactWithNeighbours() Case ELEMENT_GRAPHITE interactWithNeighbours() Case ELEMENT_EMBER interactWithNeighbours() Case ELEMENT_MAGMA interactWithNeighbours() Case ELEMENT_FIRE interactWithNeighbours() Case ELEMENT_GUNPOWDER interactWithNeighbours() If element = ELEMENT_FIRE ' explode Local a:Int = Rand(0,360) Particle.Create(ELEMENT_MISSILE,x,y-1,Sin(a),Cos(a)*2.0) xv = Rnd(-1.0,1.0)*4.0 yv = Rnd(-1.0,1.0)*4.0 End If Case ELEMENT_OIL interactWithNeighbours() Case ELEMENT_ACID interactWithNeighbours() Case ELEMENT_WOOD If getNeighbourElements() <> 0 If px[x,y+1] <> Null Local p:Particle = px[x,y+1] If p.element = ELEMENT_WATER And Rand(0,200) = 0 Then changeElement(p,ELEMENT_WOOD,0,0) End If interactWithNeighbours() Else If life > 0 Particle.Create(ELEMENT_GRAPHITE,x-1,y) Particle.Create(ELEMENT_GRAPHITE,x+1,y) Else changeElement(Self, ELEMENT_SMOKE, 0, 0) End If Case ELEMENT_SEED If frame & 1 = 0 Local match:Int = elementBitmask[ELEMENT_SAND] | elementBitmask[ELEMENT_WATER] If (getNeighbourElements() & match) = match Then If Rand(0,10) = 0 changeElement(Self, ELEMENT_SPROUT, 0, 0) energy = Rand(296,400) Else changeElement(Self, ELEMENT_GRASS, Rnd(-.2,.2), -1.0) energy = Rand(10,20) End If End If Else Local match:Int = elementBitmask[ELEMENT_WOOD] | elementBitmask[ELEMENT_WATER] If (getNeighbourElements() & match) = match Then If Rand(0,2) = 0 changeElement(Self, ELEMENT_SPROUT, 0, 0) energy = Rand(100,200) Else changeElement(Self, ELEMENT_GRASS, Rnd(-.5,.5), -.5) energy = Rand(5,10) End If End If End If Case ELEMENT_SPROUT If energy > 0 And life > 0 If energy Mod 24 = 13 ' branch Local p:Particle = Particle.Create(ELEMENT_SPROUT,x+xv,y-1,-xv,-(energy/300.0)) If p <> Null p.life = life p.energy = energy-1 End If End If Particle.Create(ELEMENT_WOOD,x,y+1) ; energy :- 1 If energy > 196 Then Particle.Create(ELEMENT_WOOD,x-1,y+1) ; energy :- 1 If energy > 128 Then Particle.Create(ELEMENT_WOOD,x+1,y+1) ; energy :- 1 End If Case ELEMENT_GRASS If energy > 0 And Rand(0,10) = 0 If xv > 1.0 xv = 1.0 If xv < -1.0 xv = -1.0 xp :+ xv If yv > 1.0 yv = 1.0 If yv < -1.0 yv = -1.0 yp :+ yv If px[xp,yp] = Null Local p:Particle = Particle.Create(ELEMENT_GRASS, xp, yp, xv*1.05, yv*.95) If p <> Null p.energy = energy-1 p.xp = xp p.yp = yp End If xp = x yp = y energy = 0 Else If Int(xp) <> x Then xp :- xv If Int(yp) <> y Then yp :- yv energy :-1 End If End If End Select End Method Method getNeighbourElements:Int() Local pTop:Particle = px[x,y-1] Local pBottom:Particle = px[x,y+1] Local pLeft:Particle = px[x-1,y] Local pRight:Particle = px[x+1,y] Local bitmask:Int = 0 If pTop <> Null bitmask :| elementBitmask[pTop.element] If pBottom <> Null bitmask :| elementBitmask[pBottom.element] If pLeft <> Null bitmask :| elementBitmask[pLeft.element] If pRight <> Null bitmask :| elementBitmask[pRight.element] Return bitmask End Method Method interactWithNeighbours() Local pTop:Particle = px[x,y-1] Local pBottom:Particle = px[x,y+1] Local pLeft:Particle = px[x-1,y] Local pRight:Particle = px[x+1,y] If pTop <> Null And pTop.element <> element Then interactWith(pTop) If pBottom <> Null And pBottom.element <> element Then interactWith(pBottom) If pLeft <> Null And pLeft.element <> element Then interactWith(pLeft) If pRight <> Null And pRight.element <> element Then interactWith(pRight) End Method Method interactWith(p:Particle) If elementInteractions[element, p.element] <> Null Local i:Interaction = elementInteractions[element, p.element] randomNumbersIndex2 :+1 If randomNumbers2[randomNumbersIndex2] < i.spreadChance Then changeElement(p, i.spreadElement) changeElement(Self, i.residueElement) End If End If End Method Function changeElement(p:Particle, targetElement:Int, xVelocity:Float=0.0, yVelocity:Float=0.0) If targetElement = ELEMENT_EMPTY Then Particle.clear(p.x,p.y) Else p.init(targetElement,xVelocity,yVelocity) End If End Function Method render() Select element Case ELEMENT_EMBER glColor4ub life*5,life*2,life,alphaByte Case ELEMENT_FIRE glColor4ub 255,life*2,life/2,alphaByte Case ELEMENT_SMOKE glColor4ub life,life,life,alphaByte Case ELEMENT_STEAM glColor4ub 250,250,255,life Case ELEMENT_WOOD glColor4ub elementRed[element], elementGreen[element]+(life/2), elementBlue[element],((y*y)+x) | $60 Case ELEMENT_GUNPOWDER glColor4ub $10+(160*yv*xv), $10+(160*yv*xv), $10+(160*yv*xv),alphaByte Case ELEMENT_MISSILE glColor4ub elementRed[element], elementGreen[element], elementBlue[element],(life/Float(lifespan[element]))*255 Default 'glColor4ub elementRed[element]+(255*yv), elementGreen[element]+(255*yv), elementBlue[element]+(255*yv),alphaByte 'glColor4ub elementRed[element]+(255*xv), elementGreen[element]+(255*xv), elementBlue[element]+(255*xv),alphaByte 'glColor4ub elementRed[element]-asleep, elementGreen[element]-asleep, elementBlue[element]-asleep,alphaByte glColor4ub elementRed[element], elementGreen[element], elementBlue[element], alphaByte End Select Select renderMode Case RENDERMODE_PIXEL glVertex2i x+xOrigin,y+yOrigin Case RENDERMODE_BLOB glVertex2i x+xOrigin,y+yOrigin Case RENDERMODE_BLOB2 glVertex2i x+xOrigin,y+yOrigin glVertex2i x+xOrigin+1,y+yOrigin+1 glVertex2i x+xOrigin,y+yOrigin+1 glVertex2i x+xOrigin+1,y+yOrigin Case RENDERMODE_IMAGE DrawImage blobImage,x-halfWidth,y-halfHeight End Select End Method End Type Type Interaction Field spreadElement:Int Field residueElement:Int Field spreadChance:Int Function Create:Interaction(sourceElement:Int, neighbourElement:Int, spreadElement:Int, residueElement:Int, spreadChance:Int) Local i:Interaction = New Interaction i.spreadChance = spreadChance i.spreadElement = spreadElement i.residueElement = residueElement elementInteractions[sourceElement, neighbourElement] = i Return i End Function End Type Type MenuItem Field x:Int, y:Int, w:Int, h:Int Field id:Int Field group:Int Field selected:Int = False Field recentlySelected:Int = False Field selectFade:Float = 0.0 Field image:Timage Field selectedImage:Timage Field selectedImageXOffset:Int Field selectedImageScale:Float Field selectedReflectionImage:TImage Field buttonReleased:Int = True Function Create:MenuItem(x:Int,y:Int,w:Int,h:Int, id:Int, image:TImage, group:Int=0, selectedImage:TImage=Null, selectedImageScale:Float=.25, selectedImageXOffset:Int=0, selectedReflectionImage:TImage = Null ) Local mi:MenuItem = New MenuItem mi.x = x mi.y = y mi.w = w mi.h = h mi.id = id mi.image = image mi.group = group menuItems.addLast(mi) If selectedImage = Null mi.selectedImage = image Else mi.selectedImage = selectedImage End If mi.selectedImageScale = selectedImageScale mi.selectedImageXOffset = selectedImageXOffset mi.selectedReflectionImage = selectedReflectionImage ' Print "created MenuItem [ x:"+x+" y:"+y+" w:"+w+" h:"+h+" id:"+id+" group:"+group+"]" Return mi End Function Function setSelectedItem(group:Int, id:Int) For Local mi:MenuItem = EachIn menuItems If mi.group = group And mi.id = id Then mi.selectItem() Next End Function Method update() If buttonReleased = False If Not MouseDown(1) Then buttonReleased = True End If If selected = True Or selectFade > 0 Then highlightSelected() If MouseX() >= x And MouseX() < x+w And MouseY() >= y And MouseY() < y+h If buttonReleased = True And MouseDown(1) selectItem() recentlySelected = True End If highlightHover() End If End Method Method hasJustBeenSelected:Int() If recentlySelected = True Then recentlySelected = False buttonReleased = False Return True End If End Method Method highlightHover() SetBlend LIGHTBLEND SetColor 64,255,255 SetAlpha .25 SetScale .25,.25 DrawImage image,x,y SetScale .25,-.25 DrawImage image,x,y+(ImageHeight(image)/4) SetScale 1,1 End Method Method highlightSelected() SetBlend LIGHTBLEND SetColor 255,255,0 If selectFade > 0 SetAlpha selectFade * .5 selectFade :- .04 Else SetAlpha .5 End If SetScale selectedImageScale,-selectedImageScale DrawImage selectedImage,x+selectedImageXOffset,y+(ImageHeight(selectedImage)*selectedImageScale) ' reflection SetAlpha (GetAlpha() *.5) SetScale selectedImageScale,selectedImageScale*.5 If selectedReflectionImage <> Null DrawImage selectedReflectionImage,x+selectedImageXOffset,y+(ImageHeight(selectedReflectionImage)*selectedImageScale)+1 Else DrawImage selectedImage,x+selectedImageXOffset,y+(ImageHeight(selectedImage)*selectedImageScale)+1 End If SetScale 1,1 End Method Method selectItem() If selected = False ' deselect any other selected menuItem in same group For Local mi:MenuItem = EachIn menuItems If mi.group = group Then mi.deselectItem() Next selected = True End If End Method Method deselectItem() If selected = True Then selected = False selectFade = 1.0 End If End Method End Type ' ----------------------------------------- Function checkCorruption() For Local x:Int = 0 To width-1 For Local y:Int = 0 To height-1 Local p:Particle = px[x,y] If p <> Null If p.x <> x Or p.y <> y Then RuntimeError("corruption detected @ " + x + " "+ y + " " + p.toString()) End If Next Next End Function ' ----------------------------------------- Function userInput() If MouseX() > (xOrigin)-20 And MouseX() < (xOrigin+width)+20 draw(selectedElement) End If Local oldSelectedElement:Int = selectedElement If KeyHit(KEY_1) selectedElement = ELEMENT_EMPTY Else If KeyHit(KEY_2) selectedElement = ELEMENT_WATER Else If KeyHit(KEY_3) selectedElement = ELEMENT_CONCRETE Else If KeyHit(KEY_4) selectedElement = ELEMENT_SALT Else If KeyHit(KEY_5) selectedElement = ELEMENT_MAGMA Else If KeyHit(KEY_6) selectedElement = ELEMENT_FIRE Else If KeyHit(KEY_7) selectedElement = ELEMENT_OIL Else If KeyHit(KEY_8) selectedElement = ELEMENT_SAND Else If KeyHit(KEY_9) selectedElement = ELEMENT_SMOKE End If If selectedElement <> oldSelectedElement Then reflectStateInMenu() Local modifier:Int = False If KeyDown(KEY_LCONTROL) Or KeyDown(KEY_LSYS) Or KeyDown(KEY_RSYS) modifier = True End If If modifier And KeyHit(KEY_N) initParticles() Else If modifier And KeyHit(KEY_O) MenuItem.setSelectedItem(ITEMGROUP_CONTROL, 0) loadSelectMode = True Else If modifier And KeyHit(KEY_S) MenuItem.setSelectedItem(ITEMGROUP_CONTROL, 1) saveSelectMode = True Else If KeyHit(KEY_D) drawMode = (drawMode + 1) Mod MAX_DRAWMODES reflectStateInMenu() Else If KeyHit(KEY_R) renderMode = (renderMode + 1) Mod MAX_RENDERMODES Else If KeyHit(KEY_S) selectedPenSize = (selectedPenSize + 1) Mod MAX_PEN_SIZE reflectStateInMenu() Else If KeyHit(KEY_SPACE) simulationRunning = Not simulationRunning reflectStateInMenu() Else If KeyHit(KEY_G) reverseGravity() Else If KeyHit(KEY_V) vSync = vSync ~ 1 Else If KeyHit(KEY_B) displayBackground = Not displayBackground Else If KeyHit(KEY_F1) initParticles() Else If KeyHit(KEY_F2) drawPreset(1) Else If KeyHit(KEY_F3) drawPreset(0) Else If KeyHit(KEY_F12) And savedGamePixmap <> Null Then Particle.initFromPixmap(savedGamePixmap) Else If KeyHit(KEY_C) checkCorruption() End If End Function Function displayZoom() Local cursorX:Int = MouseX()-xOrigin Local cursorY:Int = MouseY()-yOrigin Local tmpXOrigin:Int = xOrigin Local tmpYOrigin:Int = yOrigin Local tmpRenderMode:Int = renderMode 'xOrigin = (xOrigin + width + 10)-cursorX 'yOrigin = (GraphicsHeight()-yOrigin-128)-cursorY renderMode = RENDERMODE_BLOB SetColor 0,0,255 SetScale .25,.25 DrawImage backgroundImage,GraphicsWidth()-(xOrigin/2), GraphicsHeight()-(yOrigin+64) SetColor 64,64,64 SetScale .25,-.25 DrawImage backgroundImage,GraphicsWidth()-(xOrigin/2), GraphicsHeight()-(yOrigin+64) Particle.preRender() Local xoffset:Int = GraphicsWidth() - (cursorX+30) Local yoffset:Int = (GraphicsHeight()-(centerY-halfHeight))-(cursorY+22) For Local x:Int = cursorX-21 To cursorX+21 For Local y:Int = cursorY-21 To cursorY+21 If x >= 0 And x < width And y >= 0 And y < height If px[x,y] <> Null xOrigin = xoffset + ((x-cursorX-21)*2) yOrigin = yoffset + ((y-cursorY-21)*2) Local p:Particle = px[x,y] p.render() End If End If Next Next Particle.postRender() SetScale 1,1 SetColor 255,255,255 xOrigin = tmpXOrigin yOrigin = tmpYOrigin renderMode = tmpRenderMode SetAlpha .25 + ((frame Mod 8)/16.0) DrawRect xOrigin+width+10+62-(penSizes[selectedPenSize]*1.25),GraphicsHeight()-yOrigin-64-(penSizes[selectedPenSize]*1.25),(penSizes[selectedPenSize]*2.5),(penSizes[selectedPenSize]*2.5) If MouseX() > xOrigin And MouseX() < xOrigin+width And MouseY() > yOrigin And MouseY() < yOrigin+height Local p:Particle = px[MouseX()-xOrigin,MouseY()-yOrigin] Local eType:String = "Air" If p <> Null eType = elementLabels[p.element] SetAlpha .5 SetBlend LIGHTBLEND DrawText eType, GraphicsWidth()-xOrigin+12,GraphicsHeight()-yOrigin-126 'If p <> Null DrawText p.toString(), 0,30 End If End Function Function oldDisplayZoom() GrabImage(zoomImage,MouseX()-32,MouseY()-32) SetScale 2,2 SetBlend SOLIDBLEND SetColor 255,255,255 DrawImage zoomImage,xOrigin+width+10,GraphicsHeight()-yOrigin-128 SetScale 1,1 If MouseX() > xOrigin And MouseX() < xOrigin+width And MouseY() > yOrigin And MouseY() < yOrigin+height Local p:Particle = px[MouseX()-xOrigin,MouseY()-yOrigin] Local eType:String = "Air" If p <> Null eType = elementLabels[p.element] SetAlpha .5 SetBlend LIGHTBLEND DrawText eType, GraphicsWidth()-xOrigin+12,GraphicsHeight()-yOrigin-126 'If p <> Null DrawText p.toString(), 0,30 End If SetAlpha 1 SetBlend SOLIDBLEND End Function Function updateMenu() SetColor 255,255,255 SetOrigin 0,0 SetScale 1,1 SetBlend ALPHABLEND SetAlpha 1 DrawImage menuImage,0,yOrigin For Local mi:MenuItem = EachIn menuItems mi.update() If mi.hasJustBeenSelected() Select mi.group Case ITEMGROUP_ELEMENTS selectedElement = mi.id Case ITEMGROUP_BRUSH selectedPenSize = mi.id Mod 3 drawMode = mi.id / 3 Case ITEMGROUP_CONTROL Select mi.id Case 0 loadSelectMode = True Case 1 saveSelectMode = True Case 2 Case 3 initParticles() reflectStateInMenu() Case 4 simulationRunning = False Case 5 simulationRunning = True End Select End Select End If Next End Function Function reverseGravity() For Local i:Int = 0 To MAX_ELEMENTS-1 gravity[i] = -gravity[i] Next End Function ' -------------------------------------------------------------------- Function createGradientImage:TImage(w:Int,h:Int) Local image:TImage = CreateImage(w,h) Local pix:TPixmap = LockImage(image) Cls SetBlend SOLIDBLEND For Local y:Int = 0 To h Local c:Int = 255*(y/Float(h)) SetColor c,c,c DrawLine 0,y,w,y Next GrabImage image,0,0 UnlockImage image Return image End Function Function createBlobImage:TImage() Local image:TImage = CreateImage(8,8) Local pix:TPixmap = LockImage(image) Cls SetColor 255,255,255 SetBlend LIGHTBLEND SetAlpha .2 SetScale 1,1 DrawOval 0,0,8,8 DrawOval 1,1,6,6 DrawOval 2,2,4,4 DrawOval 3,3,2,2 GrabImage image,0,0 UnlockImage image SetImageHandle image,4,4 Return image End Function Function createButtonBackgroundImage:TImage(buttonWidth:Int=100, buttonHeight:Int=24, lowerCMult:Float = 1.0) Local image:TImage = CreateImage(buttonWidth,buttonHeight) Local pix:TPixmap = LockImage(image) Cls SetColor 255,255,255 SetBlend SOLIDBLEND DrawOval 0,0,buttonWidth/4,buttonHeight DrawOval buttonWidth - (buttonWidth/4),0,buttonWidth/4,buttonHeight DrawRect buttonWidth/8,0,buttonWidth - (buttonWidth/4),buttonHeight SetBlend SHADEBLEND For Local y:Int = 0 To buttonHeight/1.65 Local c:Int = 225-(y*3) SetColor c,c,c For Local x:Int = 0 To buttonWidth/8 Plot buttonWidth/8-x, y-((x*x)*.004) Plot (buttonWidth-buttonWidth/8)+x,y-((x*x)*.004) Next DrawLine 1+(buttonWidth/8),y,buttonWidth-(buttonWidth/8)-1,y Next For Local y:Int = buttonHeight/1.65 To buttonHeight Local c:Int = ((y*4)-205)*lowerCMult SetColor c,c,c For Local x:Int = 0 To buttonWidth/8 Plot buttonWidth/8-x, y-((x*x)*.004) Plot (buttonWidth-buttonWidth/8)+x,y-((x*x)*.004) Next DrawLine 1+(buttonWidth/8),y,buttonWidth-(buttonWidth/8)-1,y Next GrabImage image,0,0 UnlockImage image Return image End Function Function createMenuImage:TImage() Local buttonImage:TImage = createButtonBackgroundImage((xOrigin-40)*4,22*4) Local smallButtonImage:TImage = createButtonBackgroundImage((xOrigin-40),22*4) Local smallButtonReflectionImage:TImage = createButtonBackgroundImage((xOrigin-40),22*4,0.0) Local image:TImage = CreateImage(xOrigin,GraphicsHeight()) Local pix:TPixmap = LockImage(image) Cls ' elements menu SetColor 255,255,255 SetBlend SOLIDBLEND DrawText "ELEMENTS",(xOrigin-TextWidth("ELEMENTS"))/2,6 Local y:Int = 30 Local i:Int = 0 For Local label:String = EachIn elementLabels If elementCreatable[i] = True SetScale .25,-.25 SetBlend SOLIDBLEND SetColor elementRed[i]*.75,elementGreen[i]*.75,elementBlue[i]*.75 DrawImage buttonImage,20,y-4+22 SetColor 255,255,255 SetScale .25,.25 SetAlpha .25 SetBlend LIGHTBLEND DrawImage buttonImage,20,y-4 SetScale 1,1 SetBlend ALPHABLEND SetAlpha .5 SetColor 0,0,0 DrawText label, 1+((xOrigin) - TextWidth(label))/2, y+1 SetAlpha .25 DrawText label, 2+((xOrigin) - TextWidth(label))/2, y+2 SetAlpha .75 SetColor 225,225,225 DrawText label, ((xOrigin) - TextWidth(label))/2, y MenuItem.Create(20, y+40, xOrigin-40, 24, i, buttonImage, ITEMGROUP_ELEMENTS, blobImage, 1.5, xOrigin-30) y:+24 End If i:+1 Next ' brush menu Local buttonWidth:Int = ImageWidth(smallButtonImage)/4 Local buttonHeight:Int = ImageHeight(smallButtonImage)/4 Local x:Int = (xOrigin-((buttonWidth+2)*3))/2 For Local i:Int = 0 To MAX_PEN_SIZE-1 ' small button background SetBlend ALPHABLEND SetAlpha .75 SetColor 228,168,228 SetScale .25,.25 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+24 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+48 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+72 SetAlpha .45 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+120 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+144 ' small button label shadows SetScale 1,1 SetColor 0,0,0 SetAlpha .5 drawButtonLabels(i, x+1,y+1, buttonWidth, buttonHeight) SetColor 0,0,0 drawButtonLabels(i, x+2,y+2, buttonWidth, buttonHeight) ' small button label SetColor 220,220,220 SetAlpha .75 drawButtonLabels(i, x,y, buttonWidth, buttonHeight) ' small button SetBlend ALPHABLEND SetAlpha .35 SetColor 255,0,0 SetScale .25,-.25 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+24+22 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+48+22 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+72+22 SetColor 128,255,0 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+120+22 DrawImage smallButtonImage, x+((2+buttonWidth)*i),y+144+22 MenuItem.Create(x+(i*(buttonWidth+2)), y+24+44, buttonWidth-2, buttonHeight, (DRAWMODE_PEN*3)+i, smallButtonImage, ITEMGROUP_BRUSH) MenuItem.Create(x+(i*(buttonWidth+2)), y+48+44, buttonWidth-2, buttonHeight, (DRAWMODE_LINE*3)+i, smallButtonImage, ITEMGROUP_BRUSH) MenuItem.Create(x+(i*(buttonWidth+2)), y+72+44, buttonWidth-2, buttonHeight, (DRAWMODE_CIRCLE*3)+i, smallButtonImage, ITEMGROUP_BRUSH,Null, .25, 0, smallButtonReflectionImage) MenuItem.Create(x+(i*(buttonWidth+2)), y+120+44, buttonWidth-2, buttonHeight, i, smallButtonImage, ITEMGROUP_CONTROL) MenuItem.Create(x+(i*(buttonWidth+2)), y+144+44, buttonWidth-2, buttonHeight, i+3, smallButtonImage, ITEMGROUP_CONTROL, Null, .25, 0, smallButtonReflectionImage) Next GrabImage image,0,0 UnlockImage image Return image End Function Function reflectStateInMenu() MenuItem.setSelectedItem(ITEMGROUP_ELEMENTS, selectedElement) MenuItem.setSelectedItem(ITEMGROUP_BRUSH, (drawMode*3) + selectedPenSize) If simulationRunning = False Then MenuItem.setSelectedItem(ITEMGROUP_CONTROL,4) Else MenuItem.setSelectedItem(ITEMGROUP_CONTROL,5) End If End Function Function drawButtonLabels(i:Int, x:Int,y:Int, w:Int, h:Int) Local red:Int,green:Int,blue:Int GetColor(red,green,blue) ' pen DrawRect x+(w/2)+(i*(w+2))-(penSizes[i]/2), y+24+(h/2)-(penSizes[i]/2), penSizes[i],penSizes[i] ' line SetLineWidth penSizes[i] DrawLine x+(w/4)+(i*(w+2)), y+48+(h-(h/4))-2, x+(w-(w/4))+(i*(w+2)), y+48+(h/4)+2 Select i Case 0 ' load DrawPoly([ Float(x+8),Float(y+120+(h-12)), Float(x+(w-8)),Float(y+120+(h-12)), Float(x+(w/2)),Float(y+120+4)]) DrawPoly([ Float(x+4),Float(y+120+(h-4)), Float(x+(w-4)),Float(y+120+(h-4)), Float(x+(w-6)),Float(y+120+(h-8)), Float(x+6),Float(y+120+(h-8)) ]) SetColor 0,0,0 DrawPoly([ Float(x+6),Float(y+120+(h-5)), Float(x+(w-6)),Float(y+120+(h-5)), Float(x+(w-7)),Float(y+120+(h-7)), Float(x+7),Float(y+120+(h-7)) ]) SetColor red,green,blue DrawRect x+11, y+120+(h-11), 4,5 ' circle SetScale 1+(penSizes[i]/2),1+(penSizes[i]/2) DrawCircle( x+(w/2)+(i*(w+2))-(penSizes[i]/2)-1, y+72+(h/2)-(penSizes[i]/2), w/4 ) SetScale 1,1 ' new SetLineWidth 1 SetColor 0,0,128 DrawRect x+((w+2)*0)+6,y+144+5,12,10 SetColor 225,225,225 DrawLine x+((w+2)*0)+6, y+144+5, x+((w+2)*1)-10, y+144+5 DrawLine x+((w+2)*0)+6, y+144+15, x+((w+2)*1)-10, y+144+15 DrawLine x+((w+2)*1)-10, y+144+5, x+((w+2)*1)-10, y+144+15 DrawLine x+((w+2)*0)+6, y+144+5, x+((w+2)*0)+6, y+144+15 Case 1 ' save DrawPoly([ Float(x+w+2+4),Float(y+120+(h-4)), Float(x+w+2+(w-4)),Float(y+120+(h-4)), Float(x+w+2+(w-6)),Float(y+120+(h-8)), Float(x+w+2+6),Float(y+120+(h-8)) ]) SetColor 0,0,0 DrawPoly([ Float(x+w+2+6),Float(y+120+(h-5)), Float(x+w+2+(w-6)),Float(y+120+(h-5)), Float(x+w+2+(w-7)),Float(y+120+(h-7)), Float(x+w+2+7),Float(y+120+(h-7)) ]) SetColor red,green,blue DrawRect x+w+2+11, y+120+5, 4,5 DrawPoly([Float(x+w+2+8),Float(y+120+11), Float(x+w+2+(w-8)),Float(y+120+11), Float(x+w+2+(w/2)),Float(y+120+(h-5))]) 'circle SetScale 1+(penSizes[i]/2),1+(penSizes[i]/2) DrawCircle( x+(w/2)+(i*(w+2))-(penSizes[i]/2), y+72+(h/2)-(penSizes[i]/2), w/4 ) SetScale 1,1 ' pause DrawRect x+w+2+(w/4)+1, y+144+(h/4), 5,h/2 DrawRect x+w+2+(w-(w/2))+1, y+144+(h/4), 5,h/2 Case 2 'circle SetScale 1+(penSizes[i]/2),1+(penSizes[i]/2) DrawCircle( x+(w/2)+(i*(w+2))-(penSizes[i]/2), y+72+(h/2)-(penSizes[i]/2), w/4 ) SetScale 1,1 ' play DrawPoly([Float(x+w+w+2+2+(w/4)+3), Float(y+144+(h/4)), Float(x+w+w+2+2+(w/4)+3), Float(y+144+(h-(h/4))),Float( x+w+w+2+2+(w-(w/2))+6), Float(y+144+(h/2))]) End Select End Function Function DrawCircle(x:Int,y:Int,radius:Int, stepinc:Float=6.0) Local angle:Float = 0 While (angle < 360.0) DrawRect x+(Sin(angle)*radius),y+(Cos(angle)*radius),1,1 angle :+ stepinc Wend End Function Function drawPreset(preset:Int) Select preset Case 1 For Local x:Int = width/4 To width-(width/4) For Local y:Int = 50 To 100 Particle.Create(selectedElement,x,y) Next Next Case 0 For Local x:Int = 0 To width-1 Step 8 For Local y:Int = 0 To height -1 Step 8 If Rand(0,8) = 0 For Local z:Int = 0 To 7 Particle.Create(selectedElement,x+z,y+z) Particle.Create(selectedElement,x+z+1,y+z) Next 'For Local z:Int = 0 To 7 ' Particle.Create(ELEMENT_GRAPHITE,x+z,y+3) 'Next Else If Rand(0,8) = 0 For Local z:Int = 0 To 7 Particle.Create(selectedElement,(8+x)-z,y+z) Particle.Create(selectedElement,(9+x)-z,y+z) Next 'For Local z:Int = 0 To 7 ' Particle.Create(ELEMENT_GRAPHITE,x,y+z) 'Next End If Next Next End Select End Function Function countFps:Int() fpsHistory[frame Mod MAX_FPS_HISTORY] = 1000.0 / (MilliSecs() - fpstime) fpstime = MilliSecs() Local fpsTotal:Int For Local fps:Int = EachIn fpsHistory fpsTotal :+ fps Next Return fpsTotal / MAX_FPS_HISTORY End Function Function draw(element:Int) Local currentMouseX:Int = MouseX() - xOrigin Local currentMouseY:Int = MouseY() - yOrigin Local mouseReleased:Int = False If MouseDown(1) If drag = False ' start dragging dragFromX = currentMouseX dragFromY = currentMouseY drag = True End If Else If drag = True mouseReleased = True drag = False End If End If Local halfPenSize:Int = penSizes[selectedPenSize]/2 SetBlend LIGHTBLEND SetAlpha .25 + ((frame Mod 8)/16.0) Select renderMode Case RENDERMODE_PIXEL DrawRect currentMouseX+xOrigin-halfPenSize,currentMouseY+yOrigin-1-halfPenSize,penSizes[selectedPenSize],penSizes[selectedPenSize] Default DrawRect currentMouseX+xOrigin-halfPenSize-1,currentMouseY+yOrigin-1-halfPenSize,penSizes[selectedPenSize]+1,penSizes[selectedPenSize]+1 End Select Select drawMode Case DRAWMODE_LINE If mouseReleased = True Local x:Float = dragFromX Local y:Float = dragFromY Local steps:Float = Sqr((Abs(currentMouseX-x) * Abs(currentMouseX-x)) + (Abs(currentMouseY-y) * Abs(currentMouseY-y)) ) Local xInc:Float = (currentMouseX-x)/steps Local yInc:Float = (currentMouseY-y)/steps For Local i:Int = 0 To steps*2 For Local n:Int = -halfPenSize To halfPenSize Particle.Create(element,x+(yInc*n),y+(-xInc*n)) Next x :+ (xInc*.5) y :+ (yInc*.5) Next Else If drag = True SetBlend LIGHTBLEND SetAlpha .25 + ((frame Mod 8)/16.0) SetLineWidth penSizes[selectedPenSize] DrawLine dragFromX+xOrigin,dragFromY+yOrigin-1,currentMouseX+xOrigin,currentMouseY+yOrigin-1 SetLineWidth 1 End If Case DRAWMODE_PEN If drag = True Local mouseXSpeed:Float = (currentMouseX-prevMouseX)*.1 If mouseXSpeed > 1.0 mouseXSpeed = 1.0 If mouseXSpeed < -1.0 mouseXSpeed = -1.0 Local mouseYSpeed:Float = (currentMouseY-prevMouseY)*.1 If mouseYSpeed > 1.0 mouseYSpeed = 1.0 If mouseYSpeed < -1.0 mouseYSpeed = -1.0 If (currentMouseX = dragFromX) And (currentMouseY = dragFromY) For Local xx:Int=-halfPenSize To halfPenSize For Local yy:Int=-halfPenSize To halfPenSize Particle.Create(element,xx+currentMouseX,yy+currentMouseY,mouseXSpeed,mouseYSpeed) Next Next Else Local x:Float = dragFromX Local y:Float = dragFromY Local steps:Float = Sqr((Abs(currentMouseX-x) * Abs(currentMouseX-x)) + (Abs(currentMouseY-y) * Abs(currentMouseY-y)) ) Local xInc:Float = (currentMouseX-x)/steps Local yInc:Float = (currentMouseY-y)/steps For Local i:Int = 0 To steps*2 For Local n:Int = -halfPenSize To halfPenSize Particle.Create(element,x+(yInc*n),y+(-xInc*n),mouseXSpeed,mouseYSpeed) Next x :+ (xInc*.5) y :+ (yInc*.5) Next End If dragFromX = currentMouseX dragFromY = currentMouseY End If Case DRAWMODE_CIRCLE If mouseReleased = True Local x:Float = dragFromX Local y:Float = dragFromY Local radius:Float = Sqr((Abs(currentMouseX-x) * Abs(currentMouseX-x)) + (Abs(currentMouseY-y) * Abs(currentMouseY-y)) ) Local angleInc:Float = 32/(Abs(radius)+.0001) Local angle:Float = 0.0 While (angle < 360.0) Local xx:Float = Sin(angle) * radius Local yy:Float = Cos(angle) * radius For Local xxx:Int=-halfPenSize To halfPenSize For Local yyy:Int=-halfPenSize To halfPenSize Particle.Create(element,x+xx+xxx+1, y+yy+yyy+1) Next Next angle :+ angleInc Wend Else If drag = True Local radius:Float = Sqr((Abs(currentMouseX-dragFromX) * Abs(currentMouseX-dragFromX)) + (Abs(currentMouseY-dragFromY) * Abs(currentMouseY-dragFromY)) ) SetBlend LIGHTBLEND SetAlpha .25 + ((frame Mod 8)/16.0) SetScale penSizes[selectedPenSize],penSizes[selectedPenSize] DrawCircle(dragFromX+xOrigin-halfPenSize,dragFromY+yOrigin-halfPenSize,radius, Min(6.0 * (64/(Abs(radius)+.0001)),6.0) ) SetScale 1,1 End If End Select prevMouseX = currentMouseX prevMouseY = currentMouseY End Function Function initParticles() Particle.reset() ' concrete walls For Local x:Int = 1 To width-1 Particle.Create(ELEMENT_CONCRETE,x,1) Particle.Create(ELEMENT_CONCRETE,x,height-1) Next For Local y:Int = 1 To height-1 Particle.Create(ELEMENT_CONCRETE,1,y) Particle.Create(ELEMENT_CONCRETE,width-1,y) Next End Function ' try and create output directory If it doesn't exist already Function createSaveGameDir() Local dir:Int = ReadDir(OUTPUT_DIRECTORY) If Not dir Then CreateDir(OUTPUT_DIRECTORY,True) Else CloseDir(dir) End If End Function Function screenshot() Local filename:String = OUTPUT_DIRECTORY+"/elemental_"+MilliSecs()+".jpg" Local screengrab:TPixmap = GrabPixmap(0,0,GraphicsWidth(),GraphicsHeight()) SavePixmapJPeg(screengrab,filename,100) End Function Function savePixmap(pixmap:TPixmap) createSaveGameDir() Local filename:String = OUTPUT_DIRECTORY+"/elemental_"+MilliSecs()+".png" SavePixmapPNG(pixmap,filename) End Function Function rgbToString:String(red:Byte,green:Byte,blue:Byte) Return Hex( (red Shl 24) + (green Shl 16) + (blue Shl 8)) End Function Type SavedGame Global savedGames:TList = CreateList() Field pixmap:TPixmap Field sprite:TSprite Field file:TFile Field deleting:Float = -1 Function Create:SavedGame(file:TFile) Local sg:SavedGame = New SavedGame sg.pixmap = PixmapWindow(LoadPixmap(file.path+"/"+file.filename),0,0,width,height) sg.file = file sg.sprite = TSprite.Create(LoadImage(sg.pixmap, MIPMAPPEDIMAGE)) sg.file.isLoaded = True ListAddLast savedGames,sg Return sg End Function Function updateAll(selectedIndex:Int, lsmooth:Float, sliding:Int) Local i:Int = 0 Local deletedCount:Int = 0 For Local sg:SavedGame = EachIn savedGames sg.update(i, selectedIndex, deletedCount, lsmooth, sliding) If sg.deleting >= 0 deletedCount :+1 i :+ 1 Next End Function Function getSelectedSavedGame:SavedGame(selectedIndex:Int) Local i:Int = 0 For Local sg:SavedGame = EachIn savedGames If i = selectedIndex And sg.deleting < 0 Return sg End If If sg.deleting < 0 i :+ 1 Next End Function Function deleteAll() TSprite.deleteAll() ClearList(savedGames) End Function Function renderAll() SetBlend ALPHABLEND SetAlpha 1 SetColor 255,255,255 TSprite.renderAll() End Function Method update(index:Int, selectedIndex:Int, deletedCount:Int, lsmooth:Float, sliding:Int) ' calculate x,y,z coords of all sprites Local smooth:Float = lsmooth If sliding = False smooth = 0.0 sprite.x = ((index-deletedCount-selectedIndex+smooth)+(deletedCount*Abs(lsmooth)))*2 sprite.y = 0 sprite.z = -3.5 + (Cos((sprite.x)*22.5*.5)*2) If deleting > 0 sprite.x = 0 sprite.z = -1.5 - ((1.0-deleting)*4) deleting :- .02 If deleting <= 0 Then DeleteFile(file.path +"/"+ file.filename) 'Print "removed " + file.path + "/" + file.filename remove() End If End If End Method Method remove() sprite.remove() savedGames.remove(Self) End Method End Type Function selectSavedFile:TPixmap() FlushMouse() FlushKeys() MoveMouse(centerX,centerY) Local selected:TPixmap = Null Local files:TList = readImageDirFiles(OUTPUT_DIRECTORY) Local fileCount:Int = CountList(files) If fileCount = 0 Return selected SortList files For Local f:TFile = EachIn files SavedGame.Create(f) Next Local lsmoothX:Float = 0.0 Local selecting:Int = True Local sliding:Int = False While (selecting) Local sg:SavedGame = SavedGame.getSelectedSavedGame(selectedImageIndex) Cls SetBlend LIGHTBLEND SetScale 1,1 SetAlpha .5 SetOrigin 0,0 SetColor 255,255,255 DrawText "Select File",centerX-(TextWidth("Select File")/2),yOrigin+6 SetAlpha 0.5-Abs(lsmoothX) DrawText sg.file.filename, centerX-(TextWidth(sg.file.filename)/2), (halfHeight/4)+halfHeight/2 SetAlpha .75 SetColor 96,96,64 SetScale 2,.125 DrawImage backgroundImage,centerX,(centerY-(height/16)) SavedGame.updateAll(selectedImageIndex, lsmoothX, sliding) SavedGame.renderAll() If lsmoothX <> 0.0 lsmoothX :* .92 If Abs(lsmoothX) < .01 sliding = False End If If Abs(lsmoothX) < .1 If selectedImageIndex > 0 If KeyHit(KEY_LEFT) Then FlushKeys() ; MoveMouse(centerX,centerY) ; selectedImageIndex :-1 ; lsmoothX = -1.0 ; sliding = True If KeyHit(KEY_UP) Then FlushKeys() ; MoveMouse(centerX,centerY) ; selectedImageIndex = 0 ; lsmoothX = -1.0 ; sliding = True If MouseX() <= 0 Then selectedImageIndex :-1 ; lsmoothX = -1.0 ; sliding = True End If If selectedImageIndex < fileCount-1 If KeyHit(KEY_RIGHT) Then FlushKeys() ; MoveMouse(centerX,centerY) ; selectedImageIndex :+1 ; lsmoothX = 1.0 ; sliding = True If KeyHit(KEY_DOWN) Then FlushKeys() ; MoveMouse(centerX,centerY) ; selectedImageIndex = fileCount-1 ; lsmoothX = 1.0 ; sliding = True If MouseX() >= GraphicsWidth()-1 Then selectedImageIndex :+1 ; lsmoothX = 1.0 ; sliding = True End If End If If KeyHit(KEY_DELETE) And sg.deleting < 0 Then sliding = False sg.deleting = 1.0 lsmoothX = 1.0 fileCount :- 1 If fileCount <= 0 Then selecting = False Else If selectedImageIndex > fileCount-1 Then selectedImageIndex = fileCount-1 lsmoothX = -1.0 sliding = True End If End If If KeyHit(KEY_ENTER) Or MouseHit(1) Then selected = sg.pixmap ; selecting = False If KeyHit(KEY_ESCAPE) selecting = False If KeyDown(KEY_LCONTROL) And KeyHit(KEY_G) Then screenshot() Flip Delay 1 Wend SavedGame.deleteAll() Return selected End Function Function readImageDirFiles:TList(path:String) Local files:TList = CreateList() Local dirFiles:String[] dirFiles=LoadDir(path) For Local filename:String = EachIn dirFiles Local fileExtension:String = Lower(Right(filename,4)) If fileExtension = ".png" ListAddLast files, TFile.Create(path,filename,FileTime(path+"/"+filename),FileSize(path+"/"+filename) ) End If Next Return files End Function
