0
votes

In this sketch, objects' positions are calculated from left to right from a fixed point. When zooming in or out, the objects will enlarge or decrease from left to right as well.

How do I calculate the offset I need to add to x so that when I zoom in or out the objects expand or shrink based on the mouse's X position (so mouseX will be the origin of zooming)?

The code for the sketch (takes a while to load because images are taken from the internet)

PImage backgroundImage;
PFont font;
int x = 0;
int xoffset = 0;
int deltax = 5;
float scale = 512/30;
float deltascale = 0.02;
ArrayList<Object> ObjectList;
//String[] objectfile;
//String[] configfile;
//int savetimer = 0;

class Object {
  String name;
  float diameter;
  String unit;
  String sprite;
  PImage img;
  int wid;
  int hei;
  int xpos;

  Object(String Oname, float Odiameter, String Ounit, String Oimg) {
    name = Oname;
    diameter = Odiameter;
    unit = Ounit;
    img = loadImage(Oimg);
    sprite = Oimg;
    //img = loadImage(Oimg);
    wid = img.width;
    hei = img.height;
  }
}

void setup() {
  size(1500, 800);
  frameRate(200);
  //objectfile = loadStrings("objects.txt");
  //configfile = loadStrings("config.ini");
  //backgroundImage = loadImage("background.png");
  //font = createFont("Franklin Gothic Book Regular.ttf", 32);
  fill(255,255,255);
  textSize(32);
  textAlign(CENTER, CENTER);
  text("Images are loading (this may take a while)",700,400);
  ObjectList = new ArrayList();
  readObjects();
  //readConfig();
  //loadOffset();
}

void addObject(String Aname, float Adiameter, String Aunit, String Aimg) {
  //if (Aimg.equals("null")) {
  //  ObjectList.add(new Object(Aname, Adiameter, Aunit, "images/"+Aname+".png"));
  //} else {
  ObjectList.add(new Object(Aname, Adiameter, Aunit, Aimg));
  //}
}

void readObjects() {
  //for (int i = 0; i < objectfile.length; i++) {
  //  String[] currentObject = split(objectfile[i], ",");
  //  addObject(currentObject[0], float(currentObject[1]), currentObject[2], currentObject[3]);
  //}
  addObject("RED", 30, "m", "https://s-media-cache-ak0.pinimg.com/736x/8f/1b/cc/8f1bcc72c81d1e9370597f7239ee476a.jpg");
  //addObject("ORANGE", 60, "m", "http://6iee.com/data/uploads/36/489075.jpg");
  addObject("YELLOW", 70, "m", "https://i.stack.imgur.com/aez1V.jpg");
  addObject("GREEN", 100, "m", "http://space-facts.com/wp-content/uploads/mars.jpg");
  addObject("PURPLE", 105, "m", "http://space-facts.com/wp-content/uploads/jupiter.png");
  addObject("PINK", 110, "m", "https://vignette1.wikia.nocookie.net/starwars/images/4/4a/Alderaan.jpg");
  addObject("BROWN", 150, "m", "https://cdn.pixabay.com/photo/2013/07/12/16/33/venus-151142_960_720.png");
  addObject("GRAY", 180, "m", "http://www.lpi.usra.edu/lpi_40th/images/1989/neptune.jpg");
  //addObject("WHITE", 200, "m", "http://www.clipartkid.com/images/19/log-in-sign-up-upload-clipart-Dpr48a-clipart.png");
  addObject("INDIGO", 300, "m", "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/Sun_in_February.jpg/155px-Sun_in_February.jpg");
  addObject("SKYBLUE", 301, "m", "https://upload.wikimedia.org/wikipedia/commons/e/ea/Uranus_(Edited).jpg");
  addObject("BLUE", 1000, "m", "https://vignette2.wikia.nocookie.net/heman/images/3/36/Earth.jpg");
}

//void readConfig() {
//  for (int i = 0; i < configfile.length; i++) {
//    char slash = configfile[i].charAt(0);
//    if (slash != '/') {
//      //getting config values based on line number
//      if (i == 1) {
//        xoffset = int(configfile[i]);
//      } else if (i == 3) {
//        scale = float(configfile[i]);
//      }
//    }
//  }
//}


void draw() {
  //image(backgroundImage, 0, 0);
  fill(0,0,0);
  rect(0, 0, 1500, 800);

  int x = 0 + xoffset;
  for ( Object drawObject : ObjectList ) {   
    float dia = drawObject.diameter;
    //int drawWidthh = int(scale * dia);
    int drawWidth = int(scale * dia);
      int drawHeight = int(drawWidth * drawObject.hei / drawObject.wid);
    if (x+drawObject.img.width >= 0 && x <= 1500) {


      //checks if image is too small to be drawn 

      if (drawWidth > 0 && drawHeight > 0) {
        image(drawObject.img, x, 400-(drawHeight/2),drawWidth, drawHeight);
        //calculates font size relative to object's current size
        int textSize;
        //checks if the name or diameter is longer, then uses the longer one for calculation
        fill(255,255,255);
        if (drawObject.name.length() < str(drawObject.diameter).length()) {
          textSize = int(drawWidth/str(drawObject.diameter).length());
        } else {
          textSize = int(drawWidth/drawObject.name.length());
        }
        if (textSize > 0) {
          textSize(textSize);
          float texty = 418+(drawHeight/2);
          if (texty < (418+(drawHeight/2))) {
            texty = 418;
          }
          text(drawObject.name, x+(drawWidth/2), texty);
          text(drawObject.diameter+drawObject.unit, x+(drawWidth/2), texty+textSize);
        }
      }

    }
    drawObject.xpos = x;
      x += (drawWidth*0.08 + drawWidth);
    //display deltax
    textSize(32);
    text(str(deltax), 1450, 10);
    text(str(deltascale), 1450, 50);
    //display Saved
    //if (savetimer > 0) {
    //  text("Saved", 1450, 90);
    //  //text(str(savetimer),1450,130);
    //}
    //if (savetimer > 0) {
    //  //savetimer -= 1;
    //}
    println(str(scale));
  }
}

