123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- import { Point } from './point';
- import { Geometry } from './geometry';
- import { Rectangle } from './rectangle';
- export class Line extends Geometry {
- get center() {
- return new Point((this.start.x + this.end.x) / 2, (this.start.y + this.end.y) / 2);
- }
- constructor(x1, y1, x2, y2) {
- super();
- if (typeof x1 === 'number' && typeof y1 === 'number') {
- this.start = new Point(x1, y1);
- this.end = new Point(x2, y2);
- }
- else {
- this.start = Point.create(x1);
- this.end = Point.create(y1);
- }
- }
- getCenter() {
- return this.center;
- }
- /**
- * Rounds the line to the given `precision`.
- */
- round(precision = 0) {
- this.start.round(precision);
- this.end.round(precision);
- return this;
- }
- translate(tx, ty) {
- if (typeof tx === 'number') {
- this.start.translate(tx, ty);
- this.end.translate(tx, ty);
- }
- else {
- this.start.translate(tx);
- this.end.translate(tx);
- }
- return this;
- }
- /**
- * Rotate the line by `angle` around `origin`.
- */
- rotate(angle, origin) {
- this.start.rotate(angle, origin);
- this.end.rotate(angle, origin);
- return this;
- }
- /**
- * Scale the line by `sx` and `sy` about the given `origin`. If origin is not
- * specified, the line is scaled around `0,0`.
- */
- scale(sx, sy, origin) {
- this.start.scale(sx, sy, origin);
- this.end.scale(sx, sy, origin);
- return this;
- }
- /**
- * Returns the length of the line.
- */
- length() {
- return Math.sqrt(this.squaredLength());
- }
- /**
- * Useful for distance comparisons in which real length is not necessary
- * (saves one `Math.sqrt()` operation).
- */
- squaredLength() {
- const dx = this.start.x - this.end.x;
- const dy = this.start.y - this.end.y;
- return dx * dx + dy * dy;
- }
- /**
- * Scale the line so that it has the requested length. The start point of
- * the line is preserved.
- */
- setLength(length) {
- const total = this.length();
- if (!total) {
- return this;
- }
- const scale = length / total;
- return this.scale(scale, scale, this.start);
- }
- parallel(distance) {
- const line = this.clone();
- if (!line.isDifferentiable()) {
- return line;
- }
- const { start, end } = line;
- const eRef = start.clone().rotate(270, end);
- const sRef = end.clone().rotate(90, start);
- start.move(sRef, distance);
- end.move(eRef, distance);
- return line;
- }
- /**
- * Returns the vector of the line with length equal to length of the line.
- */
- vector() {
- return new Point(this.end.x - this.start.x, this.end.y - this.start.y);
- }
- /**
- * Returns the angle of incline of the line.
- *
- * The function returns `NaN` if the start and end endpoints of the line
- * both lie at the same coordinates(it is impossible to determine the angle
- * of incline of a line that appears to be a point). The
- * `line.isDifferentiable()` function may be used in advance to determine
- * whether the angle of incline can be computed for a given line.
- */
- angle() {
- const ref = new Point(this.start.x + 1, this.start.y);
- return this.start.angleBetween(this.end, ref);
- }
- /**
- * Returns a rectangle that is the bounding box of the line.
- */
- bbox() {
- const left = Math.min(this.start.x, this.end.x);
- const top = Math.min(this.start.y, this.end.y);
- const right = Math.max(this.start.x, this.end.x);
- const bottom = Math.max(this.start.y, this.end.y);
- return new Rectangle(left, top, right - left, bottom - top);
- }
- /**
- * Returns the bearing (cardinal direction) of the line.
- *
- * The return value is one of the following strings:
- * 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW' and 'N'.
- *
- * The function returns 'N' if the two endpoints of the line are coincident.
- */
- bearing() {
- return this.start.bearing(this.end);
- }
- /**
- * Returns the point on the line that lies closest to point `p`.
- */
- closestPoint(p) {
- return this.pointAt(this.closestPointNormalizedLength(p));
- }
- /**
- * Returns the length of the line up to the point that lies closest to point `p`.
- */
- closestPointLength(p) {
- return this.closestPointNormalizedLength(p) * this.length();
- }
- /**
- * Returns a line that is tangent to the line at the point that lies closest
- * to point `p`.
- */
- closestPointTangent(p) {
- return this.tangentAt(this.closestPointNormalizedLength(p));
- }
- /**
- * Returns the normalized length (distance from the start of the line / total
- * line length) of the line up to the point that lies closest to point.
- */
- closestPointNormalizedLength(p) {
- const product = this.vector().dot(new Line(this.start, p).vector());
- const normalized = Math.min(1, Math.max(0, product / this.squaredLength()));
- // normalized returns `NaN` if this line has zero length
- if (Number.isNaN(normalized)) {
- return 0;
- }
- return normalized;
- }
- /**
- * Returns a point on the line that lies `rate` (normalized length) away from
- * the beginning of the line.
- */
- pointAt(ratio) {
- const start = this.start;
- const end = this.end;
- if (ratio <= 0) {
- return start.clone();
- }
- if (ratio >= 1) {
- return end.clone();
- }
- return start.lerp(end, ratio);
- }
- /**
- * Returns a point on the line that lies length away from the beginning of
- * the line.
- */
- pointAtLength(length) {
- const start = this.start;
- const end = this.end;
- let fromStart = true;
- if (length < 0) {
- fromStart = false; // start calculation from end point
- length = -length; // eslint-disable-line
- }
- const total = this.length();
- if (length >= total) {
- return fromStart ? end.clone() : start.clone();
- }
- const rate = (fromStart ? length : total - length) / total;
- return this.pointAt(rate);
- }
- /**
- * Divides the line into two lines at the point that lies `rate` (normalized
- * length) away from the beginning of the line.
- */
- divideAt(ratio) {
- const dividerPoint = this.pointAt(ratio);
- return [
- new Line(this.start, dividerPoint),
- new Line(dividerPoint, this.end),
- ];
- }
- /**
- * Divides the line into two lines at the point that lies length away from
- * the beginning of the line.
- */
- divideAtLength(length) {
- const dividerPoint = this.pointAtLength(length);
- return [
- new Line(this.start, dividerPoint),
- new Line(dividerPoint, this.end),
- ];
- }
- /**
- * Returns `true` if the point `p` lies on the line. Return `false` otherwise.
- */
- containsPoint(p) {
- const start = this.start;
- const end = this.end;
- // cross product of 0 indicates that this line and
- // the vector to `p` are collinear.
- if (start.cross(p, end) !== 0) {
- return false;
- }
- const length = this.length();
- if (new Line(start, p).length() > length) {
- return false;
- }
- if (new Line(p, end).length() > length) {
- return false;
- }
- return true;
- }
- intersect(shape, options) {
- const ret = shape.intersectsWithLine(this, options);
- if (ret) {
- return Array.isArray(ret) ? ret : [ret];
- }
- return null;
- }
- /**
- * Returns the intersection point of the line with another line. Returns
- * `null` if no intersection exists.
- */
- intersectsWithLine(line) {
- const pt1Dir = new Point(this.end.x - this.start.x, this.end.y - this.start.y);
- const pt2Dir = new Point(line.end.x - line.start.x, line.end.y - line.start.y);
- const det = pt1Dir.x * pt2Dir.y - pt1Dir.y * pt2Dir.x;
- const deltaPt = new Point(line.start.x - this.start.x, line.start.y - this.start.y);
- const alpha = deltaPt.x * pt2Dir.y - deltaPt.y * pt2Dir.x;
- const beta = deltaPt.x * pt1Dir.y - deltaPt.y * pt1Dir.x;
- if (det === 0 || alpha * det < 0 || beta * det < 0) {
- return null;
- }
- if (det > 0) {
- if (alpha > det || beta > det) {
- return null;
- }
- }
- else if (alpha < det || beta < det) {
- return null;
- }
- return new Point(this.start.x + (alpha * pt1Dir.x) / det, this.start.y + (alpha * pt1Dir.y) / det);
- }
- /**
- * Returns `true` if a tangent line can be found for the line.
- *
- * Tangents cannot be found if both of the line endpoints are coincident
- * (the line appears to be a point).
- */
- isDifferentiable() {
- return !this.start.equals(this.end);
- }
- /**
- * Returns the perpendicular distance between the line and point. The
- * distance is positive if the point lies to the right of the line, negative
- * if the point lies to the left of the line, and `0` if the point lies on
- * the line.
- */
- pointOffset(p) {
- const ref = Point.clone(p);
- const start = this.start;
- const end = this.end;
- const determinant = (end.x - start.x) * (ref.y - start.y) -
- (end.y - start.y) * (ref.x - start.x);
- return determinant / this.length();
- }
- pointSquaredDistance(x, y) {
- const p = Point.create(x, y);
- return this.closestPoint(p).squaredDistance(p);
- }
- pointDistance(x, y) {
- const p = Point.create(x, y);
- return this.closestPoint(p).distance(p);
- }
- /**
- * Returns a line tangent to the line at point that lies `rate` (normalized
- * length) away from the beginning of the line.
- */
- tangentAt(ratio) {
- if (!this.isDifferentiable()) {
- return null;
- }
- const start = this.start;
- const end = this.end;
- const tangentStart = this.pointAt(ratio);
- const tangentLine = new Line(start, end);
- tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y);
- return tangentLine;
- }
- /**
- * Returns a line tangent to the line at point that lies `length` away from
- * the beginning of the line.
- */
- tangentAtLength(length) {
- if (!this.isDifferentiable()) {
- return null;
- }
- const start = this.start;
- const end = this.end;
- const tangentStart = this.pointAtLength(length);
- const tangentLine = new Line(start, end);
- tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y);
- return tangentLine;
- }
- relativeCcw(x, y) {
- const ref = Point.create(x, y);
- let dx1 = ref.x - this.start.x;
- let dy1 = ref.y - this.start.y;
- const dx2 = this.end.x - this.start.x;
- const dy2 = this.end.y - this.start.y;
- let ccw = dx1 * dy2 - dy1 * dx2;
- if (ccw === 0) {
- ccw = dx1 * dx2 + dy1 * dy2;
- if (ccw > 0.0) {
- dx1 -= dx2;
- dy1 -= dy2;
- ccw = dx1 * dx2 + dy1 * dy2;
- if (ccw < 0.0) {
- ccw = 0.0;
- }
- }
- }
- return ccw < 0.0 ? -1 : ccw > 0.0 ? 1 : 0;
- }
- /**
- * Return `true` if the line equals the other line.
- */
- equals(l) {
- return (l != null &&
- this.start.x === l.start.x &&
- this.start.y === l.start.y &&
- this.end.x === l.end.x &&
- this.end.y === l.end.y);
- }
- /**
- * Returns another line which is a clone of the line.
- */
- clone() {
- return new Line(this.start, this.end);
- }
- toJSON() {
- return { start: this.start.toJSON(), end: this.end.toJSON() };
- }
- serialize() {
- return [this.start.serialize(), this.end.serialize()].join(' ');
- }
- }
- (function (Line) {
- function isLine(instance) {
- return instance != null && instance instanceof Line;
- }
- Line.isLine = isLine;
- })(Line || (Line = {}));
- //# sourceMappingURL=line.js.map
|