I'm trying to build a physics simulation where DOM objects are subjected to gravity and can be dragged and thrown around while still colliding with each other and reaction to the collision in realistic ways.
As a reference: https://lovehotel.world/home
The main issue in doing such a thing is that checking for the collision of the DOM elements and having them to realistically react to it it gets super messy, mainly because we're talking about rectangles from a geometric point of view.
The issues I've been encountering are then many, mainly that it's quite easy to do with ellipses native to p5.js (codepen for that: https://codepen.io/giambrodo/pen/LYBxMZZ)
When it's about building it using links for instance it gets quite messy, I understand that checking for the rectangle collision is super messy compared to a perfect circle (where the distance from center to border is always the same).
I managed to get the links to replace the ellipses: https://editor.p5js.org/giampo/sketches/SzRduNmSm
{ "links": [
{
"label": "who are you?",
"section_link": "who"
},
{
"label": "what do you do?",
"section_link": "what"
},
{
"label": "how do you do that?",
"section_link": "how?"
},
{
"label": "why?",
"section_link": "why"
},
{
"label": "where?",
"section_link": "where"
}
]
}
let numBalls = 13;
let spring = 0.05;
let gravity = 0.05;
let friction = -0.9;
let balls = [];
let menuData;
function preload() {
menuData = loadJSON('menu.json');
}
function setup() {
createCanvas(500, 500);
for (let i = 0; i < menuData.links.length; i++) {
balls[i] = new Ball(
random(width),
random(height),
random(30, 40),
i,
balls
);
}
noStroke();
fill(255, 204);
}
function draw() {
background(0);
balls.forEach(ball => {
ball.collide();
ball.move();
ball.display();
});
}
function mousePressed() {
balls.forEach(ball => {
if ( ball.onBall(mouseX, mouseY))
ball.startDrag();
}
);
}
function mouseDragged() {
balls.forEach(ball => {
if (ball.dragging) {
ball.x = mouseX;
ball.y = mouseY;
}
});
}
function mouseReleased() {
balls.forEach(ball => {
if (ball.dragging) {
// Calculate the ball's new velocity based on how fast the mouse was moving when it was released
ball.vx = (mouseX - ball.mousex) / 10;
ball.vy = (mouseY - ball.mousey) / 10;
// Set the ball's dragging property to false to indicate that it is no longer being dragged
ball.dragging = false;
}
});
}
class Ball {
constructor(xin, yin, din, idin, oin) {
this.x = xin;
this.y = yin;
this.vx = 0;
this.vy = 0;
this.diameter = din;
this.id = idin;
this.others = oin;
this.dragging = false;
this.anchor = createA(menuData.links[this.id].href, menuData.links[this.id].label);
}
onBall(x, y) {
// Get the bounding rect of the anchor element
let rect = this.anchor.elt.getBoundingClientRect();
// Check if the point (x, y) is within the bounding rect
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
}
startDrag() {
this.dragging = true;
this.mousex = mouseX;
this.mousey = mouseY;
}
collide() {
for (let i = this.id + 1; i < menuData.links.length; i++) {
// Calculate distance and minimum distance between rectangles
let dx = this.others[i].anchor.elt.getBoundingClientRect().x - this.anchor.elt.getBoundingClientRect().x;
let dy = this.others[i].anchor.elt.getBoundingClientRect().y - this.anchor.elt.getBoundingClientRect().y;
let distance = sqrt(dx * dx + dy * dy);
let minDist = (this.others[i].anchor.elt.getBoundingClientRect().width + this.anchor.elt.getBoundingClientRect().width) / 2;
// Check if rectangles are colliding
if (distance < minDist) {
// Calculate angle between rectangles
let angle = atan2(dy, dx);
// Calculate target position for rectangles to avoid overlap
let targetX = this.anchor.elt.getBoundingClientRect().x + cos(angle) * minDist;
let targetY = this.anchor.elt.getBoundingClientRect().y + sin(angle) * minDist;
// Calculate force of collision using mass and elasticity
let ax = (targetX - this.others[i].anchor.elt.getBoundingClientRect().x) * spring;
let ay = (targetY - this.others[i].anchor.elt.getBoundingClientRect().y) * spring;
this.vx -= ax;
this.vy -= ay;
this.others[i].vx += ax;
this.others[i].vy += ay;
}
}
}
move() {
if (this.dragging) {
this.x = mouseX;
this.y = mouseY;
} else {
// gravitÃ
this.vy += gravity;
// update ball's position based on its velocity
this.x += this.vx;
this.y += this.vy;
if (this.x + this.diameter / 2 > width) {
this.x = width - this.diameter / 2;
this.vx *= friction;
} else if (this.x - this.diameter / 2 < 0) {
this.x = this.diameter / 2;
this.vx *= friction;
}
if (this.y + this.diameter / 2 > height) {
this.y = height - this.diameter / 2;
this.vy *= friction;
} else if (this.y - this.diameter / 2 < 0) {
this.y = this.diameter / 2;
this.vy *= friction;
}
}
}
display() {
this.anchor.style('position: relative');
this.anchor.position(this.x, this.y);
this.anchor.style('z-index: 3)');
this.anchor.parent('maradona');
this.anchor.style('border-style: solid');
this.anchor.style('border-color: red');
}
}
Can someone help me in finding a better way to deal with this?