Initial commit.
Initial commit.

file:b/AssemblyInfo.cs (new)
--- /dev/null
+++ b/AssemblyInfo.cs
@@ -1,1 +1,28 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
 
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("galaxy_generator")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("andy")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
+

--- /dev/null
+++ b/CartesianCoords.cs
@@ -1,1 +1,65 @@
+using System;
 
+namespace GalaxyGenerator
+{
+	public class CartesianCoords
+	{
+		public CartesianCoords(double x, double y, double z)
+		{
+			CartesianTuple.p1 = x;
+			CartesianTuple.p2 = y;
+			CartesianTuple.p3 = z;
+		}
+		public CartesianCoords(Tuple myTuple) : this(myTuple.p1, myTuple.p2, myTuple.p3) { }
+		
+		public void setTuple(double x, double y, double z)
+		{
+			CartesianTuple.p1 = x;
+			CartesianTuple.p2 = y;
+			CartesianTuple.p3 = z;
+		}
+		public void setTuple(Tuple myTuple)
+		{
+			setTuple(myTuple.p1, myTuple.p2, myTuple.p3);
+		}
+		
+		public void setX(double x)
+		{
+			CartesianTuple.p1 = x;
+		}
+		
+		public void setY(double y)
+		{
+			CartesianTuple.p2 = y;
+		}
+		
+		public void setZ(double z)
+		{
+			CartesianTuple.p3 = z;
+		}
+		
+		public Tuple getTuple()
+		{
+			return CartesianTuple;
+		}
+		
+		public double getX()
+		{
+			return CartesianTuple.p1;
+		}
+		
+		public double getY()
+		{
+			return CartesianTuple.p2;
+		}
+		
+		public double getZ()
+		{
+			return CartesianTuple.p3;
+		}
+		
+		private Tuple CartesianTuple;
+	}
+}
+
+

--- /dev/null
+++ b/GalacticallyUniqueIdentifier.cs
@@ -1,1 +1,20 @@
+using System;
 
+namespace GalaxyGenerator
+{
+	public class GalacticallyUniqueIdentifier
+	{
+		public static UInt64 NewID()
+		{
+			return ++nextID;
+		}
+		
+		// Be careful!  This does not increment nextID and MUST not be used to assign IDs to elements!
+		public static UInt64 Peek()
+		{
+			return nextID;
+		}
+		
+		private static UInt64 nextID = 0;
+	}
+}

file:b/Galaxy.cs (new)
--- /dev/null
+++ b/Galaxy.cs
@@ -1,1 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
 
