 Mandelbrot set explorer V0 - Super Zoom

Hello everyone!
After a long time of struggling with the scroll to zoom feature, I finally made it! This program can zoom into the mandelbrot set with huge accuracy. The largest zoom is 10^-17. That means that for every pixel we move on the screen, the coordinate changes by 10^-17. Beyond that, double reaches it’s limits and this appears:

I am still trying to shift the values used by double, so instead of using: x&y offset mostly between -2 and 2, and shift it into something like -2 * 10^6 and 2 * 10^6. Well it will take a while so for now, I will share this!

And this is just one of the infinite voyages the mandelbrot set offers!

I haven’t added a limit to iterations when unzoomed, so it lags quite a bit. Try to avoid black areas, since they take the longest to calculate.

I’ll try to add a drag feature, as well as a control center so you don’t need to do everything with scrolling. Well enjoy!

Code
int iter = 64*3;
double offx = -2, offy=-1.5, scale=1.0/200.0;
color clr[] = new color; //how many colors, excluding black. Any additional color just loop around
color inside = #000000;
double zoom = 0.9;
void setup() {
size(600,600);
colorMode(HSB);
for(int i = 0; i < 64; i++) clr[i] = color(i*4,255,255);
}
void mouseWheel(MouseEvent event) {
float e = -event.getCount();
float x = (float)mouseX/(float)width, y = (float)mouseY/(float)height;
double px = x*(1-zoom)*width*e, py = y*(1-zoom)*height*e;
offx+=px*scale;
offy+=py*scale;
if(e<0) scale/=zoom;
else if(e>0) scale*=zoom;
println(scale,1/scale);
}
void draw() {
for(int i = 0; i < width; i++) for(int j = 0; j < height; j++) {
complex c = getPosC((float)i,(float)j,offx,offy,scale), z = new complex(c.re,c.im);
int it = 0;
boolean loop = true;
for(it = 0; it < iter-1 && loop; it++) {z.setValue(z2pc(z,c)); if(inBounds(z)==false) loop = false;}
//if(i==mouseX&&j==mouseY) println(it);
pixels[i+j*width] = (it==iter-1? inside : clr[it%63]);
}
updatePixels();
if(mousePressed) getPosC(mouseX,mouseY,offx,offy,scale).printC();
}

boolean inBounds(complex c) {
return(c.re*c.re+c.im*c.im<4);
}
class complex {
double re, im;
complex(double re, double im) {
this.re=re;
this.im=im;
}
void setValue(complex c) {
re = c.re;
im = c.im;
}
void printC() {
println(re+" + "+im+"i");
}
}
//COMPLEX CALCULATIONS ----------------------------------------------------------------------
complex csum(complex z, complex w) {
return new complex(z.re+w.re,z.im+w.im);
}
complex cmult(complex z, complex w) {
return new complex(w.re*z.re - w.im*z.im, w.re*z.im + z.re * w.im);
}
complex csq(complex z) {
return cmult(z,z);
}
complex z2pc(complex z, complex c) { //z^2+c
return csum((csq(z)),c);
}
//VALUE ASSIGNMENT ---------------------------------------------------------------------------
complex getPosC(float x, float y, double xoff, double yoff, double scl) {
return new complex(xoff+x*scl,yoff+y*scl);
}

If you are curious how I made the zoom work, use this for reference:

Code
float zoom = 0.9;
void setup() {
size(600,600);
stroke(255); noFill();
}
void draw() {
background(0);
float x = mouseX, y = mouseY;
float nW = (float)width*zoom;
float nx = map(x,0,width,0,1), ny = map(y,0,height,0,1);

float px = nx*(width-nW), py = ny*(height-nW);
float npx = px+nW*nx, npy = py + nW*ny;
circle(npx,npy,5);
rect(px,py,nW,nW);
}

I am trying to make the mouse be on the same point even after zoom. I used desmos as inspiration and did all of the coding to mimic the zoom. After hours of failing, I finally succeeded, but I had to recode the entire program again.

So far, zoom is asymetric, so zooming out after zooming in doesn’t yield the same result. I will try to fix this in the next version.

Enjoy!

1 Like

This works just as well in full screen, just replace the size(600,600) with fullScreen()

If you want to explore different fractals, just adjust the formula in z2pc() right now it is z^2+c. If you want to make a julia set, just replace it with z^2+a; where a is a fixed complex number. I recommend to add a feature, where you can press any point on the screen and it will return the complex number and just plug that as A.

Version 001

