Fractal zoom animation

Hi.
I would like to code an animated fractal as shown as in this animation.
My first thought was to just zoom in at every frame, but the result of this code was of course not the same, because you have to split the fractal lines continuously.
After some hours watching in slow motion I still don’t know how to start.
Anyone an idea which steps to take?

ZoomingpentaKoch

2 Likes

Did you try to delete everything every frame and draw it increasing the depth by 1 every time?

Yes, but then it just zooms like this:
anikoch
I also just used the koch curve, while it should be a quasi-fractal like this. But that is an issue for later on.
quasifractal

2 Likes

In my opinion it looks almost the same

What if you display it in 3D and rotate a bit around the x axis?

That’s because I only showed 50 frames. When continuing it really zooms, because I can’t increment the depth with 1 every frame. That would be to much. When using a fraction (float), the frameRate quickly drops to 1,

That is actually a good idea. But as I said, the sketch simply stops by using float increments.

I know.

Is this a live program? To make a movie you just can let it run over night and record it. The frameRate doesn‘t matter anymore.

OR you could just calculate the area of the fractal that’s visible from the camera

You probably want to look at a glsl shader solution for performance see this shadertoy example.

1 Like

@noel Here’s a simpler glsl Mandelbrot Fractal Zoomer translated to JRubyArt

attr_reader :fractal, :start

def settings
  size(640, 360, P2D)
end

def setup
  sketch_title 'Mandelbrot Zoom'
  @fractal = load_shader(data_path('fractal.glsl'))
  fractal.set('iResolution', width.to_f, height.to_f, 0.0)
  @start = java.lang.System.current_time_millis
end

def playback_time_seconds
  (java.lang.System.current_time_millis - start) / 1000.0
end

def draw
  fractal.set('iTime', playback_time_seconds)
  shader(fractal)
  # Draw the output of the shader onto a rectangle that covers the whole viewport.
  rect(0, 0, width, height)
end

Here is the default shader should work with @sableRaph default shadertoy sketch

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

// From Processing 2.1 and up, this line is optional
#define PROCESSING_COLOR_SHADER

// if you are using the filter() function, replace the above with
// #define PROCESSING_TEXTURE_SHADER

// ----------------------
//  SHADERTOY UNIFORMS  -
// ----------------------

uniform vec3      iResolution;           // viewport resolution (in pixels)
uniform float     iTime;                 // shader playback time (in seconds) (replaces iGlobalTime which is now obsolete)


void mainImage( out vec4 fragColor, in vec2 fragCoord );

void main() {
  mainImage(gl_FragColor,gl_FragCoord.xy);
}

// ------------------------------
//  SHADERTOY CODE BEGINS HERE  -
// ------------------------------

// Classic fractal zoomer with nice targets - by TomCat/Abaddon
// not enough precision here :-(
// you can see this with more precision: https://youtu.be/lOXka6aarbQ

#define maxiter 92.0
#define next 100.0
#define Scale 0.875
#define speed 6.0
#define PI2 6.28318
#define is 0.015

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float time = mod( speed*iTime,next*5.0 );
    vec2 a = vec2( 0.34390699597256746411,-0.70062002023500613567 );
    if( time>next )
    {
    	time = time-next;
    	a = vec2( -1.25764648790205013639,0.11831488894193964434 );
    }
    if( time>next )
    {
    	time = time-next;
    	a = vec2( -0.93789936639955584496,0.31736094066985742756 );
    }
    if( time>next )
    {
    	time = time-next;
    	a = vec2( -0.72568075954437072372,-0.27254962894836931575 );
    }
    if( time>next )
    {
    	time = time-next;
    	a = vec2( -0.15713601278801156424,-1.10494558202452419770 );
    }
    if( time>maxiter ) time = maxiter;

    float zoom = is * pow( Scale,time );
    vec2 c = a.xy - zoom*( iResolution.xy*0.5 - fragCoord.xy );

    float iter = 0.;
    vec2 z = vec2( 0.,0. );
    vec2 z2 = vec2( 0.,0. );
    for( float i=0.; i<maxiter; i++ )
    {
		z = vec2( z2.x-z2.y+c.x,2.0*z.x*z.y+c.y );
        z2 = vec2( z.x*z.x,z.y*z.y );
		if( (z2.x+z2.y)>(4.0) ) break;
        iter++;
//	if( iter>time ) break;
    }
	float col = iter/maxiter;
    vec3 pal = 0.5 + 0.5 * vec3( cos(PI2*(4.*col+.67)),cos(PI2*(2.*col+.33)),cos(PI2*(1.*col+.00)) );
    if( iter>=time ) pal = vec3( 0.,0.,0. );
    fragColor = vec4( pal,1.0 );
}