+namespace GalaxyGenerator
+{
+	public class Galaxy
+	{
+		public Galaxy ()
+		{
+			Stars = new Dictionary<UInt64, Star>();
+		}
+		
+		public void Populate()
+		{
+			Random rand = GlobalParameters.rand;
+						
+			uint NumStars = GlobalParameters.NumStars;
+			uint DiskRadius = GlobalParameters.DiskRadius;
+			uint HubRadius = GlobalParameters.HubRadius;
+			uint NumArms = GlobalParameters.NumArms;
+			double ArmRotations = GlobalParameters.ArmRotations;
+			double ArmWidth = GlobalParameters.ArmWidth;
+			double Fuzz = GlobalParameters.Fuzz;
+			
+			uint GalaxyRadius = HubRadius + DiskRadius;
+			
+			double TwoPi = 2d*Math.PI;
+			
+			double Omega;
+			if (NumArms != 0)
+			{
+				Omega = TwoPi / (double)NumArms;
+			}
+			else
+			{
+				Omega = 0d;
+			}
+			
+			for (int i = 1; i <= NumStars; ++i)
+			{
+				double Radius = rand.NextDouble() * rand.NextDouble() * GalaxyRadius;
+				double Phi, Theta;  // Rotational angles for some spherical coordinates.
+				double x, y, z;  // Cartesian coordinates, maybe?
+				
+				// If the radius puts the star outside the Hub (be fuzzy), we need to align it in an arm.
+				if (Radius > (HubRadius + rand.NextDouble() * ( rand.NextDouble() * 2 - 1 ) * ( DiskRadius - HubRadius)))
+				{
+					// This spiral algorithm was taken from the python Galaxy Generator found here: http://www.ailis.de/~k/archives/29-Galaxy-Generator.html
+					// Rotate the star by the specified number of rotations, then
+					// multiply by the proportional distance from center to give
+					// curvature.
+					Phi = ((TwoPi * (double)ArmRotations * Radius / (double)DiskRadius)
+					             // Then move the point further around by a random factor up to ArmWidth
+					             + rand.NextDouble() * (double)ArmWidth
+					             // Then add a factor of omega, putting the point into one of the arms.
+					             + Omega * (double)(int)rand.Next(0, (int)NumArms)
+					             // Then add some extra "fuzz".
+					             + (rand.NextDouble() * 2d - 1d) * (double)Fuzz);
+					
+					z = rand.NextDouble() * Math.Sin(Phi) * (double)HubRadius * Math.Pow((Radius / (double)GalaxyRadius),3);
+					Theta = Math.Acos (z / Radius);
+				}
+				// If the star is not in an arm, the distribution should be spherical.
+				else
+				{
+					Phi = rand.NextDouble() * TwoPi;
+					Theta = rand.NextDouble() * TwoPi;
+					z = Math.Cos(Theta) * Math.Sin(Phi) * Radius;
+				}
+				
+				// Find the x, y cartesians from r and phi.
+				x = Math.Cos(Phi) * Radius;
+				y = Math.Sin(Phi) * Radius;
+				
+				AddStar(new Star(x, y, z));
+			}
+		}
+		
+		public void AddStar(Star star,UInt64 id)
+		{
+			CartesianCoords starCoords = star.getCoords();
+			int maxCoord = (int) Math.Max (Math.Max (starCoords.getX (), starCoords.getY()), starCoords.getZ());
+			if (Extents < maxCoord)
+			{
+				Extents = maxCoord;
+			}
+			
+			Stars.Add(id, star);
+		}
+		public void AddStar(Star star)
+		{
+			star.setID(NewID());
+			AddStar(star, star.getID());
+		}
+		
+		public void RemoveStar(UInt64 id)
+		{
+			Stars.Remove(id);
+		}
+		
+		public int getExtents()
+		{
+			return Extents;
+		}
+		
+		public Dictionary<UInt64, Star> getStars()
+		{
+			return Stars;
+		}
+		
+		public void Save(FileStream stream)
+		{
+			stream.Write(BitConverter.GetBytes(SaveCode), 0, sizeof(UInt16));
+			foreach (Star star in Stars.Values)
+			{
+				star.Save(stream);
+			}
+		}
+		
+		public void Load(byte[] stream)
+		{
+			int offset = 0;
+			UInt16 ReadCode = BitConverter.ToUInt16(stream, offset);
+			if (ReadCode != SaveCode) { throw new Exception("File not a valid galaxy blob."); }
+			offset += sizeof(UInt16);
+			while (offset < stream.Length)
+			{
+				Star star = new Star();
+				Star.Load(stream, ref offset, ref star);
+				this.AddStar(star, star.getID());
+			}
+		}
+		
+		private UInt64 NewID()
+		{
+			return GalacticallyUniqueIdentifier.NewID();
+		}
+		
+		private Dictionary<UInt64, Star> Stars;
+		private int Extents = 0;
+		private static UInt16 SaveCode = GlobalParameters.SaveCodes["Galaxy"];
+	}
+}
+
+

--- /dev/null
+++ b/GlobalParameters.cs
@@ -1,1 +1,25 @@
+using System;
+using System.Collections.Generic;
 
+namespace GalaxyGenerator
+{
+	public static class GlobalParameters
+	{
+		public const int NumStars = 90000;
+		public const int DiskRadius = 20000;
+		public const int HubRadius = 10000;
+		public const int NumArms = 5;
+		public const double ArmRotations = 0.3;
+		public const double ArmWidth = 36 * Math.PI / 180.0;
+		public const double Fuzz = 20 * Math.PI / 180.0;
+		public const int PNGSize = 1080;
+		public const int PNGFrame = 5;
+		
+		public static Random rand = new Random();
+		public static Dictionary<string, UInt16> SaveCodes = new Dictionary<string, UInt16>()
+		{
+			{"Galaxy",	1},
+			{"Star",	2}
+		};
+	}
+}

