fqtree
Version:
a flexible quadtree for JavaScript/TypeScript
159 lines (147 loc) • 5.36 kB
text/typescript
import { PointLike } from 'evjkit';
import type { LineRange, IRange } from './range';
/**
* region code used for bitwise comparisons
*/
const INSIDE = 0; // 0000
const LEFT = 1; // 0001
const RIGHT = 2; // 0010
const BOTTOM = 4; // 0100
const TOP = 8; // 1000
/**
* Compute the bit code for a point (x, y) using the clip rectangle
* bounded diagonally by (xmin, ymin), and (xmax, ymax)
* @param point - point to test
* @param bounds - bounds to test against
* @returns appropriate OutCode for the point.
*/
const ComputeOutCode = (
point: PointLike,
bounds: IRange,
// x, y, xmin, xmax, ymin, ymax
): number => {
let code;
code = INSIDE; // initialised as being inside of [[clip window]]
if (point.x < bounds.left)
// to the left of clip window
code |= LEFT;
else if (point.x > bounds.right)
// to the right of clip window
code |= RIGHT;
if (point.y > bounds.bottom)
// below the clip window
code |= BOTTOM;
else if (point.y < bounds.top)
// above the clip window
code |= TOP;
return code;
};
//
/**
* Cohen–Sutherland clipping algorithm clips a line from
* P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
* diagonal from (xmin, ymin) to (xmax, ymax).
* @param from - line from
* @param to - line to
* @param bounds - a bounding range to test against
* @returns true if the line segment clips the viewport false if not.
*/
const cohenSutherlandLineClipAndDraw = (
from: PointLike,
to: PointLike,
bounds: IRange,
): boolean => {
// compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
let outcode0 = ComputeOutCode(from, bounds); //x0, y0, xmin, xmax, ymin, ymax);
let outcode1 = ComputeOutCode(to, bounds); //x1, y1, xmin, xmax, ymin, ymax);
let accept = false;
const p0: PointLike = { x: from.x, y: from.y };
const p1: PointLike = { x: to.x, y: to.y };
const condition = true;
while (condition) {
if (!(outcode0 | outcode1)) {
// bitwise OR is 0: both points inside window; trivially accept and exit loop
accept = true;
break;
} else if (outcode0 & outcode1) {
// bitwise AND is not 0: both points share an outside zone (LEFT, RIGHT, TOP,
// or BOTTOM), so both must be outside window; exit loop (accept is false)
break;
} else {
// failed both tests, so calculate the line segment to clip
// from an outside point to an intersection with clip edge
let x: number = 0,
y: number = 0;
// At least one endpoint is outside the clip rectangle; pick it.
const outcodeOut = outcode0 ? outcode0 : outcode1;
// Now find the intersection point;
// use formulas:
// slope = (y1 - y0) / (x1 - x0)
// x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax
// y = y0 + slope * (xm - x0), where xm is xmin or xmax
// No need to worry about divide-by-zero because, in each case, the
// outcode bit being tested guarantees the denominator is non-zero
if (outcodeOut & TOP) {
// point is above the clip window
// x = x0 + ((x1 - x0) * (ymax - y0)) / (y1 - y0);
// y = ymax;
x = p0.x + ((p1.x - p0.x) * (bounds.top - p0.y)) / (p1.y - p0.y);
y = bounds.top;
} else if (outcodeOut & BOTTOM) {
// point is below the clip window
// x = x0 + ((x1 - x0) * (ymin - y0)) / (y1 - y0);
// y = ymin;
x = p0.x + ((p1.x - p0.x) * (bounds.bottom - p0.y)) / (p1.y - p0.y);
y = bounds.bottom;
} else if (outcodeOut & RIGHT) {
// point is to the right of clip window
// y = y0 + ((y1 - y0) * (xmax - x0)) / (x1 - x0);
// x = xmax;
y = p0.y + ((p1.y - p0.y) * (bounds.right - p0.x)) / (p1.x - p0.x);
x = bounds.right;
} else if (outcodeOut & LEFT) {
// point is to the left of clip window
// y = y0 + ((y1 - y0) * (xmin - x0)) / (x1 - x0);
// x = xmin;
y = p0.y + ((p1.y - p0.y) * (bounds.left - p0.x)) / (p1.x - p0.x);
x = bounds.left;
}
// Now we move outside point to intersection point to clip
// and get ready for next pass.
if (outcodeOut === outcode0) {
// x0 = x;
// y0 = y;
// outcode0 = ComputeOutCode(x0, y0, xmin, xmax, ymin, ymax);
p0.x = x;
p0.y = y;
outcode0 = ComputeOutCode(p0, bounds);
} else {
// x1 = x;
// y1 = y;
// outcode1 = ComputeOutCode(x1, y1, xmin, xmax, ymin, ymax);
p1.x = x;
p1.y = y;
outcode1 = ComputeOutCode(p1, bounds);
}
}
}
if (accept) {
// either the full or partial line is within the viewport
return true;
} else {
// the line is outside the viewport
return false;
}
};
/**
* the algorithm divides a two-dimensional space into 9 regions and then
* efficiently determines the lines and portions of lines that are visible
* in the central region of interest (the viewport).
* https://en.wikipedia.org/wiki/Cohen-Sutherland_algorithm
* @param line - Parameter description.
* @param bounds - a rectangular range.
* @returns true if the line segment clips the viewport false if not.
*/
export const csClip = (line: LineRange, bounds: IRange): boolean => {
return cohenSutherlandLineClipAndDraw(line.from, line.to, bounds);
};