Charges-and-fields-Flex

package edu.colorado.phet.chargesandfields {

import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.Stage
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.ColorTransform;
import flash.text.TextField;
import flash.ui.Keyboard;

public class BackgroundSprite extends Sprite {
    private var myWidth : Number;
    private var myHeight : Number;

    public function BackgroundSprite(w : Number, h : Number) {
        myWidth = w;
        myHeight = h;

        drawBackground();
    }

    public function changeSize(w : Number, h : Number) : void {
        myWidth = w;
        myHeight = h;
        drawBackground();
    }

    public function drawBackground() : void {
        this.graphics.beginFill(0xFFFFFF);
        this.graphics.drawRect(0, 0, myWidth, myHeight);
        this.graphics.endFill();
    }

}
}

package edu.colorado.phet.chargesandfields {

public class Charge extends Sprite {
    public var q : Number = 0;

    public var modelX : Number;
    public var modelY : Number;

    private var mosaic : VoltageMosaic;

    public function Charge(mosaic : VoltageMosaic) {

        this.mosaic = mosaic;

        // make it appear hand-like
        this.useHandCursor = true;
		this.buttonMode = true;

        addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
		addEventListener(MouseEvent.MOUSE_UP, mouseUp);
    }

    public function setDisplayPosition(x : Number, y : Number) : void {
        this.x = x;
        this.y = y;

        displayPositionToModel();
    }

    private function displayPositionToModel() : void {
        // TODO: integrate in scale. if we find this in the GUI
        modelX = x;
        modelY = y;
    }

    public function mouseDown(evt : MouseEvent) : void {
        startDrag();
    }

    public function mouseUp(evt : MouseEvent) : void {
        stopDrag();
        displayPositionToModel();
        mosaic.draw();
    }
}
}

package edu.colorado.phet.chargesandfields {
public class ChargesAndFieldsApplication extends UIComponent {

    public static const EFAC : Number = 0.2046; // E-field conversion factor: E_true = E_model*EFAC
    public static const VFAC : Number = 1.917E-3; // Voltage conversion factor: V_true = V_model*VFAC

    public var display : ChargesAndFieldsDisplay;

    public function ChargesAndFieldsApplication() {
           this.addEventListener(Event.ADDED_TO_STAGE, init);
    }

    public function init(evt : Event) : void {

display = new ChargesAndFieldsDisplay(stage);
            this.addChild(display);

            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            stage.addEventListener(Event.RESIZE, display.onResize);

    }
}
}

package edu.colorado.phet.chargesandfields {

public class ChargesAndFieldsDisplay extends Sprite {
    private var myWidth : Number;
    private var myHeight : Number;

    public var model : Model;

    private var background : BackgroundSprite;
    private var mosaic : VoltageMosaic;
    private var charges : Array = new Array();

    public function ChargesAndFieldsDisplay(tempStage : Stage) {
        myWidth = tempStage.stageWidth;
        myHeight = tempStage.stageHeight;

        model = new Model();

        background = new BackgroundSprite(myWidth, myHeight);
        addChild(background);

        mosaic = new VoltageMosaic(model, myWidth, myHeight);
        addChild(mosaic);

        for(var i : uint = 0; i < 20; i++) {
            var charge : Charge;
            if(i % 2 == 0) {
                charge = new MinusCharge(mosaic);
            } else {
                charge = new PlusCharge(mosaic);
            }
            addChild(charge);
            charges.push(charge);
            model.addCharge(charge);
            charge.setDisplayPosition(Math.random() * myWidth, Math.random() * myHeight);
        }

        mosaic.draw();

        var txt1 : TextField = new TextField();
        txt1.text = "foobar";
        txt1.alpha = 50;
        addChild(txt1);

        var txt2 : TextField = new TextField();
        txt2.text = "foobar";
        txt2.alpha = 100;
        txt2.x = 100;
        addChild(txt2);
    }

    public function onResize(evt : Event) : void {
        myWidth = this.stage.stageWidth;
        myHeight = this.stage.stageHeight;
        background.changeSize(myWidth, myHeight);
        mosaic.changeSize(myWidth, myHeight);
    }
}
}

package edu.colorado.phet.chargesandfields {

public class MinusCharge extends Charge {
    [Embed(source='assets.swf', symbol='minusCharge_mc')]
    public static var minusMC : Class;

    public function MinusCharge(mosaic : VoltageMosaic) {

        // create a child sprite containing the graphics
        var mc : Sprite = new minusMC();
        addChild(mc);

        q = -1;

        super(mosaic);
    }
}

}

