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