# Detect if over object when object is zoomed/scaled

**URL:** <https://discourse.processing.org/t/detect-if-over-object-when-object-is-zoomed-scaled/11557>\
**Category:** Coding Questions\
**Created:** [May 24, 2019, 4:16pm UTC](https://discourse.processing.org/t/detect-if-over-object-when-object-is-zoomed-scaled/11557 "2019-05-24T16:16:44Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![mala](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@mala](https://discourse.processing.org/u/mala)\
**Post date:** [May 24, 2019, 4:16pm UTC](https://discourse.processing.org/t/detect-if-over-object-when-object-is-zoomed-scaled/11557/1 "2019-05-24T16:16:44Z")

</div>

This bascially comes from an attempt at zoom and pan ( **not** using peasyCam ) it’s part of a much bigger project.  
I have a load of ‘cells’ that are clickable and I want to be able to scale them to ‘simulate’ a zoom in 2D, all cells are in thier own PGraphics buffer.  
Scaling them is no real bother, but any attempt I’ve made at getting their new position after being scaled has failed totally.  
I’m sure I’m missing something very obvious here, but I can’t figure out how to find the new x and y positions.  
Can any point me in the right direction please ?

Simplified code below uses just **up** and **down** arrows for simple zoom and ‘r’ to reset.

```auto
/* *************2D zoom & click question****************
 ***---->> ZOOM IN : PRESS DOWN ARROW <<----***
 ***---->> ZOOM OUT : PRESS UP ARROW <<----***
 ***---->> RESET VIEW : PRESS 'r' <<----***
 ********************************************* /

final int cell_size = 66;
final int padding = 2; // just to keep rect stroke within buffer
final int cell_space = 6;
final int startGrid_space = cell_size+cell_space;

int startX = startGrid_space;
int startY = startGrid_space;
float zoom = 1.0;
int cell_count = 100;
ArrayList<Cell> Cells = new ArrayList<Cell>();
int liveCell;
//
boolean bufferDebug = true; 
int nbrBufferUpdates = 0;

void setup() {
  size(864, 864);

  for (int i = 0; i < cell_count; i++) {
    Cells.add(new Cell(this, i));
  }
}

void draw() {
  background(0);

  if (bufferDebug) {
    surface.setTitle(int(frameRate) + " fps" +" BufferUpdates :" +nbrBufferUpdates);
  }

  for (Cell cell : Cells) {
    cell.display();
  }
}

void mouseMoved() {
  liveCell = -1;
  for (Cell cell : Cells) {
    if (cell.isOver()) {
      liveCell = Cells.indexOf(cell);
    }
  }
}

void mouseReleased() {
  for (Cell cell : Cells) {
    if (Cells.indexOf(cell) == liveCell) {
      cell.clicked();
    }
  }
}

void keyReleased() {
  if (key == CODED) {
    switch(keyCode) {

    case UP : //zoom out
      zoom += 0.1;
      zoom = constrain(zoom, 0.5, 1.0);
      break;

    case DOWN : // zoom in
      zoom -= 0.1;
      zoom = constrain(zoom, 0.5, 1.0);
      break;
    }
  } else if (key == 'r') { //reset all cells to start up size & position
    zoom = 1.0;
    for (Cell cell : Cells) {
      cell.setPosition(startX +(cell.index/10)*startGrid_space, startY +(cell.index%10)*startGrid_space);
    }
  }
  println("Zoom :"+zoom);
}

//----------------------------->> CELL CLASS
public class Cell { 
  PGraphics buffer;
  float x, y, w, h;
  boolean invalidBuffer, Over, pre_Over, Active;
  int index;

  Cell(PApplet app, int t_index) {
    index = t_index;
    x = startX + (index/10)*startGrid_space;
    y = startY + (index%10)*startGrid_space; 
    w = cell_size;
    h = cell_size;
    buffer = app.createGraphics(int(w+padding), int(h+padding)); 
    invalidBuffer = true;
  }

  public boolean isOver() {
    if (mouseX >x && mouseX< x+w && mouseY>y && mouseY<y+h ) { // Edit for changed position when zoomed ?
      Over = true;
    } else {
      Over = false;
    }
    if (Over != pre_Over) {
      invalidBuffer = true;
      pre_Over = Over;
    }
    return Over;
  }

  public void clicked() {
    Active = !Active;
    invalidBuffer = true;
  }

  public void setPosition(float t_X, float t_Y) {
    x = t_X;
    y = t_Y;
  }

  public void update() {
    invalidBuffer = true;
  }

  public void display() {
    if (invalidBuffer) {
      updateBuffer();
    }
    pushMatrix();
    scale(zoom);
    translate(x, y);
    image(buffer, 0, 0);
    popMatrix();
  }

  public void updateBuffer()
  {
    if (bufferDebug) {
      nbrBufferUpdates ++;
    }
    buffer.beginDraw();
    buffer.clear() ; 
    buffer.rectMode(CENTER);
    buffer.strokeWeight(2);
    buffer.noFill();
    buffer.stroke(200, 200, 200);
    if (Active) {
      buffer.stroke(0, 200, 0);
    } else if (Over) {
      buffer.stroke(253, 114, 63);
    }
    buffer.rect(w/2, h/2, w-padding, h-padding, 15);  
    buffer.endDraw();
    invalidBuffer = false;
  }
}

```

---

<div class="post-metadata">

**Author:** ![humayung](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/humayung/32/6089_2.png) [@humayung](https://discourse.processing.org/u/humayung)\
**Post date:** [May 24, 2019, 6:04pm UTC](https://discourse.processing.org/t/detect-if-over-object-when-object-is-zoomed-scaled/11557/2 "2019-05-24T18:04:24Z")

</div>

Hi!  
Nice Project!

> [@mala](#):
>
> Scaling them is no real bother, but any attempt I’ve made at getting their new position after being scaled has failed totally.

look at this formula  
 ![Dilatasi-pusat-O-e1511319524357](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/e/e15f5ea7b34382457eb411d12e31223b867a82d5.png)

let say you want

```auto
translate(width/2, height/2);
scale(scale);
circle(x, y);

```

so

```auto
xAfter = x * scale + width/2;
yAfter = y * scale + height/2;

```

if you scale before translate it

```auto
scale(scale);
translate(width/2, height/2);
circle(x, y);

```

it will be

```auto
xAfter = (x + width/2) * scale;
yAfter = (y + height/2) * scale;

```

See the implementation

```auto
void setup() {
  size(500, 500);
  originX = width/2;
  originY = height/2;
  rectMode(CENTER);
}

float originX, originY;
float scale = 1;
float d = 30;
float x = 30;
float y = 30;
void draw() {
  background(51);

  pushMatrix();
  translate(originX, originY);
  scale(scale);
  fill(255, 100);
  noStroke();
  ellipse(x, y, d, d);
  popMatrix();

  float onScreenX = x * scale + originX;
  float onScreenY = y * scale + originY;
  stroke(0, 255, 0);
  drawCoordinates(onScreenX, onScreenY);
  scale = map(mouseX, 0, width, -5, 5);

  fill(255);
  text("Scale: " + scale, 50, 50);
  
}

void mousePressed() {
  originX = mouseX;
  originY = mouseY;
}

void drawCoordinates(float px, float py) {
  strokeWeight(1);
  stroke(255, 0, 0);
  line(-width, 0, width, 0);
  line(0, -height, 0, height);
  line(0, py, px, py);
  line(px, 0, px, py);
  fill(0, 255, 0);
  strokeWeight(5);
  textAlign(CENTER);
  
  text("Origin", originX, originY - 20);
  point(originX, originY);
  text("Click to change transformation origin", width/2, 40);
  textAlign(LEFT);
  text("(" + int(px) + ", " + int(py) + ")", px, py);
  
  text("Postion after transformation", width/2, height-60);
  text("X: " + px, width/2, height-50);
  text("Y: " + py, width/2, height - 40);
}

```

---

<div class="post-metadata">

**Author:** ![mala](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@mala](https://discourse.processing.org/u/mala)\
**Post date:** [May 26, 2019, 1:39pm UTC](https://discourse.processing.org/t/detect-if-over-object-when-object-is-zoomed-scaled/11557/3 "2019-05-26T13:39:55Z")

</div>

@humayung Thank you so much that was a great help and explained very well !

I do have one further question :

If you look at my original example and watch the zoom value printed to console… when this zoom value is between approx. 0.75 - 9.90. The framerate drops considerably from 60fps to as low as 37fps sometimes.  
What could be causing this only in that specific range ?  
The exact same issue occurs when I update my code with your suggestion above, so it is nothing to do with how the values for the scale & translation are arrived at, but a problem of scaling using that range.

---

<div class="post-metadata">

**Author:** ![humayung](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/humayung/32/6089_2.png) [@humayung](https://discourse.processing.org/u/humayung)\
**Post date:** [May 26, 2019, 8:53pm UTC](https://discourse.processing.org/t/detect-if-over-object-when-object-is-zoomed-scaled/11557/4 "2019-05-26T20:53:12Z")

</div>

im not sure how that could happened.  
But i suspected such thing caused by the var zoom itself.

```auto
Zoom :0.9
Zoom :0.79999995
Zoom :0.6999999
Zoom :0.5999999

```

it is quite like doing expensive calculation with these long decimal in the scale() function itself **(im not very sure)**  
try to floor to its second decimal

```auto
void keyReleased() {
  if (key == CODED) {
    switch(keyCode) {

    case UP : //zoom out
      zoom = constrain(zoom +0.1, 0.5, 1.0);
      zoom = floor((zoom * 10))/10f; 
      break;

    case DOWN : // zoom in
      zoom = constrain(zoom - 0.1, 0.5, 1.0);
      zoom = floor((zoom * 10))/10f;
      break;
    }
......
......

```

for another, since the scaling factor are the same for every cell, the calculation would be slightly faster if you scale it once for everything. put it in the draw(), not in the display function in the cell class. it will be executed once rather than do it for every cell, costs you lot.

---

<div class="post-metadata">

**Author:** ![mala](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@mala](https://discourse.processing.org/u/mala)\
**Post date:** [May 27, 2019, 9:39am UTC](https://discourse.processing.org/t/detect-if-over-object-when-object-is-zoomed-scaled/11557/5 "2019-05-27T09:39:19Z")

</div>

Thanks again for the help.  
I had also suspected the long decimal.  
But having tried the floor() and moving the scale() to the draw, neither of these fix the problem.

I’m sure the clue is in the problem scale range 0.75 -\> 0.9 , though what that means I’m not sure.

---

<div class="post-metadata">

**Author:** ![mala](https://avatars.discourse-cdn.com/v4/letter/m/258eb7/32.png) [@mala](https://discourse.processing.org/u/mala)\
**Post date:** [May 27, 2019, 5:06pm UTC](https://discourse.processing.org/t/detect-if-over-object-when-object-is-zoomed-scaled/11557/6 "2019-05-27T17:06:43Z")

</div>

Ignoring fps hit for now, I’ve moved onto putting back in some functions from the larger project  
The cells can be selected and moved about, they should snap to a ‘virtual grid’ and not be allowed to rest on top of another cell.

I have got a bit lost again with trying to track / update postions when zoomed, if you try the code below, it works well enough when scale/zoom is 1.0, but the lower the scale, the less the position keeps up with the cursor.  
I’m not sure if this maybe made worse by my snap and “gridCheck” methods not being very efficient as well ?

```auto
/* *************2D scale & click question MK2****************
 ---->> ZOOM IN/OUT : MOUSEWHEEL 
 ---->> PAN VIEW : LMB & DRAG
 ---->> RE CENTER VIEW : DOUBLE CLICK LMB
 ---->> SELECT/DESELECT CELLS : LMB CLICK
 ---->> DESELECT ALL CELLS : DOUBLE CLICK RMB
 ---->> MOVE SELECTED CELLS : HOLD 'ALT' + LMB & DRAG 
 --->> RESET CELLS TO START GRID : PRESS 'r'
 ********************************************* /

final int cell_size = 66;
final int padding = 2; // just to keep cell stroke within buffer
final int cell_space = 6;
final int grid_space = cell_size+cell_space;
int cell_count = 100;
ArrayList<Cell> Cells = new ArrayList<Cell>();
int liveCell;
//
float scale; 
float originX, originY;
float vx, vy;
boolean altHeld = false;

//
boolean bufferDebug = true; 
int nbrBufferUpdates = 0;

void setup() {
  size(1200, 1200);

  originX = width/2;
  originY = height/2;

  scale = 1.0;

  for (int i = 0; i < cell_count; i++) {
    Cells.add(new Cell(this, i));
  }
}

void draw() {
  background(0);

  if (bufferDebug) {
    surface.setTitle(int(frameRate) + " fps" +" BufferUpdates :" +nbrBufferUpdates);
  }

  for (Cell cell : Cells) {
    cell.display();
  }
}

void mouseClicked(MouseEvent event) {
  if (mouseButton == LEFT) {
    if (event.getCount() == 2) { //re center view
      originX = width/2;
      originY = height/2;
    }
  }
  if (mouseButton == RIGHT) {
    if (event.getCount() == 2) { //de select all cells
      for (Cell cell : Cells) {
        cell.setActiveState(false);
      }
    }
  }
}

void mouseMoved() {
  liveCell = -1;
  for (Cell cell : Cells) {
    if (cell.isOver()) {
      liveCell = Cells.indexOf(cell);
    }
  }
}

void mouseReleased() {
  if (!altHeld) {
    for (Cell cell : Cells) {
      if (Cells.indexOf(cell) == liveCell) {
        cell.clicked();
      }
    }
  }
}

void mousePressed() {
  if (altHeld) { // record offset of cursor position to cell position
    for (Cell cell : Cells) {
      cell.setHeld(mouseX, mouseY);
    }
  }
}

void mouseDragged() { 
  if (altHeld) { //drag a cell to new position
    for (Cell cell : Cells) {
      cell.dragPosition();
    }
  } else { // Pan view
    vx = (pmouseX - mouseX); 
    vy = (pmouseY - mouseY); 
    originX -= vx;
    originY -= vy;
  }
}

void mouseWheel(MouseEvent event) { // scale/zoom view on mouse scroll 
  if (event.getCount()>0) {
    scale+= 0.1;
  } else {
    scale-=0.1;
  }
  scale = constrain(scale, 0.5, 1.0);
  scale = round((scale*10))/10f;
  println(scale);
}

void keyPressed() {
  if (key == CODED) {
    switch(keyCode) {
    case ALT:
      altHeld = true;
      break;
    }
  }
}

void keyReleased() {
  if (key == CODED) {
    switch(keyCode) {

    case ALT:
      altHeld = false;
      break;
    }
  } else {

    switch(key) {

    case 'r': //reset all cells to start up size & position
      scale = 1.0;
      for (Cell cell : Cells) {
        cell.reset();
      }
      break;
    }
  }
}

//----------------------------->> CELL CLASS
public class Cell { 
  PGraphics buffer;
  float x, y, hx, hy, xOffset, yOffset, w, h;
  boolean invalidBuffer, Over, pre_Over, Active, Held;
  int index;

  Cell(PApplet app, int t_index) {
    index = t_index;
    x = (originX-(grid_space*5)) + (index/10)*grid_space - originX;
    y = (originY-(grid_space*5))+ (index%10)*grid_space - originY; 
    w = cell_size;
    h = cell_size;
    buffer = app.createGraphics(int(w+padding), int(h+padding)); 
    invalidBuffer = true;
  }

  public boolean isOver() {
    if (mouseX >x*scale+originX && mouseX< x*scale+originX+(w*scale) && mouseY>y*scale+originY && mouseY<y*scale+originY+(h*scale) ) { 
      Over = true;
    } else {
      Over = false;
    }
    if (Over != pre_Over) {
      invalidBuffer = true;
      pre_Over = Over;
    }
    return Over;
  }

  public void clicked() {
    if (Held) {
      Held = false;
    }
    Active = !Active;
    invalidBuffer = true;
  }

  public void setHeld(float px, float py) {
    if (Active) {
      Held = true;
      hx = px-x;
      hy = py-y;
      invalidBuffer = true;
    }
  }

  public void dragPosition() { //does not follow cursor well when zoomed...
    float new_x = 0;
    float new_y = 0;

    if (Active && Held && altHeld) {
      new_x = (mouseX-hx);
      new_x -=new_x %grid_space;

      new_y = (mouseY-hy);
      new_y -=new_y %grid_space;
      //
      setPosition(new_x, new_y);
    }
  }

  public void setPosition(float t_x, float t_y) {
    if (checkGrid(index, t_x, t_y)) {
      x = t_x;
      y = t_y;
    }
  }

  public boolean checkGrid(int t_Index, float t_X, float t_Y) { // tests if grid-cell already occupied
    for (Cell cell : Cells) {
      if (cell.index != t_Index && t_X == cell.x && t_Y == cell.y) {
        return false;
      }
    }
    return true;
  }

  public void reset() {
    x = originX-(grid_space*5) + (index/10)*grid_space - originX;
    y = originY-(grid_space*5) + (index%10)*grid_space - originY;
  }

  public void setActiveState(boolean t_state) {
    Active = t_state;
    invalidBuffer = true;
  }

  public void update() {
    invalidBuffer = true;
  }

  public void display() {
    if (invalidBuffer) {
      updateBuffer();
    }
    pushMatrix();
    translate(x*scale+originX, y*scale+originY);
    scale(scale);
    image(buffer, 0, 0);
    popMatrix();
  }

  public void updateBuffer()
  {
    if (bufferDebug) {
      nbrBufferUpdates ++;
    }
    buffer.beginDraw();
    buffer.clear() ; 
    buffer.rectMode(CENTER);
    buffer.strokeWeight(2);
    buffer.noFill();
    buffer.stroke(200, 200, 200);
    if (Active) {
      buffer.stroke(0, 200, 0);
    } else if (Over) {
      buffer.stroke(253, 114, 63);
    }
    buffer.rect(w/2, h/2, w-padding, h-padding, 15);  
    buffer.endDraw();
    invalidBuffer = false;
  }
}

```
