Getting weird result on the distance Function

edited November 2017 in Questions about Code

The general program is this: I have a target square to match, and a "cursor square" to manipulate. So first I match the centers, then I'm rotating and scaling:

(The red square is the target. I need to change the white outlined square)

Match the centers

My design is to drag the cursor square's corner to the target square's corner, which in the process will scale and rotate to match the square.

The pushMatrix() is set so that the origin is at the center of the cursor square. (Square mode is CENTER too.)

Right now, The rotation is working but the size is weird. Here's one example:

weird size

It should match the red square, but right now it's too huge. I printed the distance from my mouse to the center of the square and it's a rather large number...

My suspicion is, I had to use P3D renderer so I could use modelX(), even though the whole thing is just 2D. Somewhere in the 3D might have messed the distance up. But I'm not sure.. I did set Z to 0 every time I have the chance..

Here's an edited version of my code. The parameters for rotation, square size and translation are GLOBAL. I'll really appreciate it if you could take a look!

void draw() {
  //omitted background and other things..
  //===========DRAW TARGET SQUARE=================
  pushMatrix();
  translate(width/2, height/2, 0); //center the drawing coordinates to the center of the screen
  Target t = targets.get(trialIndex);//generated somewhere else
  translate(t.x, t.y, 0); //center the drawing coordinates to the center of the screen
  rotateZ(radians(t.rotation));
  //omitted fill and strokes
  popMatrix();

  //===========DRAW CURSOR SQUARE=================
  pushMatrix();
  translate(width/2, height/2, 0); //center the drawing coordinates to the center of the screen
  translate(screenTransX, screenTransY, 0);
  rotateZ(radians(screenRotation));

  //omitted stroke and fills

  rect(0,0, size, size);

  //record the corner coordinates using modelX
  //up-left
  ULX = modelX(-size/2, -size/2, 0);
  ULY = modelY(-size/2, -size/2, 0);

  //.....
  //omitted code for other corners
  //other stuff......
}

void mouseMoved()
{
  //omitted other code

  //rotate and scale the square once the centers line up
  if (mode == "rotScale" && rotatingCorner != ""){

    //THIS LINE IS THE PROBLEM
    size = sqrt(2)* dist(mouseX, mouseY, 0, (ULX+URX)/2, (ULY+DLY)/2, 0);
    //THE LINE ABOVE IS THE PROBLEM

    //adding multiplies of 45 since I'm rotating the corners 
    if (rotatingCorner == "UR") screenRotation = degrees(atan2(mouseY-(ULY+DRY)/2, mouseX-(ULX+DRX)/2))-45;
    if (rotatingCorner == "DR") screenRotation = degrees(atan2(mouseY-(ULY+DRY)/2, mouseX-(ULX+DRX)/2))+45;
    if (rotatingCorner == "UL") screenRotation = degrees(atan2(mouseY-(ULY+DRY)/2, mouseX-(ULX+DRX)/2))-135;
    if (rotatingCorner == "DL") screenRotation = degrees(atan2(mouseY-(ULY+DRY)/2, mouseX-(ULX+DRX)/2))+135;
  }

}

