xxxxxxxxxx
250
// Matter.js aliases
const Engine = Matter.Engine;
const World = Matter.World;
const Bodies = Matter.Bodies;
const Body = Matter.Body;
const Constraint = Matter.Constraint;
const Composites = Matter.Composites;
const MouseConstraint = Matter.MouseConstraint;
const Mouse = Matter.Mouse;
// Additional Matter.js aliases
const Events = Matter.Events;
let engine;
let world;
let grains = [];
let walls = [];
let heavyItem;
let heavyItemSize = 50;
let dropDelay = 3000;
let heavyItemDropped = false;
let links = [];
let initialVelocities = {};
function handleCollisionStart(event) {
const pairs = event.pairs;
for (let i = 0; i < pairs.length; i++) {
const bodyA = pairs[i].bodyA;
const bodyB = pairs[i].bodyB;
initialVelocities[`${bodyA.id}-${bodyB.id}`] = {
bodyA: { x: bodyA.velocity.x, y: bodyA.velocity.y },
bodyB: { x: bodyB.velocity.x, y: bodyB.velocity.y },
};
}
}
function handleCollisionEnd(event) {
const pairs = event.pairs;
for (let i = 0; i < pairs.length; i++) {
const bodyA = pairs[i].bodyA;
const bodyB = pairs[i].bodyB;
const id = `${bodyA.id}-${bodyB.id}`;
const initialVelocityA = initialVelocities[id].bodyA;
const initialVelocityB = initialVelocities[id].bodyB;
const massA = bodyA.mass;
const massB = bodyB.mass;
const finalVelocityA = { x: bodyA.velocity.x, y: bodyA.velocity.y };
const finalVelocityB = { x: bodyB.velocity.x, y: bodyB.velocity.y };
const forceA =
massA *
Math.sqrt(
Math.pow(finalVelocityA.x - initialVelocityA.x, 2) +
Math.pow(finalVelocityA.y - initialVelocityA.y, 2)
);
const forceB =
massB *
Math.sqrt(
Math.pow(finalVelocityB.x - initialVelocityB.x, 2) +
Math.pow(finalVelocityB.y - initialVelocityB.y, 2)
);
const force = Math.max(forceA, forceB);
// Log collisions for debugging
if (force > 0.3) {
// Update the color of the grains based on the force
const grainA = grains.find(grain => grain.body.id === bodyA.id);
const grainB = grains.find(grain => grain.body.id === bodyB.id);
if (grainA) {
grainA.color = color(map(force, 0.3, 5, 255, 255), map(force, 0.3, 5, 255, 127), map(force, 0.3, 5, 255, 80));
grainA.lastColorChange = millis();
}
if (grainB) {
grainB.color = color(map(force, 0.3, 5, 255, 255), map(force, 0.3, 5, 255, 127), map(force, 0.3, 5, 255, 80));
grainB.lastColorChange = millis();
}
}
delete initialVelocities[id];
}
}
function setup() {
createCanvas(300, 300);
engine = Engine.create();
world = engine.world;
world.gravity.scale = 0; // Disable default gravity
walls.push(new Boundary(width / 2, height + 50, width, 100, 0));
walls.push(new Boundary(width / 2, -50, width, 100, 0));
walls.push(new Boundary(-50, height / 2, 100, height, 0));
walls.push(new Boundary(width + 50, height / 2, 100, height, 0));
let nbGrains = 800;
let sqrtNbGrains = sqrt(nbGrains);
for (let i = 0; i < nbGrains; i++) {
let x = (width / sqrtNbGrains) * (i % sqrtNbGrains);
let y = (height / sqrtNbGrains) * (i / sqrtNbGrains);
grains.push(new Grain(x, y, random(5, 8)));
}
//Events.on(engine, "collisionStart", handleCollisions); // listne for event collisions
Matter.Events.on(engine, "collisionStart", handleCollisionStart);
Matter.Events.on(engine, "collisionEnd", handleCollisionEnd);
}
function draw() {
background(255);
Engine.update(engine);
for (let wall of walls) {
wall.show();
}
for (let grain of grains) {
grain.resetColor();
grain.show();
}
}
function mousePressed() {
for (let grain of grains) {
if (grain.contains(mouseX, mouseY)) {
let angle = random(TWO_PI);
let forceMagnitude = 5;
let force = {
x: forceMagnitude * cos(angle),
y: forceMagnitude * sin(angle),
};
Body.applyForce(grain.body, grain.body.position, force);
}
}
// Add a link for each force applied
for (let grain of grains) {
if (grain.contains(mouseX, mouseY)) {
links.push({
bodyA: grain.body,
bodyB: null,
force: 0.05,
});
}
}
}
class Boundary {
constructor(x, y, w, h, a) {
this.w = w;
this.h = h;
this.body = Bodies.rectangle(x, y, w, h, {
isStatic: true,
angle: a,
});
World.add(world, this.body);
}
show() {
push();
fill(255);
rectMode(CENTER);
translate(this.body.position.x, this.body.position.y);
rotate(this.body.angle);
rect(0, 0, this.w, this.h);
pop();
}
}
class Grain {
constructor(x, y, r) {
this.r = r;
this.color = color(200,200); // Initialize with the default color (white)
this.defaultColor = color(200,200);
this.lastColorChange = 0; // Initialize last color change time
const options = {
friction: 0,
frictionAir: 0,
frictionStatic: 0,
restitution: 0,
density: 1,
};
this.vertices = this.generateConcavePolygonVertices(r);
this.body = Bodies.fromVertices(x, y, this.vertices, options);
World.add(world, this.body);
}
resetColor() {
const currentTime = millis();
const elapsedTime = currentTime - this.lastColorChange;
if (elapsedTime < 3000) {
this.color = lerpColor(this.color, this.defaultColor, elapsedTime / 3000);
} else {
this.color = this.defaultColor;
}
}
generateConcavePolygonVertices(r) {
const points = [];
const n = floor(random(5, 8));
for (let i = 0; i < n; i++) {
const angle = (TWO_PI / n) * i + random(-0.2, 0.2);
const x = cos(angle) * (r + random(-r * 0.3, r * 0.3));
const y = sin(angle) * (r + random(-r * 0.3, r * 0.3));
points.push({ x, y });
}
return points;
}
show() {
push();
fill(this.color);
noStroke();
beginShape();
translate(this.body.position.x, this.body.position.y);
rotate(this.body.angle);
for (const vert of this.vertices) {
vertex(vert.x, vert.y);
}
endShape(CLOSE);
pop();
}
contains(x, y) {
const point = {
x: x,
y: y,
};
return Matter.Vertices.contains(
this.vertices.map((vert) => {
return {
x: vert.x + this.body.position.x,
y: vert.y + this.body.position.y,
};
}),
point
);
}
}