package edu.colorado.phet.chargesandfields {

public class Model {

    // named ChargeGroup.as in the Flash version

    private static const k : Number = 0.5*1E6; // prefactor in E-field equation: E= k*Q/r^2
    private static const RtoD : Number = 180/Math.PI; //convert radians to degrees

    public var chargeArray : Array;
    public var sensorArray : Array;

    public function Model() {

        chargeArray = new Array();
        sensorArray = new Array();

    }

    public function addCharge(charge : Charge) : void {
        chargeArray.push(charge);
        // TODO: setChanged or notifyObservers?
    }

    /*
    public function addSensor(sensor : Sensor) {
        // TODO: lots of stuff
    }
     */

    public function removeCharge(charge : Charge) : Boolean {

        // pull out removal from array into helper function somewhere
        var len : uint = chargeArray.length;
        for(var i : uint = 0; i < chargeArray.length; i++) {
            if(charge == chargeArray[i]) {
                chargeArray.splice(i, 1);
                // TODO: setChanged or notifyObservers?

                return true;
            }
        }

        return false;
    }

    public function hasCharges() : Boolean {
        return chargeArray.length != 0;
    }

    /*
    public function removeSensor(sensor : Sensor) {
        // TODO: implement
    }
     */

    public function getE(x : Number, y : Number) : Array {

        // TODO: optimize function for AS3

		var eMag : Number; //Magnitude of E-field
		var eAng : Number; //Angle of E-field
		var len : uint = chargeArray.length;
		var sumX : Number = 0;
		var sumY : Number = 0;
        
		for(var i : uint = 0; i < len; i++) {
			var xi : Number = chargeArray[i].modelX;
			var yi : Number = chargeArray[i].modelY;
			var distSq : Number = (x - xi)*(x - xi) + (y - yi)*(y - yi)
			var distPow : Number = Math.pow(distSq, 1.5);
			sumX = sumX + chargeArray[i].q*(x - xi)/distPow;
			sumY = sumY + chargeArray[i].q*(y - yi)/distPow;
		}
		var EX : Number = k*sumX;	//prefactor depends on units
		var EY : Number = k*sumY;

		eMag = Math.sqrt(EX*EX+EY*EY);
		eAng = RtoD*Math.atan2(EY, EX);
		//trace("  sumX:  "+sumX+"  sumY:  "+sumY+"   sumY/sumX:  "+sumY/sumX+"   angel: "+EAng);

        // TODO: turn this returny goodness into a class
		return [eMag, eAng, EX, EY];
	}

    private function combineRGB(red : Number, green : Number, blue : Number) : uint {
        return (red << 16) | (green << 8) | blue;
    }

    //returns voltage and color nbr (RGB values) associated with voltage
	public function getV(x : Number, y : Number) : Array{
		var len : uint = chargeArray.length;
		var sumV : Number = 0;
		var maxV : Number = 20000;//voltage at which color will saturate
		var red : Number;
		var green : Number;
		var blue : Number;
		var colorNbr : uint;  	//RGB number of color associated with voltage

        var xi : Number;
        var yi : Number;
        var dist : Number;

		for(var i : uint = 0; i < len ; i++){
            var charge : Charge = chargeArray[i];
			xi = charge.modelX;
			yi = charge.modelY;
            
			dist = Math.sqrt((x - xi)*(x - xi) + (y - yi)*(y - yi));
			sumV = sumV + charge.q/dist;
		}
		sumV = k*sumV;	//prefactor depends on units

		//set color associated with voltage
        if(sumV>0){
            red =255;
            green = blue = Math.max(0,(1-(sumV/maxV))*255);
        }else{
            blue = 255;
            red = green = Math.max(0,(1-(-sumV/maxV))*255);
        }
		colorNbr = combineRGB(red,green,blue);
        
		return [sumV,colorNbr];
	}

    public function getEX(x : Number, y : Number) : Number{
		var sum : Number = 0;
		for(var i : uint = 0; i < chargeArray.length ; i++){
			var xi : Number = chargeArray[i].modelX;
			var yi : Number = chargeArray[i].modelY;
			var distSq : Number = (x - xi)*(x - xi) + (y - yi)*(y - yi);
			sum = sum + chargeArray[i].q*(x - xi)/Math.pow(distSq,1.5);
		}
		sum = k*sum;	//prefactor depends on units
		return sum;
	}
	//return angle of E-field at position (x,y)
	public function getEY(x:Number, y:Number):Number{
		var sum : Number = 0;
		for(var i : uint = 0; i < chargeArray.length ; i++){
			var xi : Number = chargeArray[i].modelX;
			var yi : Number = chargeArray[i].modelY;
			var distSq : Number = (x - xi)*(x - xi) + (y - yi)*(y - yi);
			sum = sum + chargeArray[i].q*(y - yi)/Math.pow(distSq,1.5);
		}
		sum = k*sum;	//prefactor depends on units
		return sum;
	}

    //starting at (xInit, yInit), find final position distance delS along equipotential
	public function getMoveToSameVPos(VInit:Number, delS:Number, xInit:Number, yInit:Number):Array{
		var E0_array : Array = this.getE(xInit,yInit);  //E_array = [EMag, EAng, EX, EY]
		//var VInit = getV(xInit, yInit)[0];	//getV(x,y) returns [V,color]
		var EInit : Number = E0_array[0];
		//var EAngInit = E0_array[1];
		var EXInit : Number = E0_array[2];
		var EYInit : Number = E0_array[3];
		var xMid : Number = xInit - delS*EYInit/EInit;
		var yMid : Number = yInit + delS*EXInit/EInit;
		var E1_array : Array = this.getE(xMid,yMid);  //E_array = [EMag, EAng, EX, EY]
		var VMid : Number = this.getV(xMid, yMid)[0];  //returns [voltage:Number, colorNbr:Number]
		var EMid : Number = E1_array[0];
		//var EAngPost = E1_array[1];
		var EXMid : Number = E1_array[2];
		var EYMid : Number = E1_array[3];
		var delX : Number = (VMid - VInit)*EXMid/(EMid*EMid);
		var xFinal : Number = xMid + delX;
		var yFinal : Number = yMid + delX*EYMid/EXMid;
		return [xFinal,yFinal];
	}

    //starting at (xInit,yInit) move along E-field direction and get (x,y) position at which voltage is targetV
	//this function unused at present
	public function getTargetVPos(targetV:Number,xInit:Number,yInit:Number):Array{
		var E_array : Array = this.getE(xInit,yInit);  //E_array = [EMag, EAng, EX, EY]
		var VInit : Number = this.getV(xInit, yInit)[0];  //returns [voltage:Number, colorNbr:Number]
		var EInit : Number = E_array[0];
		// UNUSED!!! var EAngPost : Number = E_array[1];
		var EXInit : Number = E_array[2];
		var EYInit : Number = E_array[3];
		var delX : Number = (VInit - targetV)*EXInit/(EInit*EInit);
		var xFinal : Number = xInit + delX;
		var yFinal : Number = yInit + delX*EYInit/EXInit ;
		//var delEAng = Math.abs(EAngPost-EAngPre);
		//delEAng = Math.min(delEAng, 360-delEAng);
		//trace("delEAng: "+delEAng);
		return [xFinal,yFinal];
	}//end of getTargetVPos

    // TODO: event handlers, etc

}
}

