Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/excaliburjs/Excalibur. 🎮 Your friendly TypeScript 2D game engine for the web 🗡️ excaliburjs.com
excalibur excaliburjs game-development game-engine game-framework gamedev games html5-canvas typescript
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263import type { AffineMatrix } from './affine-matrix';import { Vector } from './vector';
/** * A 2D line segment */
export class LineSegment { /** * @param begin The starting point of the line segment * @param end The ending point of the line segment */ constructor( public begin: Vector, public end: Vector ) {}
clone(dest?: LineSegment): LineSegment { const result = dest || new LineSegment(this.begin.clone(), this.end.clone()); result.begin = this.begin.clone(result.begin); result.end = this.end.clone(result.end); return result; }
transform(matrix: AffineMatrix, dest?: LineSegment): LineSegment { const result = dest || new LineSegment(Vector.Zero, Vector.Zero); result.begin = matrix.multiply(this.begin, result.begin); result.end = matrix.multiply(this.end, result.end); return result; }
/** * Gets the raw slope (m) of the line. Will return (+/-)Infinity for vertical lines. */ public get slope() { return (this.end.y - this.begin.y) / (this.end.x - this.begin.x); }
/** * Gets the Y-intercept (b) of the line. Will return (+/-)Infinity if there is no intercept. */ public get intercept() { return this.begin.y - this.slope * this.begin.x; }
private _normal!: Vector; /** * Gets the normal of the line */ public normal(): Vector { if (this._normal) { return this._normal; } return (this._normal = this.end.sub(this.begin).normal()); }
private _dir!: Vector; public dir(): Vector { if (this._dir) { return this._dir; } return (this._dir = this.end.sub(this.begin)); }
public getPoints(): Vector[] { return [this.begin, this.end]; }
private _slope!: Vector; /** * Returns the slope of the line in the form of a vector of length 1 */ public getSlope(): Vector { if (this._slope) { return this._slope; } const begin = this.begin; const end = this.end; const distance = begin.distance(end); return (this._slope = end.sub(begin).scale(1 / distance)); }
/** * Returns the edge of the line as vector, the length of the vector is the length of the edge */ public getEdge(): Vector { const begin = this.begin; const end = this.end; return end.sub(begin); }
/** * Returns the length of the line segment in pixels */ public getLength(): number { const begin = this.begin; const end = this.end; const distance = begin.distance(end); return distance; }
/** * Returns the midpoint of the edge */ public get midpoint(): Vector { return this.begin.add(this.end).scale(0.5); }
/** * Flips the direction of the line segment */ public flip(): LineSegment { return new LineSegment(this.end, this.begin); }
/** * Tests if a given point is below the line, points in the normal direction above the line are considered above. * @param point */ public below(point: Vector): boolean { const above2 = (this.end.x - this.begin.x) * (point.y - this.begin.y) - (this.end.y - this.begin.y) * (point.x - this.begin.x); return above2 >= 0; }
/** * Returns the clip point * @param sideVector Vector that traces the line * @param length Length to clip along side */ public clip(sideVector: Vector, length: number, normalize = true): LineSegment | null { let dir = sideVector; if (normalize) { dir = dir.normalize(); }
const near = dir.dot(this.begin) - length; const far = dir.dot(this.end) - length;
const results = []; if (near <= 0) { results.push(this.begin); } if (far <= 0) { results.push(this.end); }
if (near * far < 0) { const clipTime = near / (near - far); results.push(this.begin.add(this.end.sub(this.begin).scale(clipTime))); } if (results.length !== 2) { return null; }
return new LineSegment(results[0], results[1]); }
/** * Find the perpendicular distance from the line to a point * https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line * @param point */ public distanceToPoint(point: Vector, signed: boolean = false) { const x0 = point.x; const y0 = point.y;
const l = this.getLength();
const dy = this.end.y - this.begin.y; const dx = this.end.x - this.begin.x; const distance = (dy * x0 - dx * y0 + this.end.x * this.begin.y - this.end.y * this.begin.x) / l; return signed ? distance : Math.abs(distance); }
/** * Find the perpendicular line from the line to a point * https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line * (a - p) - ((a - p) * n)n * a is a point on the line * p is the arbitrary point above the line * n is a unit vector in direction of the line * @param point */ public findVectorToPoint(point: Vector): Vector { const aMinusP = this.begin.sub(point); const n = this.getSlope();
return aMinusP.sub(n.scale(aMinusP.dot(n))); }
/** * Finds a point on the line given only an X or a Y value. Given an X value, the function returns * a new point with the calculated Y value and vice-versa. * @param x The known X value of the target point * @param y The known Y value of the target point * @returns A new point with the other calculated axis value */ public findPoint(x?: number, y?: number): Vector { const m = this.slope; const b = this.intercept;
if (x != null) { return new Vector(x, m * x + b); } else if (y != null) { return new Vector((y - b) / m, y); } else { throw new Error('You must provide an X or a Y value'); } }
/** * Whether or not the given point lies on this line. This method is precise by default * meaning the point must lie exactly on the line. Adjust threshold to * loosen the strictness of the check for floating-point calculations. */ public hasPoint(x: number, y: number, threshold?: number): boolean;
/** * Whether or not the given point lies on this line. This method is precise by default * meaning the point must lie exactly on the line. Adjust threshold to * loosen the strictness of the check for floating-point calculations. */ public hasPoint(v: Vector, threshold?: number): boolean;
/** * @see http://stackoverflow.com/a/11908158/109458 */ public hasPoint(): boolean { let currPoint: Vector; let threshold = 0;
if (typeof arguments[0] === 'number' && typeof arguments[1] === 'number') { currPoint = new Vector(arguments[0], arguments[1]); threshold = arguments[2] || 0; } else if (arguments[0] instanceof Vector) { currPoint = arguments[0]; threshold = arguments[1] || 0; } else { throw 'Could not determine the arguments for Vector.hasPoint'; }
const dxc = currPoint.x - this.begin.x; const dyc = currPoint.y - this.begin.y;
const dx1 = this.end.x - this.begin.x; const dy1 = this.end.y - this.begin.y;
const cross = dxc * dy1 - dyc * dx1;
// check whether point lines on the line if (Math.abs(cross) > threshold) { return false; }
// check whether point lies in-between start and end if (Math.abs(dx1) >= Math.abs(dy1)) { return dx1 > 0 ? this.begin.x <= currPoint.x && currPoint.x <= this.end.x : this.end.x <= currPoint.x && currPoint.x <= this.begin.x; } else { return dy1 > 0 ? this.begin.y <= currPoint.y && currPoint.y <= this.end.y : this.end.y <= currPoint.y && currPoint.y <= this.begin.y; } }}