// quantumfractals.cs - Port of eeqt to Qt# // Author: Adam Treat // (c) 2002 Adam Treat // Licensed under the terms of the GNU GPL namespace Qf { using Qt; using System; using System.Threading; public class FractalViewer : QMainWindow { //Menuing private QMenuBar menubar; private QPopupMenu filemenu; private QPopupMenu shapemenu; private QPopupMenu settingsmenu; public static int Main (string[] args) { //Initialize and start the main event loop QApplication app = new QApplication (args); FractalViewer view = new FractalViewer (); app.SetMainWidget (view); view.Show (); return app.Exec (); } public FractalViewer (): base (null, "main") { SetCaption ("Quantum Fractals"); //Setup the display Display display = new Display (this); SetCentralWidget (display); //Setup the filemenu filemenu = new QPopupMenu (null, "filemenu"); filemenu.InsertItem ("&Screenshot", display, SLOT ("SlotScreenshot()")); filemenu.InsertSeparator (); filemenu.InsertItem ("&Quit", qApp, SLOT ("quit()")); //Setup the shapemenu shapemenu = new QPopupMenu (null, "typemenu"); shapemenu.InsertItem( "&Tetrahedron", 0); shapemenu.InsertItem( "&Cube", 1); shapemenu.InsertItem( "&Octahedron", 2); shapemenu.InsertItem( "&Icosahedron", 3); shapemenu.InsertItem( "&Dodecahedron", 4); shapemenu.InsertItem( "&Double Tetrahedron", 5); shapemenu.InsertItem( "&Icosidodecahedron", 6); //Connect the shapemenu QObject.Connect (shapemenu, SIGNAL ("activated(int)"), display, SLOT("SlotShapeMenu(int)")); //Setup the settingsmenu settingsmenu = new QPopupMenu (null, "settingsmenu"); settingsmenu.InsertItem ("&Alpha", display, SLOT ("SlotSetAlpha()")); //Setup the menubar menubar = new QMenuBar (this, ""); menubar.InsertItem ("&File", filemenu); menubar.InsertItem ("&Shape", shapemenu); menubar.InsertItem ("&Settings", settingsmenu); } } public class Display: QWidget, IQuantumFractal { //Labels QLabel count; QLabel shape; QLabel alpha; //Buttons QPushButton start; QPushButton stop; QPushButton reset; QPushButton gray; QPushButton intense; //Drawable region QPaintBuffer buffer; //Layouts QVBoxLayout layout; QHBoxLayout buttons; QVBoxLayout labels; //Engine controller variables int[] topDensity = new int[0]; int[] bottomDensity = new int[0]; int resolution = 400; int scale = 1; double centerX = 0; double centerY = 0; int i = 0; bool Grayscale = true; bool Intense = false; bool Running = false; bool WasRunning = false; //The engine QuantumFractals qf; Thread engine; public Display (QWidget parent): base (parent) { //Setup the sizes QSize size = new QSize (resolution, resolution); parent.SetBaseSize (size); //Some nice colors SetPaletteBackgroundColor (new QColor ("Black")); SetPaletteForegroundColor (new QColor ("LightBlue")); //Setup the buttons start = new QPushButton ("Start", this); stop = new QPushButton ("Stop", this); reset = new QPushButton ("Reset", this); gray = new QPushButton ("Color", this); intense = new QPushButton ("Intensity", this); //Setup the labels count = new QLabel (this); alpha = new QLabel (this); shape = new QLabel (this); //Setup the drawable buffer = new QPaintBuffer (this); buffer.SetMinimumSize (size); //Create the layouts layout = new QVBoxLayout (this); buttons = new QHBoxLayout (layout); //Add some buttons buttons.AddWidget (start); buttons.AddWidget (stop); buttons.AddWidget (reset); buttons.AddWidget (gray); buttons.AddWidget (intense); //Connect the buttons and SlotQuit QObject.Connect (start, SIGNAL ("clicked()"), this, SLOT ("SlotStart()")); QObject.Connect (stop, SIGNAL ("clicked()"), this, SLOT ("SlotStop()")); QObject.Connect (reset, SIGNAL ("clicked()"), this, SLOT ("SlotReset()")); QObject.Connect (gray, SIGNAL ("clicked()"), this, SLOT ("SlotGray()")); QObject.Connect (intense, SIGNAL ("clicked()"), this, SLOT ("SlotIntense()")); QObject.Connect (buffer, SIGNAL ("Painted()"), this, SLOT ("SlotSetLabels()")); QObject.Connect (qApp, SIGNAL ("lastWindowClosed ()"), this, SLOT ("SlotQuit ()")); //Layout labels labels = new QVBoxLayout (layout); labels.AddWidget (count); labels.AddWidget (shape); labels.AddWidget (alpha); //Layout buffer layout.AddWidget (buffer, 1); //Finally create the data engine qf = new QuantumFractals (this); //Handle resize events resizeEvent += new ResizeEvent (TouchResize); } //This is where the controller receives data from the engine public void UpdateData (double[] d) { i++; //Keep track of the number of points //Set the density arrays to match the resolution if (resolution * resolution != topDensity.Length) { topDensity = new int[resolution * resolution]; bottomDensity = new int[resolution * resolution]; } //setup the sphere int res = resolution; int res2 = res / 2; int x = res / 2 + (int)(res2 * scale * (d[0] - centerX)); int y = res / 2 + (int)(res2 * scale * (d[1] - centerY)); double z = d[2]; if ((x < res) && (x >= 0) && (y >= 0) && (y < res)) { if (z >= 0) topDensity[y * resolution + x]++; else bottomDensity[y * resolution + x]++; } //Convert the density into a color int top = topDensity[y * resolution + x]; //int bot = bottomDensity[y * resolution + x]; top = Math.Min (top, 255); //bot = Math.Min (bot, 255); //Log color system not working well :( if (Intense) { top = (int)(Math.Log (top + 1)); //bot = (int)(Math.Log (bot + 1)); } int topdepth = RGB (top,top,top); //int botdepth = RGB (bot,bot,bot); //Finally draw the pixel SetPixel (x, y, topdepth); //SetPixel (x, y, botdepth); } //Calls the drawable public void SetPixel (int x, int y, int depth) { buffer.PaintPixel (x, y, depth); } //Convert the color into a depth public int RGB (int r, int g, int b) { if (!Grayscale) { r = Intensity (r < 128 ? 128 - r : 0); g = Intensity (128 - Math.Abs (g - 128)); b = Intensity (b < 128 ? 0 : b - 128); } else { r = Intensity (r); g = Intensity (g); b = Intensity (b); } //Console.WriteLine ("{0} {1} {2}", r,g,b); return 256 * 256 * r + 256 * g + b; } //This provides more detail private int Intensity(int val) { int ret; double bases = 64; double scale = 256.0 / (256.0 - bases); ret = (int)(bases + ((double)val) / scale); //if gray then black, if color then white if (val == 0 && Grayscale) ret = 0; else if (val == 0) ret = 255; return ret; } //Draw the labels private void SlotSetLabels () { count.SetText ("Count: " + i.ToString ()); shape.SetText ("Shape: " + qf.GetPolytope ()); alpha.SetText ("Alpha: " + qf.Alpha.ToString ()); } //Start the engine private void SlotStart () { engine = new Thread(new ThreadStart(qf.Start)); engine.Start (); Running = true; } //Stop the engine private void SlotStop () { if (engine != null) if (engine.IsAlive) engine.Abort (); Running = false; } //Reset everything private void SlotReset () { SlotStop (); ResetBuffer (); SlotStart (); } //Reset the drawable private void ResetBuffer () { i = 0; SlotSetLabels (); topDensity = new int[0]; bottomDensity = new int[0]; buffer.Reset (); } //Toggles the color scheme private void SlotGray () { Grayscale = !Grayscale; } //Toggles log color scheme //Not working so well :( private void SlotIntense () { Intense = !Intense; } //Change the platonic shape private void SlotShapeMenu (int item) { WasRunning = Running ? true : false; SlotStop (); ResetBuffer (); switch(item) { case 0: qf.SetPolytope (0); break; case 1: qf.SetPolytope (1); break; case 2: qf.SetPolytope (2); break; case 3: qf.SetPolytope (3); break; case 4: qf.SetPolytope (4); break; case 5: qf.SetPolytope (5); break; case 6: qf.SetPolytope (6); break; Default: qf.SetPolytope (0); break; } if (WasRunning) SlotStart (); } //Save the drawable as a screenshot private void SlotScreenshot () { WasRunning = Running ? true : false; SlotStop (); string filename = QFileDialog.GetSaveFileName ( QDir.HomeDirPath (), "*", this, "save", "Save Screenshot", "*.png", true ); if (filename != null) buffer.Save (filename); if (WasRunning) SlotStart (); } //Set the alpha engine variable private void SlotSetAlpha () { WasRunning = Running ? true : false; SlotStop (); qf.Alpha = QInputDialog.GetDouble ( "Set Alpha", "Alpha: ", qf.Alpha, 0, 2, 32 ); if (WasRunning) SlotStart (); else SlotSetLabels (); } //Make sure to quit all threads upon exit private void SlotQuit () { SlotStop (); buffer.Stop (); } //Need to reset the resolution upon resize private void TouchResize (QResizeEvent e) { int height = buffer.Size ().Height (); int width = buffer.Size ().Width (); resolution = height > width ? width : height; } } [DeclareQtSignal ("Painted()")] public class QPaintBuffer : QFrame { //Drawables private QPixmap buffer; private QImage image; //Timer private TimerCallback call; private Timer timer; public QPaintBuffer (QWidget parent) : base (parent) { SetBackgroundMode (Qt.BackgroundMode.NoBackground); //Create drawables buffer = new QPixmap (); image = new QImage (Size (), 32); //Setup the event handlers paintEvent += new PaintEvent (TouchPaint); resizeEvent += new ResizeEvent (TouchResize); focusInEvent += new FocusInEvent (TouchFocus); focusOutEvent += new FocusOutEvent (TouchFocus); //Start the timer call = new TimerCallback(PaintImage); timer = new Timer(call, null, 1000, 1000); } //Resets the drawables public void Reset () { buffer = new QPixmap (); image = new QImage (Size (), 32); PaintImage (null); } //Paints a pixel to the image public void PaintPixel (int x, int y, int depth) { lock (this) { if (x < image.Width () && y < image.Height ()) image.SetPixel (x, y, (uint)depth); } } //Saves the image to a file public void Save (string filename) { image.Save (filename, "PNG"); } //Paints the image to the screen and emits Painted private void PaintImage (object state) { buffer.ConvertFromImage (image); PerformPaint (); Emit ("Painted()"); } //The actual bitblt to the screen private void PerformPaint () { BitBlt(this, 0, 0, buffer, 0, 0, -1, -1, RasterOp.CopyROP, false); } //Receive focus events private void TouchFocus (QFocusEvent e) { PerformPaint (); } //Receive paint events private void TouchPaint (QPaintEvent e) { PerformPaint (); } //Receive resize events private void TouchResize (QResizeEvent e) { image = new QImage (e.Size (), 32); buffer.Resize (e.Size()); buffer.Fill (new QColor("black")); BitBlt (buffer, 0, 0, new QPixmap (buffer), 0, 0, -1, -1, RasterOp.CopyROP, false); } //Dispose of the timer public void Stop () { timer.Dispose (); } } public interface IQuantumFractal { void UpdateData (Double [] data); } //Polytope types public enum Shapes { TETRAHEDRON = 0, CUBE = 1, OCTAHEDRON = 2, ICOSAHEDRON = 3, DODECAHEDRON = 4, DOUBLE_TETRAHEDRON = 5, ICOSIDODECAHEDRON = 6 } public class QuantumFractals { private int t = 0; private double[] p; //Detector probabilities private double[] fp; //Fractal point private double[][] n; //Detector points private double[] counter; //Detect counter private double alpha = 0.61803398874989288039384209090709; //Initialize to 1/phi private Random random; private Shapes polytope; private IQuantumFractal consumer; public QuantumFractals (IQuantumFractal consumer) { this.consumer = consumer; SetPolytope (0); Init (); } public double Alpha { get { return alpha; } set { alpha = value; } } private void Init () { random = new Random (); //Default values t = 0; counter = new double[n.Length]; //Detect counter fp = new double[3]; //Fractal point p = new double[n.Length]; //Initial state fp[0] = random.NextDouble () -0.5; fp[1] = random.NextDouble () -0.5; fp[2] = random.NextDouble () -0.5; double sum = Math.Sqrt (Product (fp, fp)); fp[0] = fp[0] / sum; fp[1] = fp[1] / sum; fp[2] = fp[2] / sum; } //Main fractal generator loop public void Start () { Init (); //double n1 = (1.0) / n.Length as double; double n1 = (1.0) / n.Length; double alpha12 = 2 * alpha / (n.Length * (1 + alpha * alpha)); do { //Increase t t++; //Calculate detector click probabilities for (int i = 0; i < p.Length; i++) p[i] = n1 + alpha12 * Product (n[i], fp); //Get next random number double r = random.NextDouble (); //Check which detector that clicked double ptmp = 0; double[] detector = null; for (int i = 0; i < p.Length; i++) { ptmp += p[i]; if (r <= ptmp) { //We found which detector clicked detector = n[i]; counter[i]++; break; } } if (detector == null) detector = n[p.Length - 1]; //Project double sc = Product (fp, detector); for (int j = 0; j < 3; j++) fp[j]= (1 - alpha * alpha) * fp[j] + 2 * alpha * (1 + alpha * sc) * detector[j]; //Normalize double norm = Math.Sqrt (Product (fp, fp)); for (int j=0; j<3; j++) fp[j] /= norm; consumer.UpdateData (fp); } while (true); } //Calculate the scalar product of two vectors private double Product (double[] v1, double[] v2) { double sc = 0; for(int i=0; i < v1.Length; i++) sc += v1[i] * v2[i]; return sc; } public string GetPolytope () { string ret = String.Empty; switch (polytope) { case Shapes.TETRAHEDRON: ret = "Tetrahedron"; break; case Shapes.CUBE: ret = "Cube"; break; case Shapes.OCTAHEDRON: ret = "Octahedron"; break; case Shapes.ICOSAHEDRON: ret = "Icosahedron"; break; case Shapes.DODECAHEDRON: ret = "Dodecahedron"; break; case Shapes.DOUBLE_TETRAHEDRON: ret = "Double Tetrahedron"; break; case Shapes.ICOSIDODECAHEDRON: ret = "Icosidodecahedron"; break; Default: ret = "Unknown"; break; } return ret; } public void SetPolytope (int type) { polytope = (Qf.Shapes)type; switch (type) { case 0: { n = new double[4][]; n[0] = new double[] {0,0,1.0}; n[1] = new double[] {0.9428090415820634,0,-0.3333333333333333}; n[2] = new double[] {-0.4714045207910317,0.816496580927726,-0.3333333333333333}; n[3] = new double[] {-0.4714045207910317, -0.816496580927726, -0.3333333333333333}; break; } case 1: { n = new double[8][]; n[0] = new double[] {0, 0, 1.0}; n[1] = new double[] {0.9428090415820634, 0, 0.3333333333333333}; n[2] = new double[] {-0.4714045207910317, 0.816496580927726, 0.3333333333333333}; n[3] = new double[] {-0.4714045207910317, -0.816496580927726, 0.3333333333333333}; n[4] = new double[] {0.4714045207910317, 0.816496580927726, -0.3333333333333333}; n[5] = new double[] {0.4714045207910317, -0.816496580927726, -0.3333333333333333}; n[6] = new double[] {-0.9428090415820634, 0, -0.3333333333333333}; n[7] = new double[] {0, 0, -1.0}; break; } case 2: { n = new double[6][]; n[0] = new double[] {0, 0, 1.0}; n[1] = new double[] {1.0, 0, 0}; n[2] = new double[] {0, 1.0, 0}; n[3] = new double[] {-1.0, 0, 0}; n[4] = new double[] {0, -1.0, 0}; n[5] = new double[] {0, 0, -1.0}; break; } case 3: { n = new double[12][]; n[0] = new double[] {0, 0, 1.0}; n[1] = new double[] {0.8944271909999159, 0, 0.4472135954999579}; n[2] = new double[] {0.276393202250021, 0.85065080835204, 0.4472135954999579}; n[3] = new double[] {-0.723606797749979, 0.5257311121191336, 0.4472135954999579}; n[4] = new double[] {-0.723606797749979, -0.5257311121191336, 0.4472135954999579}; n[5] = new double[] {0.276393202250021, -0.85065080835204, 0.4472135954999579}; n[6] = new double[] {0.723606797749979, 0.5257311121191336, -0.4472135954999579}; n[7] = new double[] {0.723606797749979, -0.5257311121191336, -0.4472135954999579}; n[8] = new double[] {-0.276393202250021, 0.85065080835204, -0.4472135954999579}; n[9] = new double[] {-0.8944271909999159, 0, -0.4472135954999579}; n[10] = new double[] {-0.276393202250021, -0.85065080835204, -0.4472135954999579}; n[11] = new double[] {0, 0, -1.0}; break; } case 4: { n = new double[20][]; n[0] = new double[] {0, 0, 1.0}; n[1] = new double[] {0.6666666666666666, 0, 0.7453559924999299}; n[2] = new double[] {-0.3333333333333333, 0.5773502691896257, 0.7453559924999299}; n[3] = new double[] {-0.3333333333333333, -0.5773502691896257, 0.7453559924999299}; n[4] = new double[] {0.7453559924999299, 0.5773502691896257, 0.3333333333333333}; n[5] = new double[] {0.7453559924999299, -0.5773502691896257, 0.3333333333333333}; n[6] = new double[] {-0.8726779962499649, 0.35682208977308993, 0.3333333333333333}; n[7] = new double[] {0.12732200375003502, 0.9341723589627157, 0.3333333333333333}; n[8] = new double[] {0.12732200375003502, -0.9341723589627157, 0.3333333333333333}; n[9] = new double[] {-0.8726779962499649, -0.35682208977308993, 0.3333333333333333}; n[10] = new double[] {0.8726779962499649, 0.35682208977308993, -0.3333333333333333}; n[11] = new double[] {0.8726779962499649, -0.35682208977308993, -0.3333333333333333}; n[12] = new double[] {-0.7453559924999299, 0.5773502691896257, -0.3333333333333333}; n[13] = new double[] {-0.12732200375003502, 0.9341723589627157, -0.3333333333333333}; n[14] = new double[] {-0.12732200375003502, -0.9341723589627157, -0.3333333333333333}; n[15] = new double[] {-0.7453559924999299, -0.5773502691896257, -0.3333333333333333}; n[16] = new double[] {0.3333333333333333, 0.5773502691896257, -0.7453559924999299}; n[17] = new double[] {0.3333333333333333, -0.5773502691896257, -0.7453559924999299}; n[18] = new double[] {-0.6666666666666666, 0, -0.7453559924999299}; n[19] = new double[] {0, 0, -1.0}; break; } case 5: { n = new double[8][]; n[0] = new double[] {0,0,1.0}; n[1] = new double[] {0.9428090415820634,0,-0.3333333333333333}; n[2] = new double[] {-0.4714045207910317,0.816496580927726,-0.3333333333333333}; n[3] = new double[] {-0.4714045207910317, -0.816496580927726, -0.3333333333333333}; n[4] = new double[] {0,0,-1.0}; n[5] = new double[] {-0.9428090415820634,0,0.3333333333333333}; n[6] = new double[] {0.4714045207910317,-0.816496580927726,0.3333333333333333}; n[7] = new double[] {0.4714045207910317, 0.816496580927726, 0.3333333333333333}; break; } case 6: { double u=0.5; double v=0.8090169943749475; // (1/2)*phi double w=0.3090169943749474; // (1/2)/phi n = new double[30][]; n[0] = new double[] {1,0,0}; n[1] = new double[] {-1,0,0}; n[2] = new double[] {0,1,0}; n[3] = new double[] {0,-1,0}; n[4] = new double[] {0,0,1}; n[5] = new double[] {0,0,-1}; n[6] = new double[] {u,v,w}; n[7] = new double[] {-u,v,w}; n[8] = new double[] {u,-v,w}; n[9] = new double[] {u,v,-w}; n[10] = new double[] {-u,-v,w}; n[11] = new double[] {u,-v,-w}; n[12] = new double[] {-u,v,-w}; n[13] = new double[] {-u,-v,-w}; n[14] = new double[] {v,w,u}; n[15] = new double[] {v,w,-u}; n[16] = new double[] {-v,w,u}; n[17] = new double[] {v,-w,u}; n[18] = new double[] {-v,w,-u}; n[19] = new double[] {-v,-w,u}; n[20] = new double[] {v,-w,-u}; n[21] = new double[] {-v,-w,-u}; n[22] = new double[] {w,u,v}; n[23] = new double[] {w,-u,v}; n[24] = new double[] {w,u,-v}; n[25] = new double[] {-w,u,v}; n[26] = new double[] {w,-u,-v}; n[27] = new double[] {-w,u,-v}; n[28] = new double[] {-w,-u,v}; n[29] = new double[] {-w,-u,-v}; break; } Default: break; } } } }