rochismo
rochismo

Reputation: 63

Make a circle not be repelled by another circle when it overlaps?

I've written a code where there are two circles that follow the mouse. I managed to make collision detection, it works just fine for the moment but I cannot make the circles stay together without being repelled.

I have tried by using a boolean that is set to true whenever they overlap and then I check if that boolean is true and I multiply the acceleration by -1 again, but it just doesn't work since they will just "merge".

I have also tried adding the other circle's radius to its position but it just makes a "teleport". This is not school homework, it's a personal project :) )

EDIT: The expected behavior is to not be repelled by the other circle but stay together and move around the circle and advance to the mouse position without being repelled. Just like on the game agar.io, when you split the cells when they are moving and are colliding they do not repel but they move around each other smoothly.

// Setting all up
const canvas = document.getElementById("cv");
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;

// Math Variables and utilities
const PI = Math.PI;
const TWO_PI = PI * 2;
const HALF_PI = PI / 2;

const random = (n1, n2 = 0) => {
  return Math.random() * (n2 - n1) + n1;
};

const distance = (n1, n2, n3, n4) => {
  let dX = n1 - n3;
  let dY = n2 - n4;
  return Math.sqrt(dX * dX + dY * dY);
};

let circles = []; // Array that stores the two circles
let mouse = {
  x: 0,
  y: 0
}; // mouse object

// Creating the circle class

function Circle(px, py, r, ctx) {
  this.x = px; // X Position
  this.y = py; // Y Position
  this.r = r; // Radius
  this.ctx = ctx; // Canvas context
  this.acc = 0.005; // HardCoded acceleration value

  // Draw circle function
  this.show = function() {
    this.ctx.beginPath();
    this.ctx.fillStyle = "#fff";
    this.ctx.arc(this.x, this.y, this.r, 0, TWO_PI, false);
    this.ctx.fill();
  };

  this.update = function(x, y) {
    // Distance between the mouse's X and Y coords and circle's         // X and Y coords
    let dist = {
      x: x - this.x,
      y: y - this.y
    };

    // Distance formula stated above
    let d = distance(x, y, this.x, this.y);
    if (d > 1) {
      this.x += dist.x * this.acc;
      this.y += dist.y * this.acc;
    }
  };

  // Circle collision
  this.collides = function(other) {
    let d1 = distance(this.x, this.y, other.x, other.y);
    if (d1 <= other.r + this.r) {
      //this.acc *= -1;
      // Do stuff to make the circle that collides to round the other
      // Without getting inside it
    }
  }
}

// Generating the circles
const genCircles = () => {
  // Just generating two circles
  for (let i = 0; i < 2; i++) {
    circles.push(new Circle(random(canvas.width / 2), random(canvas.height / 2), 50, ctx));
  }
};
genCircles();

// Displaying and updating the circles
const showCircles = () => {
  for (let i = 0; i < circles.length; i++) {
    // Mouse Event to update mouse's coords
    canvas.addEventListener("mousemove", (e) => {
      mouse.x = e.x;
      mouse.y = e.y;
    }, true);
    // Iterating over the circles to check for collision
    for (let j = 0; j < circles.length; j++) {
      if (i !== j) {
        circles[i].collides(circles[j])
      }
    }
    // Doing the movement and the display functions
    circles[i].update(mouse.x, mouse.y);
    circles[i].show();
  }
};

// Loop to make it run
const update = () => {
  requestAnimationFrame(update);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#000";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  showCircles();
};

update();
html body {
  margin: 0;
  padding: 0;
  overflow: hidden;
  display: block;
}
<canvas id="cv"></canvas>

Upvotes: 3

Views: 152

Answers (1)

Joseph Marikle
Joseph Marikle

Reputation: 78520