file:b/Main.cs (new)
--- /dev/null
+++ b/Main.cs
@@ -1,1 +1,159 @@
+using System;
+using System.IO;
+using System.Windows.Forms;
+using NDesk.Options;
+using AWDesk.AWExceptionHandler;
 
+namespace GalaxyGenerator
+{
+	class MainClass
+	{
+		private static void Save(string FileName)
+		{
+			FileStream fs = new FileStream(FileName, FileMode.Create);
+			
+			TheGalaxy.Save(fs);
+		}
+		
+		private static void Load(string FileName)
+		{
+			TheGalaxy = new Galaxy();
+			
+			byte[] stream = File.ReadAllBytes(FileName);
+			TheGalaxy.Load(stream);
+		}
+		
+		private static void Generate()
+		{
+			TheGalaxy = new Galaxy();
+			TheGalaxy.Populate();
+		}
+		
+		private static void Render(string view, string prefix)
+		{
+			PngRenderer TheRenderer = new PngRenderer();
+			TheRenderer.Render(view, prefix, TheGalaxy);
+		}
+		
+		private static void ShowHelp()
+		{
+			Console.WriteLine("Usage: galaxy_generator OPTIONS");
+			Console.WriteLine("Generates or loads a galaxy which may be saved and/or rendered to an image.");
+			Console.WriteLine("Required options: <Load|Generate> <Save|Render>.\n");
+			Console.WriteLine("Options:");
+			TheOptions.WriteOptionDescriptions(Console.Out);
+		}
+		
+		private static void PrintError(string Message)
+		{
+			Console.Error.WriteLine("galaxy_generator: " + Message);
+		}
+		
+		public static void Main (string[] args)
+		{
+			Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
+			AppDomain currentDomain = AppDomain.CurrentDomain;
+			currentDomain.UnhandledException += new UnhandledExceptionEventHandler(AWExceptionHandler.UnhandledExceptionEventHandler);
+			Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(AWExceptionHandler.Application_ThreadException);
+			
+			try
+			{
+				TheOptions.Parse(args);
+			}
+			catch (OptionException ex)
+			{
+				PrintError(ex.Message + "\n");
+				ShowHelp();
+				return;
+			}
+			
+			if (doHelp)
+			{
+				ShowHelp();
+				return;
+			}
+			if (doGenerate && doLoad)
+			{
+				PrintError("The generate and load options must not be used simultaneously.");
+				ShowHelp();
+				return;
+			}
+			if (!(doGenerate || doLoad))
+			{
+				PrintError("You must either generate or load a galaxy to render.");
+				ShowHelp();
+				return;
+			}
+			if (doGenerate)
+			{
+				Generate();
+			}
+			if (doLoad)
+			{
+				try
+				{
+					Load(TheLoadFile);
+				}
+				catch (Exception ex)
+				{
+					PrintError(ex.Message);
+					return;
+				}
+			}
+			if (doSave)
+			{
+				try
+				{
+					Save (TheSaveFile);
+				}
+				catch (Exception ex)
+				{
+					PrintError(ex.Message);
+					return;
+				}
+			}
+			if (doRender)
+			{
+				Render(TheView, ThePrefix);
+			}
+		}
+		
+		private static bool doRender = false;
+		private static bool doSave = false;
+		private static bool doLoad = false;
+		private static bool doGenerate = false;
+		private static bool doHelp = false;
+		private static string TheView = "";
+		private static string ThePrefix = "galaxy_";
+		private static string TheLoadFile = "";
+		private static string TheSaveFile = "";
+		private static Galaxy TheGalaxy;
+		private static OptionSet TheOptions = new OptionSet() {
+			{
+				"g|generate", "Generates a new galaxy.  May not be used with load.",
+				v => { doGenerate = v != null; }
+			},
+			{
+				"l|load=", "Loads a galaxy from the given filename.  Maybe not be used with generate",
+				(string v) => { doLoad = v != null; TheLoadFile = v;}
+			},
+			{
+				"r|render=", "Renders the given view of a galaxy.  Valid views are \"top\" and \"side\".",
+				(string v) => { TheView = v; doRender = v != null; }
+			},
+			{
+				"s|save=", "Saves a galaxy to the given filename.",
+				(string v) => { doSave = v != null; TheSaveFile = v; }
+			},
+			{
+				"p|prefix=", "Prepends the given value to rendered image names.",
+				(string v) => { ThePrefix = v; }
+			},
+			{
+				"h|help", "Prints this help message.",
+				v => { doHelp = v != null; }
+			},
+		};
+	}
+}
+