void mousePressed()
{
  //decide which corner the user just picked up
  if (mode == "rotScale"){
    if (dist(ULX, ULY, 0, mouseX, mouseY, 0)<5){
      rotatingCorner = "UL";
    //omitted code for other corners
    rotating = true;

    println(dist(mouseX, mouseY, 0, (ULX+URX)/2, DLY - (ULY+DLY)/2, 0));
  }
}
Tagged:

Answers

  • Share code that actually runs. Include your setup and global variables; they could matter. For example, which renderer are you using?

  • ...ah, I just saw in your comments: P3D. That was my first guess.

  • edited November 2017

    Thank you for your reply! As stated in the question, I'm using P3D so I could use the modelX() functions

    I was trying to refrain from posting the entire thing which might make the post quite unreadable. But if you're interested, here is the entire code

    import java.util.ArrayList;
    import java.util.Collections;
    
    //these are variables you should probably leave alone
    int index = 0;
    int trialCount = 8; //this will be set higher for the bakeoff
    float border = 0; //have some padding from the sides
    int trialIndex = 0; //what trial are we on
    int errorCount = 0;  //used to keep track of errors
    float errorPenalty = 0.5f; //for every error, add this to mean time
    int startTime = 0; // time starts when the first click is captured
    int finishTime = 0; //records the time of the final click
    boolean userDone = false;
    
    //customed booleans and modes
    String rotatingCorner = "";
    boolean moving = false;
    boolean rotating = false;
    boolean drawMovingConfirm = false;
    boolean drawHighlightPointYel = false;
    boolean drawHighlightPointCyan = false;
    String mode = "beforeStart";
    
    final int screenPPI = 72; //what is the DPI of the screen you are using
    //you can test this by drawing a 72x72 pixel rectangle in code, and then confirming with a ruler it is 1x1 inch. 
    
    float screenTransX = 0;
    float screenTransY = 0;
    float screenRotation = 0;
    float screenZ = 50f;
    
    //points of a transformed square
    float ULX = screenTransX;
    float ULY = screenTransY;
    float URX = screenTransX;
    float URY = screenTransY;
    float DLX = screenTransX;
    float DLY = screenTransY;
    float DRX = screenTransX;
    float DRY = screenTransY;
    
    //hightlight circle
    float circX = 0;
    float circY = 0;
    float circSize = 5;
    
    //rotating Vectors
    PVector v1, v2;
    
    
    public class Target
    {
      float x = 0;
      float y = 0;
      float rotation = 0;
      float z = 0;
    }
    
    ArrayList<Target> targets = new ArrayList<Target>();
    
    float inchesToPixels(float inch)
    {
      return inch*screenPPI;
    }
    
    void setup() {
      size(800,800,P3D); 
    
      rectMode(CENTER);
      textFont(createFont("Arial", inchesToPixels(.2f))); //sets the font to Arial that is .3" tall
      textAlign(CENTER);
    
      //don't change this! 
      border = inchesToPixels(.2f); //padding of 0.2 inches
    
      for (int i=0; i<trialCount; i++) //don't change this! 
      {
        Target t = new Target();
        t.x = random(-width/2+border, width/2-border); //set a random x with some padding
        t.y = random(-height/2+border, height/2-border); //set a random y with some padding
        t.rotation = random(0, 360); //random rotation between 0 and 360
        int j = (int)random(20);
        t.z = ((j%20)+1)*inchesToPixels(.15f); //increasing size from .15 up to 3.0" 
        targets.add(t);
        println("created target with " + t.x + "," + t.y + "," + t.rotation + "," + t.z);
      }
    
      Collections.shuffle(targets); // randomize the order of the button; don't change this.
    }
    
    void draw() {
    
      background(60); //background is dark grey
      fill(200);
      noStroke();
    
      //shouldn't really modify this printout code unless there is a really good reason to
      if (userDone)
      {
        text("User completed " + trialCount + " trials", width/2, inchesToPixels(.2f));
        text("User had " + errorCount + " error(s)", width/2, inchesToPixels(.2f)*2);
        text("User took " + (finishTime-startTime)/1000f/trialCount + " sec per target", width/2, inchesToPixels(.2f)*3);
        text("User took " + ((finishTime-startTime)/1000f/trialCount+(errorCount*errorPenalty)) + " sec per target inc. penalty", width/2, inchesToPixels(.2f)*4);
        return;
      }
    
      //===========DRAW TARGET SQUARE=================
      pushMatrix();
      translate(width/2, height/2,0); //center the drawing coordinates to the center of the screen
      Target t = targets.get(trialIndex);
      translate(t.x, t.y, 0); //center the drawing coordinates to the center of the screen
      rotateZ(radians(t.rotation));
      fill(255, 0, 0); //set color to semi translucent
      rect(0, 0, t.z, t.z);
      stroke(255, 255, 255);
      fill(255, 255, 255);
      ellipse(0,0,circSize,circSize);
      popMatrix();
    
      //===========DRAW CURSOR SQUARE=================
      pushMatrix();
      translate(width/2, height/2, 0); //center the drawing coordinates to the center of the screen
      translate(screenTransX, screenTransY, 0);
      //if it's in rotation mode, move the origin to one of the corners
      rotateZ(radians(screenRotation));
      noFill();
      strokeWeight(3f);
      stroke(160);
      rect(0,0, screenZ, screenZ);
      //up-left
      ULX = modelX(-screenZ/2, -screenZ/2, 0);
      ULY = modelY(-screenZ/2, -screenZ/2, 0);
      //up-right
      URX = modelX(screenZ/2, -screenZ/2, 0);
      URY = modelY(screenZ/2, -screenZ/2, 0);
      //down-left
      DLX = modelX(-screenZ/2, screenZ/2, 0);
      DLY = modelY(-screenZ/2, screenZ/2, 0);
      //down-right
      DRX = modelX(screenZ/2, screenZ/2, 0);
      DRY = modelY(screenZ/2, screenZ/2, 0);
      popMatrix();
    
      //===========DRAW HIGHLIGHTING CIRCLES=================
      if (drawHighlightPointYel == true) {
        fill(255,255,0);
        stroke(255,255,0);
        ellipse(circX, circY, circSize, circSize);
      }
      if (drawHighlightPointCyan == true) {
        fill(0,255,255);
        stroke(0,255,255);
        ellipse(circX, circY, circSize, circSize);
      }
    
      //===========DRAW EXAMPLE CONTROLS=================
      fill(255);
      text("Trial " + (trialIndex+1) + " of " +trialCount, width/2, inchesToPixels(.5f));
    
      //===========DRAW CONFIRMATION TO MOVE ON=================
      if (drawMovingConfirm == true) movingConfirm();
    }
    
    void movingConfirm(){
      textSize(14);
      text("confirm moving?", DLX+screenZ, DLY+20); 
      text("yes", DLX+screenZ, DLY+40); 
      fill(0, 102, 153);  
    }
    
    void mouseMoved()
    {
      if (mode == "start" || mode == "changePos"){
        if ((ULX <= mouseX && mouseX<= URX) && (ULY <= mouseY && mouseY<= DLY)){
          circX = (ULX+URX)/2;
          circY = (ULY+DLY)/2;
          drawHighlightPointYel = true;
        }
        else drawHighlightPointYel = false;
      }
      if (mode =="changePos" && drawMovingConfirm == false){
        screenTransX+= mouseX - pmouseX;
        screenTransY-= pmouseY - mouseY;
      }
      if (mode == "rotScale" && rotatingCorner==""){
        //up-left corner
        if (dist(ULX, ULY, 0, mouseX, mouseY, 0)<5){
          circX = ULX;
          circY = ULY;
          drawHighlightPointCyan = true;
        }
        //up-right corner
        else if (dist(URX, URY, 0, mouseX, mouseY, 0)<5){
          circX = URX;
          circY = URY;
          drawHighlightPointCyan = true;
        }
        //down-left corner
        else if (dist(DLX, DLY, 0, mouseX, mouseY, 0)<5){
          circX = DLX;
          circY = DLY;
          drawHighlightPointCyan = true;
        }
        //down-right corner
        else if (dist(DRX, DRY, 0, mouseX, mouseY, 0)<5){
          circX = DRX;
          circY = DRY;
          drawHighlightPointCyan = true;
        }
        else drawHighlightPointCyan = false;
      }
      if (mode == "rotScale" && rotatingCorner != ""){
        v2 = new PVector(mouseX - (ULX+URX)/2, mouseY - (ULY+DLY)/2, 0);
        screenZ = sqrt(2)* dist(mouseX, mouseY, 0, (ULX+URX)/2, (ULY+DLY)/2, 0);
        if (rotatingCorner == "UR") screenRotation = degrees(atan2(mouseY-(ULY+DRY)/2, mouseX-(ULX+DRX)/2))-45;
        if (rotatingCorner == "DR") screenRotation = degrees(atan2(mouseY-(ULY+DRY)/2, mouseX-(ULX+DRX)/2))+45;
        if (rotatingCorner == "UL") screenRotation = degrees(atan2(mouseY-(ULY+DRY)/2, mouseX-(ULX+DRX)/2))-135;
        if (rotatingCorner == "DL") screenRotation = degrees(atan2(mouseY-(ULY+DRY)/2, mouseX-(ULX+DRX)/2))+135;
      }
    
    }
    
    void mousePressed()
    {
      if (startTime == 0) //start time on the instant of the first user click
      {
        startTime = millis();
        println("time started!");
        mode = "start";
        drawHighlightPointYel = true;
      }
      if (mode == "start")
      {
        if (dist((ULX+URX)/2, (ULY+DLY)/2, 0, mouseX, mouseY, 0)<5){
          mode = "changePos";
          drawMovingConfirm = false;
        }
      }
      else if (mode == "changePos"){
        mode = "start";
        drawMovingConfirm = true;
    
      }
      if ((drawMovingConfirm == true) && ((width-mouseX)<150) && ((height-mouseY)<150)){
        drawMovingConfirm = false;
        mode = "rotScale";
        }
      if (mode == "rotScale"){
        if (dist(ULX, ULY, 0, mouseX, mouseY, 0)<5){
          rotatingCorner = "UL";
          v1 = new PVector(ULX - (ULX+URX)/2, ULY - (ULY+DLY)/2, 0);
        }
        if (dist(URX, URY, 0, mouseX, mouseY, 0)<5){
          rotatingCorner = "UR";
          v1 = new PVector(URX - (ULX+URX)/2, URY - (ULY+DLY)/2, 0);
        }
        if (dist(DLX, DRY, 0, mouseX, mouseY, 0)<5){
          rotatingCorner = "DL";
          v1 = new PVector(DLX - (ULX+URX)/2, DLY - (ULY+DLY)/2, 0);
        }
        if (dist(DRX, DRY, 0, mouseX, mouseY, 0)<5){
          rotatingCorner = "DR";
          v1 = new PVector(DRX - (ULX+URX)/2, DRY - (ULY+DLY)/2, 0);
        }
        rotating = true;
        println(dist(mouseX, mouseY, 0, (ULX+URX)/2, DLY - (ULY+DLY)/2, 0));
      }
    }
    
    //probably shouldn't modify this, but email me if you want to for some good reason.
    public boolean checkForSuccess()
    {
        Target t = targets.get(trialIndex); 
        boolean closeDist = dist(t.x,t.y, 0, screenTransX,screenTransY, 0)<inchesToPixels(.05f); //has to be within .1"
      boolean closeRotation = calculateDifferenceBetweenAngles(t.rotation,screenRotation)<=5;
        boolean closeZ = abs(t.z - screenZ)<inchesToPixels(.05f); //has to be within .1"    
    
      println("Close Enough Distance: " + closeDist + " (cursor X/Y = " + t.x + "/" + t.y + ", target X/Y = " + screenTransX + "/" + screenTransY +")");
      println("Close Enough Rotation: " + closeRotation + " (rot dist="+calculateDifferenceBetweenAngles(t.rotation,screenRotation)+")");
        println("Close Enough Z: " +  closeZ + " (cursor Z = " + t.z + ", target Z = " + screenZ +")");
    
        return closeDist && closeRotation && closeZ;    
    }
    
    //utility function I include
    double calculateDifferenceBetweenAngles(float a1, float a2)
      {
         double diff=abs(a1-a2);
          diff%=90;
          if (diff>45)
            return 90-diff;
          else
            return diff;
     }
    
  • It is a bit hard to follow your code. Can you comment more on

    I had to use P3D renderer so I could use modelX(),

    For step 1, if you want to match the center of the red square and the square cursor, you wouldn't need modelX. A center is a point and it is not affected but rotations.

    Step 2: So what I understand, after you match the center, one can trigger the second state where one would match the size and rotation of the cursor square to the target square. You know the size and rotation of the red square (since it was placed when you program was started). You wouldn't need modelX either. I could be missing the point of your program, but what I can see is that going 3D you are making a 2D problem into a 3D problem aka. more complex.

    When you match the center of the squares, what usually happen? Do you measure the distances? Do you score it? What happen if there is an offset between the centers? Likewise, what metrics do you use when matching size and rotation which are done simultaneously? If you explain this part of the program, then I can try to understand your code.

    Kf

  • edited November 2017

    Thank you so much for replying! About the code, it's more of an hci experiment than a graphical design exercise. It's about designing and studying an intuitive system that allows people to quickly and accurately match shapes when there's no snapping. One of the goal is to make all the transformations continuous, which increased the difficulty quite a bit. Subsequent studies and testings will be done.

    About why I used modelX: The first idea I had was to** track the corners**. You move the corners to match the position, then after it's matched, click another corner to rotate&scale.. This required knowing the position of the corners. That idea was kind of abandoned, since it would require a second transformation matrix to move the origin to the corner in the rotating stage, which I found hard to do without moving the shape itself. However, I still find this idea more intuitive.

    Even in this idea of matching centers, modelX is still somewhat useful. You track which corner the user clicked, and that decides the rotation from that corner, so it doesn't throw the user off.

    I will go back to try to write everything without the modelX later tonight.

  • Here is a demonstration. This program is not as complete as yours, so bare with me. I have created a series of states.

    When the program begins, it is in IDLE. Press '1' to change to center select. Press '2' to change to rotation+resizing select. Press any other number to reset to idle. Press the space bar to get the info of the current target and cursor objects.

    To move the cursor's center, you need to press 1 and then left click +hold the cursor's center to drag the cursor square. Release the left button to release the cursor square into its new position. Notice this only works if you click close to the center of the cursor square. If the center of the two objects are "close", the border will change from gray to yellow.

    Similarly, to rotate, you do the following. Not mandatory but you want to first align the cursor's center with the target's center. Then, you press 2 and then you click the top left corner of the cursor's square (you have to be click as close as you can to this corner) and then you have two options:

    1. Move up or down to resize the cursor square
    2. move left or right to adjust its angle.

    Extra to do is that when you align the cursor's top left corner to the biggest blue corner, the figures are align. I did not implement that part. However, I do provide the position of those corners in the tCorners array. I hope this helps.

    Kf

    //===========================================================================
    // FINAL FIELDS:
    final color NORMALCOL=color(150);
    final color COLSECOL=color(250, 250, 0);
    
    //STATES
    final int IDLE=0;
    final int CENTERSEL=1;
    final int ROTATIONSEL=2;
    
    //===========================================================================
    // GLOBAL VARIABLES:
    
    float tRot, cRot;
    float tSize, cSize;
    PVector tPos, cPos;
    color cColor;
    
    int state;
    float thresh=3;
    
    boolean shiftCursor=false;
    boolean rotateCursor=false;
    
    PVector[] tCorners;
    float tempCsize;
    float tempCrot;
    PVector tempCpos;
    
    //===========================================================================
    // PROCESSING DEFAULT FUNCTIONS:
    
    void settings() {
      size(400, 600);
    }
    
    void setup() {
    
      textAlign(CENTER, CENTER);
      rectMode(CENTER);
    
      fill(255);
      strokeWeight(2);
    
      //init target
      tRot=random(5, 360);
      tSize=random(80, width/4);
      tPos=new PVector(random(tSize, width-tSize), random(tSize, height-tSize));
      tCorners=new PVector[4];
    
      //Init cursor
      cRot=0;
      cSize=25;
      cPos=new PVector(width/2, height/2);
    
    
      tempCsize=0;
      tempCrot=0;
      tempCpos=new PVector();
    }
    
    
    
    void draw() {
    
      surface.setTitle("State="+(state==IDLE?"Idle":(state==CENTERSEL?"Center Sel":"Rotation Sel")));
    
    
      if (shiftCursor)
        cPos.set(mouseX, mouseY);
    
      if (rotateCursor) {
        cSize=tempCsize-(tempCpos.y-mouseY);
        if (cSize<5)
          cSize=5;
    
        cRot=tempCrot+(map(tempCpos.x-mouseX, -200, 200, -360, 360));
      }
    
      if (cPos.dist(tPos)<thresh)
        cColor=COLSECOL;
      else
        cColor=NORMALCOL;
    
      background(0);
      drawTarget();
      drawCursor(cColor);
    }
    
    void keyReleased() {
    
      if (keyCode=='h' || keyCode==' ') {
    
        println("Current target Size="+tSize+" rot="+tRot+" pos="+tPos);
        println("Current cursor Size="+cSize+" rot="+cRot+" pos="+cPos);
      }
    
      if (keyCode>='1'&&keyCode<='9') {
    
        if (keyCode=='1')
          state=CENTERSEL;
        else if (keyCode=='2')
          state=ROTATIONSEL;
        else
          state=IDLE;
    
        shiftCursor=false;
        rotateCursor=false;
      }
    }
    
    
    
    void mousePressed() {
    
      if (state==CENTERSEL) {
        if (dist(mouseX, mouseY, cPos.x, cPos.y)<thresh) {
          shiftCursor=true;
        }
      }
    
      if (state==ROTATIONSEL) {
        if (dist(mouseX, mouseY, cPos.x-cSize/2, cPos.y-cSize/2)<thresh) {
          rotateCursor=true;
          tempCsize=cSize;
          tempCrot=cRot;
          tempCpos=cPos.copy();
        }
      }
    }
    
    void mouseReleased() {
    
      if (state!=IDLE) {
        shiftCursor=false;
        rotateCursor=false;
      }
    }
    
    
    //===========================================================================
    // OTHER FUNCTIONS:
    
    void drawTarget() {
    
      pushMatrix();
      fill(255, 0, 0);
      noStroke();
      translate(tPos.x, tPos.y);
      rotate(radians(tRot));
      rect(0, 0, tSize, tSize);
      popMatrix();
    
      tCorners[0]=tPos.copy();
      tCorners[0].sub(new PVector(tSize/2*(cos(radians(tRot))-sin(radians(tRot))), tSize/2*(sin(radians(tRot))+cos(radians(tRot)))));
    
      tCorners[1]=tPos.copy();
      tCorners[1].sub(new PVector(tSize/2*(cos(radians(tRot+90))-sin(radians(tRot+90))), tSize/2*(sin(radians(tRot+90))+cos(radians(tRot+90)))));
    
      tCorners[2]=tPos.copy();
      tCorners[2].add(new PVector(tSize/2*(cos(radians(tRot))-sin(radians(tRot))), tSize/2*(sin(radians(tRot))+cos(radians(tRot)))));
    
      tCorners[3]=tPos.copy();
      tCorners[3].add(new PVector(tSize/2*(cos(radians(tRot+90))-sin(radians(tRot+90))), tSize/2*(sin(radians(tRot+90))+cos(radians(tRot+90)))));
    
      fill(0, 0, 255);
      ellipse(tCorners[0].x, tCorners[0].y, 15, 15);
      ellipse(tCorners[1].x, tCorners[1].y, 12, 12);
      ellipse(tCorners[2].x, tCorners[2].y, 9, 9);
      ellipse(tCorners[3].x, tCorners[3].y, 5, 5);
    }
    
    void drawCursor(color c) {
    
      pushMatrix();
      noFill();
      stroke(c);
      strokeWeight(4);
      translate(cPos.x, cPos.y);
      rotate(radians(cRot));
      rect(0, 0, cSize, cSize);
      popMatrix();
    }
    
Sign In or Register to comment.