// ----------------------------
//  SHADERTOY CODE ENDS HERE  -
// ----------------------------

1 Like

Hi Noel,
you must increase the iteration-depth slowly, while you zoom in. The higher the interation-depth, the more details of the Koch-curve you get.

1 Like

The difficulty is, when you zoom into a koch curve, you have to construct the whole curve giving it a more negative x start value and more positive x end value of the initial line length, each frame. When incrementing the depth value every certain frameCount amount you will end up with a tremendous amount of calculations. It’s doable, by, as @Chrisir mentioned in his first suggestion. letting it run overnight; but I would like a real time running code. I tried to code his second suggestion, calculating the area, by making an Arraylist of the koch lines that are within the visible draw area. But I couldn’t find a method to zoom it.
I am afraid that it’s far more complicated than you would expect. So I dare you to try it. :grinning:

Thanks, I’m trying to digest the code, to see if I can use the concept. But although I, myself like to code in a condensed/concise way, I now feel the difficulty myself, for others to make sence out of it.

What if you change the depth value depending on say screen Y position (kind of how far away it is)? So you have different depths in the same frame.

Not sure how feasible it is, I haven’t looked into koch curves yet (it’s on the to-do list :slight_smile: )

There are several ways to the draw it. I posted this one on the Rosetta page where I only use the functions rotate() and translate(). Another way is by using sin()/cos() to rotate, like with

this code

float x, y, side = 300, angle, yi, xi;

void setup() {
  size(400, 300);
  background(255);
  strokeWeight(2.5);
}

void draw() {
  for (int d = 0; d < 5; d++) {
    if (angle < 901) {
      background(255);
      koch(side, d);
      angle += 225;
    }
  }
  angle = 0;
  x = 50-xi;
  y = 220+yi;
  yi += 2.8;
  xi += 2;
  side += 4;
  if(y > 700) restart();
}

void koch(float side, float depth) {
  float dx, dy;
  if (depth == 0) {
    dx = cos(angle*PI/180)*side;
    dy = sin(angle*PI/180)*side;
    line(x, y, x+dx, y+dy);
    x += dx;
    y += dy;
  } else {
    koch(side/3, depth-1);
    angle += 60;
    koch(side/3, depth-1);
    angle -= 120;
    koch(side/3, depth-1);
    angle += 60;
    koch(side/3, depth-1);
  }
}

void restart(){
  x = 0; y = 0; side = 300; angle = 0; yi = 0; xi = 0;
}

where zooming is possible, but incrementing a depth value more difficult. (As is with the first code.)
The best way is to use a Kochline class where you see and modify the construction of the curve more easily. You can read an excellent (as usual) explanation of the curve by Daniel here. (Somewhere in the middle of the page - part 4) This way I made the movie of my first attempt, posted above. I will leave a code beneath without depth increment but with the area of interest within an arrayList. It remains to know how to zoom with it.
The GLSL fractal examples @monkstone posted, use scaling. So I tried this as well, drawing a vertex shape, incrementing the scale() in draw. The problem with this method is that the strokeWidth also amplifies. And trying another idea, namely to resize/amplify the curve as an image, the lines blur.
My final goal is having a continuously fractal zooming of lines, instead of texture shapes.

ArrayList<KochLine> koch_lines_a = new ArrayList<KochLine>();
ArrayList next = new ArrayList<KochLine>();
PVector start, end;
float  yh = 147;
int c, x, xe, z, depth = 6;

KochLine kl;

