4

I know this question has been asked several times, but none of the answers I found worked for me. I need to allow the user to scale the image which works so far, it's simply a scale(factor) call but now I want to scale by the mouse pointer. This proves more difficult as I can create the zoom in on the pointer effect but when the mouse moves, so does the image. as can be seen in this demo:

https://editor.p5js.org/J-Cake/sketches/1r1wmWO60

I figured i would multiply the second translation's coordinates by the scale factor but that also doesn't seem to do anything. What am I missing?

let sf = 1; // scaleFactor
let x = 0; // pan X
let y = 0; // pan Y

let mx, my; // mouse coords;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  mx = mouseX;
  my = mouseY;

  background(255);

  translate(mx, my);
  scale(sf);
  translate(-mx, -my);
  translate();

  rect(100, 100, 100, 100);

  if (mouseIsPressed) {
    x -= pmouseX - mouseX;
    y -= pmouseY - mouseY;
  }
}

window.addEventListener("wheel", function(e) {
  if (e.deltaY > 0)
    sf *= 1.05;
  else
    sf *= 0.95;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
J-Cake
  • 1,526
  • 1
  • 15
  • 35
  • This might help you https://stackoverflow.com/questions/49245168/zoom-in-out-at-mouse-position-in-canvas – Nenad Vracar Jul 21 '19 at 09:03
  • @Rabbid76 the example I provided allows you to scale the image, if you move the mouse while it is scaled, the whole thing moves. – J-Cake Jul 21 '19 at 09:04
  • the wheel change causes the scale to update. p5 takes care of the updating. – J-Cake Jul 21 '19 at 09:10

1 Answers1

10

The issue is that you've to apply the scale incrementally.

A single scale (s1) from a center point (x1, y1) can be calculated by:

model = translate(x1, y1) * scale(s1) * translate(-x1, -y1) 

But if you want to apply an new scale (s2) around the center (x2, y2), then this is:

model = translate(x2, y2) * scale(s2) * translate(-x2, -y2) * currentMode;

where currentMode is the previous (scale) transformation.
This is not the same as:

model = translate(x1+x2, y1+y2) * scale(s1*s2) * translate(-(x1+x2), -(y1+y2))

A single scale (sf) from the center (mx, my) can be calcualted by:

let tx = mx - sf * mx;
let ty = my - sf * my;
translate(tx, ty);
scale(sf);

To do more of this operations consecutively, I recommend to implement a 3x3 Matrix multiplication:

function matMult3x3(A, B) {
    C = [0, 0, 0, 0, 0, 0];
    for (let k = 0; k < 3; ++ k) {
        for (let j = 0; j < 3; ++ j) {
            C[k*3+j] = A[0*3+j] * B[k*3+0] + A[1*3+j] * B[k*3+1] + A[2*3+j] * B[k*3+2];
        }
    }
    return C;
}

The scale from the center can be expressed by the following 3x3 matrix:

m = [ sf, 0,  0, 
      0,  sf, 0,
      tx, ty, 1];

This leads to the following mouse wheel event:

window.addEventListener("wheel", function(e) {

    let mx = mouseX;
    let my = mouseY;

    let s = e.deltaY > 0 ? 1.05 : 0.95;

    let x = mx - s * mx;
    let y = my - s * my;
    m = matMult3x3([s,0,0, 0,s,0, x,y,1], [sf,0,0, 0,sf,0, tx,ty,1]);
    sf = m[0];
    tx = m[6];
    ty = m[7];
} );

In this case this can be simplified:

window.addEventListener("wheel", function(e) {

    let mx = mouseX;
    let my = mouseY;

    let s = e.deltaY > 0 ? 1.05 : 0.95;

    sf = sf * s;
    tx = mx - s * mx + s * tx;
    ty = my - s * my + s * ty;
} );

See the example. The rectangle can be scaled from the mouse cursor position either by the mouse wheel or the +/- keys:

let sf = 1, tx = 0, ty = 0;

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(127);
  translate(tx, ty);
  scale(sf);
  rect(100, 100, 100, 100);
}

function applyScale(s) {
    sf = sf * s;
    tx = mouseX * (1-s) + tx * s;
    ty = mouseY * (1-s) + ty * s;
}

window.addEventListener("wheel", function(e) {
    applyScale(e.deltaY > 0 ? 1.05 : 0.95);
} );

function keyPressed() {
    if (key == '-') {
        applyScale(0.95);
    } else if (key == '+') {
        applyScale(1.05);
    } 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • I don't fully understand where Matrix manipulation comes in here or what you mean by *incrementally* because p5 resets the transforms after every draw cycle. – J-Cake Jul 21 '19 at 10:39
  • @JacobSchneider *"incrementally"* means in this context, that each new scale depends on all the previous scales. p5.js hides the matrix multiplication. Operations like `transform` and `scale` set a matrix and multiply the current matrix by the new matrix. But what has to be don in this case is to multiply the new matrix by the current matrix, this can't be done by the existing operations. – Rabbid76 Jul 21 '19 at 10:41