Search code examples
processing

Processing Picking up ellipse from screen


I have a project that creates an array list of balls (ellipses). When I press my mouse down (left button) and hold it an ellipse follows my mouse. When I let go the ellipse is placed on the screen where my mouse was.

I wanna be able to right click and hold over an ellipse (any random one) and have it following my mouse again like previous. and again if I let go of the mouse button, it should be placed back on the screen where my mouse is currently positioned.

I am struggling to understand how I can find the x y position of the ellipse that is already on the screen and remove the ellipse from the list and have it follow my mouse again.

Any suggestion let me know- Here is my main class

ArrayList<Ball> ballList = new ArrayList<Ball>();boolean touching;
void setup() {
  size(600, 600);

}

void draw() {  
  background(150);

  // if the mouse button is held down, set the ball's coordinates to the mouse coordinates
  if (ballList.size() > 0 && mousePressed && mouseButton==LEFT) {
    ballList.get(ballList.size() - 1).xPos = mouseX;  // 'ballList.get(ballList.size() - 1)' is the java way to get the last item added to an arrayList
    ballList.get(ballList.size() - 1).yPos = mouseY;
  }
 for (Ball b : ballList) {
    b.drawBall();
 }

}

// this method will trigger once every time the user press a mouse button
void mousePressed() {
  if (mouseButton==LEFT) {
    ballList.add(new Ball(mouseX, mouseY));
  }
}

here is my ball class

class Ball {
  float xPos, yPos; 


  Ball(float xPos, float yPos) {
    this.xPos= xPos;
    this. yPos= yPos;
  }
  void drawBall() {
    ellipse(xPos, yPos, 50, 50);
    println("X" + xPos + " Y:"+ yPos);
  }

 
  void moveBall(){
    
    
  }
}