void setup() {
  size(500, 500);
  background(255);
  xe = width;
  for (int i = 0; i < 135; i++) {
    start = new PVector(x, yh);
    end = new PVector(xe, yh);
    koch_lines_a = new ArrayList<KochLine>();
    koch_lines_a.add(new KochLine(start, end));
    for (int j = 0; j < depth; j++) {
      generate();
    }
    x -= 3;
    xe += 3;
    yh += 1.73;
  }
  koch_lines_a.subList(0, 112).clear();
  koch_lines_a.subList(koch_lines_a.size()-112, koch_lines_a.size()).clear();
}

void draw() {
  background(255);
  for (KochLine l : koch_lines_a) {
    l.display();
  }
}

void generate() { 
  ArrayList next = new ArrayList<KochLine>();
  for (int i = 0; i < koch_lines_a.size(); i++) {
    KochLine l = koch_lines_a.get(i);  
    PVector a = l.koch_a();
    PVector b = l.koch_b();
    PVector c = l.koch_c();
    PVector d = l.koch_d();
    PVector e = l.koch_e();
    next.add(new KochLine(a, b));
    next.add(new KochLine(b, c));
    next.add(new KochLine(c, d));
    next.add(new KochLine(d, e));
  }
  koch_lines_a = next;
}

class KochLine { 
  PVector start;
  PVector end;

  KochLine(PVector a, PVector b) { 
    start = a.get();
    end   = b.get();
  }

  PVector koch_a() {
    return start.get();
  }

  PVector koch_b() {
    PVector temp = PVector.sub(end, start);
    temp.div(3);
    temp.add(start);
    return temp;
  }

  PVector koch_c() {
    PVector a = start.get(); 
    PVector temp = PVector.sub(end, start);
    temp.div(3);
    a.add(temp);
    temp.rotate(-radians(60)); 
    a.add(temp); 
    return a;
  }

  PVector koch_d() {
    PVector temp = PVector.sub(end, start);
    temp.mult(2/3.0);
    temp.add(start);
    return temp;
  }

  PVector koch_e() {
    return end.get();
  }

  void display() {
    stroke(0);
    line(start.x, start.y, end.x, end.y);
  }
}
4 Likes

Great answer, thanks!

Probably of no use, just to show the idea roughly:

Koch curve by noel modded

Modified your Rosetta page code a bit
int l = 300;
 
void setup() {
  size(400, 400);
  background(0, 0, 255);
  stroke(255);
  // draw from center of screen
  translate(width/2.0, height/2.0);
  // center curve from lower-left corner of base equilateral triangle
  translate(-l/2.0, l*sqrt(3)/6.0);
  for (int i = 1; i <= 3; i++) {
    kcurve(0, l);
    rotate(radians(120));
    translate(-l, 0);
  }
}
 
void kcurve(float x1, float x2) {
  float s = (x2-x1)/3;
  
  float factor = (1-(screenY(x1+1.5*s, s*sqrt(3)/2)/height));
  float minLength = int(pow(factor,1.5)*50.0)+1;
  //println(s, minLength);
  float lineThickness = (factor+.25)*4;
  strokeWeight(lineThickness);

  if (s < minLength) {
    pushMatrix();
    translate(x1, 0);
    line(0, 0, s, 0);
    line(2*s, 0, 3*s, 0);
    translate(s, 0);
    rotate(radians(60));
    line(0, 0, s, 0);
    translate(s, 0);
    rotate(radians(-120));
    line(0, 0, s, 0);
    popMatrix();
    return;
  }
  pushMatrix();
  translate(x1, 0);
  kcurve(0, s);
  kcurve(2*s, 3*s);
  translate(s, 0);
  rotate(radians(60));
  kcurve(0, s);
  translate(s, 0);
  rotate(radians(-120));
  kcurve(0, s);
  popMatrix();
}

It’s a bit exaggerated, and the lower part isn’t scaled up (zoomed in). Not sure how to do it with the zooming version.

1 Like

Movie after hours of running. Not the real-time I want.

2 Likes

This sketch uses a double reflected Koch curve moving and zooming and has a reasonable depth increment, but I can’t get a constant speed. Maybe someone can hint at how to stabilize it. Far from what I want still;

1 Like