Zoom/Pan constraints

hi all,

I have a simple code that allows drawing in the Processing canvas. Scrolling the mouse allows you to zoom in and out while press and drag the right button moves the drawing accordingly (PAN).
What I’m trying to do now is define the borders of the canvas. the zoom and pan actions move the origin point everywhere without limits and thus in fact I can never return to the starting point.

I want the first frame to be the absolute limits of the drawing space where it can’t be moved at all. And when I’m zooming in, I can’t Pan over the certain limit. A good example of this would be navigating in Google maps that zoom out always brings you back to same origin point on the X axis.

The code is attached and I would like to hear if anyone has any idea how I should do it.
Thanks!

ArrayList<line> All_draw;

float scale=1;
float translateX = 0.0;
float translateY = 0.0;

void setup(){
//  fullScreen();
  size(1500,800);
  background(51);
  All_draw = new ArrayList<line>();
}
  
void draw(){
  background(51);
 
  pushMatrix();
  
  translate(translateX,translateY);
  stroke(255);
  if (mousePressed == true && scale>=0 && mouseButton!=39) {
    All_draw.add(new line((-translateX+mouseX)/scale, (-translateY+mouseY)/scale, (-translateX+pmouseX)/scale, (-translateY+pmouseY)/scale));}
  scale (scale);
  for (line l : All_draw){
      l.display();}
  
  popMatrix();
}

void mouseDragged(MouseEvent e) {
    if (e.getButton()==39){

      translateX += mouseX - pmouseX;
      translateY += mouseY - pmouseY;}
}


void mouseWheel(MouseEvent e) {

    float p = -e.getCount()/10.0;
    float delta = pow(2, p);
    translateX -= mouseX;
    translateY -= mouseY;
    scale *= delta;
    translateX *= delta;
    translateY *= delta;
    translateX += mouseX;
    translateY += mouseY;

}

class line {
  float x1,y1,x2,y2;
  line (float x1_temp,float y1_temp,float x2_temp,float y2_temp){
    x1=x1_temp;
    y1=y1_temp;
    x2=x2_temp;
    y2=y2_temp;
  }
  
  void display(){
    line(x1, y1, x2, y2);}

}

Just checking I understand correctly: you want to stop the user from zooming out if they have zoomed out a certain amount?

Hi Schred, yes. But more than that, because the zoom is set to the position of the mouse, I want to find a way to limit it to a certain range so that it always returns to the same starting point - the original initial frame.

Then it would probably be best just not to make the zooming relative to the mouse, but relative to the center of the window instead.

So, instead of adding mouseX and mouseY to translateX and translateY, add width / 2 and height / 2. Also, to limit your zooming, introduce the variables ZOOM_MN and ZOOM_MAX and set them to whatever value you like:

final float ZOOM_MIN = 0.2, ZOOM_MAX = 5;

Finally, perform some tests in mouseWheel to see if the user has zoomed out to far. This is how the method should look like:

void mouseWheel(MouseEvent e) {
  float p = -e.getCount()/10.0;

  if (p < 0) {
    if (scale <= ZOOM_MIN)
      return;
  } else if (scale >= ZOOM_MAX)
    return;

  float delta = pow(2, p), w = width /2, h = height / 2;
  translateX -= w;
  translateY -= h;
  scale *= delta;
  translateX *= delta;
  translateY *= delta;
  translateX += w;
  translateY += h;
}

Thanks so much for your reply. But that still doesn’t solve the problem. You can still make an unlimited pan. I want to limit the drawing boundaries to the initial frame. And the zoom and pan operations will only be within those limits.

Then set ZOOM_MIN to 1.

Yes. This limits the zoom level. But what about the Pan? The main question is how to avoid exceeding the boundary of the frame when you have zoom in. and how to return back to the initial frame when you zoom out.

Hmm, okay. Try putting this at the end of mouseWheel:

translateX = max(0, min(translateX, width * scale));
translateY = max(0, min(translateY, height * scale));

This allows you to zoom in only to a certain point. But yes, the end result is good. The question now is how to allow free zoom within the borders and then return to the original. - The code is currently:

ArrayList<line> All_draw;

