UNPKG

fqtree

Version:

a flexible quadtree for JavaScript/TypeScript

268 lines (236 loc) 5.37 kB
import { clamp } from 'lodash'; import { Vector2, PointLike } from 'evjkit'; import { csClip } from './csclip'; export declare interface IRange { x: number; y: number; w: number; h: number; get left(): number; get right(): number; get top(): number; get bottom(): number; containsPoint(point: PointLike): boolean; intersects(range: IRange): boolean; } export declare type RectBounds = { x: number; y: number; w: number; h: number; }; export declare type CircleBounds = { x: number; y: number; w: number; h: number; r: number; }; export declare type LineBounds = { x: number; y: number; w: number; h: number; from: PointLike; to: PointLike; }; export class RectangleRange { x: number; y: number; w: number; h: number; /** * Represents a Rectangular Range. * @param x - x-coordinate of rectangle's center. * @param y - y-coordinate of rectangle's center. * @param w - half-width of the rectangle. * @param h - half-height of the rectangle. */ constructor(x: number, y: number, w: number, h: number) { this.x = x; this.y = y; this.w = Math.abs(w); this.h = Math.abs(h); } get left() { return this.x - this.w; } get right() { return this.x + this.w; } get top() { return this.y - this.h; } get bottom() { return this.y + this.h; } get bounds(): RectBounds { return { x: this.x, y: this.y, w: this.w, h: this.h, }; } /** * tests if a point is within this range. * @param point - point to test. * @returns true if within, false if without. */ containsPoint(point: PointLike) { return ( point.x >= this.left && point.x <= this.right && point.y >= this.top && point.y <= this.bottom ); } /** * tests if a range is within this range. * @param range - range to test. * @returns true if within, false if without. */ containsRange(range: IRange) { return ( range.left > this.left && range.right < this.right && range.top > this.top && range.bottom < this.bottom ); } /** * tests if a shape is within or partially within this range. * @param bounds - bounds to test. * @returns true if intersection exists, otherwise false. */ intersects(bounds: IRange) { return !( bounds.left > this.right || bounds.right < this.left || bounds.top > this.bottom || bounds.bottom < this.top ); } } export class CircleRange { x: number; y: number; r: number; w: number; h: number; /** * radius squared */ r2: number; /** * represents a circular area range. * @param x - x-coordiante of circle center. * @param y - y-coordinate of circle center * @param r - radius of circle. */ constructor(x: number, y: number, r: number) { this.x = x; this.y = y; this.r = r; this.r2 = r * r; this.w = r; this.h = r; } get left() { return this.x - this.r; } get right() { return this.x + this.r; } get top() { return this.y - this.r; } get bottom() { return this.y + this.r; } get bounds(): CircleBounds { return { x: this.x, y: this.y, w: this.r, h: this.r, r: this.r, }; } /** * tests if a point is within this range. * @param point - point to test. * @returns true if within, false if without. */ containsPoint(point: PointLike) { const distSq = Vector2.fromPoint(this).distanceToSq( Vector2.fromPoint(point), ); return distSq < this.r2; } /** * tests if a shape is within or partially within this range. * @param bounds - bounds to test. * @returns true if intersection exists, otherwise false. */ intersects(bounds: IRange) { // Find the closest point to the circle within the rectangle const minX = clamp(this.x, bounds.left, bounds.right); const minY = clamp(this.y, bounds.top, bounds.bottom); const distSq = Vector2.fromPoint(this).distanceToSq( new Vector2(minX, minY), ); return distSq < this.r2; } } export class LineRange { from: PointLike; to: PointLike; /** * represents a line area range. * @param from - starting point of line * @param to - ending point of line */ constructor(from: PointLike, to: PointLike) { this.from = from; this.to = to; } get left() { if (this.from.x < this.to.x) return this.from.x; else return this.to.x; } get right() { if (this.from.x > this.to.x) return this.from.x; else return this.to.x; } get top() { if (this.from.y < this.to.y) return this.from.y; else return this.to.y; } get bottom() { if (this.from.y > this.to.y) return this.from.y; else return this.to.y; } get bounds(): LineBounds { const left = this.left; const right = this.right; const top = this.top; const bottom = this.bottom; return { x: left, y: top, w: right - left, h: bottom - top, from: this.from, to: this.to, }; } /** * tests if a shape is within or partially within this range. * @param bounds - bounds to test. * @returns true if intersection exists, otherwise false. */ intersects(bounds: IRange): boolean { return csClip(this, bounds); } }