/* ParticleField.as Lee Felarca http://www.zeropointnine.com/blog 9-24-2007 v0.9 Source code licensed under a Creative Commons Attribution 3.0 License. http://creativecommons.org/licenses/by/3.0/ Some Rights Reserved. Goes with "ParticleField.fla" */ package { import flash.display.*; import flash.events.*; import flash.text.*; import flash.geom.Point; public class ParticleField extends MovieClip { private var vectorMatrix:Array; private var sprArea1:sprArea; private var sprControls1:sprControls; private const MATRIXSIDE:int = 12; private const AREASIDE:int = 100; private const AREALEFTBOUND = -25; private const AREARIGHTBOUND = 125; private const AREATOPBOUND = -25; private var particles:Array; private var mX1:Number, mX2:Number, mX3:Number; private var mY1:Number, mY2:Number, mY3:Number; private var vectorsDecay:Boolean = false; private var showVectors:Boolean = true; private var mouseIsDown:Boolean; public function ParticleField() { sprArea1 = new sprArea(); this.addChild(sprArea1); sprArea1.x = 50; sprArea1.y = 50; sprArea1.scaleX = 5; sprArea1.scaleY = 5; var sprPrompt1:sprPrompt = new sprPrompt(); sprPrompt1.x = 50; sprPrompt1.y = 50 + 500 + 10; this.addChild(sprPrompt1); sprControls1 = new sprControls(); this.addChild(sprControls1); sprControls1.x = 50; sprControls1.y = 15; sprControls1.buttonMode = true; sprControls1.sprShowVectors.alpha = showVectors ? 1 : 0.45; sprControls1.sprVectorsDecay.alpha = vectorsDecay ? 1 : 0.45; clearVectors(); init(1000); this.addEventListener(Event.ENTER_FRAME, onEnterFrame); sprControls1.addEventListener(MouseEvent.CLICK, onControlsClick); sprArea1.sprHot.addEventListener(MouseEvent.MOUSE_DOWN, onDown); sprArea1.sprHot.addEventListener(MouseEvent.MOUSE_UP, onUp); } private function init($num:int):void { // particles particles = new Array(); for (var i:int = 0; i < $num; i++) { particles[i] = newParticle(true); } sprControls1.spr500.alpha = ($num==500) ? 1 : 0.45; sprControls1.spr1000.alpha = ($num==1000) ? 1 : 0.45; sprControls1.spr2000.alpha = ($num==2000) ? 1 : 0.45; } private function onDown(e:MouseEvent):void { mouseIsDown = true; mX1 = mX2 = mX3 = translateMouseX(); mY1 = mY2 = mY3 = translateMouseY(); } private function onUp(e:MouseEvent):void { mouseIsDown = false; } private function onControlsClick(e:MouseEvent):void { switch (e.target.name) { case "spr500": init(500) break; case "spr1000": init(1000) break; case "spr2000": init(2000) break; case "sprVectorsDecay": vectorsDecay = !vectorsDecay; e.target.alpha = vectorsDecay ? 1 : 0.45; break; case "sprShowVectors": showVectors = !showVectors; e.target.alpha = showVectors ? 1 : 0.45; break; case "btnClearVectors": clearVectors(); break; } } private function clearVectors():void { vectorMatrix = new Array(); for (var y:int = 0; y < MATRIXSIDE; y++) { vectorMatrix[y] = new Array(); for (var x:int = 0; x < MATRIXSIDE; x++) { vectorMatrix[y][x] = new Point(0,0); } } } private function translateMouseX():Number { // kludgey thing to convert real mouse coordinates into 0 to 100 within the 'active area' var x:Number = this.mouseX; x -= 50; x /= 5; return x; } private function translateMouseY():Number { // kludgey thing to convert real mouse coordinates into 0 to 100 within the 'active area' var y:Number = this.mouseY; y -= 50; y /= 5; return y; } private function userInput():void { if (!mouseIsDown) return; // mouse action - adjust vector matrix magnitudes mX3 = mX2; mY3 = mY2; mX2 = mX1; mY2 = mY1; mX1 = translateMouseX(); mY1 = translateMouseY(); var quadX = int(mX1 / AREASIDE * MATRIXSIDE); var quadY = int(mY1 / AREASIDE * MATRIXSIDE); if (quadX >=0 && quadX < MATRIXSIDE && quadY >=0 && quadY < MATRIXSIDE) { vectorMatrix[quadY][quadX].x = (mX1 - mX3) / 150; vectorMatrix[quadY][quadX].y = (mY1 - mY3) / 150; } } private function newParticle(firstTime:Boolean=false):ParticleVO { var x:Number = Math.random() * AREASIDE; var y:Number = 0; if (firstTime) y = Math.random() * AREASIDE; return new ParticleVO( x, y, Math.random() * 0.25 - 0.125, Math.random() * 0.2 + 0.1); } private function drawParticles():void { sprArea1.sprContainer.graphics.clear(); var col:uint; for (var i:int = 0; i < particles.length; i++) { // particle color based on velocity col = Math.abs(particles[i].vx) + Math.abs(particles[i].vy) * 40; if (col > 120) col = 120; col = int(col * 0x222); sprArea1.sprContainer.graphics.lineStyle(1,col,1,true); sprArea1.sprContainer.graphics.moveTo(particles[i].x, particles[i].y); sprArea1.sprContainer.graphics.lineTo(particles[i].x+0.2, particles[i].y); } } private function drawOverlay():void { sprArea1.sprOverlay.graphics.clear(); var quadrantSide:Number = AREASIDE/MATRIXSIDE; // show quadrant under mouse sprArea1.sprOverlay.graphics.beginFill(0xFF4444, 0.2); sprArea1.sprOverlay.graphics.drawRoundRect( int(translateMouseX() / quadrantSide) * quadrantSide, int(translateMouseY() / quadrantSide) * quadrantSide, quadrantSide, quadrantSide, 3, 3); sprArea1.sprOverlay.graphics.endFill(); if (!mouseIsDown && !showVectors) return; // draw vector lines var alph:Number = showVectors ? 0.8 : 0.2; sprArea1.sprOverlay.graphics.lineStyle(1,0xFF0000,alph,true); for (var y:int = 0; y < MATRIXSIDE; y++) { var s:String = ""; for (var x:int = 0; x < MATRIXSIDE; x++) { var px:Number = x * quadrantSide + quadrantSide/2 var py:Number = y * quadrantSide + quadrantSide/2; var px2:Number = px + vectorMatrix[y][x].x * 40; var py2:Number = py + vectorMatrix[y][x].y * 40; sprArea1.sprOverlay.graphics.moveTo(px,py); sprArea1.sprOverlay.graphics.lineTo(px2,py2); } } } private function updateModel():void { if (vectorsDecay) { // decay vector magnitudes for (var x:int = 0; x < MATRIXSIDE; x++) { for (var y:int = 0; y < MATRIXSIDE; y++) { if (vectorMatrix[y][x].x != 0) { vectorMatrix[y][x].x *= 0.95; if (Math.abs(vectorMatrix[x][y].x) < 0.005) vectorMatrix[x][y].x = 0; } vectorMatrix[y][x].y *= 0.95; } } } // Particles: var fx:int, fy:int; for (var i:int = 0; i < particles.length; i++) { // apply vectors to particles fx = int(particles[i].x / AREASIDE * MATRIXSIDE); fy = int(particles[i].y / AREASIDE * MATRIXSIDE); if (fx >= 0 && fx < MATRIXSIDE && fy >=0 && fy < MATRIXSIDE) { particles[i].vx += (vectorMatrix[fy][fx]).x; particles[i].vy += (vectorMatrix[fy][fx]).y; } // x velocity decays particles[i].vx *= 0.96; if (Math.abs(particles[i].vx) < 0.003) particles[i].vx = 0; // y velocity - 'gravity' if (particles[i].vy <= 0) // going up particles[i].vy += 0.01; else if (particles[i].vy < 0.2) // going down slowly particles[i].vy += 0.0020; else // going down a bit faster particles[i].vy += 0.0006; // add velocity to position particles[i].x += particles[i].vx; particles[i].y += particles[i].vy; // bounds-check if (particles[i].y > AREASIDE || particles[i].x < AREALEFTBOUND || particles[i].x > AREARIGHTBOUND || particles[i].y < AREATOPBOUND) particles[i] = newParticle(); } } private function onEnterFrame(e:Event):void { userInput(); updateModel(); drawOverlay(); drawParticles(); } } // class } // package internal class ParticleVO { public function ParticleVO($x:Number, $y:Number, $vx:Number, $vy:Number) { x = $x; y = $y; vx = $vx; vy = $vy; } public var x:Number; public var y:Number; public var vx:Number; public var vy:Number; }