UNPKG

@tldraw/editor

Version:

A tiny little drawing app (editor).

152 lines (151 loc) 4.29 kB
import { Box } from "../Box.mjs"; import { Vec } from "../Vec.mjs"; import { pointInPolygon } from "../utils.mjs"; class Geometry2d { isFilled = false; isClosed = true; isLabel = false; debugColor; ignore; constructor(opts) { this.isFilled = opts.isFilled; this.isClosed = opts.isClosed; this.isLabel = opts.isLabel ?? false; this.debugColor = opts.debugColor; this.ignore = opts.ignore; } // hitTestPoint(point: Vec, margin = 0, hitInside = false) { // // We've removed the broad phase here; that should be done outside of the call // return this.distanceToPoint(point, hitInside) <= margin // } hitTestPoint(point, margin = 0, hitInside = false) { if (this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)) { return true; } return Vec.Dist2(point, this.nearestPoint(point)) <= margin * margin; } distanceToPoint(point, hitInside = false) { return point.dist(this.nearestPoint(point)) * (this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices) ? -1 : 1); } distanceToLineSegment(A, B) { if (A.equals(B)) return this.distanceToPoint(A); const { vertices } = this; let nearest; let dist = Infinity; let d, p, q; for (let i = 0; i < vertices.length; i++) { p = vertices[i]; q = Vec.NearestPointOnLineSegment(A, B, p, true); d = Vec.Dist2(p, q); if (d < dist) { dist = d; nearest = q; } } if (!nearest) throw Error("nearest point not found"); return this.isClosed && this.isFilled && pointInPolygon(nearest, this.vertices) ? -dist : dist; } hitTestLineSegment(A, B, distance = 0) { return this.distanceToLineSegment(A, B) <= distance; } nearestPointOnLineSegment(A, B) { const { vertices } = this; let nearest; let dist = Infinity; let d, p, q; for (let i = 0; i < vertices.length; i++) { p = vertices[i]; q = Vec.NearestPointOnLineSegment(A, B, p, true); d = Vec.Dist2(p, q); if (d < dist) { dist = d; nearest = q; } } if (!nearest) throw Error("nearest point not found"); return nearest; } isPointInBounds(point, margin = 0) { const { bounds } = this; return !(point.x < bounds.minX - margin || point.y < bounds.minY - margin || point.x > bounds.maxX + margin || point.y > bounds.maxY + margin); } _vertices; // eslint-disable-next-line no-restricted-syntax get vertices() { if (!this._vertices) { this._vertices = this.getVertices(); } return this._vertices; } getBounds() { return Box.FromPoints(this.vertices); } _bounds; // eslint-disable-next-line no-restricted-syntax get bounds() { if (!this._bounds) { this._bounds = this.getBounds(); } return this._bounds; } // eslint-disable-next-line no-restricted-syntax get center() { return this.bounds.center; } _area; // eslint-disable-next-line no-restricted-syntax get area() { if (!this._area) { this._area = this.getArea(); } return this._area; } getArea() { if (!this.isClosed) { return 0; } const { vertices } = this; let area = 0; for (let i = 0, n = vertices.length; i < n; i++) { const curr = vertices[i]; const next = vertices[(i + 1) % n]; area += curr.x * next.y - next.x * curr.y; } return area / 2; } toSimpleSvgPath() { let path = ""; const { vertices } = this; const n = vertices.length; if (n === 0) return path; path += `M${vertices[0].x},${vertices[0].y}`; for (let i = 1; i < n; i++) { path += `L${vertices[i].x},${vertices[i].y}`; } if (this.isClosed) { path += "Z"; } return path; } _length; // eslint-disable-next-line no-restricted-syntax get length() { if (this._length) return this._length; this._length = this.getLength(); return this._length; } getLength() { const { vertices } = this; let n1, p1 = vertices[0], length = 0; for (let i = 1; i < vertices.length; i++) { n1 = vertices[i]; length += Vec.Dist2(p1, n1); p1 = n1; } return Math.sqrt(length); } } export { Geometry2d }; //# sourceMappingURL=Geometry2d.mjs.map