UNPKG

@stringsync/vexml

Version:

MusicXML to Vexflow

122 lines (121 loc) 4.95 kB
import { Circle } from './circle'; import { Rect } from './rect'; /** Represents a collision between two shapes. */ export class Collision { /** Creates a CollisionSubject for fluent APIs. */ static is(shape) { return new CollisionSubject(shape); } } /** * A collision calculator that enables fluent APIs. * * This is done to reduce ambiguity of the semantics behind the collision checks. * * @example Collision.is(rect).collidingWith(circle); // boolean * @example Collision.is(circle).surroundedBy(rect); // boolean */ class CollisionSubject { shape; constructor(shape) { this.shape = shape; } collidingWith(shape) { const shape1 = this.shape; const shape2 = shape; if (shape1 instanceof Rect && shape2 instanceof Rect) { return this.isRectCollidingWithRect(shape1, shape2); } if (shape1 instanceof Rect && shape2 instanceof Circle) { return this.isRectCollidingWithCircle(shape1, shape2); } if (shape1 instanceof Circle && shape2 instanceof Rect) { return this.isRectCollidingWithCircle(shape2, shape1); } if (shape1 instanceof Circle && shape2 instanceof Circle) { return this.isCircleCollidingWithCircle(shape1, shape2); } throw new Error(`unsupported collision between ${shape1.constructor.name} and ${shape2.constructor.name}`); } surrounding(shape) { const shape1 = this.shape; const shape2 = shape; if (shape1 instanceof Rect && shape2 instanceof Rect) { return this.isRectSurroundingRect(shape1, shape2); } if (shape1 instanceof Rect && shape2 instanceof Circle) { return this.isRectSurroundingCircle(shape1, shape2); } if (shape1 instanceof Circle && shape2 instanceof Rect) { return this.isCircleSurroundingRect(shape1, shape2); } if (shape1 instanceof Circle && shape2 instanceof Circle) { return this.isCircleSurroundingCircle(shape1, shape2); } throw new Error(`unsupported collision between ${shape1.constructor.name} and ${shape2.constructor.name}`); } isRectCollidingWithRect(rect1, rect2) { return !(rect2.x > rect1.x + rect1.w || rect2.x + rect2.w < rect1.x || rect2.y > rect1.y + rect1.h || rect2.y + rect2.h < rect1.y); } isCircleCollidingWithCircle(circle1, circle2) { return circle1.center().distance(circle2.center()) <= circle1.r + circle2.r; } isRectCollidingWithCircle(rect, circle) { if (rect.contains(circle.center())) { return true; } if (circle.contains(rect.center())) { return true; } const x = Math.max(rect.x, Math.min(circle.x, rect.x + rect.w)); const y = Math.max(rect.y, Math.min(circle.y, rect.y + rect.h)); const dx = circle.x - x; const dy = circle.y - y; return dx * dx + dy * dy <= circle.r * circle.r; } isRectSurroundingRect(rect1, rect2) { return (rect1.x > rect2.x && rect1.y > rect2.y && rect1.x + rect1.w < rect2.x + rect2.w && rect1.y + rect1.h < rect2.y + rect2.h); } isCircleSurroundingCircle(circle1, circle2) { const distance = circle1.center().distance(circle2.center()); return distance + circle1.r < circle2.r; } isRectSurroundingCircle(rect, circle) { const circleCenter = circle.center(); const rectCenter = rect.center(); const distanceX = Math.abs(circleCenter.x - rectCenter.x); const distanceY = Math.abs(circleCenter.y - rectCenter.y); const halfRectWidth = rect.w / 2; const halfRectHeight = rect.h / 2; if (distanceX > halfRectWidth || distanceY > halfRectHeight) { return false; } if (distanceX <= halfRectWidth - circle.r && distanceY <= halfRectHeight - circle.r) { return true; } const cornerDistanceSq = (distanceX - halfRectWidth) ** 2 + (distanceY - halfRectHeight) ** 2; return cornerDistanceSq <= circle.r ** 2; } isCircleSurroundingRect(circle, rect) { const circleCenter = circle.center(); const rectCenter = rect.center(); const distanceX = Math.abs(circleCenter.x - rectCenter.x); const distanceY = Math.abs(circleCenter.y - rectCenter.y); const halfRectWidth = rect.w / 2; const halfRectHeight = rect.h / 2; if (distanceX > halfRectWidth + circle.r || distanceY > halfRectHeight + circle.r) { return false; } if (distanceX <= halfRectWidth || distanceY <= halfRectHeight) { return true; } const cornerDistanceSq = (distanceX - halfRectWidth) ** 2 + (distanceY - halfRectHeight) ** 2; return cornerDistanceSq <= circle.r ** 2; } }