file:b/PngRenderer.cs (new)
--- /dev/null
+++ b/PngRenderer.cs
@@ -1,1 +1,115 @@
+using System;
+using System.Drawing;
+using System.Collections.Generic;
 
+namespace GalaxyGenerator
+{
+	public class PngRenderer
+	{
+		public PngRenderer ()
+		{
+		}
+		
+		public void Render(string view, string prefix, Galaxy gal)
+		{
+			Dictionary<UInt64, Star> Stars = gal.getStars();
+			int Extents = gal.getExtents();
+			int PNGSize = GlobalParameters.PNGSize;
+			int PNGFrame = GlobalParameters.PNGFrame;
+			double Scale = (double)(PNGSize - PNGFrame * 2) / (double)(Extents * 2);
+			
+			Bitmap newImage = new Bitmap(PNGSize, PNGSize);
+			Graphics gfx = Graphics.FromImage((Image)newImage);
+			
+			gfx.FillRectangle(new SolidBrush(Color.Black), 0, 0, PNGSize, PNGSize);
+			
+			foreach (Star star in Stars.Values)
+			{
+				int p0, p1 = 0;
+				p0 = (int)star.getCoords().getX();
+				switch (view)
+				{
+				case "top":
+					p1 = (int)star.getCoords().getY();
+					break;
+				case "side":
+					p1 = (int)star.getCoords().getZ();
+					break;
+				default:
+					ThrowException(ERR_INVALID_VIEW);
+					break;
+				}
+				int pX = (int)(Scale * (double)p0 + (double)PNGSize / 2d);
+				int pY = (int)(Scale * (double)p1 + (double)PNGSize / 2d);
+				if (!(Math.Min(pX, pY) < 0 || Math.Max (pX, pY) >= PNGSize))
+				{
+					try
+					{
+						newImage.SetPixel(pX, pY,star.getColor());
+					}
+					catch
+					{
+						Console.WriteLine("We found a problem!  Point {0}, {1} scales to {2}, {3}", p0, p1, pX,pY);
+						Console.WriteLine("PNGSize: {0}; Scale: {1}", PNGSize, Scale);
+						
+					}
+				}
+			}
+			
+			string filename = string.Empty;
+			switch(view)
+			{
+			case "top":
+				filename = prefix + "top.png";
+				break;
+			case "side":
+				filename = prefix + "side.png";
+				break;
+			default:
+				ThrowException(ERR_INVALID_VIEW);
+				break;
+			}
+			
+			newImage.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
+		}
+		
+		private int FindExtents(List<int[]> TwoDCoords)
+		{
+			int max = 0;
+			TwoDCoords.ForEach(delegate(int[] ThisPair)
+			                  {
+				if (max < Math.Max(ThisPair[0], ThisPair[1]))
+				{
+					max = Math.Max (ThisPair[0], ThisPair[1]);
+				}
+			});
+			
+			return max;
+		}
+		
+		private void ThrowException(int errorcode, string message)
+		{
+			Console.WriteLine(message);
+			switch(errorcode)
+			{
+			case ERR_INVALID_VIEW:
+				throw new Exception("Tried to call Render for an unknown view type.  Valid views are 'top' and 'side'.");
+				break;
+			case ERR_OUT_OF_BOUND:
+				throw new Exception();
+				break;
+			default:
+				throw new Exception("PngRenderer has suffered from an unknown error.  Help!");
+			}
+		}
+		private void ThrowException(int errorcode)
+		{
+			ThrowException(errorcode, "");
+		}
+		
+		private const int ERR_INVALID_VIEW = 4;
+		private const int ERR_OUT_OF_BOUND = 8;
+}
+}
+
+

file:b/Star.cs (new)
--- /dev/null
+++ b/Star.cs
@@ -1,1 +1,106 @@
+using System;
+using System.IO;
+using System.Drawing;
 
