0
votes

I'm able to point zoom on the Mandelbrot set, as long as the mouse doesn't move after zooming has begun. I've tried calculating a normalized delta (new coordinate - old coordinate)*(oldzoom), but what happens is the image appears to jump around to a new location. I've seen this issue before. I'm struggling more here because I have to somehow convert this mouse position delta back to the -2,2 coordinate space of the Mandelbrot set.

Here's my code. What's important is the GetZoomPoint method, and then the lines of code that define x0 and y0. Also, I use the Range class to scale values from one range to another. I WAS using deltaTrans (thats the thing I was talking about earlier where I normalize the mouse delta with the old scale).

using OpenTK.Graphics.OpenGL;
using SpriteSheetMaker;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace Fractal.Fractal
{
    public class Mandelbrot : BaseTexture
    {
        private static Transform GlobalTransform = SpriteSheetMaker.Global.Transform;
        private static Vector3 GlobalScale = GlobalTransform.Scale;
        private static Vector3 GlobalTrans = GlobalTransform.Translation;
        private static Vector3 LastWindowPoint = null;
        private static Vector3 ZoomFactor = Vector3.ONE * 1.2f;
        private static Vector3 Displacement = Vector3.ZERO;
        private static int WindowSize = 100;

        public static Vector3 GetZoomPoint()
        {


            var zP = OpenGLHelpers.LastZoomPoint.Clone();
            if (LastWindowPoint == null)
            {
                LastWindowPoint = zP.Clone();
            }
            var delta = zP - LastWindowPoint;
            var oldZoom = GlobalScale / ZoomFactor;
            var deltaTrans = delta.XY * oldZoom.XY;
            var factor = ZoomFactor.Clone();

            Range xR = new Range(0, WindowSize);
            Range yR = new Range(0, WindowSize);
            Range complexRange = new Range(-2, 2);

            // Calculate displacement of zooming position.
            var dx = (zP.X - Displacement.X) * (factor.X - 1f);
            var dy = (zP.Y - Displacement.Y) * (factor.Y - 1f);
            // Compensate for displacement.
            Displacement.X -= dx;
            Displacement.Y -= dy;

            zP -= Displacement;
            var x = complexRange.ScaleValue(zP.X, xR);
            var y = complexRange.ScaleValue(zP.Y, yR);

            var rtn = new Vector3(x, y);

            LastWindowPoint = zP.Clone();

            return rtn;
        }
        public static Mandelbrot Generate()
        {
            var size = new Size(WindowSize, WindowSize);
            var radius = new Size(size.Width / 2, size.Height / 2);

            Bitmap bmp = new Bitmap(size.Width, size.Height);
            LockBitmap.LockBitmapUnsafe lbm = new LockBitmap.LockBitmapUnsafe(bmp);
            lbm.LockBits();


            var pt = Mandelbrot.GetZoomPoint();
            Parallel.For(0, size.Width, i =>
            {
                //  float x0 = complexRangeX.ScaleValue(i, xRange);
                float x0 = ((i - radius.Width) / GlobalScale.X) + pt.X;

                Parallel.For(0, size.Height, j =>
                 {
                     // float y0 = complexRangeY.ScaleValue(j, yRange);
                     float y0 = ((j - radius.Height) / GlobalScale.Y) + pt.Y;
                     float value = 0f;
                     float x = 0.0f;
                     float y = 0.0f;
                     int iteration = 0;
                     int max_iteration = 100;
                     while (x * x + y * y <= 4.0 && iteration < max_iteration)
                     {
                         float xtemp = x * x - y * y + x0;
                         y = 2.0f * x * y + y0;
                         x = xtemp;
                         iteration += 1;
                         if (iteration == max_iteration)
                         {
                             value = 255;
                             break;
                         }
                         else
                         {
                             value = iteration * 50f % 255f;
                         }
                     }

                     int v = (int)value;
                     lbm.SetPixel(i, j, new ColorLibrary.HSL(v / 255f, 1.0, 0.5).ToDotNetColor());
                 });
            });
            lbm.UnlockBits();
            var tex = new BaseTextureImage(bmp);
            var rtn = new Mandelbrot(tex);
            return rtn;
        }

        public override void Draw()
        {
            base._draw();
        }
        private Mandelbrot(BaseTextureImage graphic)
        {
            var topLeft = new Vector3(0, 1);
            var bottomLeft = new Vector3(0, 0);
            var bottomRight = new Vector3(1, 0);
            var topRight = new Vector3(1, 1);
            this.Vertices = new List<Vector3>()
            {
                topLeft,bottomLeft,bottomRight,topRight
            };
            this.Size.X = WindowSize;
            this.Size.Y = WindowSize;
            this.Texture2D = graphic;
        }
    }
}
1
What is GetZoomPoint? Googling shows this question at the top.Weather Vane
HAHA, ok yeah, I architected this pretty bad. I do that sometimes to speed up development in the short term. To keep this simple, there are global transforms (scale, translate, etc), which this has access to. Outside of this class, the global scale and translation get updated via mousewheel. GetZoomPoint attempts to use the global scale and translation to figure out where in the Mandelbrot set the focus should be set to. The generated Bitmap is essentially treated as a GPU texture, but because I override the base Draw, the transforms dont apply. Does that clear up what GetZoomPoint does?applejacks01
Maybe a better question I could ask is, given a desired window size, a mouse coordinate (which can move at any time ), and a desired scale, how can I zoom into the mandelbrot set (or any arbitrary 2 dimensional bounding space)applejacks01
If the centre is changing as it is drawn, perhaps your function supplies the location (address) of the target x,y variables which then get updated in real time.Weather Vane

1 Answers

0
votes

I refactored my code, and also figured out a solution to this problem. 2 big wins in one. Ok, so I found a solution on CodeProject written in C# which I was readily able to adapt to my project. I'm not sure why I didn't realize this when I posted the question, but what I needed to solve this issue was to create a 'window' of zoom and not think in terms of a 'point zoom'. Yes, even if I am trying to zoom directly into a point, that point is just the center of some sort of a window.

Here is the method I have, which expects start and end mousedown coordinates (screen space), and converts the mandelbrot set window size accordingly.

    public void ApplyZoom(double x0, double y0, double x1, double y1)
    {
        if (x1 == x0 && y0 == y1)
        {
            //This was just a click, no movement occurred
            return;
        }

        /*
            * XMin, YMin and XMax, YMax are the current extent of the set
            * mx0,my0 and mx1,my1 are the part we selected
            * do the math to draw the selected rectangle
            * */
        double scaleX, scaleY;

        scaleX = (XMax - XMin) / (float)BitmapSize;
        scaleY = (YMax - YMin) / (float)BitmapSize;
        XMax = (float)x1 * scaleX + XMin;
        YMax = (float)y1 * scaleY + YMin;
        XMin = (float)x0 * scaleX + XMin;
        YMin = (float)y0 * scaleY + YMin;

        this.Refresh(); // force mandelbrot to redraw

    }

Basically, whats happening is we calculate the ratio between the mandelbrot window size versus the screen size we are drawing to. Then, using that scale, we basically convert our mousedown coordinates to mandelbrot set coordinates (x1*scaleX, etc) and manipulate the current Min and Max coordinates with them, using the Min values as the pivot point.

Here's the link to the CodeProject I used as a reference: CodeProject link