Processing Picking up ellipse from screen

Viewed 73

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(){
    
    
  }
}
1 Answers

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 + "]";
  }
}
Related