How to select and drag an ellipse in old version of Processing?

Viewed 127

//The following game has been designed as an educational resource
//for Key Stage 1 and 2 children. Children are the future of
//civil engineering, and to inspire them to get involved in the
//industry is important for innovation. However, today the
//national curriculum is very structured, and many children
//can find themselves falling behind even at the age of 7 or 8.
//It is essential that children can be supported with material
//they find difficult, and given the resources to learn in a
//fun and engaging manner.
//One of the topics that many children struggle to grasp is
//fractions. It is necessary to prevent young children feeling
//like STEM subjects are too difficult for them, so that they
//have the opportunity and confidence to explore science and
//engineering subjects as they move into secondary education and
//careers.
//This game intends to set a precedent for teaching complex
//subjects to children in a simple, but fun and interactive 
//manner. It will show them that fractions can be fun, and that
//they are capable, building confidence once they return to 
//the classroom.

//The game will work by challenging the user to split a group
//of balls into three buckets depending on the fraction
//displayed on the bucket.

int number_of_balls;
float bucket_1, bucket_2, bucket_3;
int bucket_1_correct, bucket_2_correct, bucket_3_correct;
PVector basket_position, basket_dimensions;
Ball[] array_of_balls;
int linethickness;



//Random generator to give number of balls, ensuring that 
//they can be divided into the number of buckets available.
void setup()
{
  size(500,500);
  linethickness = 4;
 
  number_of_balls = int(random(1,11))*6;
  
println(number_of_balls);

  bucket_1 = 1/6;
  bucket_2 = 1/2;
  bucket_3 = 1/3;
  
//Working out the correct answers
  
  bucket_1_correct = number_of_balls*bucket_1;
  bucket_2_correct = number_of_balls*bucket_2;
  bucket_3_correct = number_of_balls*bucket_3;
  
println (bucket_1, bucket_2, bucket_3);
println (bucket_1_correct, bucket_2_correct, bucket_3_correct);

//Creating the basket

  basket_position = new PVector(width/4, height/8);
  basket_dimensions = new PVector(width/2, height/4);

//Creating the balls & placing inside basket

  array_of_balls = new Ball[number_of_balls];
  
  for (int index=0; index<number_of_balls; index++)
  {
  array_of_balls[index] = new Ball(); 
  }
  
}
 
//Drawing the balls and basket outline

void draw()
{
  background (125,95,225);
  for (int index=0; index<number_of_balls; index++)
  {
    array_of_balls[index].Draw();
  }
  
  noFill();
  stroke(180,0,0);
  strokeWeight(linethickness);
  rect(basket_position.x, basket_position.y, basket_dimensions.x, basket_dimensions.y);
}

 void mouseDragged()
  {
   if ((mouseX >= (ball_position.x - radius)) && (mouseX <= (ball_position.x + radius)) && (mouseY >= (ball_position.y - radius)) && (mouseY <= (ball_position.y + radius))) 
   {
  ball_position = new PVector (mouseX, mouseY);
   }
  }

 //Ball_class
 
 int radius;
 
 Ball()
 {
   radius = 10;
   ball_position = new PVector (random(basket_position.x + radius + linethickness, basket_position.x + basket_dimensions.x - radius - linethickness), random(basket_position.y + radius + linethickness, basket_position.y + basket_dimensions.y - radius - linethickness));
   colour = color(random(255), random(255), random(255)); 
 }
 
 void Draw()
 {
  noStroke();
  fill(colour); 
  ellipse(ball_position.x,ball_position.y,radius*2,radius*2);
  }
  
 
  
  
    
}

enter image description here

Thanks in advance for your help! I am using Processing 2.2.1 which I know is very out of date, so struggling to find help.

I have a piece of code that has created a number of balls, and I would like to be able to 'drag and drop' these to a different location on the screen as part of an educational game. I've tried playing around with mousePressed() and mouseDragged() but no luck yet. Any advice would be appreciated!

3 Answers

I'm kind of confused about what language you're using. Processing is a wrapper for Java, not JavaScript. Processing.js went up to version 1.6.6 and then was succeeded by p5.js. I'm going to assume you're using p5.js.

I don't know if this is a new thing in p5.js, but for easy, but not very user-friendly click-and-drag functionality I like to use the built-in variable mouseIsPressed.

If the ellipse coordinates are stored in an array of vectors, you might do something like this:

let balls = [];
let radius = 10;

function setup() {
  createCanvas(400, 400);
  for (let i = 0; i < 10; i++) {
    balls.push(createVector(random(width), random(height)));
  }
}

function draw() {
  background(220);
  for (let i = 0; i < balls.length && mouseIsPressed; i++) {
    if (dist(mouseX, mouseY, balls[i].x, balls[i].y) < radius) {
      balls[i] = createVector(mouseX, mouseY);
      i = balls.length;
    }
  }
  for (let i = 0; i < balls.length; i++) {
    ellipse(balls[i].x, balls[i].y, 
            2 * radius, 2 * radius
           );
  }
}

This is the quickest way I could think of, but there are better ways to do it (at least, there are in p5.js). You could make a Ball class which has numbers for x, y, and radius, as well as a boolean for whether it's being dragged. In that class, you could make a method mouseOn() which detects whether the cursor is within the radius (if it's not a circle, you can use two radii: sq((this.x - mouseX)/r1) + sq((this.y - mouseY)/r2) < 1).

