﻿package{
	import flash.display.Sprite;
	import flash.geom.Vector3D;	
	import com.physicscodes.math.Vector3DX;
	import com.physicscodes.motion.Forces3DX;	
	import com.physicscodes.objects.Ball;
	import flash.events.Event;
	import com.physicscodes.constants.*;
	//import flash.geom.Matrix3D;		
		
	public class SolarSystemBasic extends Sprite{
		// time-keeping variables
		private var _dt:Number = 0.041666666666666667; // simulation time unit is 1 day; time-step is 1 hr; this is 1/24  
		private var _numSteps:Number = 8760; // 1 year; 365*24 
		private var _animFreq:Number = 168; // once per week; 24*7
		private var _t:Number = 0;

		// gravitational constant
		private var _G:Number;

		// sun variables 
		private var _center:Vector3DX;
		private var _massSun:Number;
		private var _radiusSun:Number=30;

		// velocity and position vectors for all planets
		private var _v:Vector.<Vector3DX>;	
		private var _s:Vector.<Vector3DX>;

		// visual objects
		private var _sprite:Sprite;
		private var _sun:Ball;				
		private var _planets:Vector.<Ball>;
		private var _numPlanets:uint=4;		
		
		// planets' properties
		private var _colors:Array;				
		private var _radiuses:Array;		
		private var _masses:Array;
		private var _distances:Array;		
		private var _velos:Array;					
		
		// scaling factors
		private var _scaleTime:Number;
		private var _scaleDist:Number;
		private var _scaleMass:Number;
		private var _scaleVelo:Number;
		
		public function SolarSystemBasic():void{
			init();
		}

		private function init():void{
			_sprite = new Sprite();
			addChild(_sprite);
			setupScaling();
			setupPlanetData();
			setInitialConditions();
			setupObjects();
			simulate();
		}
		
		private function setupScaling():void{
			_scaleMass = Astro.EARTH_MASS;			
			_scaleTime = Astro.EARTH_DAY;
			_scaleDist = 1e9; // 1 million km or 1 billion meters
			_scaleVelo = _scaleDist/_scaleTime; // million km per day

			_massSun = Astro.SUN_MASS/_scaleMass;

			var G:Number = Phys.GRAVITATIONAL_CONSTANT;
			_G = _scaleMass*_scaleTime*_scaleTime*G/(_scaleDist*_scaleDist*_scaleDist);
		}
		
		private function setupPlanetData():void{
			_radiuses = [1.9, 4.7, 5, 2.7];
			_colors = [0xffffcc, 0xffcc00, 0x0099ff, 0xff6600];
			
			_masses = new Array();
			_distances = new Array();
			_velos = new Array();
			
			_masses[0] = Astro.MERCURY_MASS/_scaleMass;			
			_masses[1] = Astro.VENUS_MASS/_scaleMass;			
			_masses[2] = Astro.EARTH_MASS/_scaleMass;			
			_masses[3] = Astro.MARS_MASS/_scaleMass;		
			
			_distances[0] = Astro.MERCURY_ORBITAL_RADIUS/_scaleDist;			
			_distances[1] = Astro.VENUS_ORBITAL_RADIUS/_scaleDist;			
			_distances[2] = Astro.EARTH_ORBITAL_RADIUS/_scaleDist;			
			_distances[3] = Astro.MARS_ORBITAL_RADIUS/_scaleDist;			
			
			_velos[0] = Astro.MERCURY_ORBITAL_VELOCITY/_scaleVelo;			
			_velos[1] = Astro.VENUS_ORBITAL_VELOCITY/_scaleVelo;			
			_velos[2] = Astro.EARTH_ORBITAL_VELOCITY/_scaleVelo;			
			_velos[3] = Astro.MARS_ORBITAL_VELOCITY/_scaleVelo;			
		}
		
		private function setInitialConditions():void{
			_center = new Vector3DX(400,300,0);

			_s = new Vector.<Vector3DX>();						
			_s[0] = new Vector3DX(400+_distances[0],300,0);
			_s[1] = new Vector3DX(400+_distances[1],300,0);		
			_s[2] = new Vector3DX(400+_distances[2],300,0);			
			_s[3] = new Vector3DX(400+_distances[3],300,0);			
			
			_v = new Vector.<Vector3DX>();			
			_v[0] = new Vector3DX(0,_velos[0],0);
			_v[1] = new Vector3DX(0,_velos[1],0);
			_v[2] = new Vector3DX(0,_velos[2],0);
			_v[3] = new Vector3DX(0,_velos[3],0);
		}
		
		private function setupObjects():void{
			// sun
			_sun = new Ball(_radiusSun,0xffff00);
			_sun.pos = _center;
			_sprite.addChild(_sun);
			
			// planets
			_planets = new Vector.<Ball>();
			for (var n:uint=0; n<_numPlanets; n++){
				var planet:Ball = new Ball(_radiuses[n],_colors[n],_masses[n]);
				planet.pos = _s[n];
				planet.velo = _v[n];
				_sprite.addChild(planet);
				_planets.push(planet);
			}
		}
		
		private function simulate():void{
			for (var i:uint=0; i<_numSteps; i++){
				_t += _dt; 
				for (var n:uint=0; n<_numPlanets; n++){
					RK4(n);
					if (i%_animFreq==0){
						animate(n);
					}
				}
			}
		}
		
		private function animate(n:uint):void{
			_planets[n].pos = _s[n];
			clonePlanet(n);
			//trace(_s[n]);
		}		

		private function clonePlanet(n:uint):void{
			var pcopy:Ball = _planets[n].clone();
			pcopy.pos = _planets[n].pos;
			_sprite.addChild(pcopy);			
		}		
		
		private function getAcc(ppos:Vector3DX,pvel:Vector3DX,pn:uint):Vector3DX{
			var massPlanet:Number = _planets[pn].mass;
			var r:Vector3DX = Vector3DX.convert(ppos.subtract(_center));
			// force exerted by sun
			var force:Vector3DX = Forces3DX.gravity(_G,_massSun,massPlanet,r);
			// forces exerted by other planets
			for (var n:uint=0; n<_numPlanets; n++){
				if (n!=pn){ // exclude the current planet itself!
					r = Vector3DX.convert(ppos.subtract(_s[n]));
					var gravity:Vector3DX = Forces3DX.gravity(_G,_masses[n],massPlanet,r);;
					force = Forces3DX.add([force, gravity]);
				}
			}
			// acceleration
			return force.multiply(1/massPlanet);
		}		
		
		private function RK4(n:uint):void{			
			// step 1
			var pos1:Vector3DX = _s[n];
			var vel1:Vector3DX = _v[n];
			var acc1:Vector3DX = getAcc(pos1,vel1,n); 
			// step 2
			var pos2:Vector3DX = pos1.addScaled(vel1,_dt/2); 
			var vel2:Vector3DX = vel1.addScaled(acc1,_dt/2);
			var acc2:Vector3DX = getAcc(pos2,vel2,n); 
			// step 3
			var pos3:Vector3DX = pos1.addScaled(vel2,_dt/2); 
			var vel3:Vector3DX = vel1.addScaled(acc2,_dt/2);
			var acc3:Vector3DX = getAcc(pos3,vel3,n); 
			// step 4
			var pos4:Vector3DX = pos1.addScaled(vel3,_dt); 
			var vel4:Vector3DX = vel1.addScaled(acc3,_dt);
			var acc4:Vector3DX = getAcc(pos4,vel4,n); 
			// sum vel and acc
			var velsum:Vector3DX = vel1.addScaled(vel2,2).addScaled(vel3,2).addScaled(vel4,1);
			var accsum:Vector3DX = acc1.addScaled(acc2,2).addScaled(acc3,2).addScaled(acc4,1);
			//trace(_dt);
			// update pos and velo
			_s[n] = pos1.addScaled(velsum,_dt/6);
			_v[n] = vel1.addScaled(accsum,_dt/6);			
		}
		
	}
}
