UNPKG

@dominicstop/utils

Version:

Yet another event emitter written in typescript.

237 lines (236 loc) 7.49 kB
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)); } ; } ;