@dominicstop/utils
Version:
Yet another event emitter written in typescript.
237 lines (236 loc) • 7.49 kB
JavaScript
import { Point } from "./Point";
import { Rect } from "./Rect";
export class BoxedCircle {
constructor(args) {
this.epsilon = 1e-10;
this.radius = args.radius;
switch (args.mode) {
case 'relativeToOrigin':
this.origin = args.origin;
break;
case 'relativeToCenter':
const originX = args.center.x - args.radius;
const originY = args.center.y - args.radius;
this.origin = new Point({
x: originX,
y: originY
});
break;
}
;
}
;
get asValue() {
return {
center: this.center,
radius: this.radius,
};
}
;
get diameter() {
return this.radius * 2;
}
;
get center() {
return new Point({
x: this.origin.x + this.radius,
y: this.origin.y + this.radius,
});
}
;
set center(newCenter) {
const originX = newCenter.x - this.radius;
const originY = newCenter.y - this.radius;
this.origin = new Point({
x: originX,
y: originY
});
}
;
get boundingBox() {
const diameter = this.diameter;
return new Rect({
mode: 'originAndSize',
origin: this.origin,
size: {
width: diameter,
height: diameter,
},
});
}
;
clone() {
return new BoxedCircle({
mode: 'relativeToOrigin',
origin: this.origin,
radius: this.radius,
});
}
;
pointAlongPath(angle) {
return angle.getPointAlongCircle({
centerPoint: this.center,
radius: this.radius,
isClockwise: false,
});
}
;
isPointInside(point) {
const dx = point.x - this.center.x;
const dy = point.y - this.center.y;
// squared distance between the point and the circle's center
const distSq = dx * dx + dy * dy;
// squared radius
const radiusSq = this.radius * this.radius;
return distSq <= radiusSq;
}
;
computeDistanceToOther(other) {
return BoxedCircle.computeDistanceBetweenTwoCricles(this, other);
}
;
isEdgeToEdgeWithOther(other) {
return BoxedCircle.checkIfTwoCirclesAreEdgeToEdge(this, other, this.epsilon);
}
;
isCollidingWithOther(other) {
return BoxedCircle.checkCollisionBetweenTwoCircles(this, other, this.epsilon);
}
;
applyUniformScaleByFactor(args) {
const scaleFactor = args.percentAmount;
const oldRadius = this.radius;
const newRadius = oldRadius * scaleFactor;
switch (args.anchorReference.mode) {
case 'relativeToOrigin':
this.radius = newRadius;
break;
case 'relativeToCenter':
const currentCenter = this.center;
this.radius = newRadius;
this.center = currentCenter;
break;
case 'relativeToRectCorner':
const boundingBox = this.boundingBox;
const fixedCorner = boundingBox.getCornerPoint(args.anchorReference.cornerKey);
const newDiameter = newRadius * 2;
const newBoundingBox = new Rect({
mode: 'originAndSize',
origin: Point.zero,
size: {
width: newDiameter,
height: newDiameter,
},
});
newBoundingBox.setCornerPoint(args.anchorReference.cornerKey, fixedCorner);
this.origin = newBoundingBox.origin;
this.radius = newRadius;
break;
}
}
scaledUniformallyByFactor(args) {
const copy = this.clone();
copy.applyUniformScaleByFactor(args);
return copy;
}
;
// MARK: - Init Alias
// ------------------
static initFromValue(args) {
return new BoxedCircle({
mode: 'relativeToCenter',
center: args.center,
radius: args.radius,
});
}
;
// MARK: - Static Methods
// ----------------------
static rotateCirclesRelativeToPoint(args) {
const centerPoints = args.circles.map(circle => circle.center);
const centerPointsRotated = Point.rotatePointsRelativeToCenter({
points: centerPoints,
rotationAmount: args.rotationAmount,
center: args.centerPoint,
});
for (let index = 0; index < args.circles.length; index++) {
const centerPointTranslated = centerPointsRotated[index];
const circle = args.circles[index];
circle.center = centerPointTranslated;
}
;
}
;
static recenterCirclesRelativeToPoint(args) {
const allPoints = args.circles.reduce((acc, curr) => {
const boundingBox = curr.boundingBox;
acc.push(...boundingBox.cornerPointsAsArray);
return acc;
}, []);
const boundingBox = Point.getBoundingBoxForPoints(allPoints);
const currentCenter = boundingBox.center;
const pointAdj = currentCenter.getDelta(args.centerPoint);
args.circles.forEach(circle => {
const adjX = circle.origin.x - pointAdj.x;
const adjY = circle.origin.y - pointAdj.y;
circle.origin = new Point({
x: adjX,
y: adjY,
});
});
}
;
/**
* the distance between the circle's centers minus
* the sum of their radii: `d < (r1 + r2)`
*/
static computeDistanceBetweenTwoCricles(circleA, circleB) {
const centerDistance = circleA.center.getDistance(circleB.center);
const radiusSum = circleA.radius + circleB.radius;
return centerDistance - radiusSum;
}
;
/**
* Collision detection for two circles:
* returns true if two circles are colliding
*
* * Two circles are overlapping if the distance between their centers is less than
* the sum of their radii: `d < (r1 + r2)`
*/
static checkCollisionBetweenTwoCircles(circleA, circleB, epsilon = 1e-10) {
const distance = circleA.computeDistanceToOther(circleB);
return distance < epsilon;
}
;
/**
* Two circles are "edge-to-edge" (touching) if: `d=r1+r2`
*/
static checkIfTwoCirclesAreEdgeToEdge(circleA, circleB, epsilon = 1e-10) {
const distance = circleA.computeDistanceToOther(circleB);
return Math.abs(distance) < epsilon;
}
;
/**
*
* Collision Detection (Circle-Box)
*
* * A circle (x, y, r) is inside a box defined by minimum (xmin, ymin)
* and maximum (xmax, ymax) coordinates if:
* ```
* x - r >= xmin
* x + r <= xmax
* y - r >= ymin
* y + r <= ymax
* ```
*/
static checkIfCircleIsInsideRect(circle, rect, epsilon = 1e-10) {
const { x: centerX, y: centerY } = circle.center;
const r = circle.radius;
return ((centerX - r) >= (rect.minX - epsilon) &&
(centerX + r) <= (rect.maxX + epsilon) &&
(centerY - r) >= (rect.minY - epsilon) &&
(centerY + r) <= (rect.maxY + epsilon));
}
;
}
;