Solution

  • You can check if a ball is under the cursor by checking if the distance(dist()) between the ball's position and the mouse's position:

    if(dist(ball.x, ball.y, mouseX, mouseY) < ball.radius){
      println("mouse over ball");
    }
    

    Currently you're hardcoding the ball diameter (50), but you could easily add a radius property:

    class Ball {
      
      float xPos, yPos; 
      float diameter = 50;
      float radius = diameter * 0.5;
    
      Ball(float xPos, float yPos) {
        this.xPos= xPos;
        this. yPos= yPos;
      }
      void drawBall() {
        ellipse(xPos, yPos, diameter, diameter);
      }
    
    }
    

    The condition can be wrapped in a for loop to do the same check on every ball and into a function which either returns the first ball matching the condition or null (in case there is no ball under the cursor):

    Ball getBall(float x, float y){
      // for each ball
      for(Ball ball : ballList){
        // check if the x,y coordinates are inside any of the existing balls
        if(dist(ball.xPos, ball.yPos, x, y) < ball.radius){
          // return the 1st match
          return ball;
        }
      }
      // return null if nothing was found
      return null;
    }
    

    You're already using classes and functions, but just in case the above syntax looks unfamiliar:

    • the Ball type at the begining of the function replaces void and it means the function must return an object of type Ball (as opposed to void)
    • the return keyword both exits the function but also returns the reference to the ball (if found, null otherwise)

    To discern whether there's a selected ball or not (when switching between left and right click) you could use a Ball variable which initially is null, but gets assigned either then left click is pressed or right click is pressed and there's a mouse coordinates fall within the ball's position/radius.

    Here's a modified version of your code using the above:

    ArrayList<Ball> ballList = new ArrayList<Ball>();
    // reference to selection
    Ball selectedBall;
    
    void setup() {
      size(600, 600);
    }
    
    void draw() {  
      background(150);
      // render all balls
      for (Ball ball : ballList) {
        ball.drawBall();
      }
    }
    
    // this method will trigger once every time the user press a mouse button
    void mousePressed() {
    
      if (mouseButton == LEFT) {
        // reset the selection to the newest ball
        selectedBall = new Ball(mouseX, mouseY);
        // append it to the list
        ballList.add(selectedBall);
        println("added new ball and updated selection", selectedBall);
      }
    
      if (mouseButton == RIGHT) {
        // check if a ball is under the cursor, if so select it
        selectedBall = getBall(mouseX, mouseY);
        println("right click selection", selectedBall);
      }
    }
    
    void mouseDragged() {
      // update dragged ball coordinates if there is a previous selection
      if (selectedBall != null) {
        selectedBall.xPos = mouseX;
        selectedBall.yPos = mouseY;
        println("dagging selected ball", selectedBall);
      }
    }
    
    void mouseReleased() {
      // clear selection
      selectedBall = null;
      println("selection cleared");
    }
    
    Ball getBall(float x, float y) {
      // for each ball
      for (Ball ball : ballList) {
        // check if the x,y coordinates are inside any of the existing balls
        if ( dist(ball.xPos, ball.yPos, x, y) < ball.radius ) {
          // return the 1st match (exits loop and function immediately)
          return ball;
        }
      }
      // return null if nothing was found
      return null;
    }
    
    class Ball {
    
      float xPos, yPos; 
      float diameter = 50;
      float radius   = diameter * 0.5;
    
      Ball(float xPos, float yPos) {
        this.xPos = xPos;
        this.yPos = yPos;
      }
      
      void drawBall() {
        ellipse(xPos, yPos, diameter, diameter);
      }
      // pretty print info
      String toString(){
        return "[Ball x=" + xPos + " y="+ yPos + "]";
      }
    }
    

    I've removed a few unused variables, added toString() (so it displays info nicely using println()) and sprinkled a few optional println() statements so it's easier to see what's going as you test the code.

    Final notes:

    • currently if you left click multiple times you can add mulitple overlapping balls. You can tweak the implementation to update the check if there's a ball there first and if so only add a new ball if there aren't any existing balls at that locations already
    • looping though every ball and checking distance (which uses sqrt()) can get computationally expensive for a large number of balls. At this stage code readability is more important, but in case you code develops into something a lot more complex you could used squared distance instead of dist() and use other optimisation techniques.

    Update Here's a tweaked version of the above sketch which only adds a new ball if there isn't one already at the mouse location (allowing mouse left only dragging):

    ArrayList<Ball> ballList = new ArrayList<Ball>();
    // reference to selection
    Ball selectedBall;
    
    void setup() {
      size(600, 600);
    }
    
    void draw() {  
      background(150);
      // render all balls
      for (Ball ball : ballList) {
        ball.drawBall();
      }
    }
    
    // this method will trigger once every time the user press a mouse button
    void mousePressed() {
      // update selection
      selectedBall = getBall(mouseX, mouseY);
      
      // if there isn't a ball already, add one:
      if (selectedBall == null) {
        ballList.add(new Ball(mouseX, mouseY));
      }
    }
    
    void mouseDragged() {
      // update dragged ball coordinates if there is a previous selection
      if (selectedBall != null) {
        selectedBall.xPos = mouseX;
        selectedBall.yPos = mouseY;
      }
    }
    
    void mouseReleased() {
      // clear selection
      selectedBall = null;
    }
    
    Ball getBall(float x, float y) {
      // for each ball
      for (Ball ball : ballList) {
        // check if the x,y coordinates are inside any of the existing balls
        if ( dist(ball.xPos, ball.yPos, x, y) < ball.radius ) {
          // return the 1st match (exits loop and function immediately)
          return ball;
        }
      }
      // return null if nothing was found
      return null;
    }
    
    class Ball {
    
      float xPos, yPos; 
      float diameter = 50;
      float radius   = diameter * 0.5;
    
      Ball(float xPos, float yPos) {
        this.xPos = xPos;
        this.yPos = yPos;
      }
      
      void drawBall() {
        ellipse(xPos, yPos, diameter, diameter);
      }
      // pretty print info
      String toString(){
        return "[Ball x=" + xPos + " y="+ yPos + "]";
      }
    }
    

    If you want to pick the newly added ball immediately you can off course both add the new ball and update the selection:

    ArrayList<Ball> ballList = new ArrayList<Ball>();
    // reference to selection
    Ball selectedBall;
    
    void setup() {
      size(600, 600);
    }
    
    void draw() {  
      background(150);
      // render all balls
      for (Ball ball : ballList) {
        ball.drawBall();
      }
    }
    
    // this method will trigger once every time the user press a mouse button
    void mousePressed() {
      // update selection
      selectedBall = getBall(mouseX, mouseY);
      
      // if there isn't a ball already, add one:
      if (selectedBall == null) {
        selectedBall = new Ball(mouseX, mouseY);
        ballList.add(selectedBall);
      }
    }
    
    void mouseDragged() {
      // update dragged ball coordinates if there is a previous selection
      if (selectedBall != null) {
        selectedBall.xPos = mouseX;
        selectedBall.yPos = mouseY;
      }
    }
    
    void mouseReleased() {
      // clear selection
      selectedBall = null;
    }
    
    Ball getBall(float x, float y) {
      // for each ball
      for (Ball ball : ballList) {
        // check if the x,y coordinates are inside any of the existing balls
        if ( dist(ball.xPos, ball.yPos, x, y) < ball.radius ) {
          // return the 1st match (exits loop and function immediately)
          return ball;
        }
      }
      // return null if nothing was found
      return null;
    }
    
    class Ball {
    
      float xPos, yPos; 
      float diameter = 50;
      float radius   = diameter * 0.5;
    
      Ball(float xPos, float yPos) {
        this.xPos = xPos;
        this.yPos = yPos;
      }
      
      void drawBall() {
        ellipse(xPos, yPos, diameter, diameter);
      }
      // pretty print info
      String toString(){
        return "[Ball x=" + xPos + " y="+ yPos + "]";
      }
    }