Here’s another famous fractal that I wanted to try on Arduino: the Newton fractal. It’s named after Isaac Newton’s iterative method for finding roots of functions. The classic example applies the method to function f(z) = z3 – 1 , where z is a complex number. This function has three roots in the complex plane.
The sketch that produced this picture loops over the pixels of a 480×320 display, mapping each of them to a complex number z0, that will serve as the initial value for Newton’s iteration process:
zn+1 = zn – f(zn) / f'(zn)
The basic color of a pixel (red, green or blue) depends on to which of the three roots the process converges. Its color intensity depends on the number of iterations that were needed to reach that root within a pre-defined precision. It’s just that simple!
Apart from playing with different color mappings (always essential for producing visually appealing fractals), I wanted to use modified versions of Newton’s method, as well as to apply them to different functions. The Arduino core has no support for complex calculus, and a library that I found didn’t even compile. So I wrote a couple of basic complex functions and put them in a functions.h file. There must better ways, but it works for me.
Once you have a basic sketch, the canvas of complexity is all yours!
f(z) = z4 – 1
This is my functions.h file. It must be in the same folder as the fractal sketch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
struct complex { double im; double re; }; struct complex create(double real, double imag){ struct complex t; t.re = real; t.im = imag; return t; }; struct complex cpow2(complex z){ struct complex t; t.re = z.re*z.re - z.im*z.im; t.im = 2.0*z.re*z.im; return t; }; struct complex cpow3(complex z){ struct complex t; t.re = pow(z.re,3) - 3.0*z.re*z.im*z.im; t.im = 3.0*z.re*z.re*z.im - pow(z.im,3); return t; }; struct complex cpow4(complex z){ struct complex t; t.re = pow(z.re,4) + pow(z.im,4) - 6.0*z.re*z.re*z.im*z.im; t.im = 4.0*(pow(z.re,3)*z.im - pow(z.im,3)*z.re); return t; }; struct complex cadd(complex c, complex z) { struct complex t; t.re = c.re + z.re; t.im = c.im + z.im; return t; } struct complex csubs(complex c, complex z) { // c minus z struct complex t; t.re = c.re - z.re; t.im = c.im - z.im; return t; } struct complex cmult(complex c, complex z) { struct complex t; t.re = c.re*z.re - c.im*z.im; t.im = c.re*z.im + c.im*z.re; return t; } struct complex cdiv(complex c, complex z) { // c divided by z struct complex t; t.re = (c.re*z.re + c.im*z.im)/(z.re*z.re + z.im*z.im); t.im = (c.im*z.re - c.re*z.im)/(z.re*z.re + z.im*z.im); return t; } double cabs(complex z) { return sqrt(z.re*z.re + z.im*z.im); } |
And here’s the Newton fractal sketch. Note the #include “functions.h” on the first line.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
#include "functions.h" #include <SPI.h> #include "Adafruit_GFX.h" #include "Adafruit_HX8357.h" #define TFT_CS D8 #define TFT_DC D1 #define TFT_RST -1 Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, TFT_RST); #define BLACK 0x0000 #define BLUE 0x001F #define RED 0xF800 #define GREEN 0x07E0 // r1, r2 and r3 are the complex roots of z^3 - 1 = 0 struct complex r1 = create(1.0,0.0); struct complex r2 = create(-0.5,0.8660254038); struct complex r3 = create(-0.5,-0.8660254038); struct complex real1 = create(1.0,0.0); struct complex real3 = create(3.0,0.0); int max_count = 128; float precision = 0.0001; int col_factor = 14; struct complex z; void setup() { tft.begin(HX8357D); tft.setRotation(3); // landscape tft.fillScreen(BLACK); yield(); for(int y=0;y<320;y++) { for(int x=0;x<480;x++) { int count = 0; z.re = (x-240.0)/80.0; // -3 <= z.re < +3 z.im = (y-160.0)/80.0; // -2 <= z.im < +2 while( (count < max_count) && (cabs(csubs(z, r1)) >= precision) && (cabs(csubs(z, r2)) >= precision) && (cabs(csubs(z, r3)) >= precision) ) { if(cabs(z) > 0) { z = csubs(z,cdiv((csubs(cpow3(z), real1)),(cmult(cpow2(z), real3)))); } yield(); count++; } unsigned int kleur = max(0,255-col_factor*count); if (cabs(csubs(z, r1)) < precision) tft.drawPixel(x,y,rgb24to16(0,0,kleur)); if (cabs(csubs(z, r3)) < precision) tft.drawPixel(x,y,rgb24to16(kleur,0,0)); if (cabs(csubs(z, r2)) < precision) tft.drawPixel(x,y,rgb24to16(0,kleur,0)); yield(); } yield(); } } void loop() { delay(100); yield(); } unsigned int rgb24to16(byte r_value, byte g_value, byte b_value) { unsigned int tint = ((((r_value>>3)<<11) | ((g_value>>2)<<5) | (b_value>>3))); return tint; } |