void mouseWheel(MouseEvent event) {
  float e = event.getCount();
  if (e < 0) {
    scale *= (1 + deltascale);
  } else if (e > 0) {
    scale *= (1 - deltascale) ;
  }
}

void keyPressed() {
  if (key == CODED) {
    if (keyCode == LEFT) {
      xoffset += deltax;
    } else if (keyCode == RIGHT) {
      xoffset -= deltax;
    } else if (keyCode == UP) {
      deltax += 5;
    } else if (keyCode == DOWN) {
      deltax -= 5;
    }
  }
  if (key == 'r') {
    //reloadObjects();
  } else if (key == 'q') {
    deltascale += 0.02;
  } else if (key == 'a') {
    deltascale -= 0.02;
  }
  if (deltax < 0) {
    deltax = 0;
  } else if (deltascale < 0) {
    deltascale = 0;
  }
}


void keyReleased() {  
  //reloadObjects();
}

void reloadObjects() {
  for ( Object reloadObject : ObjectList ) {
    float dia = reloadObject.diameter;
    //int drawWidthh = int(scale * dia);
    if (reloadObject.xpos > 0 && reloadObject.xpos+reloadObject.img.width <= 1500) {
      reloadObject.img = loadImage(reloadObject.sprite);
    }
  }
}

Use the mouse scroll wheel to zoom in and out and left and right arrow keys to navigate. Thanks !

1

1 Answers

0
votes

You can get away with a bunch of nested transformations keeping track of coordinate spaces using pushMatrix()/popMatrix() calls.

You can follow the 2D Transformations Processing Tutorial for more details.

Here's a quick commented sketch that explains the concept:

//a position to scale from
PVector mouse = new PVector();
//a scale value
float scale = 1.0;
//drawing dimensions - this depends on your actual content
float w = 400;
float h = 400;

void setup(){
  size(400,400);
  rectMode(CENTER);
}
void draw(){
  background(255);
  //move everything to centre
  translate(width * .5, height * .5);
  //isolate coordinate space
  pushMatrix();
  //move in the opposite direction by half of the drawing size => transform from drawing centre
  translate(-w * .5,-h * .5);
  //move to where the transformation centre should be (this can be mixed with the above, but I left it on two lines so it's easier to understand)
  translate(mouse.x,mouse.y);
  //transform from set position
  scale(scale);
  //move the transformation back
  translate(-mouse.x,-mouse.y);
  //draw what you need to draw
  drawStuff();
  //return to global coordinate space
  popMatrix();

  //draw a preview of the transformation centre
  if(mousePressed){
    fill(192,0,0);
    ellipse(mouse.x-width*.5,mouse.y-height*.5,35,35);
  }
}
//just a bunch of boxes as a placeholder
void drawStuff(){
  for(int i = 0 ; i < 400; i++){
    int x = i % 20;
    int y = i / 20;
    pushMatrix();
    translate(x * 20, y * 20);
    rotate(radians(i));
    scale(sin(radians(i)) + 1.1);
    fill(i % 400);
    rect(0,0,15,15);
    popMatrix();
  }
}
//set the transformation centre
void mousePressed(){
  mouse.set(mouseX,mouseY);
}
//change scale by moving mouse on X axis
void mouseDragged(){
  scale = map(mouseX,0,width,0.25,2.0);
}

You can run a demo bellow:

//a position to scale from
var mouse;
//a scale value
var scl = 1.0;
//drawing dimensions - this depends on your actual content
var w = 400;
var h = 400;

function setup(){
  mouse = createVector(0,0);
  createCanvas(400,400);
  rectMode(CENTER);
}
function draw(){
  background(255);
  //move everything to centre
  translate(width * .5, height * .5);
  //isolate coordinate space
  push();
  //move in the opposite direction by half of the drawing size => transform from drawing centre
  translate(-w * .5,-h * .5);
  //move to where the transformation centre should be (this can be mixed with the above, but I left it on two lines so it's easier to understand)
  translate(mouse.x,mouse.y);
  //transform from set position
  scale(scl);
  //move the transformation back
  translate(-mouse.x,-mouse.y);
  //draw what you need to draw
  drawStuff();
  //return to global coordinate space
  pop();
  
  //draw a preview of the transformation centre
  if(mousePressed){
    fill(192,0,0);
    ellipse(mouse.x-width*.5,mouse.y-height*.5,35,35);
  }
}
//just a bunch of boxes as a placeholder
function drawStuff(){
  for(var i = 0 ; i < 400; i++){
    var x = i % 20;
    var y = i / 20;
    push();
    translate(x * 20, y * 20);
    rotate(radians(i));
    scale(sin(radians(i)) + 1.1);
    fill(i % 400);
    rect(0,0,15,15);
    pop();
  }
}
//set the transformation centre
function mousePressed(){
  mouse.set(mouseX,mouseY);
}
//change scale by moving mouse on X axis
function mouseDragged(){
  scl = map(mouseX,0,width,0.25,2.0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.8/p5.js"></script>