+namespace GalaxyGenerator
+{
+	public class Star
+	{
+		public Star(double x, double y, double z)
+		{
+			Random rand = GlobalParameters.rand;
+			
+			coords = new CartesianCoords(x, y, z);
+			
+			if (rand.Next(0, 1) == 0) {
+				ColorIndex = (uint)rand.Next(0, ValidColors.Length - 1);
+			}
+			else
+			{
+				ColorIndex = 0;
+			}
+		}
+		public Star(Tuple myTuple) : this(myTuple.p1, myTuple.p2, myTuple.p3) {}
+		public Star(CartesianCoords myCoords) : this(myCoords.getTuple()) {}
+		public Star() : this(0,0,0) {}
+		
+		public CartesianCoords getCoords()
+		{
+			return coords;
+		}
+		
+		public void setCoords(double x, double y, double z)
+		{
+			coords.setX(x);
+			coords.setY(y);
+			coords.setZ(z);
+		}
+		public void setCoords(Tuple myTuple)
+		{
+			setCoords (myTuple.p1, myTuple.p2, myTuple.p3);
+		}
+		public void setCoords(CartesianCoords myCoords)
+		{
+			setCoords(myCoords.getTuple());
+		}
+		
+		public UInt64 getID()
+		{
+			return id;
+		}
+		
+		public void setID(UInt64 newID)
+		{
+			id = newID;
+		}
+		
+		public Color getColor()
+		{
+			return ValidColors[ColorIndex];
+		}
+		
+		public void setColorIndex(uint NewIndex)
+		{
+			ColorIndex = NewIndex;
+		}
+		
+		public void Save(FileStream stream)
+		{
+			stream.Write(BitConverter.GetBytes(SaveCode), 0, sizeof(UInt16));
+			stream.Write(BitConverter.GetBytes(id), 0, sizeof(UInt64));
+			stream.Write(BitConverter.GetBytes(coords.getX()), 0, sizeof(double));
+			stream.Write(BitConverter.GetBytes(coords.getY()), 0, sizeof(double));
+			stream.Write(BitConverter.GetBytes(coords.getZ()), 0, sizeof(double));
+			stream.Write(BitConverter.GetBytes(ColorIndex), 0, sizeof(uint));
+		}
+		
+		public static void Load(byte[] stream, ref int offset, ref Star star)
+		{
+			UInt16 ReadCode = BitConverter.ToUInt16(stream, offset);
+			if (ReadCode != SaveCode) { throw new Exception("we can't handle anything other than stars yet"); }
+			offset += sizeof(UInt16);
+			UInt64 ReadID = BitConverter.ToUInt64(stream, offset);
+			offset += sizeof(UInt64);
+			double ReadX = BitConverter.ToDouble(stream, offset);
+			offset += sizeof(double);
+			double ReadY = BitConverter.ToDouble(stream, offset);
+			offset += sizeof(double);
+			double ReadZ = BitConverter.ToDouble(stream, offset);
+			offset += sizeof(double);
+			uint ReadColorIndex = BitConverter.ToUInt32(stream, offset);
+			offset += sizeof(uint);
+			
+			star.setID(ReadID);
+			star.setCoords(ReadX, ReadY, ReadZ);
+			star.setColorIndex(ReadColorIndex);
+		}
+		
+		private CartesianCoords coords = null;
+		private UInt64 id;
+		private uint ColorIndex;
+		private static UInt16 SaveCode = GlobalParameters.SaveCodes["Star"];
+		private static Color[] ValidColors = new Color[] {Color.White, Color.Red, Color.Yellow, Color.Orange, Color.OrangeRed, Color.Blue, Color.Violet, Color.BlueViolet, Color.LightBlue, Color.Green, Color.YellowGreen};
+	}
+}
+
+

file:b/Tuple.cs (new)
--- /dev/null
+++ b/Tuple.cs
@@ -1,1 +1,18 @@
+using System;
 
+namespace GalaxyGenerator
+{
+	public struct Tuple
+	{
+		public double p1, p2, p3;
+		
+		public Tuple(double i1, double i2, double i3)
+		{
+			p1 = i1;
+			p2 = i2;
+			p3 = i3;
+		}
+	}
+}
+
+