float scale=1;
float translateX = 0.0;
float translateY = 0.0;
final float ZOOM_MIN = 1, ZOOM_MAX = 5;
final float pan_MIN = 1, pan_MAX = 5;

void setup(){
//  fullScreen();
  size(1500,800);
  background(51);
  All_draw = new ArrayList<line>();
}
  
void draw(){
  background(51);
 
  pushMatrix();
  
  translate(translateX,translateY);
  stroke(255);
  if (mousePressed == true && scale>=0 && mouseButton!=39) {
    All_draw.add(new line((-translateX+mouseX)/scale, (-translateY+mouseY)/scale, (-translateX+pmouseX)/scale, (-translateY+pmouseY)/scale));}
  scale (scale);
  for (line l : All_draw){
      l.display();}
  
  popMatrix();
}

void mouseDragged(MouseEvent e) {
    if (e.getButton()==39){

      translateX += mouseX - pmouseX;
      translateY += mouseY - pmouseY;}
}


void mouseWheel(MouseEvent e) {
  float p = -e.getCount()/10.0;

  if (p < 0) {
    if (scale <= ZOOM_MIN)
      return;
  } else if (scale >= ZOOM_MAX)
    return;

  float delta = pow(2, p), w = width /2, h = height / 2;
  translateX -= w;
  translateY -= h;
  scale *= delta;
  translateX *= delta;
  translateY *= delta;
  translateX += w;
  translateY += h;
  
  translateX = min(0, max(translateX, width * scale));
  translateY = min(0, max(translateY, height * scale));
}

class line {
  float x1,y1,x2,y2;
  line (float x1_temp,float y1_temp,float x2_temp,float y2_temp){
    x1=x1_temp;
    y1=y1_temp;
    x2=x2_temp;
    y2=y2_temp;
  }
  
  void display(){
    line(x1, y1, x2, y2);}

}

In addition, I attach a code that does exactly what I’m looking for but works with image and texture. I don’t know how to implement these navigation actions that will work in my code.
(How to make a 2d Zoom and Pan)

float tx, ty;
float scale;
PImage img;
float xratio, yratio;
float minScale, maxScale;

void setup() {
  //use P2D or P3D to be able to use texture() 
  size(450, 360, P2D);
  
  //load image and calculate window/image ratio
  img = loadImage("1337.jpg");
  xratio = float(width) / img.width;
  yratio = float(height) / img.height;
  
  //minumum scale is calculated to prevent texture wrapping
  minScale = max(xratio, yratio);
  maxScale = 5;
  
  //start zoomed out
  scale = minScale;
}

void draw() {
  
  //calculate width/height of the region of interest in the texture
  float tw = img.width / scale * xratio;
  float th = img.height / scale * yratio;
  
  //limit the left-top texcoords to prevent texture wrapping
  tx = constrain(tx, 0, img.width-tw);
  ty = constrain(ty, 0, img.height-th);
 
  //draw textured rectangle 
  beginShape();
  texture(img);
  vertex(0, 0, tx, ty);
  vertex(width, 0, tx+tw, ty);
  vertex(width, height, tx+tw, ty+th);
  vertex(0, height, tx, ty+th);
  endShape();
}

void mouseDragged() {
  //move left-top texcoord using scaled mouse delta
  tx -= (mouseX-pmouseX)/scale;
  ty -= (mouseY-pmouseY)/scale;
}

void mouseWheel(MouseEvent event) {
  //zoom factor needs to be between about 0.99 and 1.01 to be able to multiply so add 1
  float zoomFactor = -event.getAmount()*.01 + 1; 
  float newScale = constrain(scale * zoomFactor, minScale, maxScale);
  
  //next two lines are the most important lines of the code.
  //subtract mouse in 'old' scale from mouse in 'new' scale and apply that to position.
  tx -= (mouseX/newScale - mouseX/scale);
  ty -= (mouseY/newScale - mouseY/scale);
  scale = newScale;
}

If you don’t want that, you can just remove that else if(scale >= ZOOM_MAX) return; part.

It seems that you do not run the code or you do not understand my intent at all. I thank you for the help but I’ll wait for someone else maybe know how to help me.