Search code examples
c++openglzoomingtranslation

How to zoom in on cursor point in Mandelbrot Set?


I'm currently trying to implement a zoom feature for the Mandelbrot Set code I've been working on. The idea is to zoom in/out where I left/right click. So far whenever I click the screen, the fractal is indeed zoomed in. The issue is that the fractal is rendered not at the origin-- in other words, it's not zoomed in on the point I want. I was hoping through here I can get both a code review and conceptual understanding of how to zoom in on a point in general.

Here's how I transformed the pixel coordinate before I used escape algorithm:

MandelBrot.Frag

vec2 normalizedFragPos = (gl_FragCoord.xy/windowSize); //normalize fragment position

dvec2 scaledFragPos = normalizedFragPos*aspectRatio;

scaledFragPos -= aspectRatio/2; //Render the fractal at center of window

scaledFragPos /= scale; //Factor to zoom in or out coordinates.

scaledFragPos -= translation; //Translate coordinate
//Escape Algorithm Below

On my left-click handle, I thought I should convert the cursor position to the same coordinate range as the Mandelbrot Range. So I basically did the same thing I did in the fragment shader:

Window.cpp

float x_coord{ float(GET_X_LPARAM(informaton_long))/size.x }; // normalized mouse x-coordinate
float y_coord{ float(GET_Y_LPARAM(informaton_long))/size.y }; // normalized mouse y-coordinate

x_coord *= aspectRatio[0]; //move point based of relative position to length of window.
y_coord *= aspectRatio[1]; //move point based of relative position to width of window.
x_coord /= scale; //Scale point to match previous zoom factor
y_coord /= scale; //Scale point to match previous zoom factor
translation[0] = x_coord;
translation[1] = y_coord;
//increment scale
scale += .15f;

Solution

  • Lets apply some algebra. Your shader does the following transformation:

    mandelbrotCoord = aspectRatio * (gl_FragCoord / windowSize - 0.5) / scale - translation
    

    When we zoom in on mouseCoord, we want to change the scale and adjust the translation such that the madelbrotCoord under the mouse stays the same. To do that we first calculate the mandelbrotCoord under the mouse using the old scale:

    mandelbrotCoord = aspectRatio * (mouseCoord / windowSize - 0.5) / scale - translation
    

    Then change the scale (which should be changed exponentially BTW):

    scale *= 1.1;
    

    Then solve for the new translation:

    translation = aspectRatio * (mouseCoord / windowSize - 0.5) / scale - mandelbrotCoord
    

    Also notice that your system probably reports the mouse coordinate with the y coordinate increasing downwards, whereas OpenGL has its window y coordinate increasing upwards (unless you override it with glClipControl). Therefore you're likely to need to flip the y coordinate of the mouseCoord too.

    mouseCoord[1] = windowSize[1] - mouseCoord[1];
    

    For best result I would also adjust the mouse coordinates to be in the middle of the pixel (+0.5, +0.5).

    Putting it all together:

    float mouseCoord[] = {
        GET_X_LPARAM(informaton_long) + 0.5,
        GET_Y_LPARAM(informaton_long) + 0.5
    };
    mouseCoord[1] = size[1] - mouseCoord[1];
    
    float anchor[] = {
        aspectRatio[0] * (mouseCoord[0] / size[0] - 0.5) / scale - translation[0],
        aspectRatio[1] * (mouseCoord[1] / size[1] - 0.5) / scale - translation[1]
    };
    
    scale *= 1.1;
    
    translation[0] = aspectRatio[0] * (mouseCoord[0] / size[0] - 0.5) / scale - anchor[0];
    translation[1] = aspectRatio[1] * (mouseCoord[1] / size[1] - 0.5) / scale - anchor[1];
    

    Note: some of the math above might be canceled away. However, if you want to implement a proper pan&zoom functionality (when you can zoom with the mouse wheel while you are panning) then you'll need to store the initial mandelbrotCoord of where the panning started, and then reuse it on subsequent motion and wheel events till the mouse is released. Surprisingly large amount of image viewers get this part wrong!