Fractopia

Code sample

The following method generates the images at the heart of Fractopia
BOOL CSlideWnd::GenerateFractal(int nWidth, int nHeight, BOOL bFullScreen=FALSE)
{
  // Tests every point in the current subset of
  // the complex plane, and colours it according
  // to it's escape behaviour
  // If bFullScreen is TRUE, we don't want to display the fractal
  // as it generates. Instead, we generate behind the scenes,
  // and display progress lights

  CFractopiaApp* pApp = (CFractopiaApp*)AfxGetApp();
  CFractopiaDlg* pParent = (CFractopiaDlg*)GetParent();

  // If the Formula dialog is active,
  // refresh its controls with current fractal data
  pParent->RefreshFormulaDialog();

  // First, we're generating, so set the "generating" flag
  // We use this flag to ensure that only one fractal image is
  // generated at a time
  SetGenerating(TRUE);

  // Next, initialise the quit flag. This may be set to true later,
  // if the user decides to interrupt fractal generation, or if we
  // need to end the app cleanly.
  SetQuitFlag(FALSE);

  // Change to a "wait" cursor
  // Note: the CWaitCursor object will be deleted when it goes out of scope
  CWaitCursor wait;
  if (bFullScreen) // we're generating a full screen fractal image
  {
    // ask the user to wait
    pParent->WriteToStatusArea("please wait...");
  }

  // Now we need a new fractal image bitmap
  if (!bFullScreen)
  {
    // delete the old thumbnail image...
    delete m_pDoc->m_pFractalImage;

    // and construct a new one
    m_pDoc->m_pFractalImage = new CDIBitmap(nWidth, nHeight, m_pDoc->m_hLogicalPalette);

    // Now clear the screen by painting the new (blank) bitmap
    // (we don't do this if we're generating a full screen image,
    // in which case we want to leave the old thumnail image
    // on screen while we generate)
    Invalidate(TRUE);

    // We are about to create a virgin, unsaved new fractal.
    // If it won't have been saved, we won't be in a position
    // to Preview it, so disable the CFractopiaDlg Preview button.
    // (and do this before the fractal is generated)
    pParent->m_btnPreview.EnableWindow(FALSE);

    // While we're at it, don't forget to update the iterations
    // spin control on the CFractopiaSSFormView.
    pParent->RefreshFormulaDialog();
  }

  // Specify our starting co-ordinates
  // in the complex plane...
  double x = m_pDoc->m_pFractalData->Z_Origin().X(),
  yi = m_pDoc->m_pFractalData->Z_Origin().Y();

  // ... and the distance between each point
  // in the plane, at the current zoom level
  double dGrainWidth  = m_pDoc->m_pFractalData->Z_Width() / nWidth;
  double dGrainHeight = m_pDoc->m_pFractalData->Z_Height() / nHeight;

  // Colour all the (complex) points
  for (int nRow = 0; nRow < nHeight; nRow++)
  {
    for (int nCol = 0; nCol < nWidth; nCol++)
    {
      m_cpTestPoint.SetX(x);
      m_cpTestPoint.SetY(yi);

      // find the escape orbit for this point
      int nEscapeOrbit = m_pDoc->TestPoint(m_cpTestPoint);

      // map the escape orbit to a colour value
      switch (nEscapeOrbit)
      {
        case -1:  m_nColourValue = 10;   // lake colour
                  break;
        default:  m_nColourValue = (nEscapeOrbit
                                   % m_pDoc->m_nPaletteEntries)
                                   + 11; // other colours
      }

      // store colour value directly in the fractal image bitmap
      if (!bFullScreen)
      {
        m_pDoc->m_pFractalImage->PaintBit(nCol, nRow, m_nColourValue);
      }
      else
      {
        m_pDoc->m_pBigFractalImage->PaintBit(nCol, nRow, m_nColourValue);
      }
      x += dGrainWidth; // advance along x axis
    }

    // Interrupt app to process *any* messages in the queue.
    // This allows the "Rendering" dialog box to be freely moved,
    // minimised or closed.
    if (!PeekAndPump()) return FALSE;

    // If the user has interrupted fractal generation, or even
    // terminated the program mid generation, exit the loop
    if (m_bQuit)
    {
      // We aren't generating anymore. Reset the flag
      OutputDebugString("*** Quitting mid fractal generation ***\n");
      SetGenerating(FALSE);
      return FALSE;
    }
    if (bFullScreen)
    {
      // if we're generating a full screen fractal image,
      // *don't* paint the line we just generated
      // - update the progress lights instead
      int nPercent = int(nRow / (float)nHeight * 16); //100);
      pParent->ShowBuildProgress(nPercent);
    }
    else
    {
      // Paint the line we just generated (immediately)
      CRect rOneLine(0, nRow, nWidth, nRow + 1);
      InvalidateRect(rOneLine, FALSE);
      UpdateWindow();
    }

    // we (may) have lost the wait cursor, so restore it.
    wait.Restore();
    x = m_pDoc->m_pFractalData->Z_Origin().X(); // return to leftmost x position
    yi -= dGrainHeight;               // go down to next yi position
  }

  // The fractal image has been generated. Now tidy up.
  if (bFullScreen)
  {
    // if we're generating a full screen fractal image...
    // reset Regenerated flag which will have been set if
    // CFractopiaDlg::OnRegenerate() was previously invoked
    m_pDoc->SetRegeneratedFlag(FALSE);

    // Beep to let the user know we've finished calculating at last!
    // (on full screen render only)
    MessageBeep(MB_OK);

    // turn off all the progress lights, and take down status message
    pParent->ResetBuildProgress();
    pParent->WriteToStatusArea(" ");
  }
  if (!bFullScreen)
  {
    // The "document" has been modified. Set the flag.
    m_pDoc->SetModifiedFlag(TRUE);
    
    // Save the new fractal to the undo list,
    // in case the user wants to UNDO later
    ((CFractopiaDlg*)GetParent())->SaveToUndoList();
  }

  // We aren't generating anymore. Reset the flag
  SetGenerating(FALSE);

  // reenable previews
  pParent->m_btnPreview.EnableWindow(TRUE);

  // Note that the default + cursor is restored at the end of this
  // function, when "wait" (the CWaitCursor object), goes out of scope.
  return TRUE;
}