I made a follow up version V001, which has: faster approximate rendering, symmetric zoom and drag option.

At the start, with the ‘quality’ 4 it looks like this

It increases to 3

2

1

And finally 0, where each pixel is calculated

No previous pixel is calculated again, just ignored and left as it is (since calculating it again would yield the same results), which is optimized. It short, it does a bit more math and takes a few % longer, but in return, you can get an approximation almost instantly.

Version 001
int iter = 64*3;
double offx = -2, offy=-1.28, scale=1.0/200.0;
color clr[] = new color; //how many colors, excluding black. Any additional color just loop around
color inside = #000000;
double zooms[] = {0.9,1,1/0.9};
boolean update = true;
int lastUpdate = 0;
int quality = 3, desiredQuality = 0; //lower the quality, better image (grid size 2^quality)
void setup() {
fullScreen();

//size(512, 512);
colorMode(HSB);
for (int i = 0; i < 64; i++) clr[i] = color(i*4, 255, 255);
}
void mouseWheel(MouseEvent event) {
update = true;
int e = event.getCount();
double zoom = zooms[e+1];
float x = (float)mouseX/(float)width, y = (float)mouseY/(float)height;
double px = -x*abs(1-(float)zoom)*width*e, py = -y*abs(1-(float)zoom)*height*e;
offx+=px*scale;
offy+=py*scale;
scale*=zoom;
println(scale, 1/scale);
}
void mouseDragged() {
offx -= (double)((double)mouseX-(double)pmouseX)*scale;
offy -= (double)((double)mouseY-(double)pmouseY)*scale;
update=true;
}

void draw() {
if(update) partialGen(offx, offy, scale, iter, 4, false);
else if(millis()-lastUpdate>100) partialGen(offx,offy,scale,iter,constrain(quality-1,desiredQuality,100),true);
if (mousePressed) getPosC(mouseX, mouseY, offx, offy, scale).printC();
}
void partialGen(double Xoff, double Yoff, double Scl, int Iter, int acc, boolean increase) {
int accuracy = (int)pow(2,acc);
for (int i = 0; i < width; i+=accuracy) for (int j = 0; j < height; j+=accuracy) {
if( increase == false || (i/accuracy)%4!=0||(j/accuracy)%4!=0) {
complex c = getPosC((float)i, (float)j, Xoff, Yoff, Scl), z = new complex(c.re, c.im);
int it = 0;
boolean loop = true;
for (it = 0; it < Iter-1 && loop; it++) {
z.setValue(z2pc(z, c));
if (inBounds(z)==false) loop = false;
}
color endClr = (it == iter-1? inside : clr[it%63]);

for (int a = 0; a < accuracy; a++) for (int b = 0; b < accuracy; b++) {
pixels[constrain(i+a,0,width-1)+constrain(j+b,0,height-1)*width] = endClr;
}
}
}
updatePixels();
update = false;
lastUpdate=millis();
quality = acc;
}

void generate(double Xoff, double Yoff, double Scl, int Iter) {
for (int i = 0; i < width; i++) for (int j = 0; j < height; j++) {
complex c = getPosC((float)i, (float)j, Xoff, Yoff, Scl), z = new complex(c.re, c.im);
int it = 0;
boolean loop = true;
for (it = 0; it < Iter-1 && loop; it++) {
z.setValue(z2pc(z, c));
if (inBounds(z)==false) loop = false;
}
pixels[i+j*width] = (it==iter-1? inside : clr[it%63]);
}
updatePixels();
update = false;
lastUpdate=millis();
quality = 0;
}

boolean inBounds(complex c) {
return(c.re*c.re+c.im*c.im<4);
}
class complex {
double re, im;
complex(double re, double im) {
this.re=re;
this.im=im;
}
void setValue(complex c) {
re = c.re;
im = c.im;
}
void printC() {
println(re+" + "+im+"i");
}
}
//COMPLEX CALCULATIONS ----------------------------------------------------------------------
complex csum(complex z, complex w) {
return new complex(z.re+w.re, z.im+w.im);
}
complex cmult(complex z, complex w) {
return new complex(w.re*z.re - w.im*z.im, w.re*z.im + z.re * w.im);
}
complex csq(complex z) {
return cmult(z, z);
}
complex z2pc(complex z, complex c) { //z^2+c
return csum((csq(z)), c);
}
//VALUE ASSIGNMENT ---------------------------------------------------------------------------
complex getPosC(float x, float y, double xoff, double yoff, double scl) {
return new complex(xoff+x*scl, yoff+y*scl);
}

1 Like