xxxxxxxxxx
283
class Shape {
constructor(x, y, mass, restitution, type) {
this.position = createVector(x, y);
this.velocity = createVector(0, 0.01);
this.mass = mass;
this.restitution = restitution;
this.isColliding = false;
this.collisionAngle = 0;
this.area = 0;
this.sleepThresholdVelocity = 0.01;
this.sleepThresholdAngularVelocity = 0.01;
this.isSleeping = false;
this.type = type;
}
update() {
if (
this.velocity.mag() < this.sleepThresholdVelocity &&
abs(this.angularVelocity) < this.sleepThresholdAngularVelocity
) {
this.isSleeping = true;
} else {
this.isSleeping = false;
this.position.add(this.velocity);
}
}
draw() {}
projectShapeOntoAxis(shape, axis) {
if (shape.type === "circle") {
let centerProjection = axis.dot(shape.position);
let radiusProjection = shape.radius * axis.mag();
let min = centerProjection - radiusProjection;
let max = centerProjection + radiusProjection;
return [min, max];
} else if (shape.type === "rectangle") {
let vertices = shape.getVertices();
let min = axis.dot(vertices[0]);
let max = min;
for (let i = 1; i < vertices.length; i++) {
let projection = axis.dot(vertices[i]);
if (projection < min) {
min = projection;
}
if (projection > max) {
max = projection;
}
}
return [min, max];
}
}
checkCollisions(shapes) {
if (!shapes || shapes.length === 0) {
return;
}
// Cycle through all current collisions and check if they are still happening
for (let i = this.collisions.length - 1; i >= 0; i--) {
let otherShape = this.collisions[i];
let collisionInfo = this.checkCollisionWithShape(otherShape);
if (collisionInfo !== null) {
let collisionAxis = collisionInfo[0];
let overlap = collisionInfo[1];
this.resolveCollisionWithShape(otherShape, collisionAxis, overlap);
} else {
// Collision is no longer happening, remove from array
this.collisions.splice(i, 1);
otherShape.collisions.splice(otherShape.collisions.indexOf(this), 1);
}
}
let aabb = this.calculateSweptAABB();
for (let i = 0; i < shapes.length; i++) {
let otherShape = shapes[i];
if (this !== otherShape) {
let otherAABB = otherShape.getAABB();
if (
aabb.minX < otherAABB.maxX &&
aabb.maxX > otherAABB.minX &&
aabb.minY < otherAABB.maxY &&
aabb.maxY > otherAABB.minY
) {
let collisionInfo = this.checkCollisionWithShape(otherShape);
if (collisionInfo !== null) {
let collisionAxis = collisionInfo[0];
let overlap = collisionInfo[1];
this.resolveCollisionWithShape(otherShape, collisionAxis, overlap);
}
}
}
}
this.checkCollisionWithWalls();
}
checkCollisionWithShape(otherShape) {
let axes = [
createVector(cos(this.angle), sin(this.angle)),
createVector(cos(this.angle + HALF_PI), sin(this.angle + HALF_PI)),
createVector(cos(otherShape.angle), sin(otherShape.angle)),
createVector(
cos(otherShape.angle + HALF_PI),
sin(otherShape.angle + HALF_PI)
),
];
let smallestOverlap = Infinity;
let smallestAxis = null;
for (let axis of axes) {
let projection1 = this.projectShapeOntoAxis(this, axis);
let projection2 = this.projectShapeOntoAxis(otherShape, axis);
let overlap = this.getOverlap(projection1, projection2);
if (overlap === 0) {
return null;
}
if (overlap < smallestOverlap) {
smallestOverlap = overlap;
smallestAxis = axis;
}
}
let direction = p5.Vector.sub(otherShape.position, this.position);
if (direction.dot(smallestAxis) > 0) {
smallestAxis.mult(-1);
}
if (otherShape.type === "circle") {
let projection1 = this.projectShapeOntoAxis(this, smallestAxis);
let projection2 = this.projectShapeOntoAxis(otherShape, smallestAxis);
let overlap = this.getOverlap(projection1, projection2);
if (overlap < smallestOverlap) {
smallestOverlap = overlap;
}
} else if (otherShape.type === "rectangle" && otherShape.getEdges) {
let edges = otherShape.getEdges();
for (let i = 0; i < edges.length; i++) {
let edge = edges[i];
let projection1 = this.projectShapeOntoAxis(this, edge);
let projection2 = otherShape.projectShapeOntoAxis(otherShape, edge);
let overlap = this.getOverlap(projection1, projection2);
if (overlap === 0) {
return null;
}
if (overlap < smallestOverlap) {
smallestOverlap = overlap;
smallestAxis = edge.copy().rotate(HALF_PI);
}
}
}
return [
smallestAxis,
smallestOverlap,
p5.Vector.sub(
otherShape.position,
p5.Vector.mult(smallestAxis, smallestOverlap)
),
];
}
getBoundingRadius() {
return 0;
}
getAABB() {
return null;
}
calculateSweptAABB() {
return null;
}
checkCollisionWithWalls() {}
getVertices() {
return null;
}
getEdges() {
return null;
}
overlap(projection1, projection2) {
return projection1[0] <= projection2[1] && projection1[1] >= projection2[0];
}
getOverlap(projection1, projection2, margin = 0) {
let min1 = projection1[0];
let max1 = projection1[1];
let min2 = projection2[0];
let max2 = projection2[1];
if (max1 + margin < min2 || max2 + margin < min1) {
return 0;
} else {
return max1 < max2 ? max1 - min2 + margin : max2 - min1 + margin;
}
}
resolveCollisionWithShape(otherShape, collisionAxis, overlap) {
let totalMass = this.mass + otherShape.mass;
let weight1 = this.mass / totalMass;
let weight2 = otherShape.mass / totalMass;
let velocity1 = this.velocity.copy();
let velocity2 = otherShape.velocity.copy();
let relativeVelocity = velocity1.sub(velocity2);
let velAlongNormal = relativeVelocity.dot(collisionAxis);
if (velAlongNormal > 0) {
return;
}
let e = Math.min(this.restitution, otherShape.restitution);
let j = -(1 + e) * velAlongNormal;
j /= 1 / this.mass + 1 / otherShape.mass;
let maxImpulse = collisionAxis
.copy()
.mult((this.mass + otherShape.mass) * 0.8);
let impulse = collisionAxis
.copy()
.mult(clamp(j, -maxImpulse.mag(), maxImpulse.mag()));
this.velocity.add(impulse.copy().mult(weight1));
otherShape.velocity.sub(impulse.copy().mult(weight2));
this.angularVelocity *= 0.8;
otherShape.angularVelocity *= 0.8;
let penetration = collisionAxis.copy().mult(overlap);
this.position.add(penetration.copy().mult(weight1));
otherShape.position.sub(penetration.copy().mult(weight2));
let torque1 = p5.Vector.cross(
p5.Vector.sub(otherShape.position, this.position),
impulse
);
let torque2 = p5.Vector.cross(
p5.Vector.sub(this.position, otherShape.position),
impulse
);
this.angularVelocity += (torque1.z / this.inertia) * weight1;
otherShape.angularVelocity += (torque2.z / otherShape.inertia) * weight2;
let combinedMass = this.mass + otherShape.mass;
let collisionNormal = collisionAxis.copy().rotate(HALF_PI);
let relativeVelocityAlongNormal = collisionNormal.dot(
velocity1.sub(velocity2)
);
this.collisionAngle = atan2(
relativeVelocityAlongNormal * ((2 * otherShape.mass) / combinedMass),
this.velocity.mag() -
otherShape.velocity.mag() * ((2 * this.mass) / combinedMass)
);
if (otherShape instanceof Circle) {
let circleCenterToCollision = p5.Vector.sub(
otherShape.position,
this.position
);
otherShape.collisionAngle = atan2(
circleCenterToCollision.y,
circleCenterToCollision.x
);
} else if (otherShape instanceof Rectangle) {
let otherCollisionNormal = collisionAxis.copy().rotate(PI);
let otherRelativeVelocityAlongNormal = otherCollisionNormal.dot(
velocity2.sub(velocity1)
);
otherShape.collisionAngle = atan2(
otherRelativeVelocityAlongNormal * ((2 * this.mass) / combinedMass),
otherShape.velocity.mag() -
this.velocity.mag() * ((2 * otherShape.mass) / combinedMass)
);
}
}
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}