It's very far from perfect, but here's what I've come up with: You can update the "center" x and y of each circle based on collided circles. for every collided circe, the x and y become a middle point between the two center values of those circles. These values are not set to this.x or this.y or they would jump oddly. Instead, this new "relative" x and y are only calculated and used when determining the distance traveled for every draw. The bottom snippet does not work exactly correctly because it doesn't end up at the right x,y in the end, but gives you an idea of what it's supposed to do. Anyone else may feel free to build on what I have here in their own answer or an edit to mine.

// Setting all up
const canvas = document.getElementById("cv");
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;

// Math Variables and utilities
const PI = Math.PI;
const TWO_PI = PI * 2;
const HALF_PI = PI / 2;

const random = (n1, n2 = 0) => {
  return Math.random() * (n2 - n1) + n1;
};

const distance = (n1, n2, n3, n4) => {
  let dX = n1 - n3;
  let dY = n2 - n4;
  return Math.sqrt(dX * dX + dY * dY);
};

let circles = []; // Array that stores the two circles
let mouse = {
  x: 0,
  y: 0
}; // mouse object

// Creating the circle class

function Circle(px, py, r, ctx) {
  this.x = px; // X Position
  this.y = py; // Y Position
  this.r = r; // Radius
  this.ctx = ctx; // Canvas context
  this.acc = 0.005; // HardCoded acceleration value

  // Draw circle function
  this.show = function() {
    this.ctx.beginPath();
    this.ctx.fillStyle = "#fff";
    this.ctx.arc(this.x, this.y, this.r, 0, TWO_PI, false);
    this.ctx.fill();
  };

  this.update = function(x, y) {
    // Distance between the mouse's X and Y coords and circle's         // X and Y coords
    
    var reletave = {x: this.x, y: this.y};
    
    circles.forEach((cir) => {
      if(cir === this) return;
      if(this.collides(cir))  {
        var floor = {x: Math.floor(cir.x, reletave.x), y: Math.floor(cir.y, reletave.y)};
        var dist = {x: Math.abs(cir.x - reletave.x), y: Math.abs(cir.y - reletave.y)};
        reletave.x = floor.x + dist.x;
        reletave.y = floor.y + dist.y;
      }
    })
    
    let dist = {
      x: x - reletave.x,
      y: y - reletave.y
    };

    // Distance formula stated above
    let d = distance(x, y, reletave.x, reletave.y);
    if (d > 0) {
      this.x += dist.x * this.acc;
      this.y += dist.y * this.acc;
    }
  };

  // Circle collision
  this.collides = function(other) {
    let d1 = distance(this.x, this.y, other.x, other.y);
    return d1 <= other.r + this.r;
  }
}

// Generating the circles
const genCircles = () => {
  // Just generating two circles
  for (let i = 0; i < 2; i++) {
    var collides = true;
    while(collides){
      var circleAdd = new Circle(random(canvas.width / 2), random(canvas.height / 2), 50, ctx);
      collides = false;
      circles.forEach((cir) => {
        collides = circleAdd.collides(cir) ? true : collides;
      });
      if(collides) delete circleAdd;
    }
    circles.push(circleAdd);
  }
};
genCircles();

// Displaying and updating the circles
const showCircles = () => {
  for (let i = 0; i < circles.length; i++) {
    // Mouse Event to update mouse's coords
    canvas.addEventListener("mousemove", (e) => {
      mouse.x = e.x;
      mouse.y = e.y;
    }, true);
    // Iterating over the circles to check for collision
    for (let j = 0; j < circles.length; j++) {
      if (i !== j) {
        if(circles[i].collides(circles[j])) {
          //circles[i].acc = circles[j].acc = 0;
        }
      }
    }
    // Doing the movement and the display functions
    circles[i].update(mouse.x, mouse.y);
    circles[i].show();
  }
};

// Loop to make it run
const update = () => {
  requestAnimationFrame(update);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#000";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  showCircles();
};

update();
html body {
  margin: 0;
  padding: 0;
  overflow: hidden;
  display: block;
}
<canvas id="cv"></canvas>

Upvotes: 1

Related Questions