When the mouse is pressed, you can cycle through all the balls in the array of balls, and test each of them with mouseOn(), and set their drag boolean to true. When the mouse is released, you can set all of their drag booleans to false. Here's what it looks like in the current version of p5.js:

function mousePressed() {
  for (let i = 0; i < balls.length; i++) {
    balls[i].drag = balls[i].mouseOn();
    if (balls[i].drag) {
      i = balls.length;
    }
  }
}

function mouseReleased() {
  for (let i = 0; i < balls.length; i++) {
    balls[i].drag = false;
  }
}

I hope this helps.

There are a lot of ways to approach this, but one way I could suggest is doing something like this:

// "Ellipse" object

function Ellipse (x, y, width, height) {
    
    // Each Ellipse object has their own x, y, width, height, and "selected" values
    
    this.x = x;
    this.y = y;
    
    this.width = width;
    this.height = height;
    
    this.selected = false;
    
    // You can call the draw function whenever you want something done with the object
    
    this.draw = function() {
        
        // Draw ellipse
        
        ellipse(this.x, this.y, this.width, this.height);
        
        // Check if mouse is touching the ellipse using math
        // https://www.desmos.com/calculator/7a9u1bpfvt
        
        var xDistance = this.x - mouseX;
        var yDistance = this.y - mouseY;
        
        // Ellipse formula: (x^2)/a + (y^2)/b = r^2
        
        // Assuming r = 1 and y = 0:
        
        // 0 + (x^2)/a = 1               Substitute values
        // ((width / 2)^2)/a = 1         x = width / 2 when y = 0
        // a = (width / 2)^2             Move numbers around
        // a = (width^2) / 4             Evaluate
        
        var a = Math.pow(this.width, 2) / 4;
        
        // Assuming r = 1 and x = 0:
        
        // 0 + (y^2)/b = 1               Substitute values
        // ((height / 2)^2)/b = 1        y = height / 2 when x = 0
        // b = (height / 2)^2            Move numbers around
        // b = (height^2) / 4            Evaluate
        
        var b = Math.pow(this.height, 2) / 4;
        
        // x^2
        
        var x2 = Math.pow(xDistance, 2);
        
        // y^2
        
        var y2 = Math.pow(yDistance, 2);
        
        // Check if coordinate is inside ellipse and mouse is pressed
        
        if(x2 / a + y2 / b < 1 && mouseIsPressed) {
            
            this.selected = true;
        }
        
        // If mouse is released, deselect the ellipse
        
        if(!mouseIsPressed) {
            
            this.selected = false;
        }
        
        // If selected, then move the ellipse
        
        if(this.selected) {
            
            // Moves ellipse with mouse
            
            this.x += mouseX - pmouseX;
            this.y += mouseY - pmouseY;
        }
    };
}

// New Ellipse object

var test = new Ellipse(100, 100, 90, 60);

draw = function() {
    
    background(255);
    
    // Do everything associated with that object
    
    test.draw();
};

The math is a bit funky, and I might not be using the right version of Processing, but hopefully you found this at least slightly helpful :)

The way your code is right now doesn't work in the current version of Processing either, but it's a pretty quick fix. I'm going to show you a way to fix that, and hopefully it'll work in the earlier version.

Here's where I think the problem is: when you use mouseDragged(), you try to change ball_position, but you don't specify which ball's position. Here's one solution, changing the mouseDragged() block and the Ball class:

void mouseDragged() {
  for (int i = 0; i < array_of_balls.length; i++) {
    if ((mouseX > (array_of_balls[i].ball_position.x - array_of_balls[i].radius)) && 
        (mouseX < (array_of_balls[i].ball_position.x + array_of_balls[i].radius)) && 
        (mouseY > (array_of_balls[i].ball_position.y - array_of_balls[i].radius)) && 
        (mouseY < (array_of_balls[i].ball_position.y + array_of_balls[i].radius))
       ) {
      array_of_balls[i].ball_position = new PVector (mouseX, mouseY);
      i = array_of_balls.length;
    }
  }
}


//Ball_class

class Ball {
  int radius;
  PVector ball_position;
  color colour;
  Ball() {
    radius = 10;
    ball_position = new PVector (random(basket_position.x + radius + linethickness, basket_position.x + basket_dimensions.x - radius - linethickness), random(basket_position.y + radius + linethickness, basket_position.y + basket_dimensions.y - radius - linethickness));
    colour = color(random(255), random(255), random(255));
  }
  void Draw() {
    noStroke();
    fill(colour); 
    ellipse(ball_position.x, ball_position.y, radius*2, radius*2);
  }
}

P.S. Since you're using a language based in Java, you should probably adhere to the finnicky parts of the language:

  • data types are very strict in Java. Avoid assigning anything that could possibly be a float to a variable that is declared as an int. For example, in your setup() block, you say bucket_1_correct = number_of_balls*bucket_1;. This might seem like not an issue, since number_of_balls*bucket_1 is always going to be a whole number. But since the computer rounds when saving bucket_1 = 1/6, multiplying it by 6 doesn't necessarily give a whole number. In this case, you can just use round(): bucket_1_correct = round(number_of_balls*bucket_1);
  • Regarding data types, you should always declare your variables with their data type. It's a little hard for me to tell, but it looks to me like you never declared ball_position or colour in your Ball class, and you never opened up the class with the typical class Ball {. This might have been a copy/paste error, though.
Related