Jump to my Home Page Send me a message Check out stuff on GitHub Check out my photography on Instagram Check out my profile on LinkedIn Check out my profile on reddit Check me out on Facebook

Mitch Richling: Tinkerbell Fractal

Author: Mitch Richling
Updated: 2024-09-07

tinkerbell_00_5.png tinkerbell_01_5.png tinkerbell_02_5.png

Table of Contents

1. Introduction

The Tinkerbell Attractor is a discrete-time dynamical system defined by the following relationship: \[ \begin{align*} x_{n+1} & = x_n^2-y_n^2+ax_n+by_n \\ y_{n+1} & = 2x_ny_n+cx_n+dy_n \end{align*} \] Most any non-zero initial conditions may be used for \( (x_1, y_1) \).

2. Algorithms

Just as with the Peter de Jong attractors we look at the probability density function, \(P(x,y)\), defined by the probability that some \((x_n, y_n)\) from the map will be in a neighborhood of \((x,y)\). While that sounds complicated, producing a visualization of this PDF is quite simple in practice – we simply visualize a histogram based on computed values of the map. The first step is to define a rectangular region of the plane over which we wish to visualize the PDF, and then define a set of discrete pixels over that region. Then we compute a few million, or billion, iterations of the map, and count the number of times we hit each pixel.

#include "ramCanvas.hpp"

std::vector<std::array<double, 11>> params {
  /*        a,         b,          c,         d,  x0,  y0,   N,     x-min,    x-max,     y-min,     y-max */
  {  0.900000, -0.220000,  2.0000000,  0.500000, 0.1, 0.1, 1e7, -1.410000, 0.370000, -1.380000, -0.350000 }, //   0
  {  0.200000,  0.150000,  2.0000000,  0.650000, 0.1, 0.1, 1e7, -1.460000, 0.950000, -1.240000, -0.150000 }, //   1
  {  0.900000, -0.601300,  2.0000000,  0.500000, 0.1, 0.1, 1e7, -1.240000, 0.470000, -1.560000,  0.550000 }, //   2
  { -0.783671, -0.450251,  0.0756921,  1.908150, 0.1, 0.1, 1e8, -0.784949, 0.190357,  0.100000,  1.012020 }, //   3
  { -0.667316,  0.789788,  1.1057300,  0.268078, 0.1, 0.1, 1e8, -0.903469, 1.087990, -0.708937,  0.319083 }, //   4
  { -1.016430, -1.458560,  1.3230100,  0.604218, 0.1, 0.1, 1e8, -0.399636, 0.403479, -0.562466,  0.225223 }, //   5
  { -0.254258,  0.925243,  0.7704410, -0.416270, 0.1, 0.1, 1e8, -0.888853, 0.939351, -0.734047,  0.346012 }, //   6
  { -1.548590, -0.249840, -0.2864630,  1.508330, 0.1, 0.1, 1e8, -0.849159, 1.357350, -0.054140,  1.033830 }, //   7
  { -0.769882, -0.743097, -1.0527500,  0.216079, 0.1, 0.1, 1e8, -0.864322, 1.141670, -0.394488,  0.686611 }, //   8
  { -0.305249, -0.507228, -1.5821000,  0.919462, 0.1, 0.1, 1e8, -1.234830, 0.745791,  0.300000,  0.881405 }, //   9
  { -1.754900, -0.904038,  0.9181340,  1.379920, 0.1, 0.1, 1e8, -0.821663, 1.448790, -2.112250, -0.100000 }, //  10
  { -1.659840, -1.286440,  0.7102750,  1.097500, 0.1, 0.1, 1e8, -0.412856, 0.803154, -1.709030, -0.800778 }, //  11
  { -1.733520, -0.647135,  0.5964760,  0.988405, 0.1, 0.1, 1e8, -0.700000, 1.800000, -0.312000, -0.273000 }, //  12
};

int main(void) {
  std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now();
  const int BSIZ = 480*8;
  mjr::ramCanvas1c16b::colorType aColor;
  aColor.setChans(1);

  for(decltype(params.size()) j=0; j<params.size(); ++j) {
    mjr::ramCanvas1c16b theRamCanvas(BSIZ, BSIZ, params[j][7], params[j][8], params[j][9], params[j][10]);

    double a = params[j][0];
    double b = params[j][1];
    double c = params[j][2];
    double d = params[j][3];

    /* Draw the Attractor on a 16-bit, greyscale canvas such that the level is the hit count for that pixel.  
       Thus we are using an "image" as a way to store field data instead of color information. */
    double x        = params[j][4];
    double y        = params[j][5];
    uint64_t maxII  = 0;
    uint64_t inCnt  = 0;
    uint64_t maxItr = static_cast<uint64_t>(params[j][6]);
    uint64_t iPrt   = maxItr / 5;
    uint64_t itr;
    for(itr=1;itr<maxItr;itr++) {
      double xNew = x*x-y*y+a*x+b*y;
      double yNew = 2*x*y+c*x+d*y;
      if ( !theRamCanvas.isCliped(x, y)) {
        inCnt++;
        theRamCanvas.drawPoint(x, y, theRamCanvas.getPxColor(x, y).tfrmAdd(aColor));
        if(theRamCanvas.getPxColor(x, y).getC0() > maxII) {
          maxII = theRamCanvas.getPxColor(x, y).getC0();
          if(maxII > 16384) { // 1/4 of max possible intensity
            std::cout << "ITER(" << j <<  "): " << itr << " MAXS: " << maxII << " EXIT: Maximum intensity reached" << std::endl;
            break;
          }
        }
      }
      if((itr % iPrt) == 0)
        std::cout << "ITER(" << j <<  "): " << itr << " MAXS: " << maxII << " INC: " << inCnt << std::endl;
      x=xNew;
      y=yNew;
    }
    std::cout << "ITER(" << j <<  "): " << itr << " MAXS: " << maxII << " INC: " << inCnt  << std::endl;

    // Log image transform
    theRamCanvas.applyHomoPixTfrm(&mjr::ramCanvas1c16b::colorType::tfrmLn1);
    maxII = static_cast<uint64_t>(std::log(static_cast<double>(maxII)));

    /* Create a new image based on csCCfractal0RYBCW -- this one is 24-bit RGB color. */
    mjr::ramCanvas3c8b anotherRamCanvas(BSIZ, BSIZ);
    typedef mjr::ramCanvas3c8b::colorType::csCCfractal0RYBCW cs_t;
    for(int yi=0;yi<theRamCanvas.getNumPixY();yi++)
      for(int xi=0;xi<theRamCanvas.getNumPixX();xi++)
        anotherRamCanvas.drawPoint(xi, yi, cs_t::c(theRamCanvas.getPxColor(xi, yi).getC0() / static_cast<mjr::ramCanvas3c8b::csFltType>(maxII)));

    anotherRamCanvas.writeTIFFfile("tinkerbell_" + mjr::fmtInt(j, 2, '0') + ".tiff");
  }
  std::chrono::duration<double> runTime = std::chrono::system_clock::now() - startTime;
  std::cout << "Total Runtime " << runTime.count() << " sec" << std::endl;
  return 0;
}

Source Link: tinkerbell.cpp

The above program will generate several images includeing some that have the "Classic Tinkerbell Atractor" shape:

tinkerbell_02_20.png

4. References

Check out the fractals section of my reading list.

All the code used to generate everything on this page may be found on github.