package edu.colorado.phet.chargesandfields {

public class PlusCharge extends Charge {
    [Embed(source='assets.swf', symbol='plusCharge_mc')]
    public static var plusMC : Class;

    public function PlusCharge(mosaic : VoltageMosaic) {

        // create a child sprite containing the graphics
        var mc : Sprite = new plusMC();
        addChild(mc);

        q = 1;

        super(mosaic);
    }
}

}

package edu.colorado.phet.chargesandfields {

public class VoltageMosaic extends Sprite {
    private var myWidth : Number;
    private var myHeight : Number;

    private var model : Model;

    public function VoltageMosaic(model : Model, w : Number, h : Number) {
        this.model = model;
        myWidth = w;
        myHeight = h;

        draw();
    }

    public function changeSize(w : Number, h : Number) : void {
        myWidth = w;
        myHeight = h;
        draw();
    }

    public function draw() : void {
        this.graphics.clear();

        var step : Number = 10;

        for(var ox : Number = 0; ox < myWidth; ox += step) {
            for(var oy : Number = 0; oy < myHeight; oy += step) {
                var color : uint = model.getV(ox + step / 2, oy + step / 2)[1];
                this.graphics.beginFill(color);
                this.graphics.drawRect(ox, oy, ox + step, oy + step);
                this.graphics.endFill();
            }
        }
    }
}
}