UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

375 lines (374 loc) • 13.4 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var Geometry2d_exports = {}; __export(Geometry2d_exports, { Geometry2d: () => Geometry2d, Geometry2dFilters: () => Geometry2dFilters, TransformedGeometry2d: () => TransformedGeometry2d }); module.exports = __toCommonJS(Geometry2d_exports); var import_utils = require("@tldraw/utils"); var import_Box = require("../Box"); var import_Mat = require("../Mat"); var import_Vec = require("../Vec"); var import_intersect = require("../intersect"); var import_utils2 = require("../utils"); const Geometry2dFilters = { EXCLUDE_NON_STANDARD: { includeLabels: false, includeInternal: false }, INCLUDE_ALL: { includeLabels: true, includeInternal: true }, EXCLUDE_LABELS: { includeLabels: false, includeInternal: true }, EXCLUDE_INTERNAL: { includeLabels: true, includeInternal: false } }; class Geometry2d { // todo: consider making accessors for these too, so that they can be overridden in subclasses by geometries with more complex logic isFilled = false; isClosed = true; isLabel = false; isEmptyLabel = false; isInternal = false; debugColor; ignore; constructor(opts) { const { isLabel = false, isEmptyLabel = false, isInternal = false } = opts; this.isFilled = opts.isFilled; this.isClosed = opts.isClosed; this.debugColor = opts.debugColor; this.ignore = opts.ignore; this.isLabel = isLabel; this.isEmptyLabel = isEmptyLabel; this.isInternal = isInternal; } isExcludedByFilter(filters) { if (!filters) return false; if (this.isLabel && !filters.includeLabels) return true; if (this.isInternal && !filters.includeInternal) return true; return false; } hitTestPoint(point, margin = 0, hitInside = false, _filters) { if (this.isClosed && (this.isFilled || hitInside) && (0, import_utils2.pointInPolygon)(point, this.vertices)) { return true; } return import_Vec.Vec.Dist2(point, this.nearestPoint(point)) <= margin * margin; } distanceToPoint(point, hitInside = false, filters) { return import_Vec.Vec.Dist(point, this.nearestPoint(point, filters)) * (this.isClosed && (this.isFilled || hitInside) && (0, import_utils2.pointInPolygon)(point, this.vertices) ? -1 : 1); } distanceToLineSegment(A, B, filters) { if (import_Vec.Vec.Equals(A, B)) return this.distanceToPoint(A, false, filters); const { vertices } = this; let nearest; let dist = Infinity; let d, p, q; const nextLimit = this.isClosed ? vertices.length : vertices.length - 1; for (let i = 0; i < vertices.length; i++) { p = vertices[i]; if (i < nextLimit) { const next = vertices[(i + 1) % vertices.length]; if ((0, import_intersect.linesIntersect)(A, B, p, next)) return 0; } q = import_Vec.Vec.NearestPointOnLineSegment(A, B, p, true); d = import_Vec.Vec.Dist2(p, q); if (d < dist) { dist = d; nearest = q; } } if (!nearest) throw Error("nearest point not found"); return this.isClosed && this.isFilled && (0, import_utils2.pointInPolygon)(nearest, this.vertices) ? -dist : dist; } hitTestLineSegment(A, B, distance = 0, filters) { return this.distanceToLineSegment(A, B, filters) <= distance; } intersectLineSegment(A, B, _filters) { const intersections = this.isClosed ? (0, import_intersect.intersectLineSegmentPolygon)(A, B, this.vertices) : (0, import_intersect.intersectLineSegmentPolyline)(A, B, this.vertices); return intersections ?? []; } intersectCircle(center, radius, _filters) { const intersections = this.isClosed ? (0, import_intersect.intersectCirclePolygon)(center, radius, this.vertices) : (0, import_intersect.intersectCirclePolyline)(center, radius, this.vertices); return intersections ?? []; } intersectPolygon(polygon, _filters) { return (0, import_intersect.intersectPolys)(polygon, this.vertices, true, this.isClosed); } intersectPolyline(polyline, _filters) { return (0, import_intersect.intersectPolys)(polyline, this.vertices, false, this.isClosed); } /** * Find a point along the edge of the geometry that is a fraction `t` along the entire way round. */ interpolateAlongEdge(t, _filters) { const { vertices } = this; if (t <= 0) return vertices[0]; const distanceToTravel = t * this.length; let distanceTraveled = 0; for (let i = 0; i < (this.isClosed ? vertices.length : vertices.length - 1); i++) { const curr = vertices[i]; const next = vertices[(i + 1) % vertices.length]; const dist = import_Vec.Vec.Dist(curr, next); const newDistanceTraveled = distanceTraveled + dist; if (newDistanceTraveled >= distanceToTravel) { const p = import_Vec.Vec.Lrp( curr, next, (0, import_utils.invLerp)(distanceTraveled, newDistanceTraveled, distanceToTravel) ); return p; } distanceTraveled = newDistanceTraveled; } return this.isClosed ? vertices[0] : vertices[vertices.length - 1]; } /** * Take `point`, find the closest point to it on the edge of the geometry, and return how far * along the edge it is as a fraction of the total length. */ uninterpolateAlongEdge(point, _filters) { const { vertices, length } = this; let closestSegment = null; let closestDistance = Infinity; let distanceTraveled = 0; for (let i = 0; i < (this.isClosed ? vertices.length : vertices.length - 1); i++) { const curr = vertices[i]; const next = vertices[(i + 1) % vertices.length]; const nearestPoint = import_Vec.Vec.NearestPointOnLineSegment(curr, next, point, true); const distance = import_Vec.Vec.Dist(nearestPoint, point); if (distance < closestDistance) { closestDistance = distance; closestSegment = { start: curr, end: next, nearestPoint, distanceToStart: distanceTraveled }; } distanceTraveled += import_Vec.Vec.Dist(curr, next); } (0, import_utils.assert)(closestSegment); const distanceAlongRoute = closestSegment.distanceToStart + import_Vec.Vec.Dist(closestSegment.start, closestSegment.nearestPoint); return distanceAlongRoute / length; } /** @deprecated Iterate the vertices instead. */ 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 = import_Vec.Vec.NearestPointOnLineSegment(A, B, p, true); d = import_Vec.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); } transform(transform, opts) { return new TransformedGeometry2d(this, transform, opts); } _vertices; // eslint-disable-next-line no-restricted-syntax get vertices() { if (!this._vertices) { this._vertices = this.getVertices(Geometry2dFilters.EXCLUDE_LABELS); } return this._vertices; } getBounds() { return import_Box.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(Geometry2dFilters.EXCLUDE_LABELS); return this._length; } getLength(_filters) { const vertices = this.getVertices(_filters ?? Geometry2dFilters.EXCLUDE_LABELS); if (vertices.length === 0) return 0; let prev = vertices[0]; let length = 0; for (let i = 1; i < vertices.length; i++) { const next = vertices[i]; length += import_Vec.Vec.Dist(prev, next); prev = next; } if (this.isClosed) { length += import_Vec.Vec.Dist(vertices[vertices.length - 1], vertices[0]); } return length; } } class TransformedGeometry2d extends Geometry2d { constructor(geometry, matrix, opts) { super(geometry); this.geometry = geometry; this.matrix = matrix; this.inverse = import_Mat.Mat.Inverse(matrix); this.decomposed = import_Mat.Mat.Decompose(matrix); if (opts) { if (opts.isLabel != null) this.isLabel = opts.isLabel; if (opts.isInternal != null) this.isInternal = opts.isInternal; if (opts.debugColor != null) this.debugColor = opts.debugColor; if (opts.ignore != null) this.ignore = opts.ignore; } (0, import_utils.assert)( (0, import_utils2.approximately)(this.decomposed.scaleX, this.decomposed.scaleY), "non-uniform scaling is not yet supported" ); } inverse; decomposed; getVertices(filters) { return this.geometry.getVertices(filters).map((v) => import_Mat.Mat.applyToPoint(this.matrix, v)); } nearestPoint(point, filters) { return import_Mat.Mat.applyToPoint( this.matrix, this.geometry.nearestPoint(import_Mat.Mat.applyToPoint(this.inverse, point), filters) ); } hitTestPoint(point, margin = 0, hitInside, filters) { return this.geometry.hitTestPoint( import_Mat.Mat.applyToPoint(this.inverse, point), margin / this.decomposed.scaleX, hitInside, filters ); } distanceToPoint(point, hitInside = false, filters) { return this.geometry.distanceToPoint(import_Mat.Mat.applyToPoint(this.inverse, point), hitInside, filters) * this.decomposed.scaleX; } distanceToLineSegment(A, B, filters) { return this.geometry.distanceToLineSegment( import_Mat.Mat.applyToPoint(this.inverse, A), import_Mat.Mat.applyToPoint(this.inverse, B), filters ) * this.decomposed.scaleX; } hitTestLineSegment(A, B, distance = 0, filters) { return this.geometry.hitTestLineSegment( import_Mat.Mat.applyToPoint(this.inverse, A), import_Mat.Mat.applyToPoint(this.inverse, B), distance / this.decomposed.scaleX, filters ); } intersectLineSegment(A, B, filters) { return import_Mat.Mat.applyToPoints( this.matrix, this.geometry.intersectLineSegment( import_Mat.Mat.applyToPoint(this.inverse, A), import_Mat.Mat.applyToPoint(this.inverse, B), filters ) ); } intersectCircle(center, radius, filters) { return import_Mat.Mat.applyToPoints( this.matrix, this.geometry.intersectCircle( import_Mat.Mat.applyToPoint(this.inverse, center), radius / this.decomposed.scaleX, filters ) ); } intersectPolygon(polygon, filters) { return import_Mat.Mat.applyToPoints( this.matrix, this.geometry.intersectPolygon(import_Mat.Mat.applyToPoints(this.inverse, polygon), filters) ); } intersectPolyline(polyline, filters) { return import_Mat.Mat.applyToPoints( this.matrix, this.geometry.intersectPolyline(import_Mat.Mat.applyToPoints(this.inverse, polyline), filters) ); } transform(transform, opts) { return new TransformedGeometry2d(this.geometry, import_Mat.Mat.Multiply(transform, this.matrix), { isLabel: opts?.isLabel ?? this.isLabel, isInternal: opts?.isInternal ?? this.isInternal, debugColor: opts?.debugColor ?? this.debugColor, ignore: opts?.ignore ?? this.ignore }); } getSvgPathData() { throw new Error("Cannot get SVG path data for transformed geometry."); } } //# sourceMappingURL=Geometry2d.js.map