fqtree
Version:
a flexible quadtree for JavaScript/TypeScript
268 lines (236 loc) • 5.37 kB
text/typescript
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);
}
}