UNPKG

bump-ts

Version:

![version](https://img.shields.io/npm/v/bump-ts) ![maintenance](https://img.shields.io/maintenance/yes/2021) ![open-issues](https://img.shields.io/github/issues-raw/hood/bump.ts)

258 lines (224 loc) 5.79 kB
import { Collision, ICoords, IRect } from '.'; import { DELTA } from './constants'; import nearest from './helpers/generic/nearest'; export function rect_getNearestCorner( rect: IRect, px: number, py: number ): ICoords { return { x: nearest(px, rect.x, rect.x + rect.w), y: nearest(py, rect.y, rect.y + rect.h), }; } // This is a generalized implementation of the liang-barsky algorithm, which also returns // the normals of the sides where the segment intersects. // Returns null if the segment never touches the rect // Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge export function rect_getSegmentIntersectionIndices( rect: IRect, x1: number, y1: number, x2: number, y2: number, ti1: number, ti2: number ): [number, number, number, number, number, number] | undefined { let _ti1 = isNaN(ti1) ? 0 : ti1; let _ti2 = isNaN(ti2) ? 1 : ti2; let dx: number = x2 - x1; let dy: number = y2 - y1; let nx: number; let ny: number; let nx1: number = 0; let ny1: number = 0; let nx2: number = 0; let ny2: number = 0; let p, q, r; for (let side = 1; side < 5; side++) { // left if (side === 1) { nx = -1; ny = 0; p = -dx; q = x1 - rect.x; } // right else if (side === 2) { nx = 1; ny = 0; p = dx; q = rect.x + rect.w - x1; } // top else if (side === 3) { nx = 0; ny = -1; p = -dy; q = y1 - rect.y; } // bottom else { nx = 0; ny = 1; p = dy; q = rect.y + rect.h - y1; } if (p === 0) { if (q <= 0) return undefined; } else { r = q / p; if (p < 0) { if (r > _ti2) return undefined; else if (r > _ti1) { _ti1 = r; nx1 = nx; ny1 = ny; } } // p > 0 else { if (r < _ti1) return undefined; else if (r < _ti2) { _ti2 = r; nx2 = nx; ny2 = ny; } } } } return [_ti1, _ti2, nx1, ny1, nx2, ny2]; } // Calculates the Minkowsky difference between 2 rects, which is another rect export function rect_getDiff(rect: IRect, otherRect: IRect): IRect { return { x: otherRect.x - rect.x - rect.w, y: otherRect.y - rect.y - rect.h, w: rect.w + otherRect.w, h: rect.h + otherRect.h, }; } export function rect_containsPoint( rect: IRect, px: number, py: number ): boolean { return ( px - rect.x > DELTA && py - rect.y > DELTA && rect.x + rect.w - px > DELTA && rect.y + rect.h - py > DELTA ); } export function rect_isIntersecting( x1: number, y1: number, w1: number, h1: number, otherRect: IRect ): boolean { return ( x1 < otherRect.x + otherRect.w && otherRect.x < x1 + w1 && y1 < otherRect.y + otherRect.h && otherRect.y < y1 + h1 ); } export function rect_getSquareDistance(rect: IRect, otherRect: IRect): number { const dx = rect.x - otherRect.x + (rect.w - otherRect.w) / 2; const dy = rect.y - otherRect.y + (rect.h - otherRect.h) / 2; return dx * dx + dy * dy; } export function rect_detectCollision( rect: IRect, otherRect: IRect, goalX?: number, goalY?: number ): undefined | Partial<Collision> { let dx: number = (goalX ?? rect.x) - rect.x; let dy: number = (goalY ?? rect.y) - rect.y; const diffRect = rect_getDiff(rect, otherRect); let overlaps: boolean; let nx, ny; let ti: number | undefined; // If the item was intersecting other if (rect_containsPoint(diffRect, 0, 0)) { let { x: px, y: py } = rect_getNearestCorner(diffRect, 0, 0); let wi: number = Math.min(rect.w, Math.abs(px)); // area of intersection let hi: number = Math.min(rect.h, Math.abs(py)); // area of intersection ti = -wi * hi; // `ti` is the negative area of intersection overlaps = true; } else { const intersections = rect_getSegmentIntersectionIndices( diffRect, 0, 0, dx, dy, -Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER ); if (intersections) { const [ti1, ti2, nx1, ny1] = intersections; // item tunnels into other if ( typeof ti1 === 'number' && ti1 < 1 && Math.abs(ti1 - (ti2 || 0)) >= DELTA && // special case for rect going through another rect's corner (0 < ti1 + DELTA || (0 === ti1 && (ti2 || 0) > 0)) ) { ti = ti1; nx = nx1; ny = ny1; overlaps = false; } } } if (typeof ti !== 'number') return; let tx: number, ty: number; if (overlaps!) if (dx === 0 && dy === 0) { //intersecting and not moving - use minimum displacement vector let { x: px, y: py } = rect_getNearestCorner(diffRect, 0, 0); // if (px >>> 1 < py >>> 1) py = 0; if (Math.abs(px) < Math.abs(py)) py = 0; else px = 0; nx = Math.sign(px); ny = Math.sign(py); tx = rect.x + px; ty = rect.y + py; } else { // Intersecting and moving - move in the opposite direction. const intersections = rect_getSegmentIntersectionIndices( diffRect, 0, 0, dx, dy, -Number.MAX_SAFE_INTEGER, 1 ); if (intersections) { const [ti1, _, _nx, _ny] = intersections; nx = _nx; ny = _ny; if (!ti1) return; tx = rect.x + dx * ti1; ty = rect.y + dy * ti1; } } //tunnel else { tx = rect.x + dx * ti; ty = rect.y + dy * ti; } return { overlaps: overlaps!, // @ts-ignore ti, move: { x: dx, y: dy }, normal: { x: nx as number, y: ny as number }, touch: { x: tx!, y: ty! }, itemRect: rect, otherRect, }; }