From bff23b04e393d3c5b6fdfc1b5593ed3ee25c00dd Mon Sep 17 00:00:00 2001 From: William Ngan Date: Mon, 7 May 2018 02:02:22 -0700 Subject: [PATCH] remove comments and build --- dist/files/Bound.js | 2 +- dist/files/Canvas.js | 6 +- dist/files/Color.js | 2 +- dist/files/Create.js | 2 +- dist/files/Dom.js | 3 +- dist/files/Form.js | 2 +- dist/files/LinearAlgebra.js | 2 +- dist/files/Num.js | 5 +- dist/files/Op.d.ts | 107 +- dist/files/Op.js | 311 ++- dist/files/Physics.d.ts | 248 +++ dist/files/Physics.js | 532 +++++ dist/files/Pt.d.ts | 10 +- dist/files/Pt.js | 20 +- dist/files/Space.js | 2 +- dist/files/Svg.js | 2 + dist/files/Typography.d.ts | 10 +- dist/files/Typography.js | 28 +- dist/files/UI.js | 2 + dist/files/Util.js | 2 +- dist/files/_lib.d.ts | 1 + dist/files/_lib.js | 3 +- dist/files/_module.d.ts | 1 + dist/files/_module.js | 19 + dist/index.js | 3783 ++++++----------------------------- dist/pts.d.ts | 375 +++- dist/pts.js | 3231 +----------------------------- dist/pts.min.js | 2 +- tsconfig.json | 3 +- 29 files changed, 2294 insertions(+), 6422 deletions(-) create mode 100644 dist/files/Physics.d.ts create mode 100644 dist/files/Physics.js create mode 100644 dist/files/_module.d.ts create mode 100644 dist/files/_module.js diff --git a/dist/files/Bound.js b/dist/files/Bound.js index 53eee6af..4a4eab4e 100644 --- a/dist/files/Bound.js +++ b/dist/files/Bound.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = require("./Pt"); /** diff --git a/dist/files/Canvas.js b/dist/files/Canvas.js index d75bd827..486544e6 100644 --- a/dist/files/Canvas.js +++ b/dist/files/Canvas.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Space_1 = require("./Space"); const Form_1 = require("./Form"); @@ -36,7 +36,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { this.id = "pts_existing_space"; } else { - ; _selector = document.querySelector(elem); _existed = true; this.id = elem; @@ -178,7 +177,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { p.resize(this.bound, evt); } } - ; this.render(this._ctx); // if it's a valid resize event and space is not playing, repaint the canvas once if (evt && !this.isPlaying) @@ -445,7 +443,7 @@ class CanvasForm extends Form_1.VisualForm { * @param c a string of text contents */ getTextWidth(c) { - return (!this._estimateTextWidth) ? this._ctx.measureText(c).width : this._estimateTextWidth(c); + return (!this._estimateTextWidth) ? this._ctx.measureText(c + " .").width : this._estimateTextWidth(c); } /** * Truncate text to fit width diff --git a/dist/files/Color.js b/dist/files/Color.js index fc40f195..4632ba7e 100644 --- a/dist/files/Color.js +++ b/dist/files/Color.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = require("./Pt"); const Util_1 = require("./Util"); diff --git a/dist/files/Create.js b/dist/files/Create.js index 44d0ac29..0e715f25 100644 --- a/dist/files/Create.js +++ b/dist/files/Create.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = require("./Pt"); const Op_1 = require("./Op"); diff --git a/dist/files/Dom.js b/dist/files/Dom.js index 9224043c..e07051c8 100644 --- a/dist/files/Dom.js +++ b/dist/files/Dom.js @@ -1,4 +1,6 @@ "use strict"; +// Source code licensed under Apache License 2.0. +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Space_1 = require("./Space"); const Form_1 = require("./Form"); @@ -136,7 +138,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { p.resize(this.bound, evt); } } - ; return this; } /** diff --git a/dist/files/Form.js b/dist/files/Form.js index 6c89e2ed..8aa4e14b 100644 --- a/dist/files/Form.js +++ b/dist/files/Form.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = require("./Util"); /** diff --git a/dist/files/LinearAlgebra.js b/dist/files/LinearAlgebra.js index 1151b086..c749457a 100644 --- a/dist/files/LinearAlgebra.js +++ b/dist/files/LinearAlgebra.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = require("./Pt"); const Op_1 = require("./Op"); diff --git a/dist/files/Num.js b/dist/files/Num.js index 5c600570..1be32b1d 100644 --- a/dist/files/Num.js +++ b/dist/files/Num.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = require("./Util"); const Op_1 = require("./Op"); @@ -345,9 +345,10 @@ class Geom { */ static reflect2D(ps, line, axis) { let pts = (!Array.isArray(ps)) ? [ps] : ps; + let mat = LinearAlgebra_1.Mat.reflectAt2DMatrix(line[0], line[1]); for (let i = 0, len = pts.length; i < len; i++) { let p = (axis) ? pts[i].$take(axis) : pts[i]; - p.to(LinearAlgebra_1.Mat.transform2D(p, LinearAlgebra_1.Mat.reflectAt2DMatrix(line[0], line[1]))); + p.to(LinearAlgebra_1.Mat.transform2D(p, mat)); } return Geom; } diff --git a/dist/files/Op.d.ts b/dist/files/Op.d.ts index c04dee00..cfbf54d6 100644 --- a/dist/files/Op.d.ts +++ b/dist/files/Op.d.ts @@ -1,4 +1,12 @@ import { Pt, PtLike, Group, GroupLike } from "./Pt"; +export declare type IntersectContext = { + which: number; + dist: number; + normal: Pt; + vertex: Pt; + edge: Group; + other?: any; +}; /** * Line class provides static functions to create and operate on lines. A line is usually represented as a Group of 2 Pts. * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. @@ -97,7 +105,14 @@ export declare class Line { * @param poly a Group of Pts representing a polygon * @param sourceIsRay a boolean value to treat the line as a ray (infinite line). Default is `false`. */ - static intersectPolygon2D(lineOrRay: GroupLike, poly: GroupLike[], sourceIsRay?: boolean): Group; + static intersectPolygon2D(lineOrRay: GroupLike, poly: GroupLike, sourceIsRay?: boolean): Group; + /** + * Find intersection points of 2 polygons. This checks all line segments in the two lists. Consider using a bounding-box check before calling this. + * @param lines1 an array of line segments + * @param lines2 an array of line segments + * @param isRay a boolean value to treat the line as a ray (infinite line). Default is `false`. + */ + static intersectLines2D(lines1: GroupLike[], lines2: GroupLike[], isRay?: boolean): Group; /** * Get two intersection Pts of a ray with a 2D grid point * @param ray a ray specified by 2 Pts @@ -114,9 +129,10 @@ export declare class Line { static intersectGridWithLine2D(line: GroupLike, gridPt: PtLike | number[]): Group; /** * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. + * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. * @param line a Group representing a line * @param rect a Group representing a rectangle + * @returns a Group of intersecting Pts */ static intersectRect2D(line: GroupLike, rect: GroupLike): Group; /** @@ -125,6 +141,24 @@ export declare class Line { * @param num number of points to get */ static subpoints(line: GroupLike | number[][], num: number): Group; + /** + * Crop this line by a circle or rectangle at end point. + * @param line line to crop + * @param size size of circle or rectangle as Pt + * @param index line's end point index, ie, 0 = start and 1 = end. + * @param cropAsCircle a boolean to specify whether the `size` parameter should be treated as circle. Default is `true`. + * @return an intersecting point on the line that can be used for cropping. + */ + static crop(line: GroupLike, size: PtLike, index?: number, cropAsCircle?: boolean): Pt; + /** + * Create an marker arrow or line, placed at an end point of this line + * @param line line to place marker + * @param size size of the marker as Pt + * @param graphic either "arrow" or "line" + * @param atTail a boolean, if `true`, the marker will be positioned at tail of the line (ie, index = 1). Default is `true`. + * @returns a Group that defines the marker's shape + */ + static marker(line: GroupLike, size: PtLike, graphic?: string, atTail?: boolean): Group; /** * Convert this line to a rectangle representation * @param line a Group representing a line @@ -227,11 +261,12 @@ export declare class Rectangle { * Check if a rectangle is within the bounds of another rectangle * @param rect1 a Group of 2 Pts representing a rectangle * @param rect2 a Group of 2 Pts representing a rectangle + * @param resetBoundingBox if `true`, reset the bounding box. Default is `false` which assumes the rect's first Pt at is its top-left corner. */ - static hasIntersectRect2D(rect1: GroupLike, rect2: GroupLike): boolean; + static hasIntersectRect2D(rect1: GroupLike, rect2: GroupLike, resetBoundingBox?: boolean): boolean; /** * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. + * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. * @param rect1 a Group of 2 Pts representing a rectangle * @param rect2 a Group of 2 Pts representing a rectangle */ @@ -285,7 +320,7 @@ export declare class Circle { static intersectCircle2D(pts: GroupLike, circle: GroupLike): Group; /** * Quick way to check rectangle intersection with a circle. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. + * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. * @param pts a Group of 2 Pts representing a circle * @param rect a Group of 2 Pts representing a rectangle * @returns a Group of intersection points, or an empty Group if no intersection is found @@ -391,6 +426,20 @@ export declare class Polygon { * @param pts a Group of Pts representing a polygon */ static centroid(pts: GroupLike): Pt; + /** + * Create a rectangular polygon + * @param center center point of the rectangle + * @param widthOrSize width as number, or a Pt representing the size of the rectangle + * @param height optional height + */ + static rectangle(center: PtLike, widthOrSize: number | PtLike, height?: number): Group; + static fromCenter(center: PtLike, radius: number, sides: number): Group; + /** + * Given a Group of Pts that defines a polygon, get one edge using an index + * @param pts a Group + * @param idx index of a Pt in the Group + */ + static lineAt(pts: GroupLike, idx: number): Group; /** * Get the line segments in this polygon * @param pts a Group of Pts @@ -443,13 +492,6 @@ export declare class Polygon { * @returns a group of Pt that defines the convex hull polygon */ static convexHull(pts: GroupLike, sorted?: boolean): Group; - /** - * Find intersection points of 2 polygons - * @param poly a Group representing a polygon - * @param linesOrRays an array of Groups representing lines - * @param sourceIsRay a boolean value to treat the line as a ray (infinite line). Default is `false`. - */ - static intersect2D(poly: GroupLike[], linesOrRays: GroupLike[], sourceIsRay?: boolean): Group[]; /** * Given a point in the polygon as an origin, get an array of lines that connect all the remaining points to the origin point. * @param pts a Group representing a polygon @@ -463,11 +505,50 @@ export declare class Polygon { * @returns an index in the pts indicating the nearest Pt, or -1 if none found */ static nearestPt(pts: GroupLike, pt: PtLike): number; + /** + * Project axis (eg, for use in Separation Axis Theorem) + * @param poly + * @param unitAxis + */ + static projectAxis(poly: GroupLike, unitAxis: Pt): Pt; + /** + * Check overlap dist from projected axis + * @param poly1 first polygon + * @param poly2 second polygon + * @param unitAxis unit axis + */ + protected static _axisOverlap(poly1: any, poly2: any, unitAxis: any): number; + /** + * Check if a Pt is inside a convex polygon + * @param poly a Group of Pt defining a convex polygon + * @param pt the Pt to check + */ + static hasIntersectPoint(poly: GroupLike, pt: PtLike): boolean; + /** + * Check if a convex polygon and a circle has intersections using Separating Axis Theorem. + * @param poly a Group representing a convex polygon + * @param circle a Group representing a circle + * @returns an `IntersectContext` object that stores the intersection info, or undefined if there's no intersection + */ + static hasIntersectCircle(poly: GroupLike, circle: GroupLike): IntersectContext; + /** + * Check if two convex polygons has intersections using Separating Axis Theorem. + * @param poly1 a Group representing a convex polygon + * @param poly2 a Group representing a convex polygon + * @return an `IntersectContext` object that stores the intersection info, or undefined if there's no intersection + */ + static hasIntersectPolygon(poly1: GroupLike, poly2: GroupLike): IntersectContext; + /** + * Find intersection points of 2 polygons by checking every side of both polygons + * @param poly1 a Group representing a polygon + * @param poly2 another Group representing a polygon + */ + static intersectPolygon2D(poly1: GroupLike, poly2: GroupLike): Group; /** * Get a bounding box for each polygon group, as well as a union bounding-box for all groups * @param polys an array of Groups, or an array of Pt arrays */ - static toRects(poly: GroupLike[]): GroupLike[]; + static toRects(polys: GroupLike[]): GroupLike[]; } /** * Curve class provides static functions to interpolate curves. A curve is usually represented as a Group of 3 control points. diff --git a/dist/files/Op.js b/dist/files/Op.js index 164aba50..38bf5f11 100644 --- a/dist/files/Op.js +++ b/dist/files/Op.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = require("./Util"); const Num_1 = require("./Num"); @@ -179,12 +179,31 @@ class Line { let fn = sourceIsRay ? Line.intersectLineWithRay2D : Line.intersectLine2D; let pts = new Pt_1.Group(); for (let i = 0, len = poly.length; i < len; i++) { - let d = fn(poly[i], lineOrRay); + let next = (i === len - 1) ? 0 : i + 1; + let d = fn([poly[i], poly[next]], lineOrRay); if (d) pts.push(d); } return (pts.length > 0) ? pts : undefined; } + /** + * Find intersection points of 2 polygons. This checks all line segments in the two lists. Consider using a bounding-box check before calling this. + * @param lines1 an array of line segments + * @param lines2 an array of line segments + * @param isRay a boolean value to treat the line as a ray (infinite line). Default is `false`. + */ + static intersectLines2D(lines1, lines2, isRay = false) { + let group = new Pt_1.Group(); + let fn = isRay ? Line.intersectLineWithRay2D : Line.intersectLine2D; + for (let i = 0, len = lines1.length; i < len; i++) { + for (let k = 0, lenk = lines2.length; k < lenk; k++) { + let _ip = fn(lines1[i], lines2[k]); + if (_ip) + group.push(_ip); + } + } + return group; + } /** * Get two intersection Pts of a ray with a 2D grid point * @param ray a ray specified by 2 Pts @@ -217,12 +236,16 @@ class Line { } /** * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. + * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. * @param line a Group representing a line * @param rect a Group representing a rectangle + * @returns a Group of intersecting Pts */ static intersectRect2D(line, rect) { - return Rectangle.intersectRect2D(Line.toRect(line), rect); + let box = Num_1.Geom.boundingBox(Pt_1.Group.fromPtArray(line)); + if (!Rectangle.hasIntersectRect2D(box, rect)) + return new Pt_1.Group(); + return Line.intersectLines2D([line], Rectangle.sides(rect)); } /** * Get evenly distributed points on a line @@ -236,6 +259,60 @@ class Line { } return pts; } + /** + * Crop this line by a circle or rectangle at end point. + * @param line line to crop + * @param size size of circle or rectangle as Pt + * @param index line's end point index, ie, 0 = start and 1 = end. + * @param cropAsCircle a boolean to specify whether the `size` parameter should be treated as circle. Default is `true`. + * @return an intersecting point on the line that can be used for cropping. + */ + static crop(line, size, index = 0, cropAsCircle = true) { + let tdx = (index === 0) ? 1 : 0; + let ls = line[tdx].$subtract(line[index]); + if (ls[0] === 0 || size[0] === 0) + return line[index]; + if (cropAsCircle) { + let d = ls.unit().multiply(size[1]); + return line[index].$add(d); + } + else { + let rect = Rectangle.fromCenter(line[index], size); + let sides = Rectangle.sides(rect); + let sideIdx = 0; + if (Math.abs(ls[1] / ls[0]) > Math.abs(size[1] / size[0])) { + sideIdx = (ls[1] < 0) ? 0 : 2; + } + else { + sideIdx = (ls[0] < 0) ? 3 : 1; + } + return Line.intersectRay2D(sides[sideIdx], line); + } + } + /** + * Create an marker arrow or line, placed at an end point of this line + * @param line line to place marker + * @param size size of the marker as Pt + * @param graphic either "arrow" or "line" + * @param atTail a boolean, if `true`, the marker will be positioned at tail of the line (ie, index = 1). Default is `true`. + * @returns a Group that defines the marker's shape + */ + static marker(line, size, graphic = ("arrow" || "line"), atTail = true) { + let h = atTail ? 0 : 1; + let t = atTail ? 1 : 0; + let unit = line[h].$subtract(line[t]); + if (unit.magnitudeSq() === 0) + return new Pt_1.Group(); + unit.unit(); + let ps = Num_1.Geom.perpendicular(unit).multiply(size[0]).add(line[t]); + if (graphic == "arrow") { + ps.add(unit.$multiply(size[1])); + return new Pt_1.Group(line[t], ps[0], ps[1]); + } + else { + return new Pt_1.Group(ps[0], ps[1]); + } + } /** * Convert this line to a rectangle representation * @param line a Group representing a line @@ -400,23 +477,29 @@ class Rectangle { * Check if a rectangle is within the bounds of another rectangle * @param rect1 a Group of 2 Pts representing a rectangle * @param rect2 a Group of 2 Pts representing a rectangle + * @param resetBoundingBox if `true`, reset the bounding box. Default is `false` which assumes the rect's first Pt at is its top-left corner. */ - static hasIntersectRect2D(rect1, rect2) { - let pts = Rectangle.corners(rect1); - for (let i = 0, len = pts.length; i < len; i++) { - if (Num_1.Geom.withinBound(pts[i], rect2[0], rect2[1])) - return true; + static hasIntersectRect2D(rect1, rect2, resetBoundingBox = false) { + if (resetBoundingBox) { + rect1 = Num_1.Geom.boundingBox(rect1); + rect2 = Num_1.Geom.boundingBox(rect2); } - return false; + if (rect1[0][0] > rect2[1][0] || rect2[0][0] > rect1[1][0]) + return false; + if (rect1[0][1] > rect2[1][1] || rect2[0][1] > rect1[1][1]) + return false; + return true; } /** * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. + * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. * @param rect1 a Group of 2 Pts representing a rectangle * @param rect2 a Group of 2 Pts representing a rectangle */ static intersectRect2D(rect1, rect2) { - return Util_1.Util.flatten(Polygon.intersect2D(Rectangle.sides(rect1), Rectangle.sides(rect2))); + if (!Rectangle.hasIntersectRect2D(rect1, rect2)) + return new Pt_1.Group(); + return Line.intersectLines2D(Rectangle.sides(rect1), Rectangle.sides(rect2)); } } exports.Rectangle = Rectangle; @@ -536,7 +619,7 @@ class Circle { } /** * Quick way to check rectangle intersection with a circle. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. + * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. * @param pts a Group of 2 Pts representing a circle * @param rect a Group of 2 Pts representing a rectangle * @returns a Group of intersection points, or an empty Group if no intersection is found @@ -732,13 +815,40 @@ class Polygon { static centroid(pts) { return Num_1.Geom.centroid(pts); } + /** + * Create a rectangular polygon + * @param center center point of the rectangle + * @param widthOrSize width as number, or a Pt representing the size of the rectangle + * @param height optional height + */ + static rectangle(center, widthOrSize, height) { + return Rectangle.corners(Rectangle.fromCenter(center, widthOrSize, height)); + } + static fromCenter(center, radius, sides) { + let g = new Pt_1.Group(); + for (let i = 0; i < sides; i++) { + let ang = Math.PI * 2 * i / sides; + g.push(new Pt_1.Pt(Math.cos(ang) * radius, Math.sin(ang) * radius).add(center)); + } + return g; + } + /** + * Given a Group of Pts that defines a polygon, get one edge using an index + * @param pts a Group + * @param idx index of a Pt in the Group + */ + static lineAt(pts, idx) { + if (idx < 0 || idx >= pts.length) + throw new Error("index out of the Polygon's range"); + return new Pt_1.Group(pts[idx], (idx === pts.length - 1) ? pts[0] : pts[idx + 1]); + } /** * Get the line segments in this polygon * @param pts a Group of Pts * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). * @returns an array of Groups which has 2 Pts in each group */ - static lines(pts, closePath = false) { + static lines(pts, closePath = true) { if (pts.length < 2) return _errorLength(new Pt_1.Group(), 2); let sp = Util_1.Util.split(pts, 2, 1); @@ -902,21 +1012,6 @@ class Polygon { } return hull; } - /** - * Find intersection points of 2 polygons - * @param poly a Group representing a polygon - * @param linesOrRays an array of Groups representing lines - * @param sourceIsRay a boolean value to treat the line as a ray (infinite line). Default is `false`. - */ - static intersect2D(poly, linesOrRays, sourceIsRay = false) { - let groups = []; - for (let i = 0, len = linesOrRays.length; i < len; i++) { - let _ip = Line.intersectPolygon2D(linesOrRays[i], poly, sourceIsRay); - if (_ip) - groups.push(_ip); - } - return groups; - } /** * Given a point in the polygon as an origin, get an array of lines that connect all the remaining points to the origin point. * @param pts a Group representing a polygon @@ -948,12 +1043,166 @@ class Polygon { } return _item; } + /** + * Project axis (eg, for use in Separation Axis Theorem) + * @param poly + * @param unitAxis + */ + static projectAxis(poly, unitAxis) { + let dot = unitAxis.dot(poly[0]); + let d = new Pt_1.Pt(dot, dot); + for (let n = 1, len = poly.length; n < len; n++) { + dot = unitAxis.dot(poly[n]); + d = new Pt_1.Pt(Math.min(dot, d[0]), Math.max(dot, d[1])); + } + return d; + } + /** + * Check overlap dist from projected axis + * @param poly1 first polygon + * @param poly2 second polygon + * @param unitAxis unit axis + */ + static _axisOverlap(poly1, poly2, unitAxis) { + let pa = Polygon.projectAxis(poly1, unitAxis); + let pb = Polygon.projectAxis(poly2, unitAxis); + return (pa[0] < pb[0]) ? pb[0] - pa[1] : pa[0] - pb[1]; + } + /** + * Check if a Pt is inside a convex polygon + * @param poly a Group of Pt defining a convex polygon + * @param pt the Pt to check + */ + static hasIntersectPoint(poly, pt) { + let c = false; + for (let i = 0, len = poly.length; i < len; i++) { + let ln = Polygon.lineAt(poly, i); + if (((ln[0][1] > pt[1]) != (ln[1][1] > pt[1])) && + (pt[0] < (ln[1][0] - ln[0][0]) * (pt[1] - ln[0][1]) / (ln[1][1] - ln[0][1]) + ln[0][0])) { + c = !c; + } + } + return c; + } + /** + * Check if a convex polygon and a circle has intersections using Separating Axis Theorem. + * @param poly a Group representing a convex polygon + * @param circle a Group representing a circle + * @returns an `IntersectContext` object that stores the intersection info, or undefined if there's no intersection + */ + static hasIntersectCircle(poly, circle) { + let info = { + which: -1, + dist: 0, + normal: null, + edge: null, + vertex: null, + }; + let c = circle[0]; + let r = circle[1][0]; + let minDist = Number.MAX_SAFE_INTEGER; + for (let i = 0, len = poly.length; i < len; i++) { + let edge = Polygon.lineAt(poly, i); + let axis = new Pt_1.Pt(edge[0].y - edge[1].y, edge[1].x - edge[0].x).unit(); + let poly2 = new Pt_1.Group(c.$add(axis.$multiply(r)), c.$subtract(axis.$multiply(r))); + let dist = Polygon._axisOverlap(poly, poly2, axis); + if (dist > 0) { + return null; + } + else if (Math.abs(dist) < minDist) { + // Fix edge case and make sure the circle is intersecting. To be improved. + let check = Rectangle.withinBound(edge, Line.perpendicularFromPt(edge, c)) || Circle.intersectLine2D(circle, edge).length > 0; + if (check) { + info.edge = edge; + info.normal = axis; + minDist = Math.abs(dist); + info.which = i; + } + } + } + if (!info.edge) + return null; + // direction + let dir = c.$subtract(Polygon.centroid(poly)).dot(info.normal); + if (dir < 0) + info.normal.multiply(-1); + info.dist = minDist; + info.vertex = c; + return info; + } + /** + * Check if two convex polygons has intersections using Separating Axis Theorem. + * @param poly1 a Group representing a convex polygon + * @param poly2 a Group representing a convex polygon + * @return an `IntersectContext` object that stores the intersection info, or undefined if there's no intersection + */ + static hasIntersectPolygon(poly1, poly2) { + // Reference: https://www.gamedev.net/articles/programming/math-and-physics/a-verlet-based-approach-for-2d-game-physics-r2714/ + let info = { + which: -1, + dist: 0, + normal: new Pt_1.Pt(), + edge: new Pt_1.Group(), + vertex: new Pt_1.Pt() // the vertex on a polygon that has intersected + }; + let minDist = Number.MAX_SAFE_INTEGER; + for (let i = 0, plen = (poly1.length + poly2.length); i < plen; i++) { + let edge = (i < poly1.length) ? Polygon.lineAt(poly1, i) : Polygon.lineAt(poly2, i - poly1.length); + let axis = new Pt_1.Pt(edge[0].y - edge[1].y, edge[1].x - edge[0].x).unit(); // unit of a perpendicular vector + let dist = Polygon._axisOverlap(poly1, poly2, axis); + if (dist > 0) { + return null; + } + else if (Math.abs(dist) < minDist) { + // store intersected edge and a normal vector + info.edge = edge; + info.normal = axis; + minDist = Math.abs(dist); + info.which = (i < poly1.length) ? 0 : 1; + } + } + info.dist = minDist; + // flip if neded to make sure vertex and edge are in corresponding polygons + let b1 = (info.which === 0) ? poly2 : poly1; + let b2 = (info.which === 0) ? poly1 : poly2; + let c1 = Polygon.centroid(b1); + let c2 = Polygon.centroid(b2); + // direction + let dir = c1.$subtract(c2).dot(info.normal); + if (dir < 0) + info.normal.multiply(-1); + // find vertex at smallest distance + let smallest = Number.MAX_SAFE_INTEGER; + for (let i = 0, len = b1.length; i < len; i++) { + let d = info.normal.dot(b1[i].$subtract(c2)); + if (d < smallest) { + smallest = d; + info.vertex = b1[i]; + } + } + return info; + } + /** + * Find intersection points of 2 polygons by checking every side of both polygons + * @param poly1 a Group representing a polygon + * @param poly2 another Group representing a polygon + */ + static intersectPolygon2D(poly1, poly2) { + let lp = Polygon.lines(poly1); + let g = []; + for (let i = 0, len = lp.length; i < len; i++) { + let ins = Line.intersectPolygon2D(lp[i], poly2, false); + if (ins) + g.push(ins); + } + return Util_1.Util.flatten(g, true); + } /** * Get a bounding box for each polygon group, as well as a union bounding-box for all groups * @param polys an array of Groups, or an array of Pt arrays */ - static toRects(poly) { - let boxes = poly.map((g) => Num_1.Geom.boundingBox(g)); + static toRects(polys) { + let boxes = polys.map((g) => Num_1.Geom.boundingBox(g)); let merged = Util_1.Util.flatten(boxes, false); boxes.unshift(Num_1.Geom.boundingBox(merged)); return boxes; diff --git a/dist/files/Physics.d.ts b/dist/files/Physics.d.ts new file mode 100644 index 00000000..c4b65713 --- /dev/null +++ b/dist/files/Physics.d.ts @@ -0,0 +1,248 @@ +import { Pt, PtLike, Group, GroupLike } from "./Pt"; +import { Bound } from "./Bound"; +/** + * A `World` stores and manages `Body` and `Particle` for 2D physics simulation + */ +export declare class World { + private _lastTime; + protected _gravity: Pt; + protected _friction: number; + protected _damping: number; + protected _bound: Bound; + protected _particles: Particle[]; + protected _bodies: Body[]; + protected _names: { + p: {}; + b: {}; + }; + protected _drawParticles: (p: Particle, i: number) => void; + protected _drawBodies: (p: Body, i: number) => void; + /** + * Create a `World` for 2D physics simulation + * @param bound a rectangular bounding box defined by a Group + * @param friction a value between 0 to 1 where 1 means no friction. Default is 1 + * @param gravity a number of a Pt to define gravitational force. Using a number is a shorthand to set `new Pt(0, n)`. Default is 0. + */ + constructor(bound: Group, friction?: number, gravity?: PtLike | number); + gravity: Pt; + friction: number; + damping: number; + /** + * Get the number of bodies + */ + readonly bodyCount: number; + /** + * Get the number of particles + */ + readonly particleCount: number; + /** + * Get a body in this world by index or string id + * @param id numeric index of the body, or a string id that associates with it. + */ + body(id: number | string): Body; + /** + * Get a particle in this world by index or string id + * @param id numeric index of the particle, or a string id that associates with it. + */ + particle(id: number): Particle; + /** + * Update this world one time step + * @param ms change in time in milliseconds + */ + update(ms: number): void; + /** + * Draw particles using the provided function + * @param fn a function that draws particles passed in the parameters `(particles, index)`. + */ + drawParticles(fn: (p: Particle, i: number) => void): void; + /** + * Draw bodies using the provided function + * @param fn a function that draws bodies passed in the parameters `(bodies, index)`. + */ + drawBodies(fn: (p: Body, i: number) => void): void; + /** + * Add a particle or body to this world. + * @param p `Particle` or `Body` instance + * @param name optional name, which can be referenced in `body()` or `particle()` function to retrieve this back. + */ + add(p: Particle | Body, name?: string): this; + /** + * Remove either body or particle from this world. Support removing a range and negative index. + * @param which Either "body" or "particle" + * @param index Start index, which can be negative (where -1 is at index 0, -2 at index 1, etc) + * @param count Number of items to remove. Default is 1. + */ + remove(which: "body" | "particle", index: number, count?: number): this; + /** + * Static function to calculate edge constraints between 2 particles. + * @param p1 particle 1 + * @param p2 particle 1 + * @param dist distance between particles + * @param stiff stiffness between 0 to 1. + * @param precise use precise distance calculation. Default is `false`. + */ + static edgeConstraint(p1: Particle, p2: Particle, dist: number, stiff?: number, precise?: boolean): Particle; + /** + * Static function to calculate bounding box constraints. + * @param p particle + * @param rect bounding box defined by a Group + * @param damping damping between 0 to 1, where 1 means no damping. Default is 0.75. + */ + static boundConstraint(p: Particle, rect: Group, damping?: number): void; + /** + * Internal integrate function + * @param p particle + * @param dt time changed + * @param prevDt previous change in time, optional + */ + protected integrate(p: Particle, dt: number, prevDt?: number): Particle; + /** + * Internal function to update particles + */ + protected _updateParticles(dt: number): void; + /** + * Internal function to update bodies + */ + protected _updateBodies(dt: number): void; +} +/** + * Particle is a Pt that has radius and mass. It's usually added into `World` to create physics simulations. + */ +export declare class Particle extends Pt { + protected _mass: number; + protected _radius: number; + protected _force: Pt; + protected _prev: Pt; + protected _body: Body; + protected _lock: boolean; + protected _lockPt: Pt; + /** + * Create a particle + * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties + */ + constructor(...args: any[]); + mass: number; + radius: number; + /** + * Get previous position + */ + previous: Pt; + /** + * Get current accumulated force + */ + force: Pt; + /** + * Get the body of this particle, if any. + */ + body: Body; + /** + * + */ + lock: boolean; + /** + * Get the change in position since last time step + */ + readonly changed: Pt; + /** + * Set a new position, and update previous and lock states if needed. + */ + position: Pt; + /** + * Set the size of this particle. This sets both the radius and the mass. + * @param r `radius` value, and also set `mass` to the same value. + */ + size(r: number): this; + /** + * Add to the accumulated force + * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties + */ + addForce(...args: any[]): Pt; + /** + * Verlet integration + * @param dt change in time + * @param friction friction from 0 to 1, where 1 means no friction + * @param lastDt optional last change in time + */ + verlet(dt: number, friction: number, lastDt?: number): this; + /** + * Hit this particle with an impulse + * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties + * @example `hit(10, 20)`, `hit( new Pt(5, 9) )` + */ + hit(...args: any[]): this; + /** + * Check and respoond to collisions between two particles + * @param p2 another particle + * @param damp damping value between 0 to 1, where 1 means no damping. + */ + collide(p2: Particle, damp?: number): void; + toString(): string; +} +/** + * Body consists of a group of `Particles` and edge constraints. It is usually added into a `World` to create physics simulations + */ +export declare class Body extends Group { + protected _cs: Array; + protected _stiff: number; + protected _locks: { + [index: string]: Particle; + }; + protected _mass: number; + /** + * Create an empty Body, this is usually followed by `init` to populate the Body. Alternatively, use static function `fromGroup` to create and initate a body directly. + */ + constructor(); + /** + * Create and populate a body with a group of Pts. + * @param list a group of Pts + * @param stiff stiffness value from 0 to 1, where 1 is the most stiff. Default is 1. + * @param autoLink Automatically create links between the Pts. This usually works for regular convex polygons. Default is true. + * @param autoMass Automatically calculate the mass based on the area of the polygon. Default is true. + */ + static fromGroup(list: GroupLike, stiff?: number, autoLink?: boolean, autoMass?: boolean): Body; + /** + * Initiate a body + * @param list a group of Pts + * @param stiff stiffness value from 0 to 1, where 1 is the most stiff. Default is 1. + */ + init(list: GroupLike, stiff?: number): this; + /** + * Get mass of this body. + */ + mass: number; + /** + * Automatically calculate `mass` to body's polygon area. + */ + autoMass(): this; + /** + * Create a linked edge between two points + * @param index1 first point by index + * @param index2 first point by index + * @param stiff optionally stiffness value between 0 to 1, where 1 is the most stiff. + */ + link(index1: number, index2: number, stiff?: number): this; + /** + * Automatically create links for all the points to preserve the initial body shape. This usually works for regular convex polygon. + * @param stiff optionally stiffness value between 0 to 1, where 1 is the most stiff. + */ + linkAll(stiff: number): void; + /** + * Return a list of all the linked edges as line segments. + * @returns an array of Groups, each of which represents an edge + */ + linksToLines(): Group[]; + /** + * Recalculate all edge constraints + */ + processEdges(): void; + /** + * Check and respond to collisions between two bodies + * @param b another body + */ + processBody(b: Body): void; + /** + * Check and respond to collisions between this body and a particle + * @param b a particle + */ + processParticle(b: Particle): void; +} diff --git a/dist/files/Physics.js b/dist/files/Physics.js new file mode 100644 index 00000000..1095b8e5 --- /dev/null +++ b/dist/files/Physics.js @@ -0,0 +1,532 @@ +"use strict"; +// Source code licensed under Apache License 2.0. +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) +Object.defineProperty(exports, "__esModule", { value: true }); +const Pt_1 = require("./Pt"); +const Bound_1 = require("./Bound"); +const Op_1 = require("./Op"); +/** + * A `World` stores and manages `Body` and `Particle` for 2D physics simulation + */ +class World { + /** + * Create a `World` for 2D physics simulation + * @param bound a rectangular bounding box defined by a Group + * @param friction a value between 0 to 1 where 1 means no friction. Default is 1 + * @param gravity a number of a Pt to define gravitational force. Using a number is a shorthand to set `new Pt(0, n)`. Default is 0. + */ + constructor(bound, friction = 1, gravity = 0) { + this._lastTime = null; + this._gravity = new Pt_1.Pt(); + this._friction = 1; // general friction + this._damping = 0.75; // collision damping + this._particles = []; + this._bodies = []; + this._names = { p: {}, b: {} }; + this._bound = Bound_1.Bound.fromGroup(bound); + this._friction = friction; + this._gravity = (typeof gravity === "number") ? new Pt_1.Pt(0, gravity) : new Pt_1.Pt(gravity); + return this; + } + get gravity() { return this._gravity; } + set gravity(g) { this._gravity = g; } + get friction() { return this._friction; } + set friction(f) { this._friction = f; } + get damping() { return this._damping; } + set damping(f) { this._damping = f; } + /** + * Get the number of bodies + */ + get bodyCount() { return this._bodies.length; } + /** + * Get the number of particles + */ + get particleCount() { return this._particles.length; } + /** + * Get a body in this world by index or string id + * @param id numeric index of the body, or a string id that associates with it. + */ + body(id) { return this._bodies[(typeof id === "string") ? this._names.b[id] : id]; } + /** + * Get a particle in this world by index or string id + * @param id numeric index of the particle, or a string id that associates with it. + */ + particle(id) { return this._particles[(typeof id === "string") ? this._names.p[id] : id]; } + /** + * Update this world one time step + * @param ms change in time in milliseconds + */ + update(ms) { + let dt = ms / 1000; + this._updateParticles(dt); + this._updateBodies(dt); + } + /** + * Draw particles using the provided function + * @param fn a function that draws particles passed in the parameters `(particles, index)`. + */ + drawParticles(fn) { + this._drawParticles = fn; + } + /** + * Draw bodies using the provided function + * @param fn a function that draws bodies passed in the parameters `(bodies, index)`. + */ + drawBodies(fn) { + this._drawBodies = fn; + } + /** + * Add a particle or body to this world. + * @param p `Particle` or `Body` instance + * @param name optional name, which can be referenced in `body()` or `particle()` function to retrieve this back. + */ + add(p, name) { + if (p instanceof Body) { + this._bodies.push(p); + if (name) + this._names.b[name] = this._bodies.length - 1; + } + else { + this._particles.push(p); + if (name) + this._names.p[name] = this._particles.length - 1; + } + return this; + } + /** + * Remove either body or particle from this world. Support removing a range and negative index. + * @param which Either "body" or "particle" + * @param index Start index, which can be negative (where -1 is at index 0, -2 at index 1, etc) + * @param count Number of items to remove. Default is 1. + */ + remove(which, index, count = 1) { + let param = (index < 0) ? [index * -1 - 1, count] : [index, count]; + if (which == "body") { + this._bodies.splice(param[0], param[1]); + } + else { + this._particles.splice(param[0], param[1]); + } + return this; + } + /** + * Static function to calculate edge constraints between 2 particles. + * @param p1 particle 1 + * @param p2 particle 1 + * @param dist distance between particles + * @param stiff stiffness between 0 to 1. + * @param precise use precise distance calculation. Default is `false`. + */ + static edgeConstraint(p1, p2, dist, stiff = 1, precise = false) { + const m1 = 1 / (p1.mass || 1); + const m2 = 1 / (p2.mass || 1); + const mm = m1 + m2; + let delta = p2.$subtract(p1); + let distSq = dist * dist; + let d = (precise) ? (dist / delta.magnitude() - 1) : (distSq / (delta.dot(delta) + distSq) - 0.5); // approx square root + let f = delta.$multiply(d * stiff); + p1.subtract(f.$multiply(m1 / mm)); + p2.add(f.$multiply(m2 / mm)); + return p1; + } + /** + * Static function to calculate bounding box constraints. + * @param p particle + * @param rect bounding box defined by a Group + * @param damping damping between 0 to 1, where 1 means no damping. Default is 0.75. + */ + static boundConstraint(p, rect, damping = 0.75) { + let bound = rect.boundingBox(); + let np = p.$min(bound[1].subtract(p.radius)).$max(bound[0].add(p.radius)); + if (np[0] === bound[0][0] || np[0] === bound[1][0]) { + let c = p.changed.$multiply(damping); + p.previous = np.$subtract(new Pt_1.Pt(-c[0], c[1])); + } + else if (np[1] === bound[0][1] || np[1] === bound[1][1]) { + let c = p.changed.$multiply(damping); + p.previous = np.$subtract(new Pt_1.Pt(c[0], -c[1])); + } + p.to(np); + } + /** + * Internal integrate function + * @param p particle + * @param dt time changed + * @param prevDt previous change in time, optional + */ + integrate(p, dt, prevDt) { + p.addForce(this._gravity); + p.verlet(dt, this._friction, prevDt); + return p; + } + /** + * Internal function to update particles + */ + _updateParticles(dt) { + for (let i = 0, len = this._particles.length; i < len; i++) { + let p = this._particles[i]; + // force and integrate + this.integrate(p, dt, this._lastTime); + // constraints + World.boundConstraint(p, this._bound, this._damping); + // collisions + for (let k = i + 1; k < len; k++) { + if (i !== k) { + let p2 = this._particles[k]; + p.collide(p2, this._damping); + } + } + // render + if (this._drawParticles) + this._drawParticles(p, i); + } + this._lastTime = dt; + } + /** + * Internal function to update bodies + */ + _updateBodies(dt) { + for (let i = 0, len = this._bodies.length; i < len; i++) { + let b = this._bodies[i]; + // integrate + for (let k = 0, klen = b.length; k < klen; k++) { + let bk = b[k]; + World.boundConstraint(bk, this._bound, this._damping); + this.integrate(bk, dt, this._lastTime); + } + for (let k = i + 1; k < len; k++) { + b.processBody(this._bodies[k]); + } + for (let m = 0, mlen = this._particles.length; m < mlen; m++) { + b.processParticle(this._particles[m]); + } + // constraints + b.processEdges(); + // render + if (this._drawBodies) + this._drawBodies(b, i); + } + } +} +exports.World = World; +/** + * Particle is a Pt that has radius and mass. It's usually added into `World` to create physics simulations. + */ +class Particle extends Pt_1.Pt { + /** + * Create a particle + * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties + */ + constructor(...args) { + super(...args); + this._mass = 1; + this._radius = 0; + this._force = new Pt_1.Pt(); + this._prev = new Pt_1.Pt(); + this._lock = false; + this._prev = this.clone(); + } + get mass() { return this._mass; } + set mass(m) { this._mass = m; } + get radius() { return this._radius; } + set radius(f) { this._radius = f; } + /** + * Get previous position + */ + get previous() { return this._prev; } + set previous(p) { this._prev = p; } + /** + * Get current accumulated force + */ + get force() { return this._force; } + set force(g) { this._force = g; } + /** + * Get the body of this particle, if any. + */ + get body() { return this._body; } + set body(b) { this._body = b; } + /** + * + */ + get lock() { return this._lock; } + set lock(b) { + this._lock = b; + this._lockPt = new Pt_1.Pt(this); + } + /** + * Get the change in position since last time step + */ + get changed() { return this.$subtract(this._prev); } + /** + * Set a new position, and update previous and lock states if needed. + */ + set position(p) { + this.previous.to(this); + if (this._lock) + this._lockPt = p; + this.to(p); + } + /** + * Set the size of this particle. This sets both the radius and the mass. + * @param r `radius` value, and also set `mass` to the same value. + */ + size(r) { + this._mass = r; + this._radius = r; + return this; + } + /** + * Add to the accumulated force + * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties + */ + addForce(...args) { + this._force.add(...args); + return this._force; + } + /** + * Verlet integration + * @param dt change in time + * @param friction friction from 0 to 1, where 1 means no friction + * @param lastDt optional last change in time + */ + verlet(dt, friction, lastDt) { + // Positional verlet: curr + (curr - prev) + a * dt * dt + if (this._lock) { + this.to(this._lockPt); + // this._prev.to( this._lockPt ); + } + else { + // time corrected (https://en.wikipedia.org/wiki/Verlet_integration#Non-constant_time_differences) + let lt = (lastDt) ? lastDt : dt; + let a = this._force.multiply(dt * (dt + lt) / 2); + let v = this.changed.multiply(friction * dt / lt).add(a); + this._prev = this.clone(); + this.add(v); + this._force = new Pt_1.Pt(); + } + return this; + } + /** + * Hit this particle with an impulse + * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties + * @example `hit(10, 20)`, `hit( new Pt(5, 9) )` + */ + hit(...args) { + this._prev.subtract(new Pt_1.Pt(...args).$divide(Math.sqrt(this._mass))); + return this; + } + /** + * Check and respoond to collisions between two particles + * @param p2 another particle + * @param damp damping value between 0 to 1, where 1 means no damping. + */ + collide(p2, damp = 1) { + // reference: http://codeflow.org/entries/2010/nov/29/verlet-collision-with-impulse-preservation + // simultaneous collision not yet resolved. Possible solutions in this paper: https://www2.msm.ctw.utwente.nl/sluding/PAPERS/dem07.pdf + let p1 = this; + let dp = p1.$subtract(p2); + let distSq = dp.magnitudeSq(); + let dr = p1.radius + p2.radius; + if (distSq < dr * dr) { + let c1 = p1.changed; + let c2 = p2.changed; + let dist = Math.sqrt(distSq); + let d = dp.$multiply(((dist - dr) / dist) / 2); + let np1 = p1.$subtract(d); + let np2 = p2.$add(d); + p1.to(np1); + p2.to(np2); + let f1 = damp * dp.dot(c1) / distSq; + let f2 = damp * dp.dot(c2) / distSq; + let dm1 = p1.mass / (p1.mass + p2.mass); + let dm2 = p2.mass / (p1.mass + p2.mass); + c1.add(new Pt_1.Pt(f2 * dp[0] - f1 * dp[0], f2 * dp[1] - f1 * dp[1]).$multiply(dm2)); + c2.add(new Pt_1.Pt(f1 * dp[0] - f2 * dp[0], f1 * dp[1] - f2 * dp[1]).$multiply(dm1)); + p1.previous = p1.$subtract(c1); + p2.previous = p2.$subtract(c2); + } + } + toString() { + return `Particle: ${this[0]} ${this[1]} | previous ${this._prev[0]} ${this._prev[1]} | mass ${this._mass}`; + } +} +exports.Particle = Particle; +/** + * Body consists of a group of `Particles` and edge constraints. It is usually added into a `World` to create physics simulations + */ +class Body extends Pt_1.Group { + /** + * Create an empty Body, this is usually followed by `init` to populate the Body. Alternatively, use static function `fromGroup` to create and initate a body directly. + */ + constructor() { + super(); + this._cs = []; + this._stiff = 1; + this._locks = {}; + this._mass = 1; + } + /** + * Create and populate a body with a group of Pts. + * @param list a group of Pts + * @param stiff stiffness value from 0 to 1, where 1 is the most stiff. Default is 1. + * @param autoLink Automatically create links between the Pts. This usually works for regular convex polygons. Default is true. + * @param autoMass Automatically calculate the mass based on the area of the polygon. Default is true. + */ + static fromGroup(list, stiff = 1, autoLink = true, autoMass = true) { + let b = new Body().init(list); + if (autoLink) + b.linkAll(stiff); + if (autoMass) + b.autoMass(); + return b; + } + /** + * Initiate a body + * @param list a group of Pts + * @param stiff stiffness value from 0 to 1, where 1 is the most stiff. Default is 1. + */ + init(list, stiff = 1) { + let c = new Pt_1.Pt(); + for (let i = 0, len = list.length; i < len; i++) { + let p = new Particle(list[i]); + p.body = this; + c.add(list[i]); + this.push(p); + } + this._stiff = stiff; + return this; + } + /** + * Get mass of this body. + */ + get mass() { return this._mass; } + set mass(m) { + this._mass = m; + for (let i = 0, len = this.length; i < len; i++) { + this[i].mass = this._mass; + } + } + /** + * Automatically calculate `mass` to body's polygon area. + */ + autoMass() { + this.mass = Math.sqrt(Op_1.Polygon.area(this)) / 10; + return this; + } + /** + * Create a linked edge between two points + * @param index1 first point by index + * @param index2 first point by index + * @param stiff optionally stiffness value between 0 to 1, where 1 is the most stiff. + */ + link(index1, index2, stiff) { + if (index1 < 0 || index1 >= this.length) + throw new Error("index1 is not in the Group's indices"); + if (index2 < 0 || index2 >= this.length) + throw new Error("index1 is not in the Group's indices"); + let d = this[index1].$subtract(this[index2]).magnitude(); + this._cs.push([index1, index2, d, stiff || this._stiff]); + return this; + } + /** + * Automatically create links for all the points to preserve the initial body shape. This usually works for regular convex polygon. + * @param stiff optionally stiffness value between 0 to 1, where 1 is the most stiff. + */ + linkAll(stiff) { + let half = this.length / 2; + for (let i = 0, len = this.length; i < len; i++) { + let n = (i >= len - 1) ? 0 : i + 1; + this.link(i, n, stiff); + if (len > 4) { + let nd = (Math.floor(half / 2)) + 1; + let n2 = (i >= len - nd) ? i % len : i + nd; + this.link(i, n2, stiff); + } + if (i <= half - 1) { + this.link(i, Math.min(this.length - 1, i + Math.floor(half))); + } + } + } + /** + * Return a list of all the linked edges as line segments. + * @returns an array of Groups, each of which represents an edge + */ + linksToLines() { + let gs = []; + for (let i = 0, len = this._cs.length; i < len; i++) { + let ln = this._cs[i]; + gs.push(new Pt_1.Group(this[ln[0]], this[ln[1]])); + } + return gs; + } + /** + * Recalculate all edge constraints + */ + processEdges() { + for (let i = 0, len = this._cs.length; i < len; i++) { + let [m, n, d, s] = this._cs[i]; + World.edgeConstraint(this[m], this[n], d, s); + } + } + /** + * Check and respond to collisions between two bodies + * @param b another body + */ + processBody(b) { + let b1 = this; + let b2 = b; + let hit = Op_1.Polygon.hasIntersectPolygon(b1, b2); + if (hit) { + let cv = hit.normal.$multiply(hit.dist); + let t; + let eg = hit.edge; + if (Math.abs(eg[0][0] - eg[1][0]) > Math.abs(eg[0][1] - eg[1][1])) { + t = (hit.vertex[0] - cv[0] - eg[0][0]) / (eg[1][0] - eg[0][0]); + } + else { + t = (hit.vertex[1] - cv[1] - eg[0][1]) / (eg[1][1] - eg[0][1]); + } + let lambda = 1 / (t * t + (1 - t) * (1 - t)); + let m0 = hit.vertex.body.mass || 1; + let m1 = hit.edge[0].body.mass || 1; + let mr0 = m0 / (m0 + m1); + let mr1 = m1 / (m0 + m1); + eg[0].subtract(cv.$multiply(mr0 * (1 - t) * lambda / 2)); + eg[1].subtract(cv.$multiply(mr0 * t * lambda / 2)); + hit.vertex.add(cv.$multiply(mr1)); + } + } + /** + * Check and respond to collisions between this body and a particle + * @param b a particle + */ + processParticle(b) { + let b1 = this; + let b2 = b; + let hit = Op_1.Polygon.hasIntersectCircle(b1, Op_1.Circle.fromCenter(b, b.radius)); + if (hit) { + let cv = hit.normal.$multiply(hit.dist); + let t; + let eg = hit.edge; + if (Math.abs(eg[0][0] - eg[1][0]) > Math.abs(eg[0][1] - eg[1][1])) { + t = (hit.vertex[0] - cv[0] - eg[0][0]) / (eg[1][0] - eg[0][0]); + } + else { + t = (hit.vertex[1] - cv[1] - eg[0][1]) / (eg[1][1] - eg[0][1]); + } + let lambda = 1 / (t * t + (1 - t) * (1 - t)); + let m0 = hit.vertex.mass || b2.mass || 1; + let m1 = hit.edge[0].body.mass || 1; + let mr0 = m0 / (m0 + m1); + let mr1 = m1 / (m0 + m1); + eg[0].subtract(cv.$multiply(mr0 * (1 - t) * lambda / 2)); + eg[1].subtract(cv.$multiply(mr0 * t * lambda / 2)); + let c1 = b.changed.add(cv.$multiply(mr1)); + b.previous = b.$subtract(c1); + // let c2 = b2.changed.add( cv.$multiply(mr0) ); + // b2.previous = b2.$subtract( c2 ); + } + } +} +exports.Body = Body; +//# sourceMappingURL=Physics.js.map \ No newline at end of file diff --git a/dist/files/Pt.d.ts b/dist/files/Pt.d.ts index 50c22811..28cccad6 100644 --- a/dist/files/Pt.d.ts +++ b/dist/files/Pt.d.ts @@ -147,10 +147,16 @@ export declare class Pt extends PtBaseArray implements IPt, Iterable { */ $cross(...args: any[]): Pt; /** - * Calculate vector projection of this Pt on another Pt. Returns result as a new Pt. + * Calculate vector projection of this Pt on another Pt. * @param p a list of numbers, an array of number, or an object with {x,y,z,w} properties + * @returns the projection vector as a Pt */ - $project(p: Pt): Pt; + $project(...args: any[]): Pt; + /** + * Calculate scalar projection + * @param p a list of numbers, an array of number, or an object with {x,y,z,w} properties + */ + projectScalar(...args: any[]): number; /** * Absolute values for all values in this pt */ diff --git a/dist/files/Pt.js b/dist/files/Pt.js index 0dd86cea..b1e0f02f 100644 --- a/dist/files/Pt.js +++ b/dist/files/Pt.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = require("./Util"); const Num_1 = require("./Num"); @@ -221,15 +221,19 @@ class Pt extends exports.PtBaseArray { */ $cross(...args) { return LinearAlgebra_1.Vec.cross(this, Util_1.Util.getArgs(args)); } /** - * Calculate vector projection of this Pt on another Pt. Returns result as a new Pt. + * Calculate vector projection of this Pt on another Pt. * @param p a list of numbers, an array of number, or an object with {x,y,z,w} properties + * @returns the projection vector as a Pt */ - $project(p) { - let m = p.magnitude(); - let a = this.$unit(); - let b = p.$divide(m); - let dot = a.dot(b); - return a.multiply(m * dot); + $project(...args) { + return this.$multiply(this.dot(...args) / this.magnitudeSq()); + } + /** + * Calculate scalar projection + * @param p a list of numbers, an array of number, or an object with {x,y,z,w} properties + */ + projectScalar(...args) { + return this.dot(...args) / this.magnitude(); } /** * Absolute values for all values in this pt diff --git a/dist/files/Space.js b/dist/files/Space.js index 91768d73..6325725b 100644 --- a/dist/files/Space.js +++ b/dist/files/Space.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Bound_1 = require("./Bound"); const Pt_1 = require("./Pt"); diff --git a/dist/files/Svg.js b/dist/files/Svg.js index 3ac38253..f6f9f3ee 100644 --- a/dist/files/Svg.js +++ b/dist/files/Svg.js @@ -1,4 +1,6 @@ "use strict"; +// Source code licensed under Apache License 2.0. +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Form_1 = require("./Form"); const Num_1 = require("./Num"); diff --git a/dist/files/Typography.d.ts b/dist/files/Typography.d.ts index 9b931264..2c269839 100644 --- a/dist/files/Typography.d.ts +++ b/dist/files/Typography.d.ts @@ -23,5 +23,13 @@ export declare class Typography { * @param ratio font-size change ratio. Default is 1. * @returns a function where input parameter is a new box, and returns the new font size value */ - static fontSizeToBox(box: GroupLike, ratio?: number): (b: GroupLike) => number; + static fontSizeToBox(box: GroupLike, ratio?: number, byHeight?: boolean): (GroupLike) => number; + /** + * Get a function to scale font size based on a threshold value + * @param defaultSize default font size to base on + * @param threshold threshold value + * @param direction if negative, get a font size <= defaultSize; if positive, get a font size >= defaultSize; Default is 0 which will scale font without min or max limits. + * @returns a function where input parameter is the default font size and a value to compare with threshold, and returns new font size value + */ + static fontSizeToThreshold(threshold: number, direction?: number): (a: number, b: number) => number; } diff --git a/dist/files/Typography.js b/dist/files/Typography.js index 310ce5e1..12163113 100644 --- a/dist/files/Typography.js +++ b/dist/files/Typography.js @@ -1,4 +1,6 @@ "use strict"; +// Source code licensed under Apache License 2.0. +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = require("./Pt"); /** Various functions to support typography */ @@ -23,7 +25,7 @@ class Typography { * @param tail text to indicate overflow such as "...". Default is empty "". */ static truncate(fn, str, width, tail = "") { - let trim = Math.floor(str.length * Math.min(1, width / (fn(str) * 1.1))); + let trim = Math.floor(str.length * Math.min(1, width / fn(str))); if (trim < str.length) { trim = Math.max(0, trim - tail.length); return [str.substr(0, trim) + tail, trim]; @@ -38,14 +40,32 @@ class Typography { * @param ratio font-size change ratio. Default is 1. * @returns a function where input parameter is a new box, and returns the new font size value */ - static fontSizeToBox(box, ratio = 1) { - let h = (box[1][1] - box[0][1]); + static fontSizeToBox(box, ratio = 1, byHeight = true) { + let i = byHeight ? 1 : 0; + let h = (box[1][i] - box[0][i]); let f = ratio * h; return function (b) { - let nh = (b[1][1] - b[0][1]) / h; + let nh = (b[1][i] - b[0][i]) / h; return f * nh; }; } + /** + * Get a function to scale font size based on a threshold value + * @param defaultSize default font size to base on + * @param threshold threshold value + * @param direction if negative, get a font size <= defaultSize; if positive, get a font size >= defaultSize; Default is 0 which will scale font without min or max limits. + * @returns a function where input parameter is the default font size and a value to compare with threshold, and returns new font size value + */ + static fontSizeToThreshold(threshold, direction = 0) { + return function (defaultSize, val) { + let d = defaultSize * val / threshold; + if (direction < 0) + return Math.min(d, defaultSize); + if (direction > 0) + return Math.max(d, defaultSize); + return d; + }; + } } exports.Typography = Typography; //# sourceMappingURL=Typography.js.map \ No newline at end of file diff --git a/dist/files/UI.js b/dist/files/UI.js index ce126589..de68ad1b 100644 --- a/dist/files/UI.js +++ b/dist/files/UI.js @@ -1,4 +1,6 @@ "use strict"; +// Source code licensed under Apache License 2.0. +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Op_1 = require("./Op"); /** diff --git a/dist/files/Util.js b/dist/files/Util.js index 54bbc104..01115876 100644 --- a/dist/files/Util.js +++ b/dist/files/Util.js @@ -1,6 +1,6 @@ "use strict"; // Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan) +// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = require("./Pt"); /** diff --git a/dist/files/_lib.d.ts b/dist/files/_lib.d.ts index e69de29b..cb0ff5c3 100644 --- a/dist/files/_lib.d.ts +++ b/dist/files/_lib.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/files/_lib.js b/dist/files/_lib.js index 5f9e4e23..f06c570f 100644 --- a/dist/files/_lib.js +++ b/dist/files/_lib.js @@ -14,6 +14,7 @@ const _Util = require("./Util"); const _Dom = require("./Dom"); const _Svg = require("./Svg"); const _Typography = require("./Typography"); +const _Physics = require("./Physics"); // A function to switch scope for Pts library. eg, Pts.scope( Pts, window ); let namespace = (sc) => { let lib = module.exports; @@ -23,5 +24,5 @@ let namespace = (sc) => { } } }; -module.exports = Object.assign({ namespace }, _Bound, _Canvas, _Create, _Form, _LinearAlgebra, _Op, _Num, _Pt, _Space, _Util, _Color, _Dom, _Svg, _Typography); +module.exports = Object.assign({ namespace }, _Bound, _Canvas, _Create, _Form, _LinearAlgebra, _Op, _Num, _Pt, _Space, _Util, _Color, _Dom, _Svg, _Typography, _Physics); //# sourceMappingURL=_lib.js.map \ No newline at end of file diff --git a/dist/files/_module.d.ts b/dist/files/_module.d.ts new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/dist/files/_module.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/files/_module.js b/dist/files/_module.js new file mode 100644 index 00000000..b26ec511 --- /dev/null +++ b/dist/files/_module.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const _Bound = require("./Bound"); +const _Canvas = require("./Canvas"); +const _Create = require("./Create"); +const _Form = require("./Form"); +const _LinearAlgebra = require("./LinearAlgebra"); +const _Num = require("./Num"); +const _Op = require("./Op"); +const _Pt = require("./Pt"); +const _Space = require("./Space"); +const _Color = require("./Color"); +const _Util = require("./Util"); +const _Dom = require("./Dom"); +const _Svg = require("./Svg"); +const _Typography = require("./Typography"); +const _Physics = require("./Physics"); +module.exports = Object.assign({}, _Bound, _Canvas, _Create, _Form, _LinearAlgebra, _Op, _Num, _Pt, _Space, _Util, _Color, _Dom, _Svg, _Typography, _Physics); +//# sourceMappingURL=_module.js.map \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 981d6671..4a728ee4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -84,28 +84,15 @@ return /******/ (function(modules) { // webpackBootstrap "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = __webpack_require__(1); const Num_1 = __webpack_require__(3); -const LinearAlgebra_1 = __webpack_require__(4); +const LinearAlgebra_1 = __webpack_require__(5); exports.PtBaseArray = Float32Array; -/** - * Pt is a subclass of Float32Array with additional properties and functions to support vector and geometric calculations. - * See [Pt guide](../../guide/Pt-0200.html) for details - */ class Pt extends exports.PtBaseArray { - /** - * Create a Pt. If no parameter is provided, this will instantiate a Pt with 2 dimensions [0, 0]. - * - * Note that `new Pt(3)` will only instantiate Pt with length of 3 (ie, same as `new Float32Array(3)` ). If you need a Pt with 1 dimension of value 3, use `new Pt([3])`. - * @example `new Pt()`, `new Pt(1,2,3,4,5)`, `new Pt([1,2])`, `new Pt({x:0, y:1})`, `new Pt(pt)` - * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ constructor(...args) { if (args.length === 1 && typeof args[0] == "number") { - super(args[0]); // init with the TypedArray's length. Needed this in order to make ".map", ".slice" etc work. + super(args[0]); } else { super((args.length > 0) ? Util_1.Util.getArgs(args) : [0, 0]); @@ -132,17 +119,9 @@ class Pt extends exports.PtBaseArray { set y(n) { this[1] = n; } set z(n) { this[2] = n; } set w(n) { this[3] = n; } - /** - * Clone this Pt - */ clone() { return new Pt(this); } - /** - * Check if another Pt is equal to this Pt, within a threshold - * @param p another Pt to compare with - * @param threshold a threshold value within which the two Pts are considered equal. Default is 0.000001. - */ equals(p, threshold = 0.000001) { for (let i = 0, len = this.length; i < len; i++) { if (Math.abs(this[i] - p[i]) > threshold) @@ -150,10 +129,6 @@ class Pt extends exports.PtBaseArray { } return true; } - /** - * Update the values of this Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ to(...args) { let p = Util_1.Util.getArgs(args); for (let i = 0, len = Math.min(this.length, p.length); i < len; i++) { @@ -161,42 +136,20 @@ class Pt extends exports.PtBaseArray { } return this; } - /** - * Like `to()` but returns a new Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $to(...args) { return this.clone().to(...args); } - /** - * Update the values of this Pt to point at a specific angle - * @param radian target angle in radian - * @param magnitude Optional magnitude if known. If not provided, it'll calculate and use this Pt's magnitude. - * @param anchorFromPt If `true`, translate to new position from current position. Default is `false` which update the position from origin (0,0); - */ toAngle(radian, magnitude, anchorFromPt = false) { let m = (magnitude != undefined) ? magnitude : this.magnitude(); let change = [Math.cos(radian) * m, Math.sin(radian) * m]; return (anchorFromPt) ? this.add(change) : this.to(change); } - /** - * Create an operation using this Pt, passing this Pt into a custom function's first parameter. See the [Op guide](../../guide/Op-0400.html) for details. - * For example: `let myOp = pt.op( fn ); let result = myOp( [1,2,3] );` - * @param fn any function that takes a Pt as its first parameter - * @returns a resulting function that takes other parameters required in `fn` - */ op(fn) { let self = this; return (...params) => { return fn(self, ...params); }; } - /** - * This combines a series of operations into an array. See `op()` for details. - * For example: `let myOps = pt.ops([fn1, fn2, fn3]); let results = myOps.map( (op) => op([1,2,3]) );` - * @param fns an array of functions for `op` - * @returns an array of resulting functions - */ ops(fns) { let _ops = []; for (let i = 0, len = fns.length; i < len; i++) { @@ -204,10 +157,6 @@ class Pt extends exports.PtBaseArray { } return _ops; } - /** - * Take specific dimensional values from this Pt and create a new Pt - * @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions - */ $take(axis) { let p = []; for (let i = 0, len = axis.length; i < len; i++) { @@ -215,177 +164,79 @@ class Pt extends exports.PtBaseArray { } return new Pt(p); } - /** - * Concatenate this Pt with addition dimensional values and return as a new Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $concat(...args) { return new Pt(this.toArray().concat(Util_1.Util.getArgs(args))); } - /** - * Add scalar or vector values to this Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ add(...args) { (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.add(this, args[0]) : LinearAlgebra_1.Vec.add(this, Util_1.Util.getArgs(args)); return this; } - /** - * Like `add`, but returns result as a new Pt - */ $add(...args) { return this.clone().add(...args); } - /** - * Subtract scalar or vector values from this Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ subtract(...args) { (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.subtract(this, args[0]) : LinearAlgebra_1.Vec.subtract(this, Util_1.Util.getArgs(args)); return this; } - /** - * Like `subtract`, but returns result as a new Pt - */ $subtract(...args) { return this.clone().subtract(...args); } - /** - * Multiply scalar or vector values (as element-wise) with this Pt. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ multiply(...args) { (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.multiply(this, args[0]) : LinearAlgebra_1.Vec.multiply(this, Util_1.Util.getArgs(args)); return this; } - /** - * Like `multiply`, but returns result as a new Pt - */ $multiply(...args) { return this.clone().multiply(...args); } - /** - * Divide this Pt over scalar or vector values (as element-wise) - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ divide(...args) { (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.divide(this, args[0]) : LinearAlgebra_1.Vec.divide(this, Util_1.Util.getArgs(args)); return this; } - /** - * Like `divide`, but returns result as a new Pt - */ $divide(...args) { return this.clone().divide(...args); } - /** - * Get the sqaured distance (magnitude) of this Pt from origin - */ magnitudeSq() { return LinearAlgebra_1.Vec.dot(this, this); } - /** - * Get the distance (magnitude) of this Pt from origin - */ magnitude() { return LinearAlgebra_1.Vec.magnitude(this); } - /** - * Convert to a unit vector, which is a normalized vector whose magnitude equals 1. - * @param magnitude Optional: if the magnitude is known, pass it as a parameter to avoid duplicate calculation. - */ unit(magnitude = undefined) { LinearAlgebra_1.Vec.unit(this, magnitude); return this; } - /** - * Get a unit vector from this Pt - */ $unit(magnitude = undefined) { return this.clone().unit(magnitude); } - /** - * Dot product of this Pt and another Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ dot(...args) { return LinearAlgebra_1.Vec.dot(this, Util_1.Util.getArgs(args)); } - /** - * 2D Cross product of this Pt and another Pt. Return results as a new Pt. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ cross2D(...args) { return LinearAlgebra_1.Vec.cross2D(this, Util_1.Util.getArgs(args)); } - /** - * 3D Cross product of this Pt and another Pt. Return results as a new Pt. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $cross(...args) { return LinearAlgebra_1.Vec.cross(this, Util_1.Util.getArgs(args)); } - /** - * Calculate vector projection of this Pt on another Pt. Returns result as a new Pt. - * @param p a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ - $project(p) { - let m = p.magnitude(); - let a = this.$unit(); - let b = p.$divide(m); - let dot = a.dot(b); - return a.multiply(m * dot); - } - /** - * Absolute values for all values in this pt - */ + $project(...args) { + return this.$multiply(this.dot(...args) / this.magnitudeSq()); + } + projectScalar(...args) { + return this.dot(...args) / this.magnitude(); + } abs() { LinearAlgebra_1.Vec.abs(this); return this; } - /** - * Get a new Pt with absolute values of this Pt - */ $abs() { return this.clone().abs(); } - /** - * Floor values for all values in this pt - */ floor() { LinearAlgebra_1.Vec.floor(this); return this; } - /** - * Get a new Pt with floor values of this Pt - */ $floor() { return this.clone().floor(); } - /** - * Ceil values for all values in this pt - */ ceil() { LinearAlgebra_1.Vec.ceil(this); return this; } - /** - * Get a new Pt with ceil values of this Pt - */ $ceil() { return this.clone().ceil(); } - /** - * Round values for all values in this pt - */ round() { LinearAlgebra_1.Vec.round(this); return this; } - /** - * Get a new Pt with round values of this Pt - */ $round() { return this.clone().round(); } - /** - * Find the minimum value across all dimensions in this Pt - * @returns an object with `value` and `index` which returns the minimum value and its dimensional index - */ minValue() { return LinearAlgebra_1.Vec.min(this); } - /** - * Find the maximum value across all dimensions in this Pt - * @returns an object with `value` and `index` which returns the maximum value and its dimensional index - */ maxValue() { return LinearAlgebra_1.Vec.max(this); } - /** - * Get a new Pt that has the minimum dimensional values of this Pt and another Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $min(...args) { let p = Util_1.Util.getArgs(args); let m = this.clone(); @@ -394,10 +245,6 @@ class Pt extends exports.PtBaseArray { } return m; } - /** - * Get a new Pt that has the maximum dimensional values of this Pt and another Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $max(...args) { let p = Util_1.Util.getArgs(args); let m = this.clone(); @@ -406,102 +253,50 @@ class Pt extends exports.PtBaseArray { } return m; } - /** - * Get angle of this vector from origin - * @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions - */ angle(axis = Util_1.Const.xy) { return Math.atan2(this[axis[1]], this[axis[0]]); } - /** - * Get the angle between this and another Pt - * @param p the other Pt - * @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions - */ angleBetween(p, axis = Util_1.Const.xy) { return Num_1.Geom.boundRadian(this.angle(axis)) - Num_1.Geom.boundRadian(p.angle(axis)); } - /** - * Scale this Pt from origin or from an anchor point - * @param scale scale ratio - * @param anchor optional anchor point to scale from - */ scale(scale, anchor) { Num_1.Geom.scale(this, scale, anchor || Pt.make(this.length, 0)); return this; } - /** - * Rotate this Pt from origin or from an anchor point in 2D - * @param angle rotate angle - * @param anchor optional anchor point to scale from - * @param axis optional string such as "yz" to specify a 2D plane - */ rotate2D(angle, anchor, axis) { Num_1.Geom.rotate2D(this, angle, anchor || Pt.make(this.length, 0), axis); return this; } - /** - * Shear this Pt from origin or from an anchor point in 2D - * @param shear shearing value which can be a number or an array of 2 numbers - * @param anchor optional anchor point to scale from - * @param axis optional string such as "yz" to specify a 2D plane - */ shear2D(scale, anchor, axis) { Num_1.Geom.shear2D(this, scale, anchor || Pt.make(this.length, 0), axis); return this; } - /** - * Reflect this Pt along a 2D line - * @param line a Group of 2 Pts that defines a line for reflection - * @param axis optional axis such as "yz" to define a 2D plane of reflection - */ reflect2D(line, axis) { Num_1.Geom.reflect2D(this, line, axis); return this; } - /** - * A string representation of this Pt: "Pt(1, 2, 3)" - */ toString() { return `Pt(${this.join(", ")})`; } - /** - * Convert this Pt to a javascript Array - */ toArray() { return [].slice.call(this); } } exports.Pt = Pt; -/** - * A Group is a subclass of Array. It should onnly contain Pt instances. You can think of it as an array of arrays (Float32Arrays to be specific). - * See [Group guide](../../guide/Group-0300.html) for details - */ class Group extends Array { constructor(...args) { super(...args); } get id() { return this._id; } set id(s) { this._id = s; } - /** The first Pt in this group */ get p1() { return this[0]; } - /** The second Pt in this group */ get p2() { return this[1]; } - /** The third Pt in this group */ get p3() { return this[2]; } - /** The forth Pt in this group */ get p4() { return this[3]; } - /** The last Pt in this group */ get q1() { return this[this.length - 1]; } - /** The second-last Pt in this group */ get q2() { return this[this.length - 2]; } - /** The third-last Pt in this group */ get q3() { return this[this.length - 3]; } - /** The forth-last Pt in this group */ get q4() { return this[this.length - 4]; } - /** - * Depp clone this group and its Pts - */ clone() { let group = new Group(); for (let i = 0, len = this.length; i < len; i++) { @@ -509,11 +304,6 @@ class Group extends Array { } return group; } - /** - * Convert an array of numeric arrays into a Group of Pts - * @param list an array of numeric arrays - * @example `Group.fromArray( [[1,2], [3,4], [5,6]] )` - */ static fromArray(list) { let g = new Group(); for (let i = 0, len = list.length; i < len; i++) { @@ -522,96 +312,39 @@ class Group extends Array { } return g; } - /** - * Convert an array of Pts into a Group. - * @param list an array of Pts - */ static fromPtArray(list) { return Group.from(list); } - /** - * Split this Group into an array of sub-groups - * @param chunkSize number of items per sub-group - * @param stride forward-steps after each sub-group - * @param loopBack if `true`, always go through the array till the end and loop back to the beginning to complete the segments if needed - */ split(chunkSize, stride, loopBack = false) { let sp = Util_1.Util.split(this, chunkSize, stride, loopBack); return sp; } - /** - * Insert a Pt into this group - * @param pts Another group of Pts - * @param index the index position to insert into - */ insert(pts, index = 0) { Group.prototype.splice.apply(this, [index, 0, ...pts]); return this; } - /** - * Like Array's splice function, with support for negative index and a friendlier name. - * @param index start index, which can be negative (where -1 is at index 0, -2 at index 1, etc) - * @param count number of items to remove - * @returns The items that are removed. - */ remove(index = 0, count = 1) { let param = (index < 0) ? [index * -1 - 1, count] : [index, count]; return Group.prototype.splice.apply(this, param); } - /** - * Split this group into an array of sub-group segments - * @param pts_per_segment number of Pts in each segment - * @param stride forward-step to take - * @param loopBack if `true`, always go through the array till the end and loop back to the beginning to complete the segments if needed - */ segments(pts_per_segment = 2, stride = 1, loopBack = false) { return this.split(pts_per_segment, stride, loopBack); } - /** - * Get all the line segments (ie, edges in a graph) of this group - */ lines() { return this.segments(2, 1); } - /** - * Find the centroid of this group's Pts, which is the average middle point. - */ centroid() { return Num_1.Geom.centroid(this); } - /** - * Find the rectangular bounding box of this group's Pts. - * @returns a Group of 2 Pts representing the top-left and bottom-right of the rectangle - */ boundingBox() { return Num_1.Geom.boundingBox(this); } - /** - * Anchor all the Pts in this Group using a target Pt as origin. (ie, subtract all Pt with the target anchor to get a relative position). All the Pts' values will be updated. - * @param ptOrIndex a Pt, or a numeric index to target a specific Pt in this Group - */ anchorTo(ptOrIndex = 0) { Num_1.Geom.anchor(this, ptOrIndex, "to"); } - /** - * Anchor all the Pts in this Group by its absolute position from a target Pt. (ie, add all Pt with the target anchor to get an absolute position). All the Pts' values will be updated. - * @param ptOrIndex a Pt, or a numeric index to target a specific Pt in this Group - */ anchorFrom(ptOrIndex = 0) { Num_1.Geom.anchor(this, ptOrIndex, "from"); } - /** - * Create an operation using this Group, passing this Group into a custom function's first parameter. See the [Op guide](../../guide/Op-0400.html) for details. - * For example: `let myOp = group.op( fn ); let result = myOp( [1,2,3] );` - * @param fn any function that takes a Group as its first parameter - * @returns a resulting function that takes other parameters required in `fn` - */ op(fn) { let self = this; return (...params) => { return fn(self, ...params); }; } - /** - * This combines a series of operations into an array. See `op()` for details. - * For example: `let myOps = pt.ops([fn1, fn2, fn3]); let results = myOps.map( (op) => op([1,2,3]) );` - * @param fns an array of functions for `op` - * @returns an array of resulting functions - */ ops(fns) { let _ops = []; for (let i = 0, len = fns.length; i < len; i++) { @@ -619,10 +352,6 @@ class Group extends Array { } return _ops; } - /** - * Get an interpolated point on the line segments defined by this Group - * @param t a value between 0 to 1 usually - */ interpolate(t) { t = Num_1.Num.clamp(t, 0, 1); let chunk = this.length - 1; @@ -630,81 +359,41 @@ class Group extends Array { let idx = Math.floor(t / tc); return Num_1.Geom.interpolate(this[idx], this[Math.min(this.length - 1, idx + 1)], (t - idx * tc) * chunk); } - /** - * Move every Pt's position by a specific amount. Same as `add`. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ moveBy(...args) { return this.add(...args); } - /** - * Move the first Pt in this group to a specific position, and move all the other Pts correspondingly - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ moveTo(...args) { let d = new Pt(Util_1.Util.getArgs(args)).subtract(this[0]); this.moveBy(d); return this; } - /** - * Scale this group's Pts from an anchor point. Default anchor point is the first Pt in this group. - * @param scale scale ratio - * @param anchor optional anchor point to scale from - */ scale(scale, anchor) { for (let i = 0, len = this.length; i < len; i++) { Num_1.Geom.scale(this[i], scale, anchor || this[0]); } return this; } - /** - * Rotate this group's Pt from an anchor point in 2D. Default anchor point is the first Pt in this group. - * @param angle rotate angle - * @param anchor optional anchor point to scale from - * @param axis optional string such as "yz" to specify a 2D plane - */ rotate2D(angle, anchor, axis) { for (let i = 0, len = this.length; i < len; i++) { Num_1.Geom.rotate2D(this[i], angle, anchor || this[0], axis); } return this; } - /** - * Shear this group's Pt from an anchor point in 2D. Default anchor point is the first Pt in this group. - * @param shear shearing value which can be a number or an array of 2 numbers - * @param anchor optional anchor point to scale from - * @param axis optional string such as "yz" to specify a 2D plane - */ shear2D(scale, anchor, axis) { for (let i = 0, len = this.length; i < len; i++) { Num_1.Geom.shear2D(this[i], scale, anchor || this[0], axis); } return this; } - /** - * Reflect this group's Pts along a 2D line. Default anchor point is the first Pt in this group. - * @param line a Group of 2 Pts that defines a line for reflection - * @param axis optional axis such as "yz" to define a 2D plane of reflection - */ reflect2D(line, axis) { for (let i = 0, len = this.length; i < len; i++) { Num_1.Geom.reflect2D(this[i], line, axis); } return this; } - /** - * Sort this group's Pts by values in a specific dimension - * @param dim dimensional index - * @param desc if true, sort descending. Default is false (ascending) - */ sortByDimension(dim, desc = false) { return this.sort((a, b) => (desc) ? b[dim] - a[dim] : a[dim] - b[dim]); } - /** - * Update each Pt in this Group with a Pt function - * @param ptFn string name of an existing Pt function. Note that the function must return Pt. - * @param args arguments for the function specified in ptFn - */ forEachPt(ptFn, ...args) { if (!this[0][ptFn]) { Util_1.Util.warn(`${ptFn} is not a function of Pt`); @@ -715,71 +404,30 @@ class Group extends Array { } return this; } - /** - * Add scalar or vector values to this group's Pts. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ add(...args) { return this.forEachPt("add", ...args); } - /** - * Subtract scalar or vector values from this group's Pts. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ subtract(...args) { return this.forEachPt("subtract", ...args); } - /** - * Multiply scalar or vector values (as element-wise) with this group's Pts. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ multiply(...args) { return this.forEachPt("multiply", ...args); } - /** - * Divide this group's Pts over scalar or vector values (as element-wise) - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ divide(...args) { return this.forEachPt("divide", ...args); } - /** - * Apply this group as a matrix and calculate matrix addition - * @param g a scalar number, an array of numeric arrays, or a group of Pt - * @returns a new Group - */ $matrixAdd(g) { return LinearAlgebra_1.Mat.add(this, g); } - /** - * Apply this group as a matrix and calculate matrix multiplication - * @param g a scalar number, an array of numeric arrays, or a Group of K Pts, each with N dimensions (K-rows, N-columns) -- or if transposed is true, then N Pts with K dimensions - * @param transposed (Only applicable if it's not elementwise multiplication) If true, then a and b's columns should match (ie, each Pt should have the same dimensions). Default is `false`. - * @param elementwise if true, then the multiplication is done element-wise. Default is `false`. - * @returns If not elementwise, this will return a new Group with M Pt, each with N dimensions (M-rows, N-columns). - */ $matrixMultiply(g, transposed = false, elementwise = false) { return LinearAlgebra_1.Mat.multiply(this, g, transposed, elementwise); } - /** - * Zip one slice of an array of Pt. Imagine the Pts are organized in rows, then this function will take the values in a specific column. - * @param idx index to zip at - * @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead. - */ zipSlice(index, defaultValue = false) { return LinearAlgebra_1.Mat.zipSlice(this, index, defaultValue); } - /** - * Zip a group of Pt. eg, [[1,2],[3,4],[5,6]] => [[1,3,5],[2,4,6]] - * @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead. - * @param useLongest If true, find the longest list of values in a Pt and use its length for zipping. Default is false, which uses the first item's length for zipping. - */ $zip(defaultValue = undefined, useLongest = false) { return LinearAlgebra_1.Mat.zip(this, defaultValue, useLongest); } - /** - * Get a string representation of this group - */ toString() { return "Group[ " + this.reduce((p, c) => p + c.toString() + " ", "") + " ]"; } @@ -793,13 +441,8 @@ exports.Group = Group; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); -/** - * Various constant values for enumerations and calculations - */ exports.Const = { xy: "xy", yz: "yz", @@ -807,89 +450,52 @@ exports.Const = { xyz: "xyz", horizontal: 0, vertical: 1, - /* represents identical point or value */ identical: 0, - /* represents right position or direction */ right: 4, - /* represents bottom right position or direction */ bottom_right: 5, - /* represents bottom position or direction */ bottom: 6, - /* represents bottom left position or direction */ bottom_left: 7, - /* represents left position or direction */ left: 8, - /* represents top left position or direction */ top_left: 1, - /* represents top position or direction */ top: 2, - /* represents top right position or direction */ top_right: 3, - /* represents an arbitrary very small number. It is set as 0.0001 here. */ epsilon: 0.0001, - /* represents Number.MAX_VALUE */ max: Number.MAX_VALUE, - /* represents Number.MIN_VALUE */ min: Number.MIN_VALUE, - /* pi radian (180 deg) */ pi: Math.PI, - /* two pi radian (360deg) */ two_pi: 6.283185307179586, - /* half pi radian (90deg) */ half_pi: 1.5707963267948966, - /* pi/4 radian (45deg) */ quarter_pi: 0.7853981633974483, - /* pi/180: 1 degree in radian */ one_degree: 0.017453292519943295, - /* multiply this constant with a radian to get a degree */ rad_to_deg: 57.29577951308232, - /* multiply this constant with a degree to get a radian */ deg_to_rad: 0.017453292519943295, - /* Gravity acceleration (unit: m/s^2) and gravity force (unit: Newton) on 1kg of mass. */ gravity: 9.81, - /* 1 Newton: 0.10197 Kilogram-force */ newton: 0.10197, - /* Gaussian constant (1 / Math.sqrt(2 * Math.PI)) */ gaussian: 0.3989422804014327 }; -/** - * Util provides various helper functions - */ class Util { - /** - * Convert different kinds of parameters (arguments, array, object) into an array of numbers - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ static getArgs(args) { if (args.length < 1) return []; let pos = []; let isArray = Array.isArray(args[0]) || ArrayBuffer.isView(args[0]); - // positional arguments: x,y,z,w,... if (typeof args[0] === 'number') { pos = Array.prototype.slice.call(args); - // as an object of {x, y?, z?, w?} } else if (typeof args[0] === 'object' && !isArray) { let a = ["x", "y", "z", "w"]; let p = args[0]; for (let i = 0; i < a.length; i++) { if ((p.length && i >= p.length) || !(a[i] in p)) - break; // check for length and key exist + break; pos.push(p[a[i]]); } - // as an array of values } else if (isArray) { pos = [].slice.call(args[0]); } return pos; } - /** - * Send a warning message based on Util.warnLevel global setting. This allows you to dynamically set whether minor errors should be thrown or printed in console or muted. - * @param message any error or warning message - * @param defaultReturn optional return value - */ static warn(message = "error", defaultReturn = undefined) { if (Util.warnLevel == "error") { throw new Error(message); @@ -902,13 +508,6 @@ class Util { static randomInt(range, start = 0) { return Math.floor(Math.random() * range) + start; } - /** - * Split an array into chunks of sub-array - * @param pts an array - * @param size chunk size, ie, number of items in a chunk - * @param stride optional parameter to "walk through" the array in steps - * @param loopBack if `true`, always go through the array till the end and loop back to the beginning to complete the segments if needed - */ static split(pts, size, stride, loopBack = false) { let st = stride || size; let chunks = []; @@ -929,21 +528,10 @@ class Util { } return chunks; } - /** - * Flatten an array of arrays such as Group[] to a flat Array or Group - * @param pts an array, usually an array of Groups - * @param flattenAsGroup a boolean to specify whether the return type should be a Group or Array. Default is `true` which returns a Group. - */ static flatten(pts, flattenAsGroup = true) { let arr = (flattenAsGroup) ? new Pt_1.Group() : new Array(); return arr.concat.apply(arr, pts); } - /** - * Given two arrays of object, and a function that operate on two object, return an array of T - * @param a an array of object, eg [ Group, Group, ... ] - * @param b another array of object - * @param op a function that takes two parameters (a, b) and returns a T - */ static combine(a, b, op) { let result = []; for (let i = 0, len = a.length; i < len; i++) { @@ -953,10 +541,6 @@ class Util { } return result; } - /** - * Zip arrays. eg, [[1,2],[3,4],[5,6]] => [[1,3,5],[2,4,6]] - * @param arrays an array of arrays - */ static zip(...arrays) { let z = []; for (let i = 0, len = arrays[0].length; i < len; i++) { @@ -968,15 +552,6 @@ class Util { } return z; } - /** - * Create a convenient stepper. This returns a function which you can call repeatedly to step a counter. - * @param max Maximum of the stepper range. The resulting stepper will return (min to max-1) values. - * @param min Minimum of the stepper range. Default is 0. - * @param stride Stride of the step. Default is 1. - * @param callback An optional callback function( step ), which will be called each tiem when stepper function is called. - * @example `let counter = stepper(100); let c = counter(); c = counter(); ...` - * @returns a function which will increment the stepper and return its value at each call. - */ static stepper(max, min = 0, stride = 1, callback) { let c = min; return function () { @@ -989,12 +564,6 @@ class Util { return c; }; } - /** - * A convenient way to step through a range. Same as `for (i=0; i Util_1.Util.warn("Group's length is less than " + param, obj); let _errorOutofBound = (obj, param = "") => Util_1.Util.warn(`Index ${param} is out of bound in Group`, obj); -/** - * Line class provides static functions to create and operate on lines. A line is usually represented as a Group of 2 Pts. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Line { - /** - * Create a line by "drawing" from an anchor point, given an angle and a magnitude - * @param anchor an anchor Pt - * @param angle an angle in radian - * @param magnitude magnitude of the line - * @return a Group of 2 Pts representing a line segement - */ static fromAngle(anchor, angle, magnitude) { let g = new Pt_1.Group(new Pt_1.Pt(anchor), new Pt_1.Pt(anchor)); g[1].toAngle(angle, magnitude, true); return g; } - /** - * Calculate the slope of a line - * @param p1 line's first end point - * @param p2 line's second end point - */ static slope(p1, p2) { return (p2[0] - p1[0] === 0) ? undefined : (p2[1] - p1[1]) / (p2[0] - p1[0]); } - /** - * Calculate the slope and xy intercepts of a line - * @param p1 line's first end point - * @param p2 line's second end point - * @returns an object with `slope`, `xi`, `yi` properties - */ static intercept(p1, p2) { if (p2[0] - p1[0] === 0) { return undefined; @@ -1064,49 +608,20 @@ class Line { return { slope: m, yi: c, xi: (m === 0) ? undefined : -c / m }; } } - /** - * Given a 2D path and a point, find whether the point is on left or right side of the line - * @param line a Group of at least 2 Pts - * @param pt a Pt - * @returns a negative value if on left and a positive value if on right. If collinear, then the return value is 0. - */ static sideOfPt2D(line, pt) { return (line[1][0] - line[0][0]) * (pt[1] - line[0][1]) - (pt[0] - line[0][0]) * (line[1][1] - line[0][1]); } - /** - * Check if three Pts are collinear, ie, on the same straight path. - * @param p1 first Pt - * @param p2 second Pt - * @param p3 third Pt - * @param threshold a threshold where a smaller value means higher precision threshold for the straight line. Default is 0.01. - */ static collinear(p1, p2, p3, threshold = 0.01) { - // Use cross product method let a = new Pt_1.Pt(0, 0, 0).to(p1).$subtract(p2); let b = new Pt_1.Pt(0, 0, 0).to(p1).$subtract(p3); return a.$cross(b).divide(1000).equals(new Pt_1.Pt(0, 0, 0), threshold); } - /** - * Get magnitude of a line segment - * @param line a Group of at least 2 Pts - */ static magnitude(line) { return (line.length >= 2) ? line[1].$subtract(line[0]).magnitude() : 0; } - /** - * Get squared magnitude of a line segment - * @param line a Group of at least 2 Pts - */ static magnitudeSq(line) { return (line.length >= 2) ? line[1].$subtract(line[0]).magnitudeSq() : 0; } - /** - * Find a point on a line that is perpendicular (shortest distance) to a target point - * @param pt a target Pt - * @param ln a group of Pts that defines a line - * @param asProjection if true, this returns the projection vector instead. Default is false. - * @returns a Pt on the line that is perpendicular to the target Pt, or a projection vector if `asProjection` is true. - */ static perpendicularFromPt(line, pt, asProjection = false) { if (line[0].equals(line[1])) return undefined; @@ -1115,21 +630,9 @@ class Line { let proj = b.$subtract(a.$project(b)); return (asProjection) ? proj : proj.$add(pt); } - /** - * Given a line and a point, find the shortest distance from the point to the line - * @param line a Group of 2 Pts - * @param pt a Pt - * @see `Line.perpendicularFromPt` - */ static distanceFromPt(line, pt) { return Line.perpendicularFromPt(line, pt, true).magnitude(); } - /** - * Given two lines as rays (infinite lines), find their intersection point if any. - * @param la a Group of 2 Pts representing a ray - * @param lb a Group of 2 Pts representing a ray - * @returns an intersection Pt or undefined if no intersection - */ static intersectRay2D(la, lb) { let a = Line.intercept(la[0], la[1]); let b = Line.intercept(lb[0], lb[1]); @@ -1138,12 +641,10 @@ class Line { if (a == undefined) { if (b == undefined) return undefined; - // one of them is vertical line, while the other is not, so they will intersect - let y1 = -b.slope * (pb[0] - pa[0]) + pb[1]; // -slope * x + y + let y1 = -b.slope * (pb[0] - pa[0]) + pb[1]; return new Pt_1.Pt(pa[0], y1); } else { - // diff slope, or b slope is vertical line if (b == undefined) { let y1 = -a.slope * (pa[0] - pb[0]) + pa[1]; return new Pt_1.Pt(pb[0], y1); @@ -1163,48 +664,37 @@ class Line { } } } - /** - * Given two line segemnts, find their intersection point if any. - * @param la a Group of 2 Pts representing a line segment - * @param lb a Group of 2 Pts representing a line segment - * @returns an intersection Pt or undefined if no intersection - */ static intersectLine2D(la, lb) { let pt = Line.intersectRay2D(la, lb); return (pt && Num_1.Geom.withinBound(pt, la[0], la[1]) && Num_1.Geom.withinBound(pt, lb[0], lb[1])) ? pt : undefined; } - /** - * Given a line segemnt and a ray (infinite line), find their intersection point if any. - * @param line a Group of 2 Pts representing a line segment - * @param ray a Group of 2 Pts representing a ray - * @returns an intersection Pt or undefined if no intersection - */ static intersectLineWithRay2D(line, ray) { let pt = Line.intersectRay2D(line, ray); return (pt && Num_1.Geom.withinBound(pt, line[0], line[1])) ? pt : undefined; } - /** - * Given a line segemnt and a ray (infinite line), find its intersection point(s) with a polygon. - * @param lineOrRay a Group of 2 Pts representing a line or ray - * @param poly a Group of Pts representing a polygon - * @param sourceIsRay a boolean value to treat the line as a ray (infinite line). Default is `false`. - */ static intersectPolygon2D(lineOrRay, poly, sourceIsRay = false) { let fn = sourceIsRay ? Line.intersectLineWithRay2D : Line.intersectLine2D; let pts = new Pt_1.Group(); for (let i = 0, len = poly.length; i < len; i++) { - let d = fn(poly[i], lineOrRay); + let next = (i === len - 1) ? 0 : i + 1; + let d = fn([poly[i], poly[next]], lineOrRay); if (d) pts.push(d); } return (pts.length > 0) ? pts : undefined; } - /** - * Get two intersection Pts of a ray with a 2D grid point - * @param ray a ray specified by 2 Pts - * @param gridPt a Pt on the grid - * @returns a group of two intersecting Pts. The first one is horizontal intersection and the second one is vertical intersection. - */ + static intersectLines2D(lines1, lines2, isRay = false) { + let group = new Pt_1.Group(); + let fn = isRay ? Line.intersectLineWithRay2D : Line.intersectLine2D; + for (let i = 0, len = lines1.length; i < len; i++) { + for (let k = 0, lenk = lines2.length; k < lenk; k++) { + let _ip = fn(lines1[i], lines2[k]); + if (_ip) + group.push(_ip); + } + } + return group; + } static intersectGridWithRay2D(ray, gridPt) { let t = Line.intercept(new Pt_1.Pt(ray[0]).subtract(gridPt), new Pt_1.Pt(ray[1]).subtract(gridPt)); let g = new Pt_1.Group(); @@ -1214,12 +704,6 @@ class Line { g.push(new Pt_1.Pt(gridPt[0], gridPt[1] + t.yi)); return g; } - /** - * Get two intersection Pts of a line segment with a 2D grid point - * @param ray a ray specified by 2 Pts - * @param gridPt a Pt on the grid - * @returns a group of two intersecting Pts. The first one is horizontal intersection and the second one is vertical intersection. - */ static intersectGridWithLine2D(line, gridPt) { let g = Line.intersectGridWithRay2D(line, gridPt); let gg = new Pt_1.Group(); @@ -1229,20 +713,12 @@ class Line { } return gg; } - /** - * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. - * @param line a Group representing a line - * @param rect a Group representing a rectangle - */ static intersectRect2D(line, rect) { - return Rectangle.intersectRect2D(Line.toRect(line), rect); + let box = Num_1.Geom.boundingBox(Pt_1.Group.fromPtArray(line)); + if (!Rectangle.hasIntersectRect2D(box, rect)) + return new Pt_1.Group(); + return Line.intersectLines2D([line], Rectangle.sides(rect)); } - /** - * Get evenly distributed points on a line - * @param line a Group representing a line - * @param num number of points to get - */ static subpoints(line, num) { let pts = new Pt_1.Group(); for (let i = 1; i <= num; i++) { @@ -1250,14 +726,6 @@ class Line { } return pts; } - /** - * Crop this line by a circle or rectangle at end point. - * @param line line to crop - * @param size size of circle or rectangle as Pt - * @param index line's end point index, ie, 0 = start and 1 = end. - * @param cropAsCircle a boolean to specify whether the `size` parameter should be treated as circle. Default is `true`. - * @return an intersecting point on the line that can be used for cropping. - */ static crop(line, size, index = 0, cropAsCircle = true) { let tdx = (index === 0) ? 1 : 0; let ls = line[tdx].$subtract(line[index]); @@ -1280,14 +748,6 @@ class Line { return Line.intersectRay2D(sides[sideIdx], line); } } - /** - * Create an marker arrow or line, placed at an end point of this line - * @param line line to place marker - * @param size size of the marker as Pt - * @param graphic either "arrow" or "line" - * @param atTail a boolean, if `true`, the marker will be positioned at tail of the line (ie, index = 1). Default is `true`. - * @returns a Group that defines the marker's shape - */ static marker(line, size, graphic = ("arrow" || "line"), atTail = true) { let h = atTail ? 0 : 1; let t = atTail ? 1 : 0; @@ -1304,95 +764,44 @@ class Line { return new Pt_1.Group(ps[0], ps[1]); } } - /** - * Convert this line to a rectangle representation - * @param line a Group representing a line - */ static toRect(line) { return new Pt_1.Group(line[0].$min(line[1]), line[0].$max(line[1])); } } exports.Line = Line; -/** - * Rectangle class provides static functions to create and operate on rectangles. A rectangle is usually represented as a Group of 2 Pts, marking the top-left and bottom-right corners of the rectangle. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Rectangle { - /** - * Same as `Rectangle.fromTopLeft` - */ static from(topLeft, widthOrSize, height) { return Rectangle.fromTopLeft(topLeft, widthOrSize, height); } - /** - * Create a rectangle given a top-left position and a size - * @param topLeft top-left point - * @param widthOrSize width as a number, or a Pt that defines its size - * @param height optional height as a number - */ static fromTopLeft(topLeft, widthOrSize, height) { let size = (typeof widthOrSize == "number") ? [widthOrSize, (height || widthOrSize)] : widthOrSize; return new Pt_1.Group(new Pt_1.Pt(topLeft), new Pt_1.Pt(topLeft).add(size)); } - /** - * Create a rectangle given a center position and a size - * @param topLeft top-left point - * @param widthOrSize width as a number, or a Pt that defines its size - * @param height optional height as a number - */ static fromCenter(center, widthOrSize, height) { let half = (typeof widthOrSize == "number") ? [widthOrSize / 2, (height || widthOrSize) / 2] : new Pt_1.Pt(widthOrSize).divide(2); return new Pt_1.Group(new Pt_1.Pt(center).subtract(half), new Pt_1.Pt(center).add(half)); } - /** - * Convert this rectangle to a circle that fits within the rectangle - * @returns a Group that represents a circle - * @see `Circle` - */ static toCircle(pts) { return Circle.fromRect(pts); } - /** - * Create a square that either fits within or encloses a rectangle - * @param pts a Group of 2 Pts representing a rectangle - * @param enclose if `true`, the square will enclose the rectangle. Default is `false`, which will fit the square inside the rectangle. - */ static toSquare(pts, enclose = false) { let s = Rectangle.size(pts); let m = (enclose) ? s.maxValue().value : s.minValue().value; return Rectangle.fromCenter(Rectangle.center(pts), m, m); } - /** - * Get the size of this rectangle as a Pt - * @param pts a Group of 2 Pts representing a Rectangle - */ static size(pts) { return pts[0].$max(pts[1]).subtract(pts[0].$min(pts[1])); } - /** - * Get the center of this rectangle - * @param pts a Group of 2 Pts representing a Rectangle - */ static center(pts) { let min = pts[0].$min(pts[1]); let max = pts[0].$max(pts[1]); return min.add(max.$subtract(min).divide(2)); } - /** - * Get the 4 corners of this rectangle as a Group - * @param rect a Group of 2 Pts representing a Rectangle - */ static corners(rect) { let p0 = rect[0].$min(rect[1]); let p2 = rect[0].$max(rect[1]); return new Pt_1.Group(p0, new Pt_1.Pt(p2.x, p0.y), p2, new Pt_1.Pt(p0.x, p2.y)); } - /** - * Get the 4 sides of this rectangle as an array of 4 Groups - * @param rect a Group of 2 Pts representing a Rectangle - * @returns an array of 4 Groups, each of which represents a line segment - */ static sides(rect) { let [p0, p1, p2, p3] = Rectangle.corners(rect); return [ @@ -1400,22 +809,13 @@ class Rectangle { new Pt_1.Group(p2, p3), new Pt_1.Group(p3, p0) ]; } - /** - * Same as `Rectangle.sides` - */ static lines(rect) { return Rectangle.sides(rect); } - /** - * Given an array of rectangles, get a rectangle that bounds all of them - * @param rects an array of Groups that represent rectangles - * @returns the bounding rectangle as a Group - */ static boundingBox(rects) { let merged = Util_1.Util.flatten(rects, false); let min = Pt_1.Pt.make(2, Number.MAX_VALUE); let max = Pt_1.Pt.make(2, Number.MIN_VALUE); - // calculate min max in a single pass for (let i = 0, len = merged.length; i < len; i++) { for (let k = 0; k < 2; k++) { min[k] = Math.min(min[k], merged[i][k]); @@ -1424,30 +824,14 @@ class Rectangle { } return new Pt_1.Group(min, max); } - /** - * Convert this rectangle into a Group representing a polygon - * @param rect a Group of 2 Pts representing a Rectangle - */ static polygon(rect) { return Rectangle.corners(rect); } - /** - * Subdivide a rectangle into 4 rectangles, one for each quadrant - * @param rect a Group of 2 Pts representing a Rectangle - * @returns an array of 4 Groups of rectangles - */ static quadrants(rect, center) { let corners = Rectangle.corners(rect); let _center = (center != undefined) ? new Pt_1.Pt(center) : Rectangle.center(rect); return corners.map((c) => new Pt_1.Group(c, _center).boundingBox()); } - /** - * Subdivde a rectangle into 2 rectangles, by row or by column - * @param rect Group of 2 Pts representing a Rectangle - * @param ratio a value between 0 to 1 to indicate the split ratio - * @param asRows if `true`, split into 2 rows. Default is `false` which splits into 2 columns. - * @returns an array of 2 Groups of rectangles - */ static halves(rect, ratio = 0.5, asRows = false) { let min = rect[0].$min(rect[1]); let max = rect[0].$max(rect[1]); @@ -1456,49 +840,28 @@ class Rectangle { ? [new Pt_1.Group(min, new Pt_1.Pt(max[0], mid)), new Pt_1.Group(new Pt_1.Pt(min[0], mid), max)] : [new Pt_1.Group(min, new Pt_1.Pt(mid, max[1])), new Pt_1.Group(new Pt_1.Pt(mid, min[1]), max)]; } - /** - * Check if a point is within a rectangle - * @param rect a Group of 2 Pts representing a Rectangle - * @param pt the point to check - */ static withinBound(rect, pt) { return Num_1.Geom.withinBound(pt, rect[0], rect[1]); } - /** - * Check if a rectangle is within the bounds of another rectangle - * @param rect1 a Group of 2 Pts representing a rectangle - * @param rect2 a Group of 2 Pts representing a rectangle - */ - static hasIntersectRect2D(rect1, rect2) { - let pts = Rectangle.corners(rect1); - for (let i = 0, len = pts.length; i < len; i++) { - if (Num_1.Geom.withinBound(pts[i], rect2[0], rect2[1])) - return true; + static hasIntersectRect2D(rect1, rect2, resetBoundingBox = false) { + if (resetBoundingBox) { + rect1 = Num_1.Geom.boundingBox(rect1); + rect2 = Num_1.Geom.boundingBox(rect2); } - return false; + if (rect1[0][0] > rect2[1][0] || rect2[0][0] > rect1[1][0]) + return false; + if (rect1[0][1] > rect2[1][1] || rect2[0][1] > rect1[1][1]) + return false; + return true; } - /** - * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. - * @param rect1 a Group of 2 Pts representing a rectangle - * @param rect2 a Group of 2 Pts representing a rectangle - */ static intersectRect2D(rect1, rect2) { - return Util_1.Util.flatten(Polygon.intersect2D(Rectangle.sides(rect1), Rectangle.sides(rect2))); + if (!Rectangle.hasIntersectRect2D(rect1, rect2)) + return new Pt_1.Group(); + return Line.intersectLines2D(Rectangle.sides(rect1), Rectangle.sides(rect2)); } } exports.Rectangle = Rectangle; -/** - * Circle class provides static functions to create and operate on circles. A circle is usually represented as a Group of 2 Pts, where the first Pt specifies the center, and the second Pt specifies the radius. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Circle { - /** - * Create a circle that either fits within or encloses a rectangle - * @param pts a Group of 2 Pts representing a rectangle - * @param enclose if `true`, the circle will enclose the rectangle. Default is `false`, which will fit the circle inside the rectangle. - */ static fromRect(pts, enclose = false) { let r = 0; let min = r = Rectangle.size(pts).minValue().value / 2; @@ -1511,30 +874,13 @@ class Circle { } return new Pt_1.Group(Rectangle.center(pts), new Pt_1.Pt(r, r)); } - /** - * Create a circle based on a center point and a radius - * @param pt center point of circle - * @param radius radius of circle - */ static fromCenter(pt, radius) { return new Pt_1.Group(new Pt_1.Pt(pt), new Pt_1.Pt(radius, radius)); } - /** - * Check if a point is within a circle - * @param pts a Group of 2 Pts representing a circle - * @param pt the point to checks - * @param threshold an optional small number to set threshold. Default is 0. - */ static withinBound(pts, pt, threshold = 0) { let d = pts[0].$subtract(pt); return d.dot(d) + threshold < pts[1].x * pts[1].x; } - /** - * Get the intersection points between a circle and a ray (infinite line) - * @param pts a Group of 2 Pts representing a circle - * @param ray a Group of 2 Pts representing a ray - * @returns a Group of intersection points, or an empty Group if no intersection is found - */ static intersectRay2D(pts, ray) { let d = ray[0].$subtract(ray[1]); let f = pts[0].$subtract(ray[0]); @@ -1543,7 +889,7 @@ class Circle { let c = f.dot(f) - pts[1].x * pts[1].x; let p = b / a; let q = c / a; - let disc = p * p - q; // discriminant + let disc = p * p - q; if (disc < 0) { return new Pt_1.Group(); } @@ -1558,12 +904,6 @@ class Circle { return new Pt_1.Group(p1, p2); } } - /** - * Get the intersection points between a circle and a line segment - * @param pts a Group of 2 Pts representing a circle - * @param ray a Group of 2 Pts representing a line - * @returns a Group of intersection points, or an empty Group if no intersection is found - */ static intersectLine2D(pts, line) { let ps = Circle.intersectRay2D(pts, line); let g = new Pt_1.Group(); @@ -1575,12 +915,6 @@ class Circle { } return g; } - /** - * Get the intersection points between two circles - * @param pts a Group of 2 Pts representing a circle - * @param circle a Group of 2 Pts representing a circle - * @returns a Group of intersection points, or an empty Group if no intersection is found - */ static intersectCircle2D(pts, circle) { let dv = circle[0].$subtract(pts[0]); let dr2 = dv.magnitudeSq(); @@ -1602,13 +936,6 @@ class Circle { return new Pt_1.Group(new Pt_1.Pt(p.x + h * dv.y / dr, p.y - h * dv.x / dr), new Pt_1.Pt(p.x - h * dv.y / dr, p.y + h * dv.x / dr)); } } - /** - * Quick way to check rectangle intersection with a circle. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. - * @param pts a Group of 2 Pts representing a circle - * @param rect a Group of 2 Pts representing a rectangle - * @returns a Group of intersection points, or an empty Group if no intersection is found - */ static intersectRect2D(pts, rect) { let sides = Rectangle.sides(rect); let g = []; @@ -1619,27 +946,15 @@ class Circle { } return Util_1.Util.flatten(g); } - /** - * Convert this cirlce to a rectangle that encloses this circle - * @param pts a Group of 2 Pts representing a circle - */ static toRect(pts) { let r = pts[1][0]; return new Pt_1.Group(pts[0].$subtract(r), pts[0].$add(r)); } - /** - * Convert this cirlce to a rectangle that fits within this circle - * @param pts a Group of 2 Pts representing a circle - */ static toInnerRect(pts) { let r = pts[1][0]; let half = Math.sqrt(r * r) / 2; return new Pt_1.Group(pts[0].$subtract(half), pts[0].$add(half)); } - /** - * Convert this cirlce to a triangle that fits within this circle - * @param pts a Group of 2 Pts representing a circle - */ static toInnerTriangle(pts) { let ang = -Math.PI / 2; let inc = Math.PI * 2 / 3; @@ -1652,16 +967,7 @@ class Circle { } } exports.Circle = Circle; -/** - * Triangle class provides static functions to create and operate on trianges. A triange is usually represented as a Group of 3 Pts. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Triangle { - /** - * Create a triangle from a rectangle. The triangle will be isosceles, with the bottom of the rectangle as its base. - * @param rect a Group of 2 Pts representing a rectangle - */ static fromRect(rect) { let top = rect[0].$add(rect[1]).divide(2); top.y = rect[0][1]; @@ -1669,37 +975,17 @@ class Triangle { left.x = rect[0][0]; return new Pt_1.Group(top, rect[1].clone(), left); } - /** - * Create a triangle that fits within a circle - * @param circle a Group of 2 Pts representing a circle - */ static fromCircle(circle) { return Circle.toInnerTriangle(circle); } - /** - * Create an equilateral triangle based on a center point and a size - * @param pt the center point - * @param size size is the magnitude of lines from center to the triangle's vertices, like a "radius". - */ static fromCenter(pt, size) { return Triangle.fromCircle(Circle.fromCenter(pt, size)); } - /** - * Get the medial, which is an inner triangle formed by connecting the midpoints of this triangle's sides - * @param pts a Group of Pts - * @returns a Group representing a medial triangle - */ static medial(pts) { if (pts.length < 3) return _errorLength(new Pt_1.Group(), 3); return Polygon.midpoints(pts, true); } - /** - * Given a point of the triangle, the opposite side is the side which the point doesn't touch. - * @param pts a Group of Pts - * @param index a Pt on the triangle group - * @returns a Group that represents a line of the opposite side - */ static oppositeSide(pts, index) { if (pts.length < 3) return _errorLength(new Pt_1.Group(), 3); @@ -1713,12 +999,6 @@ class Triangle { return Pt_1.Group.fromPtArray([pts[0], pts[1]]); } } - /** - * Get a triangle's altitude, which is a line from a triangle's point to its opposite side, and perpendicular to its opposite side. - * @param pts a Group of Pts - * @param index a Pt on the triangle group - * @returns a Group that represents the altitude line - */ static altitude(pts, index) { let opp = Triangle.oppositeSide(pts, index); if (opp.length > 1) { @@ -1728,11 +1008,6 @@ class Triangle { return new Pt_1.Group(); } } - /** - * Get orthocenter, which is the intersection point of a triangle's 3 altitudes (the 3 lines that are perpendicular to its 3 opposite sides). - * @param pts a Group of Pts - * @returns the orthocenter as a Pt - */ static orthocenter(pts) { if (pts.length < 3) return _errorLength(undefined, 3); @@ -1740,11 +1015,6 @@ class Triangle { let b = Triangle.altitude(pts, 1); return Line.intersectRay2D(a, b); } - /** - * Get incenter, which is the center point of its inner circle, and also the intersection point of its 3 angle bisector lines (each of which cuts one of the 3 angles in half). - * @param pts a Group of Pts - * @returns the incenter as a Pt - */ static incenter(pts) { if (pts.length < 3) return _errorLength(undefined, 3); @@ -1752,11 +1022,6 @@ class Triangle { let b = Polygon.bisector(pts, 1).add(pts[1]); return Line.intersectRay2D(new Pt_1.Group(pts[0], a), new Pt_1.Group(pts[1], b)); } - /** - * Get an interior circle, which is the largest circle completed enclosed by this triangle - * @param pts a Group of Pts - * @param center Optional parameter if the incenter is already known. Otherwise, leave it empty and the incenter will be calculated - */ static incircle(pts, center) { let c = (center) ? center : Triangle.incenter(pts); let area = Polygon.area(pts); @@ -1764,22 +1029,12 @@ class Triangle { let r = 2 * area / perim.total; return Circle.fromCenter(c, r); } - /** - * Get circumcenter, which is the intersection point of its 3 perpendicular bisectors lines ( each of which divides a side in half and is perpendicular to the side) - * @param pts a Group of Pts - * @returns the circumcenter as a Pt - */ static circumcenter(pts) { let md = Triangle.medial(pts); let a = [md[0], Num_1.Geom.perpendicular(pts[0].$subtract(md[0])).p1.$add(md[0])]; let b = [md[1], Num_1.Geom.perpendicular(pts[1].$subtract(md[1])).p1.$add(md[1])]; return Line.intersectRay2D(a, b); } - /** - * Get circumcenter, which is the intersection point of its 3 perpendicular bisectors lines ( each of which divides a side in half and is perpendicular to the side) - * @param pts a Group of Pts - * @param center Optional parameter if the circumcenter is already known. Otherwise, leave it empty and the circumcenter will be calculated - */ static circumcircle(pts, center) { let c = (center) ? center : Triangle.circumcenter(pts); let r = pts[0].$subtract(c).magnitude(); @@ -1787,26 +1042,27 @@ class Triangle { } } exports.Triangle = Triangle; -/** - * Polygon class provides static functions to create and operate on polygons. A polygon is usually represented as a Group of 3 or more Pts. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Polygon { - /** - * Get the centroid of a polygon, which is the average of all its points. - * @param pts a Group of Pts representing a polygon - */ static centroid(pts) { return Num_1.Geom.centroid(pts); } - /** - * Get the line segments in this polygon - * @param pts a Group of Pts - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - * @returns an array of Groups which has 2 Pts in each group - */ - static lines(pts, closePath = false) { + static rectangle(center, widthOrSize, height) { + return Rectangle.corners(Rectangle.fromCenter(center, widthOrSize, height)); + } + static fromCenter(center, radius, sides) { + let g = new Pt_1.Group(); + for (let i = 0; i < sides; i++) { + let ang = Math.PI * 2 * i / sides; + g.push(new Pt_1.Pt(Math.cos(ang) * radius, Math.sin(ang) * radius).add(center)); + } + return g; + } + static lineAt(pts, idx) { + if (idx < 0 || idx >= pts.length) + throw new Error("index out of the Polygon's range"); + return new Pt_1.Group(pts[idx], (idx === pts.length - 1) ? pts[0] : pts[idx + 1]); + } + static lines(pts, closePath = true) { if (pts.length < 2) return _errorLength(new Pt_1.Group(), 2); let sp = Util_1.Util.split(pts, 2, 1); @@ -1814,12 +1070,6 @@ class Polygon { sp.push(new Pt_1.Group(pts[pts.length - 1], pts[0])); return sp.map((g) => g); } - /** - * Get a new polygon group that is derived from midpoints in this polygon - * @param pts a Group of Pts - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - * @param t a value between 0 to 1 for interpolation. Default to 0.5 which will get the middle point. - */ static midpoints(pts, closePath = false, t = 0.5) { if (pts.length < 2) return _errorLength(new Pt_1.Group(), 2); @@ -1827,12 +1077,6 @@ class Polygon { let mids = sides.map((s) => Num_1.Geom.interpolate(s[0], s[1], t)); return mids; } - /** - * Given a Pt in the polygon group, the adjacent sides are the two sides which the Pt touches. - * @param pts a group of Pts - * @param index the target Pt - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - */ static adjacentSides(pts, index, closePath = false) { if (pts.length < 2) return _errorLength(new Pt_1.Group(), 2); @@ -1851,13 +1095,6 @@ class Polygon { gs.push(new Pt_1.Group(pts[index], pts[right])); return gs; } - /** - * Get a bisector which is a line that split between two sides of a polygon equally. - * @param pts a group of Pts - * @param index the Pt in the polygon to bisect from - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - * @returns a bisector Pt that's a normalized unit vector - */ static bisector(pts, index) { let sides = Polygon.adjacentSides(pts, index, true); if (sides.length >= 2) { @@ -1869,12 +1106,6 @@ class Polygon { return undefined; } } - /** - * Find the perimeter of this polygon, ie, the lengths of its sides. - * @param pts a group of Pts - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - * @returns an object with `total` length, and `segments` which is a Pt that stores each segment's length - */ static perimeter(pts, closePath = false) { if (pts.length < 2) return _errorLength(new Pt_1.Group(), 2); @@ -1891,14 +1122,9 @@ class Polygon { segments: p }; } - /** - * Find the area of a *convex* polygon. - * @param pts a group of Pts - */ static area(pts) { if (pts.length < 3) return _errorLength(new Pt_1.Group(), 3); - // determinant let det = (a, b) => a[0] * b[1] - a[1] * b[0]; let area = 0; for (let i = 0, len = pts.length; i < len; i++) { @@ -1911,13 +1137,6 @@ class Polygon { } return Math.abs(area / 2); } - /** - * Get a convex hull of the point set using Melkman's algorithm - * (Reference: http://geomalgorithms.com/a12-_hull-3.html) - * @param pts a group of Pt - * @param sorted a boolean value to indicate if the group is pre-sorted by x position. Default is false. - * @returns a group of Pt that defines the convex hull polygon - */ static convexHull(pts, sorted = false) { if (pts.length < 3) return _errorLength(new Pt_1.Group(), 3); @@ -1925,17 +1144,14 @@ class Polygon { pts = pts.slice(); pts.sort((a, b) => a[0] - b[0]); } - // check if is on left of ray a-b let left = (a, b, c) => { return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]) > 0; }; - // double end queue let dq = []; let bot = pts.length - 2; let top = bot + 3; dq[bot] = pts[2]; dq[top] = pts[2]; - // first 3 pt as counter-clockwise triangle if (left(pts[0], pts[1], pts[2])) { dq[bot + 1] = pts[0]; dq[bot + 2] = pts[1]; @@ -1944,20 +1160,16 @@ class Polygon { dq[bot + 1] = pts[1]; dq[bot + 2] = pts[0]; } - // remaining pts for (let i = 3, len = pts.length; i < len; i++) { let pt = pts[i]; - // if inside the hull if (left(dq[bot], dq[bot + 1], pt) && left(dq[top - 1], dq[top], pt)) { continue; } - // rightmost tangent while (!left(dq[bot], dq[bot + 1], pt)) { bot += 1; } bot -= 1; dq[bot] = pt; - // leftmost tangent while (!left(dq[top - 1], dq[top], pt)) { top -= 1; } @@ -1970,26 +1182,6 @@ class Polygon { } return hull; } - /** - * Find intersection points of 2 polygons - * @param poly a Group representing a polygon - * @param linesOrRays an array of Groups representing lines - * @param sourceIsRay a boolean value to treat the line as a ray (infinite line). Default is `false`. - */ - static intersect2D(poly, linesOrRays, sourceIsRay = false) { - let groups = []; - for (let i = 0, len = linesOrRays.length; i < len; i++) { - let _ip = Line.intersectPolygon2D(linesOrRays[i], poly, sourceIsRay); - if (_ip) - groups.push(_ip); - } - return groups; - } - /** - * Given a point in the polygon as an origin, get an array of lines that connect all the remaining points to the origin point. - * @param pts a Group representing a polygon - * @param originIndex the origin point's index in the polygon - */ static network(pts, originIndex = 0) { let g = []; for (let i = 0, len = pts.length; i < len; i++) { @@ -1998,12 +1190,6 @@ class Polygon { } return g; } - /** - * Given a target Pt, find a Pt in a Group that's nearest to it. - * @param pts a Group of Pt - * @param pt Pt to check - * @returns an index in the pts indicating the nearest Pt, or -1 if none found - */ static nearestPt(pts, pt) { let _near = Number.MAX_VALUE; let _item = -1; @@ -2016,28 +1202,129 @@ class Polygon { } return _item; } - /** - * Get a bounding box for each polygon group, as well as a union bounding-box for all groups - * @param polys an array of Groups, or an array of Pt arrays - */ - static toRects(poly) { - let boxes = poly.map((g) => Num_1.Geom.boundingBox(g)); + static projectAxis(poly, unitAxis) { + let dot = unitAxis.dot(poly[0]); + let d = new Pt_1.Pt(dot, dot); + for (let n = 1, len = poly.length; n < len; n++) { + dot = unitAxis.dot(poly[n]); + d = new Pt_1.Pt(Math.min(dot, d[0]), Math.max(dot, d[1])); + } + return d; + } + static _axisOverlap(poly1, poly2, unitAxis) { + let pa = Polygon.projectAxis(poly1, unitAxis); + let pb = Polygon.projectAxis(poly2, unitAxis); + return (pa[0] < pb[0]) ? pb[0] - pa[1] : pa[0] - pb[1]; + } + static hasIntersectPoint(poly, pt) { + let c = false; + for (let i = 0, len = poly.length; i < len; i++) { + let ln = Polygon.lineAt(poly, i); + if (((ln[0][1] > pt[1]) != (ln[1][1] > pt[1])) && + (pt[0] < (ln[1][0] - ln[0][0]) * (pt[1] - ln[0][1]) / (ln[1][1] - ln[0][1]) + ln[0][0])) { + c = !c; + } + } + return c; + } + static hasIntersectCircle(poly, circle) { + let info = { + which: -1, + dist: 0, + normal: null, + edge: null, + vertex: null, + }; + let c = circle[0]; + let r = circle[1][0]; + let minDist = Number.MAX_SAFE_INTEGER; + for (let i = 0, len = poly.length; i < len; i++) { + let edge = Polygon.lineAt(poly, i); + let axis = new Pt_1.Pt(edge[0].y - edge[1].y, edge[1].x - edge[0].x).unit(); + let poly2 = new Pt_1.Group(c.$add(axis.$multiply(r)), c.$subtract(axis.$multiply(r))); + let dist = Polygon._axisOverlap(poly, poly2, axis); + if (dist > 0) { + return null; + } + else if (Math.abs(dist) < minDist) { + let check = Rectangle.withinBound(edge, Line.perpendicularFromPt(edge, c)) || Circle.intersectLine2D(circle, edge).length > 0; + if (check) { + info.edge = edge; + info.normal = axis; + minDist = Math.abs(dist); + info.which = i; + } + } + } + if (!info.edge) + return null; + let dir = c.$subtract(Polygon.centroid(poly)).dot(info.normal); + if (dir < 0) + info.normal.multiply(-1); + info.dist = minDist; + info.vertex = c; + return info; + } + static hasIntersectPolygon(poly1, poly2) { + let info = { + which: -1, + dist: 0, + normal: new Pt_1.Pt(), + edge: new Pt_1.Group(), + vertex: new Pt_1.Pt() + }; + let minDist = Number.MAX_SAFE_INTEGER; + for (let i = 0, plen = (poly1.length + poly2.length); i < plen; i++) { + let edge = (i < poly1.length) ? Polygon.lineAt(poly1, i) : Polygon.lineAt(poly2, i - poly1.length); + let axis = new Pt_1.Pt(edge[0].y - edge[1].y, edge[1].x - edge[0].x).unit(); + let dist = Polygon._axisOverlap(poly1, poly2, axis); + if (dist > 0) { + return null; + } + else if (Math.abs(dist) < minDist) { + info.edge = edge; + info.normal = axis; + minDist = Math.abs(dist); + info.which = (i < poly1.length) ? 0 : 1; + } + } + info.dist = minDist; + let b1 = (info.which === 0) ? poly2 : poly1; + let b2 = (info.which === 0) ? poly1 : poly2; + let c1 = Polygon.centroid(b1); + let c2 = Polygon.centroid(b2); + let dir = c1.$subtract(c2).dot(info.normal); + if (dir < 0) + info.normal.multiply(-1); + let smallest = Number.MAX_SAFE_INTEGER; + for (let i = 0, len = b1.length; i < len; i++) { + let d = info.normal.dot(b1[i].$subtract(c2)); + if (d < smallest) { + smallest = d; + info.vertex = b1[i]; + } + } + return info; + } + static intersectPolygon2D(poly1, poly2) { + let lp = Polygon.lines(poly1); + let g = []; + for (let i = 0, len = lp.length; i < len; i++) { + let ins = Line.intersectPolygon2D(lp[i], poly2, false); + if (ins) + g.push(ins); + } + return Util_1.Util.flatten(g, true); + } + static toRects(polys) { + let boxes = polys.map((g) => Num_1.Geom.boundingBox(g)); let merged = Util_1.Util.flatten(boxes, false); boxes.unshift(Num_1.Geom.boundingBox(merged)); return boxes; } } exports.Polygon = Polygon; -/** - * Curve class provides static functions to interpolate curves. A curve is usually represented as a Group of 3 control points. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Curve { - /** - * Get a precalculated coefficients per step - * @param steps number of steps - */ static getSteps(steps) { let ts = new Pt_1.Group(); for (let i = 0; i <= steps; i++) { @@ -2046,27 +1333,14 @@ class Curve { } return ts; } - /** - * Given an index for the starting position in a Pt group, get the control and/or end points of a curve segment - * @param pts a group of Pt - * @param index start index in `pts` array. Default is 0. - * @param copyStart an optional boolean value to indicate if the start index should be used twice. Default is false. - * @returns a group of 4 Pts - */ static controlPoints(pts, index = 0, copyStart = false) { if (index > pts.length - 1) return new Pt_1.Group(); let _index = (i) => (i < pts.length - 1) ? i : pts.length - 1; let p0 = pts[index]; index = (copyStart) ? index : index + 1; - // get points based on index return new Pt_1.Group(p0, pts[_index(index++)], pts[_index(index++)], pts[_index(index++)]); } - /** - * Calulcate weighted sum to get the interpolated points - * @param ctrls anchors - * @param params parameters - */ static _calcPt(ctrls, params) { let x = ctrls.reduce((a, c, i) => a + c.x * params[i], 0); let y = ctrls.reduce((a, c, i) => a + c.y * params[i], 0); @@ -2076,18 +1350,11 @@ class Curve { } return new Pt_1.Pt(x, y); } - /** - * Create a Catmull-Rom curve. Catmull-Rom is a kind of Cardinal curve with smooth-looking curve. - * @param pts a group of anchor Pt - * @param steps the number of line segments per curve. Defaults to 10 steps. - * @returns a curve as a group of interpolated Pt - */ static catmullRom(pts, steps = 10) { if (pts.length < 2) return new Pt_1.Group(); let ps = new Pt_1.Group(); let ts = Curve.getSteps(steps); - // use first point twice let c = Curve.controlPoints(pts, 0, true); for (let i = 0; i <= steps; i++) { ps.push(Curve.catmullRomStep(ts[i], c)); @@ -2104,36 +1371,15 @@ class Curve { } return ps; } - /** - * Interpolate to get a point on Catmull-Rom curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @return an interpolated Pt on the curve - */ static catmullRomStep(step, ctrls) { - /* - * Basis Matrix (http://mrl.nyu.edu/~perlin/courses/fall2002/hw/12.html) - * [-0.5, 1.5, -1.5, 0.5], - * [ 1 , -2.5, 2 ,-0.5], - * [-0.5, 0 , 0.5, 0 ], - * [ 0 , 1 , 0 , 0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-0.5, 1, -0.5, 0), new Pt_1.Pt(1.5, -2.5, 0, 1), new Pt_1.Pt(-1.5, 2, 0.5, 0), new Pt_1.Pt(0.5, -0.5, 0, 0)); return Curve._calcPt(ctrls, LinearAlgebra_1.Mat.multiply([step], m, true)[0]); } - /** - * Create a Cardinal spline curve - * @param pts a group of anchor Pt - * @param steps the number of line segments per curve. Defaults to 10 steps. - * @param tension optional value between 0 to 1 to specify a "tension". Default to 0.5 which is the tension for Catmull-Rom curve. - * @returns a curve as a group of interpolated Pt - */ static cardinal(pts, steps = 10, tension = 0.5) { if (pts.length < 2) return new Pt_1.Group(); let ps = new Pt_1.Group(); let ts = Curve.getSteps(steps); - // use first point twice let c = Curve.controlPoints(pts, 0, true); for (let i = 0; i <= steps; i++) { ps.push(Curve.cardinalStep(ts[i], c, tension)); @@ -2150,21 +1396,7 @@ class Curve { } return ps; } - /** - * Interpolate to get a point on Catmull-Rom curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @param tension optional value between 0 to 1 to specify a "tension". Default to 0.5 which is the tension for Catmull-Rom curve - * @return an interpolated Pt on the curve - */ static cardinalStep(step, ctrls, tension = 0.5) { - /* - * Basis Matrix (http://algorithmist.wordpress.com/2009/10/06/cardinal-splines-part-4/) - * [ -s 2-s s-2 s ] - * [ 2s s-3 3-2s -s ] - * [ -s 0 s 0 ] - * [ 0 1 0 0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-1, 2, -1, 0), new Pt_1.Pt(-1, 1, 0, 0), new Pt_1.Pt(1, -2, 1, 0), new Pt_1.Pt(1, -1, 0, 0)); let h = LinearAlgebra_1.Mat.multiply([step], m, true)[0].multiply(tension); let h2 = (2 * step[0] - 3 * step[1] + 1); @@ -2176,12 +1408,6 @@ class Curve { pt.z += h2 * ctrls[1].z + h3 * ctrls[2].z; return pt; } - /** - * Create a Bezier curve. In a cubic bezier curve, the first and 4th anchors are end-points, and 2nd and 3rd anchors are control-points. - * @param pts a group of anchor Pt - * @param steps the number of line segments per curve. Defaults to 10 steps. - * @returns a curve as a group of interpolated Pt - */ static bezier(pts, steps = 10) { if (pts.length < 4) return new Pt_1.Group(); @@ -2194,36 +1420,15 @@ class Curve { for (let i = 0; i <= steps; i++) { ps.push(Curve.bezierStep(ts[i], c)); } - // go to the next set of point, but assume current end pt is next start pt k += 3; } } return ps; } - /** - * Interpolate to get a point on a cubic Bezier curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @return an interpolated Pt on the curve - */ static bezierStep(step, ctrls) { - /* - * Bezier basis matrix - * [ -1, 3, -3, 1 ] - * [ 3, -6, 3, 0 ] - * [ -3, 3, 0, 0 ] - * [ 1, 0, 0, 0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-1, 3, -3, 1), new Pt_1.Pt(3, -6, 3, 0), new Pt_1.Pt(-3, 3, 0, 0), new Pt_1.Pt(1, 0, 0, 0)); return Curve._calcPt(ctrls, LinearAlgebra_1.Mat.multiply([step], m, true)[0]); } - /** - * Create a B-spline curve - * @param pts a group of anchor Pt - * @param steps the number of line segments per curve. Defaults to 10 steps. - * @param tension optional value between 0 to n to specify a "tension". Default is 1 which is the usual tension. - * @returns a curve as a group of interpolated Pt - */ static bspline(pts, steps = 10, tension = 1) { if (pts.length < 2) return new Pt_1.Group(); @@ -2248,38 +1453,11 @@ class Curve { } return ps; } - /** - * Interpolate to get a point on a B-spline curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @return an interpolated Pt on the curve - */ static bsplineStep(step, ctrls) { - /* - * Basis matrix: - * [ -1.0/6.0, 3.0/6.0, -3.0/6.0, 1.0/6.0 ], - * [ 3.0/6.0, -6.0/6.0, 3.0/6.0, 0.0 ], - * [ -3.0/6.0, 0.0, 3.0/6.0, 0.0 ], - * [ 1.0/6.0, 4.0/6.0, 1.0/6.0, 0.0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-0.16666666666666666, 0.5, -0.5, 0.16666666666666666), new Pt_1.Pt(0.5, -1, 0, 0.6666666666666666), new Pt_1.Pt(-0.5, 0.5, 0.5, 0.16666666666666666), new Pt_1.Pt(0.16666666666666666, 0, 0, 0)); return Curve._calcPt(ctrls, LinearAlgebra_1.Mat.multiply([step], m, true)[0]); } - /** - * Interpolate to get a point on a B-spline curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @param tension optional value between 0 to n to specify a "tension". Default to 1 which is the usual tension. - * @return an interpolated Pt on the curve - */ static bsplineTensionStep(step, ctrls, tension = 1) { - /* - * Basis matrix: - * [ -1/6a, 2 - 1.5a, 1.5a - 2, 1/6a ] - * [ 0.5a, 2a-3, 3-2.5a 0 ] - * [ -0.5a, 0, 0.5a, 0 ] - * [ 1/6a, 1 - 1/3a, 1/6a, 0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-0.16666666666666666, 0.5, -0.5, 0.16666666666666666), new Pt_1.Pt(-1.5, 2, 0, -0.3333333333333333), new Pt_1.Pt(1.5, -2.5, 0.5, 0.16666666666666666), new Pt_1.Pt(0.16666666666666666, 0, 0, 0)); let h = LinearAlgebra_1.Mat.multiply([step], m, true)[0].multiply(tension); let h2 = (2 * step[0] - 3 * step[1] + 1); @@ -2301,51 +1479,21 @@ exports.Curve = Curve; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = __webpack_require__(1); const Op_1 = __webpack_require__(2); const Pt_1 = __webpack_require__(0); -const LinearAlgebra_1 = __webpack_require__(4); -/** - * Num class provides various helper functions for basic numeric operations - */ +const LinearAlgebra_1 = __webpack_require__(5); class Num { - /** - * Check if two numbers are equal or almost equal within a threshold - * @param a number a - * @param b number b - * @param threshold a threshold within which the two numbers are considered equal - */ static equals(a, b, threshold = 0.00001) { return Math.abs(a - b) < threshold; } - /** - * Linear interpolation - * @param a start value - * @param b end value - * @param t usually a value between 0 to 1 - */ static lerp(a, b, t) { return (1 - t) * a + t * b; } - /** - * Clamp values between min and max - * @param val value to clamp - * @param min min value - * @param max max value - */ static clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } - /** - * Different from Num.clamp in that the value out-of-bound will be "looped back" to the other end. - * @param val value to bound - * @param min min value - * @param max max value - * @example `boundValue(361, 0, 360)` will return 1 - */ static boundValue(val, min, max) { let len = Math.abs(max - min); let a = val % len; @@ -2355,40 +1503,18 @@ class Num { a += len; return a; } - /** - * Check if a value is within - * @param p - * @param a - * @param b - */ static within(p, a, b) { return p >= Math.min(a, b) && p <= Math.max(a, b); } - /** - * Get a random number within a range - * @param a range value 1 - * @param b range value 2 - */ static randomRange(a, b = 0) { let r = (a > b) ? (a - b) : (b - a); return a + Math.random() * r; } - /** - * Normalize a value within a range - * @param n the value to normalize - * @param a range value 1 - * @param b range value 1 - */ static normalizeValue(n, a, b) { let min = Math.min(a, b); let max = Math.max(a, b); return (n - min) / (max - min); } - /** - * Sum a group of numeric arrays - * @param pts an array of numeric arrays - * @returns a array of sums - */ static sum(pts) { let c = new Pt_1.Pt(pts[0]); for (let i = 1, len = pts.length; i < len; i++) { @@ -2396,31 +1522,12 @@ class Num { } return c; } - /** - * Sum a group of numeric arrays - * @param pts an array of numeric arrays - * @returns a array of sums - */ static average(pts) { return Num.sum(pts).divide(pts.length); } - /** - * Given a value between 0 to 1, returns a value that cycles between 0 -> 1 -> 0 using sine method. - * @param t a value between 0 to 1 - * @return a value between 0 to 1 - */ static cycle(t) { return (Math.sin(Math.PI * 2 * t) + 1) / 2; } - /** - * Map a value from one range to another - * @param n a value in the first range - * @param currMin lower bound of the first range - * @param currMax upper bound of the first range - * @param targetMin lower bound of the second range - * @param targetMax upper bound of the second range - * @returns a remapped value in the second range - */ static mapToRange(n, currA, currB, targetA, targetB) { if (currA == currB) throw new Error("[currMin, currMax] must define a range that is not zero"); @@ -2430,58 +1537,27 @@ class Num { } } exports.Num = Num; -/** - * Geom class provides various helper functions for basic geometric operations - */ class Geom { - /** - * Bound an angle between 0 to 360 degrees - */ static boundAngle(angle) { return Num.boundValue(angle, 0, 360); } - /** - * Bound a radian between 0 to 2-PI - */ static boundRadian(angle) { return Num.boundValue(angle, 0, Util_1.Const.two_pi); } - /** - * Convert an angle in degree to radian - */ static toRadian(angle) { return angle * Util_1.Const.deg_to_rad; } - /** - * Convert an angle in radian to degree - */ static toDegree(radian) { return radian * Util_1.Const.rad_to_deg; } - /** - * Get a bounding box for a set of Pts - * @param pts a Group or an array of Pts - * @return a Group of two Pts, representing the top-left and bottom-right corners. - */ static boundingBox(pts) { let minPt = pts.reduce((a, p) => a.$min(p)); let maxPt = pts.reduce((a, p) => a.$max(p)); return new Pt_1.Group(minPt, maxPt); } - /** - * Get a centroid (the average middle point) for a set of Pts - * @param pts a Group or an array of Pts - * @return a centroid Pt - */ static centroid(pts) { return Num.average(pts); } - /** - * Given an anchor Pt, rebase all Pts in this group either to or from this anchor base. - * @param pts a Group or array of Pt - * @param ptOrIndex an index for the Pt array, or an external Pt - * @param direction "to" (subtract all Pt with this anchor base) or "from" (add all Pt from this anchor base) - */ static anchor(pts, ptOrIndex = 0, direction = "to") { let method = (direction == "to") ? "subtract" : "add"; for (let i = 0, len = pts.length; i < len; i++) { @@ -2494,13 +1570,6 @@ class Geom { } } } - /** - * Get an interpolated (or extrapolated) value between two Pts - * @param a first Pt - * @param b second Pt - * @param t a value between 0 to 1 to interpolate, or any other value to extrapolate - * @returns interpolated point as a new Pt - */ static interpolate(a, b, t = 0.5) { let len = Math.min(a.length, b.length); let d = Pt_1.Pt.make(len); @@ -2509,11 +1578,6 @@ class Geom { } return d; } - /** - * Find two Pt that are perpendicular to this Pt (2D) - * @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions - * @returns an array of two Pt that are perpendicular to this Pt - */ static perpendicular(pt, axis = Util_1.Const.xy) { let y = axis[1]; let x = axis[0]; @@ -2526,18 +1590,9 @@ class Geom { pb[y] = -p[x]; return new Pt_1.Group(pa, pb); } - /** - * Check if two Pts (vectors) are perpendicular to each other - */ static isPerpendicular(p1, p2) { return new Pt_1.Pt(p1).dot(p2) === 0; } - /** - * Check if a Pt is within the rectangular boundary defined by two Pts - * @param pt the Pt to check - * @param boundPt1 boundary Pt 1 - * @param boundPt2 boundary Pt 2 - */ static withinBound(pt, boundPt1, boundPt2) { for (let i = 0, len = Math.min(pt.length, boundPt1.length, boundPt2.length); i < len; i++) { if (!Num.within(pt[i], boundPt1[i], boundPt2[i])) @@ -2545,11 +1600,6 @@ class Geom { } return true; } - /** - * Sort the Pts so that their edges will form a non-overlapping polygon - * Ref: https://stackoverflow.com/questions/6989100/sort-points-in-clockwise-order - * @param pts an array of Pts - */ static sortEdges(pts) { let bounds = Geom.boundingBox(pts); let center = bounds[1].add(bounds[0]).divide(2); @@ -2567,24 +1617,15 @@ class Geom { return (da[1] > db[1]) ? 1 : -1; return (db[1] > da[1]) ? 1 : -1; } - // compute the cross product of vectors (center -> a) x (center -> b) let det = da.cross2D(db); if (det < 0) return 1; if (det > 0) return -1; - // points a and b are on the same line from the center - // check which point is closer to the center return (da[0] * da[0] + da[1] * da[1] > db[0] * db[0] + db[1] * db[1]) ? 1 : -1; }; return pts.sort(fn); } - /** - * Scale a Pt or a Group of Pts - * @param ps a Pt or a Group of Pts - * @param scale scale value - * @param anchor optional anchor point to scale from - */ static scale(ps, scale, anchor) { let pts = (!Array.isArray(ps)) ? [ps] : ps; let scs = (typeof scale == "number") ? Pt_1.Pt.make(pts[0].length, scale) : scale; @@ -2598,13 +1639,6 @@ class Geom { } return Geom; } - /** - * Rotate a Pt or a Group of Pts in 2D space - * @param ps a Pt or a Group of Pts - * @param angle rotate angle - * @param anchor optional anchor point to rotate from - * @param axis optional axis such as "yz" to define a 2D plane of rotation - */ static rotate2D(ps, angle, anchor, axis) { let pts = (!Array.isArray(ps)) ? [ps] : ps; let fn = (anchor) ? LinearAlgebra_1.Mat.rotateAt2DMatrix : LinearAlgebra_1.Mat.rotate2DMatrix; @@ -2618,13 +1652,6 @@ class Geom { } return Geom; } - /** - * Shear a Pt or a Group of Pts in 2D space - * @param ps a Pt or a Group of Pts - * @param scale shearing value which can be a number or an array of 2 numbers - * @param anchor optional anchor point to shear from - * @param axis optional axis such as "yz" to define a 2D plane of shearing - */ static shear2D(ps, scale, anchor, axis) { let pts = (!Array.isArray(ps)) ? [ps] : ps; let s = (typeof scale == "number") ? [scale, scale] : scale; @@ -2639,24 +1666,15 @@ class Geom { } return Geom; } - /** - * Reflect a Pt or a Group of Pts along a 2D line - * @param ps a Pt or a Group of Pts - * @param line a Group of 2 Pts that defines a line for reflection - * @param axis optional axis such as "yz" to define a 2D plane of reflection - */ static reflect2D(ps, line, axis) { let pts = (!Array.isArray(ps)) ? [ps] : ps; + let mat = LinearAlgebra_1.Mat.reflectAt2DMatrix(line[0], line[1]); for (let i = 0, len = pts.length; i < len; i++) { let p = (axis) ? pts[i].$take(axis) : pts[i]; - p.to(LinearAlgebra_1.Mat.transform2D(p, LinearAlgebra_1.Mat.reflectAt2DMatrix(line[0], line[1]))); + p.to(LinearAlgebra_1.Mat.transform2D(p, mat)); } return Geom; } - /** - * Generate a sine and cosine lookup table - * @returns an object with 2 tables (array of 360 values) and 2 functions to get sin/cos given a radian parameter. { sinTable:Float64Array, cosTable:Float64Array, sin:(rad)=>number, cos:(rad)=>number } - */ static cosTable() { let cos = new Float64Array(360); for (let i = 0; i < 360; i++) @@ -2664,10 +1682,6 @@ class Geom { let find = (rad) => cos[Math.floor(Geom.boundAngle(Geom.toDegree(rad)))]; return { table: cos, cos: find }; } - /** - * Generate a sine and cosine lookup table - * @returns an object with 2 tables (array of 360 values) and 2 functions to get sin/cos given a radian parameter. { sinTable:Float64Array, cosTable:Float64Array, sin:(rad)=>number, cos:(rad)=>number } - */ static sinTable() { let sin = new Float64Array(360); for (let i = 0; i < 360; i++) @@ -2677,175 +1691,72 @@ class Geom { } } exports.Geom = Geom; -/** - * Shaping provides various shaping/easing functions to interpolate a value non-linearly. - */ class Shaping { - /** - * Linear mapping - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static linear(t, c = 1) { return c * t; } - /** - * Quadratic in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static quadraticIn(t, c = 1) { return c * t * t; } - /** - * Quadratic out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static quadraticOut(t, c = 1) { return -c * t * (t - 2); } - /** - * Quadratic in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static quadraticInOut(t, c = 1) { let dt = t * 2; return (t < 0.5) ? c / 2 * t * t * 4 : -c / 2 * ((dt - 1) * (dt - 3) - 1); } - /** - * Cubic in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static cubicIn(t, c = 1) { return c * t * t * t; } - /** - * Cubic out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static cubicOut(t, c = 1) { let dt = t - 1; return c * (dt * dt * dt + 1); } - /** - * Cubic in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static cubicInOut(t, c = 1) { let dt = t * 2; return (t < 0.5) ? c / 2 * dt * dt * dt : c / 2 * ((dt - 2) * (dt - 2) * (dt - 2) + 2); } - /** - * Exponential ease In, adapted from Golan Levin's [polynomial shapers](http://www.flong.com/texts/code/shapers_poly/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p a value between 0 to 1 to control the curve. Default is 0.25. - */ static exponentialIn(t, c = 1, p = 0.25) { return c * Math.pow(t, 1 / p); } - /** - * Exponential ease out, adapted from Golan Levin's [polynomial shapers](http://www.flong.com/texts/code/shapers_poly/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p a value between 0 to 1 to control the curve. Default is 0.25. - */ static exponentialOut(t, c = 1, p = 0.25) { return c * Math.pow(t, p); } - /** - * Sinuous in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static sineIn(t, c = 1) { return -c * Math.cos(t * Util_1.Const.half_pi) + c; } - /** - * Sinuous out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static sineOut(t, c = 1) { return c * Math.sin(t * Util_1.Const.half_pi); } - /** - * Sinuous in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static sineInOut(t, c = 1) { return -c / 2 * (Math.cos(Math.PI * t) - 1); } - /** - * A faster way to approximate cosine ease in-out using Blinn-Wyvill Approximation. Adapated from Golan Levin's [polynomial shaping](http://www.flong.com/texts/code/shapers_poly/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static cosineApprox(t, c = 1) { let t2 = t * t; let t4 = t2 * t2; let t6 = t4 * t2; return c * (4 * t6 / 9 - 17 * t4 / 9 + 22 * t2 / 9); } - /** - * Circular in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static circularIn(t, c = 1) { return -c * (Math.sqrt(1 - t * t) - 1); } - /** - * Circular out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static circularOut(t, c = 1) { let dt = t - 1; return c * Math.sqrt(1 - dt * dt); } - /** - * Circular in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static circularInOut(t, c = 1) { let dt = t * 2; return (t < 0.5) ? -c / 2 * (Math.sqrt(1 - dt * dt) - 1) : c / 2 * (Math.sqrt(1 - (dt - 2) * (dt - 2)) + 1); } - /** - * Elastic in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p elastic parmeter between 0 to 1. The lower the number, the more elastic it will be. Default is 0.7. - */ static elasticIn(t, c = 1, p = 0.7) { let dt = t - 1; let s = (p / Util_1.Const.two_pi) * 1.5707963267948966; return c * (-Math.pow(2, 10 * dt) * Math.sin((dt - s) * Util_1.Const.two_pi / p)); } - /** - * Elastic out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p elastic parmeter between 0 to 1. The lower the number, the more elastic it will be. Default is 0.7. - */ static elasticOut(t, c = 1, p = 0.7) { let s = (p / Util_1.Const.two_pi) * 1.5707963267948966; return c * (Math.pow(2, -10 * t) * Math.sin((t - s) * Util_1.Const.two_pi / p)) + c; } - /** - * Elastic in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p elastic parmeter between 0 to 1. The lower the number, the more elastic it will be. Default is 0.6. - */ static elasticInOut(t, c = 1, p = 0.6) { let dt = t * 2; let s = (p / Util_1.Const.two_pi) * 1.5707963267948966; @@ -2858,19 +1769,9 @@ class Shaping { return c * (0.5 * (Math.pow(2, -10 * dt) * Math.sin((dt - s) * Util_1.Const.two_pi / p))) + c; } } - /** - * Bounce in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static bounceIn(t, c = 1) { return c - Shaping.bounceOut((1 - t), c); } - /** - * Bounce out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static bounceOut(t, c = 1) { if (t < (1 / 2.75)) { return c * (7.5625 * t * t); @@ -2888,30 +1789,13 @@ class Shaping { return c * (7.5625 * t * t + 0.984375); } } - /** - * Bounce in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static bounceInOut(t, c = 1) { return (t < 0.5) ? Shaping.bounceIn(t * 2, c) / 2 : Shaping.bounceOut(t * 2 - 1, c) / 2 + c / 2; } - /** - * Sigmoid curve changes its shape adapted from the input value, but always returns a value between 0 to 1. - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p the larger the value, the "steeper" the curve will be. Default is 10. - */ static sigmoid(t, c = 1, p = 10) { let d = p * (t - 0.5); return c / (1 + Math.exp(-d)); } - /** - * The Logistic Sigmoid is a useful curve. Adapted from Golan Levin's [shaping function](http://www.flong.com/texts/code/shapers_exp/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p a parameter between 0 to 1 to control the steepness of the curve. Higher is steeper. Default is 0.7. - */ static logSigmoid(t, c = 1, p = 0.7) { p = Math.max(Util_1.Const.epsilon, Math.min(1 - Util_1.Const.epsilon, p)); p = 1 / (1 - p); @@ -2920,12 +1804,6 @@ class Shaping { let C = 1 / (1 + Math.exp(-p)); return c * (A - B) / (C - B); } - /** - * An exponential seat curve. Adapted from Golan Levin's [shaping functions](http://www.flong.com/texts/code/shapers_exp/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p a parameter between 0 to 1 to control the steepness of the curve. Higher is steeper. Default is 0.5. - */ static seat(t, c = 1, p = 0.5) { if ((t < 0.5)) { return c * (Math.pow(2 * t, 1 - p)) / 2; @@ -2934,12 +1812,6 @@ class Shaping { return c * (1 - (Math.pow(2 * (1 - t), 1 - p)) / 2); } } - /** - * Quadratic bezier curve. Adapted from Golan Levin's [shaping functions](http://www.flong.com/texts/code/shapers_exp/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p1 a Pt object specifying the first control Pt, or a value specifying the control Pt's x position (its y position will default to 0.5). Default is `Pt(0.95, 0.95) - */ static quadraticBezier(t, c = 1, p = [0.05, 0.95]) { let a = (typeof p != "number") ? p[0] : p; let b = (typeof p != "number") ? p[1] : 0.5; @@ -2950,23 +1822,10 @@ class Shaping { let d = (Math.sqrt(a * a + om2a * t) - a) / om2a; return c * ((1 - 2 * b) * (d * d) + (2 * b) * d); } - /** - * Cubic bezier curve. This reuses the bezier functions in Curve class. - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p1` a Pt object specifying the first control Pt. Default is `Pt(0.1, 0.7). - * @parma p2` a Pt object specifying the second control Pt. Default is `Pt(0.9, 0.2). - */ static cubicBezier(t, c = 1, p1 = [0.1, 0.7], p2 = [0.9, 0.2]) { let curve = new Pt_1.Group(new Pt_1.Pt(0, 0), new Pt_1.Pt(p1), new Pt_1.Pt(p2), new Pt_1.Pt(1, 1)); return c * Op_1.Curve.bezierStep(new Pt_1.Pt(t * t * t, t * t, t, 1), Op_1.Curve.controlPoints(curve)).y; } - /** - * Give a Pt, draw a quadratic curve that will pass through that Pt as closely as possible. Adapted from Golan Levin's [shaping functions](http://www.flong.com/texts/code/shapers_poly/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p1` a Pt object specifying the Pt to pass through. Default is `Pt(0.2, 0.35) - */ static quadraticTarget(t, c = 1, p1 = [0.2, 0.35]) { let a = Math.min(1 - Util_1.Const.epsilon, Math.max(Util_1.Const.epsilon, p1[0])); let b = Math.min(1, Math.max(0, p1[1])); @@ -2975,23 +1834,9 @@ class Shaping { let y = A * (t * t) - B * t; return c * Math.min(1, Math.max(0, y)); } - /** - * Step function is a simple jump from 0 to 1 at a specific Pt in time - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p usually a value between 0 to 1, which specify the Pt to "jump". Default is 0.5 which is in the middle. - */ static cliff(t, c = 1, p = 0.5) { return (t > p) ? c : 0; } - /** - * Convert any shaping functions into a series of steps - * @parma fn the original shaping function - * @parma steps the number of steps - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma args optional paramters to pass to original function - */ static step(fn, steps, t, c, ...args) { let s = 1 / steps; let tt = Math.floor(t / s) * s; @@ -2999,36 +1844,15 @@ class Shaping { } } exports.Shaping = Shaping; -/** - * Range object keeps track of a Group of n-dimensional Pts to provide its minimum, maximum, and magnitude in each dimension. - * It also provides convenient functions such as mapping the Group to another range. - */ class Range { - /** - * Construct a Range instance for a Group of Pts, - * @param g a Group or an array of Pts - */ constructor(g) { this._dims = 0; this._source = Pt_1.Group.fromPtArray(g); this.calc(); } - /** - * Get this Range's maximum values per dimension - */ get max() { return this._max.clone(); } - /** - * Get this Range's minimum values per dimension - */ get min() { return this._min.clone(); } - /** - * Get this Range's magnitude in each dimension - */ get magnitude() { return this._mag.clone(); } - /** - * Go through the group and find its min and max values. - * Usually you don't need to call this function directly. - */ calc() { if (!this._source) return; @@ -3053,12 +1877,6 @@ class Range { this._mag = mag; return this; } - /** - * Map this Range to another range of values - * @param min target range's minimum value - * @param max target range's maximum value - * @param exclude Optional boolean array where `true` means excluding the conversion in that specific dimension. - */ mapTo(min, max, exclude) { let target = new Pt_1.Group(); for (let i = 0, len = this._source.length; i < len; i++) { @@ -3071,11 +1889,6 @@ class Range { } return target; } - /** - * Add more Pts to this Range and recalculate its min and max values - * @param g a Group or an array of Pts to append to this Range - * @param update Optional. Set the parameter to `false` if you want to append without immediately updating this Range's min and max values. Default is `true`. - */ append(g, update = true) { if (g[0].length !== this._dims) throw new Error(`Dimensions don't match. ${this._dims} dimensions in Range and ${g[0].length} provided in parameter. `); @@ -3084,10 +1897,6 @@ class Range { this.calc(); return this; } - /** - * Create a number of evenly spaced "ticks" that span this Range's min and max value. - * @param count number of subdivision. For example, 10 subdivision will return 11 tick values, which include first(min) and last(max) values. - */ ticks(count) { let g = new Pt_1.Group(); for (let i = 0; i <= count; i++) { @@ -3109,19 +1918,127 @@ exports.Range = Range; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) +Object.defineProperty(exports, "__esModule", { value: true }); +const Pt_1 = __webpack_require__(0); +class Bound extends Pt_1.Group { + constructor(...args) { + super(...args); + this._center = new Pt_1.Pt(); + this._size = new Pt_1.Pt(); + this._topLeft = new Pt_1.Pt(); + this._bottomRight = new Pt_1.Pt(); + this._inited = false; + this.init(); + } + static fromBoundingRect(rect) { + let b = new Bound(new Pt_1.Pt(rect.left || 0, rect.top || 0), new Pt_1.Pt(rect.right || 0, rect.bottom || 0)); + if (rect.width && rect.height) + b.size = new Pt_1.Pt(rect.width, rect.height); + return b; + } + static fromGroup(g) { + if (g.length < 2) + throw new Error("Cannot create a Bound from a group that has less than 2 Pt"); + return new Bound(g[0], g[g.length - 1]); + } + init() { + if (this.p1) { + this._size = this.p1.clone(); + this._inited = true; + } + if (this.p1 && this.p2) { + let a = this.p1; + let b = this.p2; + this.topLeft = a.$min(b); + this._bottomRight = a.$max(b); + this._updateSize(); + this._inited = true; + } + } + clone() { + return new Bound(this._topLeft.clone(), this._bottomRight.clone()); + } + _updateSize() { + this._size = this._bottomRight.$subtract(this._topLeft).abs(); + this._updateCenter(); + } + _updateCenter() { + this._center = this._size.$multiply(0.5).add(this._topLeft); + } + _updatePosFromTop() { + this._bottomRight = this._topLeft.$add(this._size); + this._updateCenter(); + } + _updatePosFromBottom() { + this._topLeft = this._bottomRight.$subtract(this._size); + this._updateCenter(); + } + _updatePosFromCenter() { + let half = this._size.$multiply(0.5); + this._topLeft = this._center.$subtract(half); + this._bottomRight = this._center.$add(half); + } + get size() { return new Pt_1.Pt(this._size); } + set size(p) { + this._size = new Pt_1.Pt(p); + this._updatePosFromTop(); + } + get center() { return new Pt_1.Pt(this._center); } + set center(p) { + this._center = new Pt_1.Pt(p); + this._updatePosFromCenter(); + } + get topLeft() { return new Pt_1.Pt(this._topLeft); } + set topLeft(p) { + this._topLeft = new Pt_1.Pt(p); + this[0] = this._topLeft; + this._updateSize(); + } + get bottomRight() { return new Pt_1.Pt(this._bottomRight); } + set bottomRight(p) { + this._bottomRight = new Pt_1.Pt(p); + this[1] = this._bottomRight; + this._updateSize(); + } + get width() { return (this._size.length > 0) ? this._size.x : 0; } + set width(w) { + this._size.x = w; + this._updatePosFromTop(); + } + get height() { return (this._size.length > 1) ? this._size.y : 0; } + set height(h) { + this._size.y = h; + this._updatePosFromTop(); + } + get depth() { return (this._size.length > 2) ? this._size.z : 0; } + set depth(d) { + this._size.z = d; + this._updatePosFromTop(); + } + get x() { return this.topLeft.x; } + get y() { return this.topLeft.y; } + get z() { return this.topLeft.z; } + get inited() { return this._inited; } + update() { + this._topLeft = this[0]; + this._bottomRight = this[1]; + this._updateSize(); + return this; + } +} +exports.Bound = Bound; + + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); const Op_1 = __webpack_require__(2); -/** - * Vec provides static function for vector operations. It's not yet optimized but good enough to use. - */ class Vec { - /** - * Add b to vector `a` - * @returns vector `a` - */ static add(a, b) { if (typeof b == "number") { for (let i = 0, len = a.length; i < len; i++) @@ -3133,10 +2050,6 @@ class Vec { } return a; } - /** - * Subtract `b` from vector `a` - * @returns vector `a` - */ static subtract(a, b) { if (typeof b == "number") { for (let i = 0, len = a.length; i < len; i++) @@ -3148,10 +2061,6 @@ class Vec { } return a; } - /** - * Multiply `b` with vector `a` - * @returns vector `a` - */ static multiply(a, b) { if (typeof b == "number") { for (let i = 0, len = a.length; i < len; i++) @@ -3166,10 +2075,6 @@ class Vec { } return a; } - /** - * Divide `a` over `b` - * @returns vector `a` - */ static divide(a, b) { if (typeof b == "number") { if (b === 0) @@ -3186,9 +2091,6 @@ class Vec { } return a; } - /** - * Dot product of `a` and `b` - */ static dot(a, b) { if (a.length != b.length) throw new Error("Array lengths don't match"); @@ -3198,65 +2100,33 @@ class Vec { } return d; } - /** - * 2D cross product of `a` and `b` - */ static cross2D(a, b) { return a[0] * b[1] - a[1] * b[0]; } - /** - * 3D Cross product of `a` and `b` - */ static cross(a, b) { return new Pt_1.Pt((a[1] * b[2] - a[2] * b[1]), (a[2] * b[0] - a[0] * b[2]), (a[0] * b[1] - a[1] * b[0])); } - /** - * Magnitude of `a` - */ static magnitude(a) { return Math.sqrt(Vec.dot(a, a)); } - /** - * Unit vector of `a`. If magnitude of `a` is already known, pass it in the second paramter to optimize calculation. - */ static unit(a, magnitude = undefined) { let m = (magnitude === undefined) ? Vec.magnitude(a) : magnitude; if (m === 0) throw new Error("Cannot calculate unit vector because magnitude is 0"); return Vec.divide(a, m); } - /** - * Set `a` to its absolute value in each dimension - * @returns vector `a` - */ static abs(a) { return Vec.map(a, Math.abs); } - /** - * Set `a` to its floor value in each dimension - * @returns vector `a` - */ static floor(a) { return Vec.map(a, Math.floor); } - /** - * Set `a` to its ceiling value in each dimension - * @returns vector `a` - */ static ceil(a) { return Vec.map(a, Math.ceil); } - /** - * Set `a` to its rounded value in each dimension - * @returns vector `a` - */ static round(a) { return Vec.map(a, Math.round); } - /** - * Find the max value within a vector's dimensions - * @returns an object with `value` and `index` that specifies the max value and its corresponding dimension. - */ static max(a) { let m = Number.MIN_VALUE; let index = 0; @@ -3267,10 +2137,6 @@ class Vec { } return { value: m, index: index }; } - /** - * Find the min value within a vector's dimensions - * @returns an object with `value` and `index` that specifies the min value and its corresponding dimension. - */ static min(a) { let m = Number.MAX_VALUE; let index = 0; @@ -3281,19 +2147,12 @@ class Vec { } return { value: m, index: index }; } - /** - * Sum all the dimensions' values - */ static sum(a) { let s = 0; for (let i = 0, len = a.length; i < len; i++) s += a[i]; return s; } - /** - * Given a mapping function, update `a`'s value in each dimension - * @returns vector `a` - */ static map(a, fn) { for (let i = 0, len = a.length; i < len; i++) { a[i] = fn(a[i], i, a); @@ -3302,16 +2161,7 @@ class Vec { } } exports.Vec = Vec; -/** - * Mat provides static function for matrix operations. It's not yet optimized but good enough to use. - */ class Mat { - /** - * Matrix additions. Matrices should have the same rows and columns. - * @param a a group of Pt - * @param b a scalar number, an array of numeric arrays, or a group of Pt - * @returns a group with the same rows and columns as a and b - */ static add(a, b) { if (typeof b != "number") { if (a[0].length != b[0].length) @@ -3326,14 +2176,6 @@ class Mat { } return g; } - /** - * Matrix multiplication - * @param a a Group of M Pts, each with K dimensions (M-rows, K-columns) - * @param b a scalar number, an array of numeric arrays, or a Group of K Pts, each with N dimensions (K-rows, N-columns) -- or if transposed is true, then N Pts with K dimensions - * @param transposed (Only applicable if it's not elementwise multiplication) If true, then a and b's columns should match (ie, each Pt should have the same dimensions). Default is `false`. - * @param elementwise if true, then the multiplication is done element-wise. Default is `false`. - * @returns If not elementwise, this will return a group with M Pt, each with N dimensions (M-rows, N-columns). - */ static multiply(a, b, transposed = false, elementwise = false) { let g = new Pt_1.Group(); if (typeof b != "number") { @@ -3367,12 +2209,6 @@ class Mat { } return g; } - /** - * Zip one slice of an array of Pt. Imagine the Pts are organized in rows, then this function will take the values in a specific column. - * @param g a group of Pt - * @param idx index to zip at - * @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead. - */ static zipSlice(g, index, defaultValue = false) { let z = []; for (let i = 0, len = g.length; i < len; i++) { @@ -3382,12 +2218,6 @@ class Mat { } return new Pt_1.Pt(z); } - /** - * Zip a group of Pt. eg, [[1,2],[3,4],[5,6]] => [[1,3,5],[2,4,6]] - * @param g a group of Pt - * @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead. - * @param useLongest If true, find the longest list of values in a Pt and use its length for zipping. Default is false, which uses the first item's length for zipping. - */ static zip(g, defaultValue = false, useLongest = false) { let ps = new Pt_1.Group(); let len = (useLongest) ? g.reduce((a, b) => Math.max(a, b.length), 0) : g[0].length; @@ -3396,79 +2226,44 @@ class Mat { } return ps; } - /** - * Same as `zip` function - */ static transpose(g, defaultValue = false, useLongest = false) { return Mat.zip(g, defaultValue, useLongest); } - /** - * Transform a 2D point given a 2x3 or 3x3 matrix - * @param pt a Pt to be transformed - * @param m 2x3 or 3x3 matrix - * @returns a new transformed Pt - */ static transform2D(pt, m) { let x = pt[0] * m[0][0] + pt[1] * m[1][0] + m[2][0]; let y = pt[0] * m[0][1] + pt[1] * m[1][1] + m[2][1]; return new Pt_1.Pt(x, y); } - /** - * Get a scale matrix for use in `transform2D` - */ static scale2DMatrix(x, y) { return new Pt_1.Group(new Pt_1.Pt(x, 0, 0), new Pt_1.Pt(0, y, 0), new Pt_1.Pt(0, 0, 1)); } - /** - * Get a rotate matrix for use in `transform2D` - */ static rotate2DMatrix(cosA, sinA) { return new Pt_1.Group(new Pt_1.Pt(cosA, sinA, 0), new Pt_1.Pt(-sinA, cosA, 0), new Pt_1.Pt(0, 0, 1)); } - /** - * Get a shear matrix for use in `transform2D` - */ static shear2DMatrix(tanX, tanY) { return new Pt_1.Group(new Pt_1.Pt(1, tanX, 0), new Pt_1.Pt(tanY, 1, 0), new Pt_1.Pt(0, 0, 1)); } - /** - * Get a translate matrix for use in `transform2D` - */ static translate2DMatrix(x, y) { return new Pt_1.Group(new Pt_1.Pt(1, 0, 0), new Pt_1.Pt(0, 1, 0), new Pt_1.Pt(x, y, 1)); } - /** - * Get a matrix to scale a point from an origin point. For use in `transform2D` - */ static scaleAt2DMatrix(sx, sy, at) { let m = Mat.scale2DMatrix(sx, sy); m[2][0] = -at[0] * sx + at[0]; m[2][1] = -at[1] * sy + at[1]; return m; } - /** - * Get a matrix to rotate a point from an origin point. For use in `transform2D` - */ static rotateAt2DMatrix(cosA, sinA, at) { let m = Mat.rotate2DMatrix(cosA, sinA); m[2][0] = at[0] * (1 - cosA) + at[1] * sinA; m[2][1] = at[1] * (1 - cosA) - at[0] * sinA; return m; } - /** - * Get a matrix to shear a point from an origin point. For use in `transform2D` - */ static shearAt2DMatrix(tanX, tanY, at) { let m = Mat.shear2DMatrix(tanX, tanY); m[2][0] = -at[1] * tanY; m[2][1] = -at[0] * tanX; return m; } - /** - * Get a matrix to reflect a point along a line. For use in `transform2D` - * @param p1 first end point to define the reflection line - * @param p1 second end point to define the reflection line - */ static reflectAt2DMatrix(p1, p2) { let intercept = Op_1.Line.intercept(p1, p2); if (intercept == undefined) { @@ -3494,188 +2289,19 @@ class Mat { exports.Mat = Mat; -/***/ }), -/* 5 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) -Object.defineProperty(exports, "__esModule", { value: true }); -const Pt_1 = __webpack_require__(0); -/** - * Bound is a subclass of Group that represents a rectangular boundary. - * It includes some convenient properties such as `x`, `y`, bottomRight`, `center`, and `size`. - */ -class Bound extends Pt_1.Group { - /** - * Create a Bound. This is similar to the Group constructor. - * @param args a list of Pt as parameters - */ - constructor(...args) { - super(...args); - this._center = new Pt_1.Pt(); - this._size = new Pt_1.Pt(); - this._topLeft = new Pt_1.Pt(); - this._bottomRight = new Pt_1.Pt(); - this._inited = false; - this.init(); - } - /** - * Create a Bound from a [ClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) object. - * @param rect an object has top/left/bottom/right/width/height properties - * @returns a Bound object - */ - static fromBoundingRect(rect) { - let b = new Bound(new Pt_1.Pt(rect.left || 0, rect.top || 0), new Pt_1.Pt(rect.right || 0, rect.bottom || 0)); - if (rect.width && rect.height) - b.size = new Pt_1.Pt(rect.width, rect.height); - return b; - } - static fromGroup(g) { - if (g.length < 2) - throw new Error("Cannot create a Bound from a group that has less than 2 Pt"); - return new Bound(g[0], g[g.length - 1]); - } - /** - * Initiate the bound's properties. - */ - init() { - if (this.p1) { - this._size = this.p1.clone(); - this._inited = true; - } - if (this.p1 && this.p2) { - let a = this.p1; - let b = this.p2; - this.topLeft = a.$min(b); - this._bottomRight = a.$max(b); - this._updateSize(); - this._inited = true; - } - } - /** - * Clone this bound and return a new one - */ - clone() { - return new Bound(this._topLeft.clone(), this._bottomRight.clone()); - } - /** - * Recalculte size and center - */ - _updateSize() { - this._size = this._bottomRight.$subtract(this._topLeft).abs(); - this._updateCenter(); - } - /** - * Recalculate center - */ - _updateCenter() { - this._center = this._size.$multiply(0.5).add(this._topLeft); - } - /** - * Recalculate based on top-left position and size - */ - _updatePosFromTop() { - this._bottomRight = this._topLeft.$add(this._size); - this._updateCenter(); - } - /** - * Recalculate based on bottom-right position and size - */ - _updatePosFromBottom() { - this._topLeft = this._bottomRight.$subtract(this._size); - this._updateCenter(); - } - /** - * Recalculate based on center position and size - */ - _updatePosFromCenter() { - let half = this._size.$multiply(0.5); - this._topLeft = this._center.$subtract(half); - this._bottomRight = this._center.$add(half); - } - get size() { return new Pt_1.Pt(this._size); } - set size(p) { - this._size = new Pt_1.Pt(p); - this._updatePosFromTop(); - } - get center() { return new Pt_1.Pt(this._center); } - set center(p) { - this._center = new Pt_1.Pt(p); - this._updatePosFromCenter(); - } - get topLeft() { return new Pt_1.Pt(this._topLeft); } - set topLeft(p) { - this._topLeft = new Pt_1.Pt(p); - this[0] = this._topLeft; - this._updateSize(); - } - get bottomRight() { return new Pt_1.Pt(this._bottomRight); } - set bottomRight(p) { - this._bottomRight = new Pt_1.Pt(p); - this[1] = this._bottomRight; - this._updateSize(); - } - get width() { return (this._size.length > 0) ? this._size.x : 0; } - set width(w) { - this._size.x = w; - this._updatePosFromTop(); - } - get height() { return (this._size.length > 1) ? this._size.y : 0; } - set height(h) { - this._size.y = h; - this._updatePosFromTop(); - } - get depth() { return (this._size.length > 2) ? this._size.z : 0; } - set depth(d) { - this._size.z = d; - this._updatePosFromTop(); - } - get x() { return this.topLeft.x; } - get y() { return this.topLeft.y; } - get z() { return this.topLeft.z; } - get inited() { return this._inited; } - /** - * If the Group elements are changed, call this function to update the Bound's properties. - * It's preferable to change the topLeft/bottomRight etc properties instead of changing the Group array directly. - */ - update() { - this._topLeft = this[0]; - this._bottomRight = this[1]; - this._updateSize(); - return this; - } -} -exports.Bound = Bound; - - /***/ }), /* 6 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = __webpack_require__(1); -/** -* Form is an abstract class that represents a form that's used in a Space for expressions. -*/ class Form { constructor() { this._ready = false; } - /** - * get whether the Form has received the Space's rendering context - */ get ready() { return this._ready; } - /** - * Check number of items in a Group against a required number - * @param pts - */ static _checkSize(pts, required = 2) { if (pts.length < required) { Util_1.Util.warn("Requires 2 or more Pts in this Group."); @@ -3685,10 +2311,6 @@ class Form { } } exports.Form = Form; -/** -* VisualForm is an abstract class that represents a form that can be used to express Pts visually. -* For example, CanvasForm is an implementation of VisualForm that draws on CanvasSpace which represents a html canvas. -*/ class VisualForm extends Form { constructor() { super(...arguments); @@ -3709,42 +2331,20 @@ class VisualForm extends Form { } return this; } - /** - * Set fill color (not implemented) - */ fill(c) { return this; } - /** - * Set current fill style and without stroke. - * @example `form.fillOnly("#F90")`, `form.fillOnly("rgba(0,0,0,.5")` - * @param c fill color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle)) - */ fillOnly(c) { this.stroke(false); return this.fill(c); } - /** - * Set stroke style (not implemented) - */ stroke(c, width, linejoin, linecap) { return this; } - /** - * Set current stroke style and without fill. - * @example `form.strokeOnly("#F90")`, `form.strokeOnly("#000", 0.5, 'round', 'square')` - * @param c stroke color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle) - */ strokeOnly(c, width, linejoin, linecap) { this.fill(false); return this.stroke(c, width, linejoin, linecap); } - /** - * Draw multiple points at once - * @param pts an array of Pt or an array of number arrays - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - */ points(pts, radius, shape) { if (!pts) return; @@ -3753,56 +2353,24 @@ class VisualForm extends Form { } return this; } - /** - * Draw multiple circles at once - * @param groups an array of Groups that defines multiple circles - */ circles(groups) { return this._multiple(groups, "circle"); } - /** - * Draw multiple squares at once - * @param groups an array of Groups that defines multiple circles - */ squares(groups) { return this._multiple(groups, "square"); } - /** - * Draw multiple lines at once - * @param groups An array of Groups of Pts - */ lines(groups) { return this._multiple(groups, "line"); } - /** - * Draw multiple polygons at once - * @param groups An array of Groups of Pts - */ polygons(groups) { return this._multiple(groups, "polygon"); } - /** - * Draw multiple rectangles at once - * @param groups An array of Groups of Pts - */ rects(groups) { return this._multiple(groups, "rect"); } } exports.VisualForm = VisualForm; -/** -* Font class lets you create a specific font style with properties for its size and style -*/ class Font { - /** - * Create a font style - * @param size font size. Defaults is 12px. - * @param face Optional font-family, use css-like string such as "Helvetica" or "Helvetica, sans-serif". Default is "sans-serif". - * @param weight Optional font weight such as "bold". Default is "" (none). - * @param style Optional font style such as "italic". Default is "" (none). - * @param lineHeight Optional line height. Default is 1.5. - * @example `new Font(12, "Frutiger, sans-serif", "bold", "underline", 1.5)` - */ constructor(size = 12, face = "sans-serif", weight = "", style = "", lineHeight = 1.5) { this.size = size; this.face = face; @@ -3810,13 +2378,7 @@ class Font { this.weight = weight; this.lineHeight = lineHeight; } - /** - * Get a string representing the font style, in css-like string such as "italic bold 12px/1.5 sans-serif" - */ get value() { return `${this.style} ${this.weight} ${this.size}px/${this.lineHeight} ${this.face}`; } - /** - * Get a string representing the font style, in css-like string such as "italic bold 12px/1.5 sans-serif" - */ toString() { return this.value; } } exports.Font = Font; @@ -3828,16 +2390,10 @@ exports.Font = Font; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); -const Bound_1 = __webpack_require__(5); +const Bound_1 = __webpack_require__(4); const Pt_1 = __webpack_require__(0); const UI_1 = __webpack_require__(12); -/** -* Space is an abstract class that represents a general context for expressing Pts. -* See [Space guide](../../guide/Space-0500.html) for details. -*/ class Space { constructor() { this.id = "space"; @@ -3852,23 +2408,10 @@ class Space { this._isReady = false; this._playing = false; } - /** - * Set whether the rendering should be repainted on each frame - * @param b a boolean value to set whether to repaint each frame - */ refresh(b) { this._refresh = b; return this; } - /** - * Add an IPlayer to this space. An IPlayer can define the following callback functions: - * - `animate( time, ftime, space )` - * - `start(bound, space)` - * - `resize( size, event )` - * - `action( type, x, y, event )` - * Subclasses of Space may define other callback functions. - * @param player an IPlayer object with animate function, or simply a function(time, ftime){} - */ add(p) { let player = (typeof p == "function") ? { animate: p } : p; let k = this.playerCount++; @@ -3877,31 +2420,18 @@ class Space { player.animateID = pid; if (player.resize && this.bound.inited) player.resize(this.bound); - // if _refresh is not set, set it to true if (this._refresh === undefined) this._refresh = true; return this; } - /** - * Remove a player from this Space - * @param player an IPlayer that has an `animateID` property - */ remove(player) { delete this.players[player.animateID]; return this; } - /** - * Remove all players from this Space - */ removeAll() { this.players = {}; return this; } - /** - * Main play loop. This implements window.requestAnimationFrame and calls it recursively. - * Override this `play()` function to implemenet your own animation loop. - * @param time current time - */ play(time = 0) { this._animID = requestAnimationFrame(this.play.bind(this)); if (this._pause) @@ -3918,150 +2448,77 @@ class Space { } return this; } - /** - * Replay the animation after `stop()`. This resets the end-time counter. - * You may also use `pause()` and `resume()` for temporary pause. - */ replay() { this._time.end = -1; this.play(); } - /** - * Main animate function. This calls all the items to perform - * @param time current time - */ playItems(time) { this._playing = true; - // clear before draw if refresh is true if (this._refresh) this.clear(); - // animate all players if (this._isReady) { for (let k in this.players) { if (this.players[k].animate) this.players[k].animate(time, this._time.diff, this); } } - // stop if time ended if (this._time.end >= 0 && time > this._time.end) { cancelAnimationFrame(this._animID); this._playing = false; } } - /** - * Pause the animation - * @param toggle a boolean value to set if this function call should be a toggle (between pause and resume) - */ pause(toggle = false) { this._pause = (toggle) ? !this._pause : true; return this; } - /** - * Resume the pause animation - */ resume() { this._pause = false; return this; } - /** - * Specify when the animation should stop: immediately, after a time period, or never stops. - * @param t a value in millisecond to specify a time period to play before stopping, or `-1` to play forever, or `0` to end immediately. Default is 0 which will stop the animation immediately. - */ stop(t = 0) { this._time.end = t; return this; } - /** - * Play animation loop, and then stop after `duration` time has passed. - * @param duration a value in millisecond to specify a time period to play before stopping, or `-1` to play forever - */ playOnce(duration = 5000) { this.play(); this.stop(duration); return this; } - /** - * Custom rendering - * @param context rendering context - */ render(context) { if (this._renderFunc) this._renderFunc(context, this); return this; } - /** - * Set a custom rendering `function(graphics_context, canvas_space)` if needed - */ set customRendering(f) { this._renderFunc = f; } get customRendering() { return this._renderFunc; } - /** - * Get a boolean to indicate whether the animation is playing - */ get isPlaying() { return this._playing; } - /** - * Get this space's bounding box - */ get outerBound() { return this.bound.clone(); } - /** - * The bounding box of the canvas - */ get innerBound() { return new Bound_1.Bound(Pt_1.Pt.make(this.size.length, 0), this.size.clone()); } - /** - * Get the size of this bounding box as a Pt - */ get size() { return this.bound.size.clone(); } - /** - * Get the size of this bounding box as a Pt - */ get center() { return this.size.divide(2); } - /** - * Get width of canvas - */ get width() { return this.bound.width; } - /** - * Get height of canvas - */ get height() { return this.bound.height; } } exports.Space = Space; class MultiTouchSpace extends Space { constructor() { super(...arguments); - // track mouse dragging this._pressed = false; this._dragged = false; this._hasMouse = false; this._hasTouch = false; } - /** - * Get the mouse or touch pointer that stores the last action - */ get pointer() { let p = this._pointer.clone(); p.id = this._pointer.id; return p; } - /** - * Bind event listener in canvas element. You can also use `bindMouse` or `bindTouch` to bind mouse or touch events conveniently. - * @param evt an event string such as "mousedown" - * @param callback callback function for this event - */ bindCanvas(evt, callback) { this._canvas.addEventListener(evt, callback); } - /** - * Unbind a callback from the event listener - * @param evt an event string such as "mousedown" - * @param callback callback function to unbind - */ unbindCanvas(evt, callback) { this._canvas.removeEventListener(evt, callback); } - /** - * A convenient method to bind (or unbind) all mouse events in canvas element. All "players" added to this space that implements an `action` callback property will receive mouse event callbacks. The types of mouse actions are defined by UIPointerActions constants: "up", "down", "move", "drag", "drop", "over", and "out". See `Space`'s `add()` function for more details. - * @param _bind a boolean value to bind mouse events if set to `true`. If `false`, all mouse events will be unbound. Default is true. - * @see Space`'s [`add`](./_space_.space.html#add) function - */ bindMouse(_bind = true) { if (_bind) { this.bindCanvas("mousedown", this._mouseDown.bind(this)); @@ -4081,11 +2538,6 @@ class MultiTouchSpace extends Space { } return this; } - /** - * A convenient method to bind (or unbind) all touch events in canvas element. All "players" added to this space that implements an `action` callback property will receive mouse event callbacks. The types of mouse actions are: "up", "down", "move", "drag", "drop", "over", and "out". - * @param _bind a boolean value to bind touch events if set to `true`. If `false`, all mouse events will be unbound. Default is true. - * @see Space`'s [`add`](./_space_.space.html#add) function - */ bindTouch(_bind = true) { if (_bind) { this.bindCanvas("touchstart", this._mouseDown.bind(this)); @@ -4103,12 +2555,6 @@ class MultiTouchSpace extends Space { } return this; } - /** - * A convenient method to convert the touch points in a touch event to an array of `Pt`. - * @param evt a touch event which contains touches, changedTouches, and targetTouches list - * @param which a string to select a touches list: "touches", "changedTouches", or "targetTouches". Default is "touches" - * @return an array of Pt, whose origin position (0,0) is offset to the top-left of this space - */ touchesToPoints(evt, which = "touches") { if (!evt || !evt[which]) return []; @@ -4119,11 +2565,6 @@ class MultiTouchSpace extends Space { } return ts; } - /** - * Go through all the `players` and call its `action` callback function - * @param type an UIPointerActions constant or string: "up", "down", "move", "drag", "drop", "over", and "out" - * @param evt mouse or touch event - */ _mouseAction(type, evt) { let px = 0, py = 0; if (evt instanceof MouseEvent) { @@ -4155,19 +2596,11 @@ class MultiTouchSpace extends Space { this._pointer.id = type; } } - /** - * MouseDown handler - * @param evt - */ _mouseDown(evt) { this._mouseAction(UI_1.UIPointerActions.down, evt); this._pressed = true; return false; } - /** - * MouseUp handler - * @param evt - */ _mouseUp(evt) { this._mouseAction(UI_1.UIPointerActions.up, evt); if (this._dragged) @@ -4176,10 +2609,6 @@ class MultiTouchSpace extends Space { this._dragged = false; return false; } - /** - * MouseMove handler - * @param evt - */ _mouseMove(evt) { this._mouseAction(UI_1.UIPointerActions.move, evt); if (this._pressed) { @@ -4188,18 +2617,10 @@ class MultiTouchSpace extends Space { } return false; } - /** - * MouseOver handler - * @param evt - */ _mouseOver(evt) { this._mouseAction(UI_1.UIPointerActions.over, evt); return false; } - /** - * MouseOut handler - * @param evt - */ _mouseOut(evt) { this._mouseAction(UI_1.UIPointerActions.out, evt); if (this._dragged) @@ -4207,10 +2628,6 @@ class MultiTouchSpace extends Space { this._dragged = false; return false; } - /** - * TouchMove handler - * @param evt - */ _touchMove(evt) { this._mouseMove(evt); evt.preventDefault(); @@ -4226,31 +2643,14 @@ exports.MultiTouchSpace = MultiTouchSpace; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); -/** Various functions to support typography */ class Typography { - /** - * Create a heuristic text width estimate function. It will be less accurate but faster. - * @param fn a reference function that can measure text width accurately - * @param samples a list of string samples. Default is ["M", "n", "."] - * @param distribution a list of the samples' probability distribution. Default is [0.06, 0.8, 0.14]. - * @return a function that can estimate text width - */ static textWidthEstimator(fn, samples = ["M", "n", "."], distribution = [0.06, 0.8, 0.14]) { let m = samples.map(fn); let avg = new Pt_1.Pt(distribution).dot(m); return (str) => str.length * avg; } - /** - * Truncate text to fit width - * @param fn a function that can measure text width - * @param str text to truncate - * @param width width to fit - * @param tail text to indicate overflow such as "...". Default is empty "". - */ static truncate(fn, str, width, tail = "") { let trim = Math.floor(str.length * Math.min(1, width / fn(str))); if (trim < str.length) { @@ -4261,12 +2661,6 @@ class Typography { return [str, str.length]; } } - /** - * Get a function to scale font size proportionally to text box size changes. - * @param box Initial box as a Group - * @param ratio font-size change ratio. Default is 1. - * @returns a function where input parameter is a new box, and returns the new font size value - */ static fontSizeToBox(box, ratio = 1, byHeight = true) { let i = byHeight ? 1 : 0; let h = (box[1][i] - box[0][i]); @@ -4276,13 +2670,6 @@ class Typography { return f * nh; }; } - /** - * Get a function to scale font size based on a threshold value - * @param defaultSize default font size to base on - * @param threshold threshold value - * @param direction if negative, get a font size <= defaultSize; if positive, get a font size >= defaultSize; Default is 0 which will scale font without min or max limits. - * @returns a function where input parameter is the default font size and a value to compare with threshold, and returns new font size value - */ static fontSizeToThreshold(threshold, direction = 0) { return function (defaultSize, val) { let d = defaultSize * val / threshold; @@ -4303,24 +2690,13 @@ exports.Typography = Typography; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Space_1 = __webpack_require__(7); const Form_1 = __webpack_require__(6); -const Bound_1 = __webpack_require__(5); +const Bound_1 = __webpack_require__(4); const Util_1 = __webpack_require__(1); const Pt_1 = __webpack_require__(0); -/** - * A Space for DOM elements - */ class DOMSpace extends Space_1.MultiTouchSpace { - /** - * Create a DOMSpace which represents a Space for DOM elements - * @param elem Specify an element by its "id" attribute as string, or by the element object itself. Use css to customize its appearance if needed. - * @param callback an optional callback `function(boundingBox, spaceElement)` to be called when canvas is appended and ready. Alternatively, a "ready" event will also be fired from the element when it's appended, which can be traced with `spaceInstance.canvas.addEventListener("ready")` - * @example `new DOMSpace( "#myElementID" )` - */ constructor(elem, callback) { super(); this.id = "domspace"; @@ -4330,7 +2706,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { var _selector = null; var _existed = false; this.id = "pts"; - // check element or element id string if (elem instanceof Element) { _selector = elem; this.id = "pts_existing_space"; @@ -4340,7 +2715,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { _existed = true; this.id = elem.substr(1); } - // if selector is not defined, create a canvas if (!_selector) { this._container = DOMSpace.createElement("div", "pts_container"); this._canvas = DOMSpace.createElement("div", "pts_element"); @@ -4352,15 +2726,8 @@ class DOMSpace extends Space_1.MultiTouchSpace { this._canvas = _selector; this._container = _selector.parentElement; } - // no mutation observer, so we set a timeout for ready event setTimeout(this._ready.bind(this, callback), 50); } - /** - * Helper function to create a DOM element - * @param elem element tag name - * @param id element id attribute - * @param appendTo Optional, if specified, the created element will be appended to this element - */ static createElement(elem = "div", id, appendTo) { let d = document.createElement(elem); if (id) @@ -4369,10 +2736,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { appendTo.appendChild(d); return d; } - /** - * Handle callbacks after element is mounted in DOM - * @param callback - */ _ready(callback) { if (!this._container) throw new Error(`Cannot initiate #${this.id} element`); @@ -4387,17 +2750,10 @@ class DOMSpace extends Space_1.MultiTouchSpace { } } this._pointer = this.center; - this.refresh(false); // No need to clear and redraw for every frame in DOM + this.refresh(false); if (callback) callback(this.bound, this._canvas); } - /** - * Set up various options for DOMSpace. The `opt` parameter is an object with the following fields. This is usually set during instantiation, eg `new DOMSpace(...).setup( { opt } )` - * @param opt an object with optional settings, as follows. - * @param opt.bgcolor a hex or rgba string to set initial background color of the canvas, or use `false` or "transparent" to set a transparent background. You may also change it later with `clear()` - * @param opt.resize a boolean to set whether `` size should auto resize to match its container's size. You can also set it manually with `autoSize()` - * @example `space.setup({ bgcolor: "#f00", resize: true })` - */ setup(opt) { if (opt.bgcolor) { this._bgcolor = opt.bgcolor; @@ -4405,16 +2761,9 @@ class DOMSpace extends Space_1.MultiTouchSpace { this.autoResize = (opt.resize != undefined) ? opt.resize : false; return this; } - /** - * Not implemented. See SVGSpace and HTMLSpace for implementation - */ getForm() { return null; } - /** - * Set whether the canvas element should resize when its container is resized. - * @param auto a boolean value indicating if auto size is set - */ set autoResize(auto) { this._autoResize = auto; if (auto) { @@ -4427,11 +2776,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { } } get autoResize() { return this._autoResize; } - /** - * This overrides Space's `resize` function. It's used as a callback function for window's resize event and not usually called directly. You can keep track of resize events with `resize: (bound ,evt)` callback in your player objects (See `Space`'s `add()` function). - * @param b a Bound object to resize to - * @param evt Optionally pass a resize event - */ resize(b, evt) { this.bound = b; this.styles({ width: `${b.width}px`, height: `${b.height}px` }, true); @@ -4444,10 +2788,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { } return this; } - /** - * Window resize handling - * @param evt - */ _resizeHandler(evt) { let b = Bound_1.Bound.fromBoundingRect(this._container.getBoundingClientRect()); if (this._autoResize) { @@ -4458,59 +2798,30 @@ class DOMSpace extends Space_1.MultiTouchSpace { } this.resize(b, evt); } - /** - * Get this DOM element - */ get element() { return this._canvas; } - /** - * Get the parent DOM element that contains this DOM element - */ get parent() { return this._container; } - /** - * A property to indicate if the Space is ready - */ get ready() { return this._isReady; } - /** - * Clear the element's contents, and ptionally set a new backgrounc color. Overrides Space's `clear` function. - * @param bg Optionally specify a custom background color in hex or rgba string, or "transparent". If not defined, it will use its `bgcolor` property as background color to clear the canvas. - */ clear(bg) { if (bg) this.background = bg; this._canvas.innerHTML = ""; return this; } - /** - * Set a background color on the container element - @param bg background color as hex or rgba string - */ set background(bg) { this._bgcolor = bg; this._container.style.backgroundColor = this._bgcolor; } get background() { return this._bgcolor; } - /** - * Add or update a style definition, and optionally update that style in the Element - * @param key style name - * @param val style value - * @param update a boolean to update the element's style immediately if set to `true`. Default is `false`. - */ style(key, val, update = false) { this._css[key] = val; if (update) this._canvas.style[key] = val; return this; } - /** - * Add of update a list of style definitions, and optionally update those styles in the Element - * @param styles a key-value objects of style definitions - * @param update a boolean to update the element's style immediately if set to `true`. Default is `false`. - * @return this - */ styles(styles, update = false) { for (let k in styles) { if (styles.hasOwnProperty(k)) @@ -4518,12 +2829,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { } return this; } - /** - * A static helper function to add or update Element attributes - * @param elem Element to update - * @param data an object with key-value pairs - * @returns this DOM element - */ static setAttr(elem, data) { for (let k in data) { if (data.hasOwnProperty(k)) { @@ -4532,12 +2837,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { } return elem; } - /** - * A static helper function to compose an inline style string from a object of styles - * @param elem Element to update - * @param data an object with key-value pairs - * @exmaple DOMSpace.getInlineStyles( {width: "100px", "font-size": "10px"} ); // returns "width: 100px; font-size: 10px" - */ static getInlineStyles(data) { let str = ""; for (let k in data) { @@ -4550,24 +2849,10 @@ class DOMSpace extends Space_1.MultiTouchSpace { } } exports.DOMSpace = DOMSpace; -/** - * HTMLSpace. Note that this is currently experimental and may change in future. - */ class HTMLSpace extends DOMSpace { - /** - * Get a new `HTMLForm` for drawing - * @see `HTMLForm` - */ getForm() { return new HTMLForm(this); } - /** - * A static function to add a DOM element inside a node. Usually you don't need to use this directly. See methods in `DOMForm` instead. - * @param parent the parent element, or `null` to use current `` as parent. - * @param name a string of element name, such as `rect` or `circle` - * @param id id attribute of the new element - * @param autoClass add a class based on the id (from char 0 to index of "-"). Default is true. - */ static htmlElement(parent, name, id, autoClass = true) { if (!parent || !parent.appendChild) throw new Error("parent is not a valid DOM element"); @@ -4581,10 +2866,6 @@ class HTMLSpace extends DOMSpace { } return elem; } - /** - * Remove an item from this Space - * @param item a player item with an auto-assigned `animateID` property - */ remove(player) { let temp = this._container.querySelectorAll("." + HTMLForm.scopeID(player)); temp.forEach((el) => { @@ -4592,18 +2873,12 @@ class HTMLSpace extends DOMSpace { }); return super.remove(player); } - /** - * Remove all items from this Space - */ removeAll() { this._container.innerHTML = ""; return super.removeAll(); } } exports.HTMLSpace = HTMLSpace; -/** - * Form for HTMLSpace. Note that this is currently experimental and may change in future. - */ class HTMLForm extends Form_1.VisualForm { constructor(space) { super(); @@ -4641,22 +2916,11 @@ class HTMLForm extends Form_1.VisualForm { } }); } get space() { return this._space; } - /** - * Update a style in _ctx context or throw an Erorr if the style doesn't exist - * @param k style key - * @param v style value - * @param unit Optional unit like 'px' to append to value - */ styleTo(k, v, unit = '') { if (this._ctx.style[k] === undefined) throw new Error(`${k} style property doesn't exist`); this._ctx.style[k] = `${v}${unit}`; } - /** - * Set current fill style. Provide a valid color string or `false` to specify no fill color. - * @example `form.fill("#F90")`, `form.fill("rgba(0,0,0,.5")`, `form.fill(false)` - * @param c fill color - */ fill(c) { if (typeof c == "boolean") { this.styleTo("filled", c); @@ -4669,14 +2933,6 @@ class HTMLForm extends Form_1.VisualForm { } return this; } - /** - * Set current stroke style. Provide a valid color string or `false` to specify no stroke color. - * @example `form.stroke("#F90")`, `form.stroke("rgba(0,0,0,.5")`, `form.stroke(false)`, `form.stroke("#000", 0.5, 'round', 'square')` - * @param c stroke color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle)) - * @param width Optional value (can be floating point) to set line width - * @param linejoin not implemented in HTMLForm - * @param linecap not implemented in HTMLForm - */ stroke(c, width, linejoin, linecap) { if (typeof c == "boolean") { this.styleTo("stroked", c); @@ -4690,20 +2946,10 @@ class HTMLForm extends Form_1.VisualForm { } return this; } - /** - * Set current text color style. Provide a valid color string. - * @example `form.fill("#F90")`, `form.fill("rgba(0,0,0,.5")`, `form.fill(false)` - * @param c fill color - */ fillText(c) { this.styleTo("color", c); return this; } - /** - * Add custom class to the created element - * @param c custom class name or `false` to reset it - * @example `form.fill("#f00").cls("myClass").rects(r)` `form.cls(false).circles(c)` - */ cls(c) { if (typeof c == "boolean") { this._ctx.currentClass = ""; @@ -4713,15 +2959,6 @@ class HTMLForm extends Form_1.VisualForm { } return this; } - /** - * Set the current font - * @param sizeOrFont either a number to specify font-size, or a `Font` object to specify all font properties - * @param weight Optional font-weight string such as "bold" - * @param style Optional font-style string such as "italic" - * @param lineHeight Optional line-height number suchas 1.5 - * @param family Optional font-family such as "Helvetica, sans-serif" - * @example `form.font( myFont )`, `form.font(14, "bold")` - */ font(sizeOrFont, weight, style, lineHeight, family) { if (typeof sizeOrFont == "number") { this._font.size = sizeOrFont; @@ -4740,9 +2977,6 @@ class HTMLForm extends Form_1.VisualForm { } return this; } - /** - * Reset the context's common styles to this form's styles. This supports using multiple forms on the same canvas context. - */ reset() { this._ctx.style = { "filled": true, "stroked": true, @@ -4753,12 +2987,6 @@ class HTMLForm extends Form_1.VisualForm { this._ctx.font = this._font.value; return this; } - /** - * Set this form's group scope by an ID, and optionally define the group's parent element. A group scope keeps track of elements by their generated IDs, and updates their properties as needed. See also `scope()`. - * @param group_id a string to use as prefix for the group's id. For example, group_id "hello" will create elements with id like "hello-1", "hello-2", etc - * @param group Optional DOM element to define this group's parent element - * @returns this form's context - */ updateScope(group_id, group) { this._ctx.group = group; this._ctx.groupID = group_id; @@ -4766,46 +2994,22 @@ class HTMLForm extends Form_1.VisualForm { this.nextID(); return this._ctx; } - /** - * Set the current group scope to an item added into space, in order to keep track of any point, circle, etc created within it. The item must have an `animateID` property, so that elements created within the item will have generated IDs like "item-{animateID}-{count}". - * @param item a "player" item that's added to space (see `space.add(...)`) and has an `animateID` property - * @returns this form's context - */ scope(item) { if (!item || item.animateID == null) throw new Error("item not defined or not yet added to Space"); return this.updateScope(HTMLForm.scopeID(item), this.space.element); } - /** - * Get next available id in the current group - * @returns an id string - */ nextID() { this._ctx.groupCount++; this._ctx.currentID = `${this._ctx.groupID}-${this._ctx.groupCount}`; return this._ctx.currentID; } - /** - * A static function to generate an ID string based on a context object - * @param ctx a context object for an HTMLForm - */ static getID(ctx) { return ctx.currentID || `p-${HTMLForm.domID++}`; } - /** - * A static function to generate an ID string for a scope, based on a "player" item in the Space - * @param item a "player" item that's added to space (see `space.add(...)`) and has an `animateID` property - */ static scopeID(item) { return `item-${item.animateID}`; } - /** - * A static function to help adding style object to an element. This put all styles into `style` attribute instead of individual attributes, so that the styles can be parsed by Adobe Illustrator. - * @param elem A DOM element to add to - * @param styles an object of style properties - * @example `HTMLForm.style(elem, {fill: "#f90", stroke: false})` - * @returns DOM element - */ static style(elem, styles) { let st = []; if (!styles["filled"]) @@ -4830,13 +3034,6 @@ class HTMLForm extends Form_1.VisualForm { } return HTMLSpace.setAttr(elem, { style: st.join(";") }); } - /** - * A helper function to set top, left, width, height of DOM element - * @param x left position - * @param y top position - * @param w width - * @param h height - */ static rectStyle(ctx, pt, size) { ctx.style["left"] = pt[0] + "px"; ctx.style["top"] = pt[1] + "px"; @@ -4844,14 +3041,6 @@ class HTMLForm extends Form_1.VisualForm { ctx.style["height"] = size[1] + "px"; return ctx; } - /** - * Draws a point - * @param ctx a context object of HTMLForm - * @param pt a Pt object or numeric array - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `HTMLForm.point( p )`, `HTMLForm.point( p, 10, "circle" )` - */ static point(ctx, pt, radius = 5, shape = "square") { if (shape === "circle") { return HTMLForm.circle(ctx, pt, radius); @@ -4860,13 +3049,6 @@ class HTMLForm extends Form_1.VisualForm { return HTMLForm.square(ctx, pt, radius); } } - /** - * Draws a point - * @param p a Pt object - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `form.point( p )`, `form.point( p, 10, "circle" )` - */ point(pt, radius = 5, shape = "square") { this.nextID(); if (shape == "circle") @@ -4874,12 +3056,6 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.point(this._ctx, pt, radius, shape); return this; } - /** - * A static function to draw a circle - * @param ctx a context object of HTMLForm - * @param pt center position of the circle - * @param radius radius of the circle - */ static circle(ctx, pt, radius = 10) { let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx)); HTMLSpace.setAttr(elem, { class: `pts-form pts-circle ${ctx.currentClass}` }); @@ -4887,23 +3063,12 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.style(elem, ctx.style); return elem; } - /** - * Draw a circle - * @param pts usually a Group of 2 Pts, but it can also take an array of two numeric arrays [ [position], [size] ] - * @see [`Circle.fromCenter`](./_op_.circle.html#frompt) - */ circle(pts) { this.nextID(); this.styleTo("border-radius", "100%"); HTMLForm.circle(this._ctx, pts[0], pts[1][0]); return this; } - /** - * A static function to draw a square - * @param ctx a context object of HTMLForm - * @param pt center position of the square - * @param halfsize half size of the square - */ static square(ctx, pt, halfsize) { let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx)); HTMLSpace.setAttr(elem, { class: `pts-form pts-square ${ctx.currentClass}` }); @@ -4911,21 +3076,11 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.style(elem, ctx.style); return elem; } - /** - * Draw a square, given a center and its half-size - * @param pt center Pt - * @param halfsize half-size - */ square(pt, halfsize) { this.nextID(); HTMLForm.square(this._ctx, pt, halfsize); return this; } - /** - * A static function to draw a rectangle - * @param ctx a context object of HTMLForm - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ static rect(ctx, pts) { if (!this._checkSize(pts)) return; @@ -4935,23 +3090,12 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.style(elem, ctx.style); return elem; } - /** - * Draw a rectangle - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ rect(pts) { this.nextID(); this.styleTo("border-radius", "0"); HTMLForm.rect(this._ctx, pts); return this; } - /** - * A static function to draw text - * @param ctx a context object of HTMLForm - * @param `pt` a Point object to specify the anchor point - * @param `txt` a string of text to draw - * @param `maxWidth` specify a maximum width per line - */ static text(ctx, pt, txt) { let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx)); HTMLSpace.setAttr(elem, { @@ -4964,43 +3108,23 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.style(elem, ctx.style); return elem; } - /** - * Draw text on canvas - * @param `pt` a Pt or numeric array to specify the anchor point - * @param `txt` text - * @param `maxWidth` specify a maximum width per line - */ text(pt, txt) { this.nextID(); HTMLForm.text(this._ctx, pt, txt); return this; } - /** - * A convenient way to draw some text on canvas for logging or debugging. It'll be draw on the top-left of the canvas as an overlay. - * @param txt text - */ log(txt) { this.fill("#000").stroke("#fff", 0.5).text([10, 14], txt); return this; } - /** - * Arc is not implemented in HTMLForm - */ arc(pt, radius, startAngle, endAngle, cc) { Util_1.Util.warn("arc is not implemented in HTMLForm"); return this; } - /** - * Line is not implemented in HTMLForm - */ line(pts) { Util_1.Util.warn("line is not implemented in HTMLForm"); return this; } - /** - * Polygon is not implemented in HTMLForm - * @param pts - */ polygon(pts) { Util_1.Util.warn("polygon is not implemented in HTMLForm"); return this; @@ -5018,11 +3142,11 @@ exports.HTMLForm = HTMLForm; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const _Bound = __webpack_require__(5); +const _Bound = __webpack_require__(4); const _Canvas = __webpack_require__(11); const _Create = __webpack_require__(13); const _Form = __webpack_require__(6); -const _LinearAlgebra = __webpack_require__(4); +const _LinearAlgebra = __webpack_require__(5); const _Num = __webpack_require__(3); const _Op = __webpack_require__(2); const _Pt = __webpack_require__(0); @@ -5032,7 +3156,8 @@ const _Util = __webpack_require__(1); const _Dom = __webpack_require__(9); const _Svg = __webpack_require__(15); const _Typography = __webpack_require__(8); -module.exports = Object.assign({}, _Bound, _Canvas, _Create, _Form, _LinearAlgebra, _Op, _Num, _Pt, _Space, _Util, _Color, _Dom, _Svg, _Typography); +const _Physics = __webpack_require__(16); +module.exports = Object.assign({}, _Bound, _Canvas, _Create, _Form, _LinearAlgebra, _Op, _Num, _Pt, _Space, _Util, _Color, _Dom, _Svg, _Typography, _Physics); /***/ }), @@ -5041,27 +3166,15 @@ module.exports = Object.assign({}, _Bound, _Canvas, _Create, _Form, _LinearAlgeb "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Space_1 = __webpack_require__(7); const Form_1 = __webpack_require__(6); -const Bound_1 = __webpack_require__(5); +const Bound_1 = __webpack_require__(4); const Pt_1 = __webpack_require__(0); const Util_1 = __webpack_require__(1); const Typography_1 = __webpack_require__(8); const Op_1 = __webpack_require__(2); -/** -* CanvasSpace is an implementation of the abstract class Space. It represents a space for HTML Canvas. -* Learn more about the concept of Space in [this guide](..guide/Space-0500.html) -*/ class CanvasSpace extends Space_1.MultiTouchSpace { - /** - * Create a CanvasSpace which represents a HTML Canvas Space - * @param elem Specify an element by its "id" attribute as string, or by the element object itself. An element can be an existing ``, or a `
` container in which a new `` will be created. If left empty, a `
` will be added to DOM. Use css to customize its appearance if needed. - * @param callback an optional callback `function(boundingBox, spaceElement)` to be called when canvas is appended and ready. Alternatively, a "ready" event will also be fired from the `` element when it's appended, which can be traced with `spaceInstance.canvas.addEventListener("ready")` - * @example `new CanvasSpace( "#myElementID" )` - */ constructor(elem, callback) { super(); this._pixelScale = 1; @@ -5072,7 +3185,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { var _selector = null; var _existed = false; this.id = "pt"; - // check element or element id string if (elem instanceof Element) { _selector = elem; this.id = "pts_existing_space"; @@ -5082,51 +3194,32 @@ class CanvasSpace extends Space_1.MultiTouchSpace { _existed = true; this.id = elem; } - // if selector is not defined, create a canvas if (!_selector) { this._container = this._createElement("div", this.id + "_container"); this._canvas = this._createElement("canvas", this.id); this._container.appendChild(this._canvas); document.body.appendChild(this._container); _existed = false; - // if selector is element but not canvas, create a canvas inside it } else if (_selector.nodeName.toLowerCase() != "canvas") { this._container = _selector; this._canvas = this._createElement("canvas", this.id + "_canvas"); this._container.appendChild(this._canvas); this._initialResize = true; - // if selector is an existing canvas } else { this._canvas = _selector; this._container = _selector.parentElement; this._autoResize = false; } - // if size is known then set it immediately - // if (_existed) { - // let b = this._container.getBoundingClientRect(); - // this.resize( Bound.fromBoundingRect(b) ); - // } - // no mutation observer, so we set a timeout for ready event setTimeout(this._ready.bind(this, callback), 100); - // store canvas 2d rendering context this._ctx = this._canvas.getContext('2d'); } - /** - * Helper function to create a DOM element - * @param elem element tag name - * @param id element id attribute - */ _createElement(elem = "div", id) { let d = document.createElement(elem); d.setAttribute("id", id); return d; } - /** - * Handle callbacks after element is mounted in DOM - * @param callback - */ _ready(callback) { if (!this._container) throw new Error(`Cannot initiate #${this.id} element`); @@ -5141,19 +3234,10 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } } this._pointer = this.center; - this._initialResize = false; // unset + this._initialResize = false; if (callback) callback(this.bound, this._canvas); } - /** - * Set up various options for CanvasSpace. The `opt` parameter is an object with the following fields. This is usually set during instantiation, eg `new CanvasSpace(...).setup( { opt } )` - * @param opt an object with optional settings, as follows. - * @param opt.bgcolor a hex or rgba string to set initial background color of the canvas, or use `false` or "transparent" to set a transparent background. You may also change it later with `clear()` - * @param opt.resize a boolean to set whether `` size should auto resize to match its container's size. You can also set it manually with `autoSize()` - * @param opt.retina a boolean to set if device pixel scaling should be used. This may make drawings on retina displays look sharper but may reduce performance slightly. Default is `true`. - * @param opt.offscreen a boolean to set if a duplicate canvas should be created for offscreen rendering. Default is `false`. - * @example `space.setup({ bgcolor: "#f00", retina: true, resize: true })` - */ setup(opt) { if (opt.bgcolor) this._bgcolor = opt.bgcolor; @@ -5173,10 +3257,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } return this; } - /** - * Set whether the canvas element should resize when its container is resized. - * @param auto a boolean value indicating if auto size is set - */ set autoResize(auto) { this._autoResize = auto; if (auto) { @@ -5187,11 +3267,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } } get autoResize() { return this._autoResize; } - /** - * This overrides Space's `resize` function. It's used as a callback function for window's resize event and not usually called directly. You can keep track of resize events with `resize: (bound ,evt)` callback in your player objects (See `Space`'s `add()` function). - * @param b a Bound object to resize to - * @param evt Optionally pass a resize event - */ resize(b, evt) { this.bound = b; this._canvas.width = this.bound.size.x * this._pixelScale; @@ -5201,8 +3276,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { if (this._offscreen) { this._offCanvas.width = this.bound.size.x * this._pixelScale; this._offCanvas.height = this.bound.size.y * this._pixelScale; - // this._offCanvas.style.width = Math.floor(this.bound.size.x) + "px"; - // this._offCanvas.style.height = Math.floor(this.bound.size.y) + "px"; } if (this._pixelScale != 1) { this._ctx.scale(this._pixelScale, this._pixelScale); @@ -5220,81 +3293,39 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } } this.render(this._ctx); - // if it's a valid resize event and space is not playing, repaint the canvas once if (evt && !this.isPlaying) this.playOnce(0); return this; } - /** - * Window resize handling - * @param evt - */ _resizeHandler(evt) { let b = (this._autoResize || this._initialResize) ? this._container.getBoundingClientRect() : this._canvas.getBoundingClientRect(); if (b) { let box = Bound_1.Bound.fromBoundingRect(b); - // Need to compute offset from window scroll. See outerBound calculation in Space's _mouseAction box.center = box.center.add(window.pageXOffset, window.pageYOffset); this.resize(box, evt); } } - /** - * Set a background color for this canvas. Alternatively, you may use `clear()` function. - @param bg background color as hex or rgba string - */ set background(bg) { this._bgcolor = bg; } get background() { return this._bgcolor; } - /** - * `pixelScale` property returns a number that let you determine if the screen is "retina" (when value >= 2) - */ get pixelScale() { return this._pixelScale; } - /** - * Check if an offscreen canvas is created - */ get hasOffscreen() { return this._offscreen; } - /** - * Get the rendering context of offscreen canvas (if created via `setup()`) - */ get offscreenCtx() { return this._offCtx; } - /** - * Get the offscreen canvas element - */ get offscreenCanvas() { return this._offCanvas; } - /** - * Get a new `CanvasForm` for drawing - * @see `CanvasForm` - */ getForm() { return new CanvasForm(this); } - /** - * Get the html canvas element - */ get element() { return this._canvas; } - /** - * Get the parent element that contains the canvas element - */ get parent() { return this._container; } - /** - * A property to indicate if the Space is ready - */ get ready() { return this._isReady; } - /** - * Get the rendering context of canvas - */ get ctx() { return this._ctx; } - /** - * Clear the canvas with its background color. Overrides Space's `clear` function. - * @param bg Optionally specify a custom background color in hex or rgba string, or "transparent". If not defined, it will use its `bgcolor` property as background color to clear the canvas. - */ clear(bg) { if (bg) this._bgcolor = bg; @@ -5309,10 +3340,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { this._ctx.fillStyle = lastColor; return this; } - /** - * Similiar to `clear()` but clear the offscreen canvas instead - * @param bg Optionally specify a custom background color in hex or rgba string, or "transparent". If not defined, it will use its `bgcolor` property as background color to clear the canvas. - */ clearOffscreen(bg) { if (this._offscreen) { if (bg) { @@ -5325,10 +3352,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } return this; } - /** - * Main animation function. Call `Space.playItems`. - * @param time current time - */ playItems(time) { if (this._isReady) { this._ctx.save(); @@ -5343,20 +3366,9 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } } exports.CanvasSpace = CanvasSpace; -/** -* CanvasForm is an implementation of abstract class VisualForm. It provide methods to express Pts on CanvasSpace. -* You may extend CanvasForm to implement your own expressions for CanvasSpace. -*/ class CanvasForm extends Form_1.VisualForm { - /** - * Create a new CanvasForm. You may also use `space.getForm()` to get the default form. - * @param space an instance of CanvasSpace - */ constructor(space) { super(); - /** - * store common styles so that they can be restored to canvas context when using multiple forms. See `reset()`. - */ this._style = { fillStyle: "#f03", strokeStyle: "#fff", lineWidth: 1, lineJoin: "bevel", lineCap: "butt", @@ -5371,35 +3383,18 @@ class CanvasForm extends Form_1.VisualForm { this._ready = true; } }); } - /** - * get the CanvasSpace instance that this form is associated with - */ get space() { return this._space; } - /** - * Toggle whether to draw on offscreen canvas (if offscreen is set in CanvasSpace) - * @param off if `true`, draw on offscreen canvas instead of the visible canvas. Default is `true` - * @param clear optionally provide a valid color string to fill a bg color. see CanvasSpace's `clearOffscreen` function. - */ useOffscreen(off = true, clear = false) { if (clear) this._space.clearOffscreen((typeof clear == "string") ? clear : null); this._ctx = (this._space.hasOffscreen && off) ? this._space.offscreenCtx : this._space.ctx; return this; } - /** - * Render the offscreen canvas's content on the visible canvas - * @param offset Optional offset on the top-left position when drawing on the visible canvas - */ renderOffscreen(offset = [0, 0]) { if (this._space.hasOffscreen) { this._space.ctx.drawImage(this._space.offscreenCanvas, offset[0], offset[1], this._space.width, this._space.height); } } - /** - * Set current fill style. Provide a valid color string or `false` to specify no fill color. - * @example `form.fill("#F90")`, `form.fill("rgba(0,0,0,.5")`, `form.fill(false)` - * @param c fill color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle)) - */ fill(c) { if (typeof c == "boolean") { this.filled = c; @@ -5411,14 +3406,6 @@ class CanvasForm extends Form_1.VisualForm { } return this; } - /** - * Set current stroke style. Provide a valid color string or `false` to specify no stroke color. - * @example `form.stroke("#F90")`, `form.stroke("rgba(0,0,0,.5")`, `form.stroke(false)`, `form.stroke("#000", 0.5, 'round', 'square')` - * @param c stroke color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle)) - * @param width Optional value (can be floating point) to set line width - * @param linejoin Optional string to set line joint style. Can be "miter", "bevel", or "round". - * @param linecap Optional string to set line cap style. Can be "butt", "round", or "square". - */ stroke(c, width, linejoin, linecap) { if (typeof c == "boolean") { this.stroked = c; @@ -5442,15 +3429,6 @@ class CanvasForm extends Form_1.VisualForm { } return this; } - /** - * Set the current font - * @param sizeOrFont either a number to specify font-size, or a `Font` object to specify all font properties - * @param weight Optional font-weight string such as "bold" - * @param style Optional font-style string such as "italic" - * @param lineHeight Optional line-height number suchas 1.5 - * @param family Optional font-family such as "Helvetica, sans-serif" - * @example `form.font( myFont )`, `form.font(14, "bold")` - */ font(sizeOrFont, weight, style, lineHeight, family) { if (typeof sizeOrFont == "number") { this._font.size = sizeOrFont; @@ -5467,42 +3445,20 @@ class CanvasForm extends Form_1.VisualForm { else { this._font = sizeOrFont; } - // If using estimate, reapply it when font changes. if (this._estimateTextWidth) this.fontWidthEstimate(true); return this; } - /** - * Set whether to use ctx.measureText or a faster but less accurate heuristic function. - * @param estimate `true` to use heuristic function, or `false` to use ctx.measureText - */ fontWidthEstimate(estimate = true) { this._estimateTextWidth = (estimate) ? Typography_1.Typography.textWidthEstimator(((c) => this._ctx.measureText(c).width)) : undefined; return this; } - /** - * Get the width of this text. It will return an actual measurement or an estimate based on `fontWidthEstimate` setting. Default is an actual measurement using canvas context's measureText. - * @param c a string of text contents - */ getTextWidth(c) { return (!this._estimateTextWidth) ? this._ctx.measureText(c + " .").width : this._estimateTextWidth(c); } - /** - * Truncate text to fit width - * @param str text to truncate - * @param width width to fit - * @param tail text to indicate overflow such as "...". Default is empty "". - */ _textTruncate(str, width, tail = "") { return Typography_1.Typography.truncate(this.getTextWidth.bind(this), str, width, tail); } - /** - * Align text within a rectangle box - * @param box a Group that defines a rectangular box - * @param vertical a string that specifies the vertical alignment in the box: "top", "bottom", "middle", "start", "end" - * @param offset Optional offset from the edge (like padding) - * @param center Optional center position - */ _textAlign(box, vertical, offset, center) { if (!center) center = Op_1.Rectangle.center(box); @@ -5522,9 +3478,6 @@ class CanvasForm extends Form_1.VisualForm { } return (offset) ? new Pt_1.Pt(px + offset[0], py + offset[1]) : new Pt_1.Pt(px, py); } - /** - * Reset the rendering context's common styles to this form's styles. This supports using multiple forms on the same canvas context. - */ reset() { for (let k in this._style) { if (this._style.hasOwnProperty(k)) { @@ -5541,13 +3494,6 @@ class CanvasForm extends Form_1.VisualForm { if (this._stroked) this._ctx.stroke(); } - /** - * Draws a point - * @param p a Pt object - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `form.point( p )`, `form.point( p, 10, "circle" )` - */ point(p, radius = 5, shape = "square") { if (!p) return; @@ -5557,12 +3503,6 @@ class CanvasForm extends Form_1.VisualForm { this._paint(); return this; } - /** - * A static function to draw a circle - * @param ctx canvas rendering context - * @param pt center position of the circle - * @param radius radius of the circle - */ static circle(ctx, pt, radius = 10) { if (!pt) return; @@ -5570,50 +3510,22 @@ class CanvasForm extends Form_1.VisualForm { ctx.arc(pt[0], pt[1], radius, 0, Util_1.Const.two_pi, false); ctx.closePath(); } - /** - * Draw a circle - * @param pts usually a Group of 2 Pts, but it can also take an array of two numeric arrays [ [position], [size] ] - * @see [`Circle.fromCenter`](./_op_.circle.html#frompt) - */ circle(pts) { CanvasForm.circle(this._ctx, pts[0], pts[1][0]); this._paint(); return this; } - /** - * A static function to draw an arc. - * @param ctx canvas rendering context - * @param pt center position - * @param radius radius of the arc circle - * @param startAngle start angle of the arc - * @param endAngle end angle of the arc - * @param cc an optional boolean value to specify if it should be drawn clockwise (`false`) or counter-clockwise (`true`). Default is clockwise. - */ static arc(ctx, pt, radius, startAngle, endAngle, cc) { if (!pt) return; ctx.beginPath(); ctx.arc(pt[0], pt[1], radius, startAngle, endAngle, cc); } - /** - * Draw an arc. - * @param pt center position - * @param radius radius of the arc circle - * @param startAngle start angle of the arc - * @param endAngle end angle of the arc - * @param cc an optional boolean value to specify if it should be drawn clockwise (`false`) or counter-clockwise (`true`). Default is clockwise. - */ arc(pt, radius, startAngle, endAngle, cc) { CanvasForm.arc(this._ctx, pt, radius, startAngle, endAngle, cc); this._paint(); return this; } - /** - * A static function to draw a square - * @param ctx canvas rendering context - * @param pt center position of the square - * @param halfsize half size of the square - */ static square(ctx, pt, halfsize) { if (!pt) return; @@ -5621,7 +3533,6 @@ class CanvasForm extends Form_1.VisualForm { let y1 = pt[1] - halfsize; let x2 = pt[0] + halfsize; let y2 = pt[1] + halfsize; - // faster than using `rect` ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x1, y2); @@ -5629,21 +3540,11 @@ class CanvasForm extends Form_1.VisualForm { ctx.lineTo(x2, y1); ctx.closePath(); } - /** - * Draw a square, given a center and its half-size - * @param pt center Pt - * @param halfsize half-size - */ square(pt, halfsize) { CanvasForm.square(this._ctx, pt, halfsize); this._paint(); return this; } - /** - * A static function to draw a line - * @param ctx canvas rendering context - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ static line(ctx, pts) { if (pts.length < 2) return; @@ -5654,20 +3555,11 @@ class CanvasForm extends Form_1.VisualForm { ctx.lineTo(pts[i][0], pts[i][1]); } } - /** - * Draw a line or polyline - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ line(pts) { CanvasForm.line(this._ctx, pts); this._paint(); return this; } - /** - * A static function to draw polygon - * @param ctx canvas rendering context - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ static polygon(ctx, pts) { if (pts.length < 2) return; @@ -5679,20 +3571,11 @@ class CanvasForm extends Form_1.VisualForm { } ctx.closePath(); } - /** - * Draw a polygon - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ polygon(pts) { CanvasForm.polygon(this._ctx, pts); this._paint(); return this; } - /** - * A static function to draw a rectangle - * @param ctx canvas rendering context - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ static rect(ctx, pts) { if (pts.length < 2) return; @@ -5703,45 +3586,20 @@ class CanvasForm extends Form_1.VisualForm { ctx.lineTo(pts[1][0], pts[0][1]); ctx.closePath(); } - /** - * Draw a rectangle - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ rect(pts) { CanvasForm.rect(this._ctx, pts); this._paint(); return this; } - /** - * A static function to draw text - * @param ctx canvas rendering context - * @param `pt` a Point object to specify the anchor point - * @param `txt` a string of text to draw - * @param `maxWidth` specify a maximum width per line - */ static text(ctx, pt, txt, maxWidth) { if (!pt) return; ctx.fillText(txt, pt[0], pt[1], maxWidth); } - /** - * Draw text on canvas - * @param `pt` a Pt or numeric array to specify the anchor point - * @param `txt` text - * @param `maxWidth` specify a maximum width per line - */ text(pt, txt, maxWidth) { CanvasForm.text(this._ctx, pt, txt, maxWidth); return this; } - /** - * Fit a single-line text in a rectangular box - * @param box a rectangle box defined by a Group - * @param txt string of text - * @param tail text to indicate overflow such as "...". Default is empty "". - * @param verticalAlign "top", "middle", or "bottom" to specify vertical alignment inside the box - * @param overrideBaseline If `true`, use the corresponding baseline as verticalAlign. If `false`, use the current canvas context's textBaseline setting. Default is `true`. - */ textBox(box, txt, verticalAlign = "middle", tail = "", overrideBaseline = true) { if (overrideBaseline) this._ctx.textBaseline = verticalAlign; @@ -5750,19 +3608,10 @@ class CanvasForm extends Form_1.VisualForm { this.text(this._textAlign(box, verticalAlign), t[0]); return this; } - /** - * Fit multi-line text in a rectangular box. Note that this will also set canvas context's textBaseline to "top". - * @param box a rectangle box defined by a Group - * @param txt string of text - * @param lineHeight line height as a ratio of font size. Default is 1.2. - * @param verticalAlign "top", "middle", or "bottom" to specify vertical alignment inside the box - * @param crop a boolean to specify whether to crop text when overflowing - */ paragraphBox(box, txt, lineHeight = 1.2, verticalAlign = "top", crop = true) { let size = Op_1.Rectangle.size(box); - this._ctx.textBaseline = "top"; // override textBaseline + this._ctx.textBaseline = "top"; let lstep = this._font.size * lineHeight; - // find next lines recursively let nextLine = (sub, buffer = [], cc = 0) => { if (!sub) return buffer; @@ -5771,13 +3620,11 @@ class CanvasForm extends Form_1.VisualForm { if (cc > 10000) throw new Error("max recursion reached (10000)"); let t = this._textTruncate(sub, size[0], ""); - // new line let newln = t[0].indexOf("\n"); if (newln >= 0) { buffer.push(t[0].substr(0, newln)); return nextLine(sub.substr(newln + 1), buffer, cc + 1); } - // word wrap let dt = t[0].lastIndexOf(" ") + 1; if (dt <= 0 || t[1] === sub.length) dt = undefined; @@ -5785,8 +3632,8 @@ class CanvasForm extends Form_1.VisualForm { buffer.push(line); return (t[1] <= 0 || t[1] === sub.length) ? buffer : nextLine(sub.substr((dt || t[1])), buffer, cc + 1); }; - let lines = nextLine(txt); // go through all lines - let lsize = lines.length * lstep; // total height + let lines = nextLine(txt); + let lsize = lines.length * lstep; let lbox = box; if (verticalAlign == "middle" || verticalAlign == "center") { let lpad = (size[1] - lsize) / 2; @@ -5806,11 +3653,6 @@ class CanvasForm extends Form_1.VisualForm { } return this; } - /** - * Set text alignment and baseline (eg, vertical-align) - * @param alignment Canvas' textAlign option: "left", "right", "center", "start", or "end" - * @param baseline Canvas' textBaseline option: "top", "hanging", "middle", "alphabetic", "ideographic", "bottom". For convenience, you can also use "center" (same as "middle"), and "baseline" (same as "alphabetic") - */ alignText(alignment = "left", baseline = "alphabetic") { if (baseline == "center") baseline = "middle"; @@ -5820,10 +3662,6 @@ class CanvasForm extends Form_1.VisualForm { this._ctx.textBaseline = baseline; return this; } - /** - * A convenient way to draw some text on canvas for logging or debugging. It'll be draw on the top-left of the canvas as an overlay. - * @param txt text - */ log(txt) { let w = this._ctx.measureText(txt).width + 20; this.stroke(false).fill("rgba(0,0,0,.4)").rect([[0, 0], [w, 20]]); @@ -5840,13 +3678,8 @@ exports.CanvasForm = CanvasForm; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Op_1 = __webpack_require__(2); -/** - * An enumeration of different UI types - */ var UIShape; (function (UIShape) { UIShape[UIShape["Rectangle"] = 0] = "Rectangle"; @@ -5859,9 +3692,6 @@ exports.UIPointerActions = { up: "up", down: "down", move: "move", drag: "drag", drop: "drop", over: "over", out: "out" }; class UI { - /** - * Wrap an UI insider a group - */ constructor(group, shape, states, id) { this.group = group; this.shape = shape; @@ -5869,41 +3699,19 @@ class UI { this._states = states; this._actions = {}; } - /** - * Get and set uique id - */ get id() { return this._id; } set id(d) { this._id = d; } - /** - * Get a state - * @param key state's name - */ state(key) { return this._states[key] || false; } - /** - * Add an event handler - * @param key event key - * @param fn handler function - */ on(key, fn) { this._actions[key] = fn; return this; } - /** - * Remove an event handler - * @param key even key - * @param fn - */ off(key) { delete this._actions[key]; return this; } - /** - * Listen for interactions and trigger action handlers - * @param key action key - * @param p point to check - */ listen(key, p) { if (this._actions[key] !== undefined) { if (this._trigger(p)) { @@ -5913,17 +3721,9 @@ class UI { } return false; } - /** - * Take a custom render function to render this UI - * @param fn render function - */ render(fn) { fn(this.group, this._states); } - /** - * Check intersection using a specific function based on UIShape - * @param p a point to check - */ _trigger(p) { let fn = null; if (this.shape === UIShape.Rectangle) { @@ -5942,31 +3742,16 @@ class UI { } } exports.UI = UI; -/** - * A simple UI button that can track clicks and hovers - */ class UIButton extends UI { constructor(group, shape, states, id) { super(group, shape, states, id); this._clicks = 0; } - /** - * Get the total number of clicks on this UIButton - */ get clicks() { return this._clicks; } - /** - * Add a click handler - * @param fn a function to handle clicks - */ onClick(fn) { this._clicks++; this.on(exports.UIPointerActions.up, fn); } - /** - * Add hover handler - * @param over a function to handle when pointer enters hover - * @param out a function to handle when pointer exits hover - */ onHover(over, out) { this.on(exports.UIPointerActions.over, over); this.on(exports.UIPointerActions.out, out); @@ -5981,24 +3766,13 @@ exports.UIButton = UIButton; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); const Op_1 = __webpack_require__(2); const Util_1 = __webpack_require__(1); const Num_1 = __webpack_require__(3); -const LinearAlgebra_1 = __webpack_require__(4); -/** - * The `Create` class provides various convenient functions to create structures or shapes. - */ +const LinearAlgebra_1 = __webpack_require__(5); class Create { - /** - * Create a set of random points inside a bounday - * @param bound the rectangular boundary - * @param count number of random points to create - * @param dimensions number of dimensions in each point - */ static distributeRandom(bound, count, dimensions = 2) { let pts = new Pt_1.Group(); for (let i = 0; i < count; i++) { @@ -6011,25 +3785,12 @@ class Create { } return pts; } - /** - * Create a set of points that distribute evenly on a line - * @param line a Group representing a line - * @param count number of points to create - */ static distributeLinear(line, count) { let ln = Op_1.Line.subpoints(line, count - 2); ln.unshift(line[0]); ln.push(line[line.length - 1]); return ln; } - /** - * Create an evenly distributed set of points (like a grid of points) inside a boundary. - * @param bound the rectangular boundary - * @param columns number of columns - * @param rows number of rows - * @param orientation a Pt or number array to specify where the point should be inside a cell. Default is [0.5, 0.5] which places the point in the middle. - * @returns a Group of Pts - */ static gridPts(bound, columns, rows, orientation = [0.5, 0.5]) { if (columns === 0 || rows === 0) throw new Error("grid columns and rows cannot be 0"); @@ -6043,17 +3804,10 @@ class Create { } return g; } - /** - * Create a grid inside a boundary - * @param bound the rectangular boundary - * @param columns number of columns - * @param rows number of rows - * @returns an array of Groups, where each group represents a rectangular cell - */ static gridCells(bound, columns, rows) { if (columns === 0 || rows === 0) throw new Error("grid columns and rows cannot be 0"); - let unit = bound.size.$subtract(1).divide(columns, rows); // subtract 1 to fill whole border of rectangles + let unit = bound.size.$subtract(1).divide(columns, rows); let g = []; for (let r = 0; r < rows; r++) { for (let c = 0; c < columns; c++) { @@ -6062,12 +3816,6 @@ class Create { } return g; } - /** - * Create a set of Pts around a circular path - * @param center circle center - * @param radius circle radius - * @param count number of Pts to create - */ static radialPts(center, radius, count) { let g = new Pt_1.Group(); let a = Util_1.Const.two_pi / count; @@ -6076,14 +3824,6 @@ class Create { } return g; } - /** - * Given a group of Pts, return a new group of `Noise` Pts. - * @param pts a Group or an array of Pts - * @param dx small increment value in x dimension - * @param dy small increment value in y dimension - * @param rows Optional row count to generate 2D noise - * @param columns Optional column count to generate 2D noise - */ static noisePts(pts, dx = 0.01, dy = 0.01, rows = 0, columns = 0) { let seed = Math.random(); let g = new Pt_1.Group(); @@ -6097,27 +3837,16 @@ class Create { } return g; } - /** - * Create a Delaunay Group. Use the `.delaunay()` and `.voronoi()` functions in the returned group to generate tessellations. - * @param pts a Group or an array of Pts - * @returns an instance of the Delaunay class - */ static delaunay(pts) { return Delaunay.from(pts); } } exports.Create = Create; -/** - * Perlin noise gradient indices - */ const grad3 = [ [1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0], [1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1], [0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1] ]; -/** - * Perlin noise permutation table - */ const permTable = [151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, @@ -6132,42 +3861,19 @@ const permTable = [151, 160, 137, 91, 90, 15, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 ]; -/** - * A class to generate Perlin noise. Currently it implements a basic 2D noise. More to follow. - * Based on https://gist.github.com/banksean/304522 - */ class Noise extends Pt_1.Pt { - /** - * Create a Noise Pt that's capable of generating noise - * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ constructor(...args) { super(...args); this.perm = []; this._n = new Pt_1.Pt(0.01, 0.01); - // For easier index wrapping, double the permutation table length this.perm = permTable.concat(permTable); } - /** - * Set the initial noise values - * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - * @example `noise.initNoise( 0.01, 0.1 )` - */ initNoise(...args) { this._n = new Pt_1.Pt(...args); } - /** - * Add a small increment to the noise values - * @param x step in x dimension - * @param y step in y dimension - */ step(x = 0, y = 0) { this._n.add(x, y); } - /** - * Specify a seed for this Noise - * @param s seed value - */ seed(s) { if (s > 0 && s < 1) s *= 65536; @@ -6179,9 +3885,6 @@ class Noise extends Pt_1.Pt { this.perm[i] = this.perm[i + 256] = v; } } - /** - * Generate a 2D Perlin noise value - */ noise2D() { let i = Math.floor(this._n[0]) % 255; let j = Math.floor(this._n[1]) % 255; @@ -6197,69 +3900,49 @@ class Noise extends Pt_1.Pt { } } exports.Noise = Noise; -/** - * Delaunay is a Group of Pts that can generate Delaunay and Voronoi tessellations. The triangulation algorithm is ported from [Pt](https://github.com/williamngan/pt) - * This implementation is based on [Paul Bourke's algorithm](http://paulbourke.net/papers/triangulate/) - * with reference to its [javascript implementation by ironwallaby](https://github.com/ironwallaby/delaunay) - */ class Delaunay extends Pt_1.Group { constructor() { super(...arguments); this._mesh = []; } - /** - * Generate Delaunay triangles. This function also caches the mesh that is used to generate Voronoi tessellation in `voronoi()`. - * @param triangleOnly if true, returns an array of triangles in Groups, otherwise return the whole DelaunayShape - * @returns an array of Groups or an array of DelaunayShapes `{i, j, k, triangle, circle}` which records the indices of the vertices, and the calculated triangles and circumcircles - */ delaunay(triangleOnly = true) { if (this.length < 3) return []; this._mesh = []; let n = this.length; - // sort the points and store the sorted index let indices = []; for (let i = 0; i < n; i++) indices[i] = i; indices.sort((i, j) => this[j][0] - this[i][0]); - // duplicate the points list and add super triangle's points to it let pts = this.slice(); let st = this._superTriangle(); pts = pts.concat(st); - // arrays to store edge buffer and opened triangles let opened = [this._circum(n, n + 1, n + 2, st)]; let closed = []; let tris = []; - // Go through each point using the sorted indices for (let i = 0, len = indices.length; i < len; i++) { let c = indices[i]; let edges = []; let j = opened.length; if (!this._mesh[c]) this._mesh[c] = {}; - // Go through each opened triangles while (j--) { let circum = opened[j]; let radius = circum.circle[1][0]; let d = pts[c].$subtract(circum.circle[0]); - // if point is to the right of circumcircle, add it to closed list and don't check again if (d[0] > 0 && d[0] * d[0] > radius * radius) { closed.push(circum); tris.push(circum.triangle); opened.splice(j, 1); continue; } - // if it's outside the circumcircle, skip if (d[0] * d[0] + d[1] * d[1] - radius * radius > Util_1.Const.epsilon) { continue; } - // otherwise it's inside the circumcircle, so we add to edge buffer and remove it from the opened list edges.push(circum.i, circum.j, circum.j, circum.k, circum.k, circum.i); opened.splice(j, 1); } - // dedup edges Delaunay._dedupe(edges); - // Go through the edge buffer and create a triangle for each edge j = edges.length; while (j > 1) { opened.push(this._circum(edges[--j], edges[--j], c, false, pts)); @@ -6275,10 +3958,6 @@ class Delaunay extends Pt_1.Group { } return (triangleOnly) ? tris : closed; } - /** - * Generate Voronoi cells. `delaunay()` must be called before calling this function. - * @returns an array of Groups, each of which represents a Voronoi cell - */ voronoi() { let vs = []; let n = this._mesh; @@ -6287,19 +3966,9 @@ class Delaunay extends Pt_1.Group { } return vs; } - /** - * Get the cached mesh. The mesh is an array of objects, each of which representing the enclosing triangles around a Pt in this Delaunay group - * @return an array of objects that store a series of DelaunayShapes - */ mesh() { return this._mesh; } - /** - * Given an index of a Pt in this Delaunay Group, returns its neighboring Pts in the network - * @param i index of a Pt - * @param sort if true, sort the neighbors so that their edges will form a polygon - * @returns an array of Pts - */ neighborPts(i, sort = false) { let cs = new Pt_1.Group(); let n = this._mesh; @@ -6309,11 +3978,6 @@ class Delaunay extends Pt_1.Group { } return (sort) ? Num_1.Geom.sortEdges(cs) : cs; } - /** - * Given an index of a Pt in this Delaunay Group, returns its neighboring DelaunayShapes - * @param i index of a Pt - * @returns an array of DelaunayShapes `{i, j, k, triangle, circle}` - */ neighbors(i) { let cs = []; let n = this._mesh; @@ -6323,19 +3987,11 @@ class Delaunay extends Pt_1.Group { } return cs; } - /** - * Record a DelaunayShape in the mesh - * @param o DelaunayShape instance - */ _cache(o) { this._mesh[o.i][`${Math.min(o.j, o.k)}-${Math.max(o.j, o.k)}`] = o; this._mesh[o.j][`${Math.min(o.i, o.k)}-${Math.max(o.i, o.k)}`] = o; this._mesh[o.k][`${Math.min(o.i, o.j)}-${Math.max(o.i, o.j)}`] = o; } - /** - * Get the initial "super triangle" that contains all the points in this set - * @returns a Group representing a triangle - */ _superTriangle() { let minPt = this[0]; let maxPt = this[0]; @@ -6348,24 +4004,9 @@ class Delaunay extends Pt_1.Group { let dmax = Math.max(d[0], d[1]); return new Pt_1.Group(mid.$subtract(20 * dmax, dmax), mid.$add(0, 20 * dmax), mid.$add(20 * dmax, -dmax)); } - /** - * Get a triangle from 3 points in a list of points - * @param i index 1 - * @param j index 2 - * @param k index 3 - * @param pts a Group of Pts - */ _triangle(i, j, k, pts = this) { return new Pt_1.Group(pts[i], pts[j], pts[k]); } - /** - * Get a circumcircle and triangle from 3 points in a list of points - * @param i index 1 - * @param j index 2 - * @param k index 3 - * @param tri a Group representing a triangle, or `false` to create it from indices - * @param pts a Group of Pts - */ _circum(i, j, k, tri, pts = this) { let t = tri || this._triangle(i, j, k, pts); return { @@ -6376,10 +4017,6 @@ class Delaunay extends Pt_1.Group { circle: Op_1.Triangle.circumcircle(t) }; } - /** - * Dedupe the edges array - * @param edges - */ static _dedupe(edges) { let j = edges.length; while (j > 1) { @@ -6408,29 +4045,16 @@ exports.Delaunay = Delaunay; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); const Util_1 = __webpack_require__(1); const Num_1 = __webpack_require__(3); -/** - * Color is a subclass of Pt. You can think of a color as a point in a color space. The Color class provides support for many color spaces. - */ class Color extends Pt_1.Pt { - /** - * Create a Color. Same as creating a Pt. - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ constructor(...args) { super(...args); this._mode = "rgb"; this._isNorm = false; } - /** - * Create a Color object with defaults to 4 dimensions - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ static from(...args) { let p = [1, 1, 1, 1]; let c = Util_1.Util.getArgs(args); @@ -6440,13 +4064,9 @@ class Color extends Pt_1.Pt { } return new Color(p); } - /** - * Convert a rgb hex string like #FF0000 or #F00 to a Color object - * @param hex a hex string, with optional '#' prefix - */ static fromHex(hex) { if (hex[0] == "#") - hex = hex.substr(1); // remove '#' if needed + hex = hex.substr(1); if (hex.length <= 3) { let fn = (i) => hex[i] || "F"; hex = `${fn(0)}${fn(0)}${fn(1)}${fn(1)}${fn(2)}${fn(2)}`; @@ -6459,72 +4079,22 @@ class Color extends Pt_1.Pt { let hexVal = parseInt(hex, 16); return new Color(hexVal >> 16, hexVal >> 8 & 0xFF, hexVal & 0xFF, alpha); } - /** - * Create RGB Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static rgb(...args) { return Color.from(...args).toMode("rgb"); } - /** - * Create HSL Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static hsl(...args) { return Color.from(...args).toMode("hsl"); } - /** - * Create HSB Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static hsb(...args) { return Color.from(...args).toMode("hsb"); } - /** - * Create LAB Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static lab(...args) { return Color.from(...args).toMode("lab"); } - /** - * Create LCH Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static lch(...args) { return Color.from(...args).toMode("lch"); } - /** - * Create LUV Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static luv(...args) { return Color.from(...args).toMode("luv"); } - /** - * Create XYZ Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static xyz(...args) { return Color.from(...args).toMode("xyz"); } - /** - * Get a Color object whose values are the maximum of its mode - * @param mode a mode string such as "rgb" or "lab" - * @example Color.maxValue("rgb") will return a rgb Color object with values (255,255,255) - */ static maxValues(mode) { return Color.ranges[mode].zipSlice(1).$take([0, 1, 2]); } - /** - * Get a hex string such as "#FF0000". Same as `toString("hex")` - */ get hex() { return this.toString("hex"); } - /** - * Get a rgb string such as "rgb(255,0,0)". Same as `toString("rgb")` - */ get rgb() { return this.toString("rgb"); } - /** - * Get a rgba string such as "rgb(255,0,0,0.5)". Same as `toString("rgba")` - */ get rgba() { return this.toString("rgba"); } - /** - * Clone this Color - */ clone() { let c = new Color(this); c.toMode(this._mode); return c; } - /** - * Convert this color from current color space to another color space - * @param mode a ColorType string: "rgb" "hsl" "hsb" "lab" "lch" "luv" "xyz"; - * @param convert if `true`, convert this Color to the new color space specified in `mode`. Default is `false`, which only sets the color mode without converting color values. - */ toMode(mode, convert = false) { if (convert) { let fname = this._mode.toUpperCase() + "to" + mode.toUpperCase(); @@ -6538,18 +4108,13 @@ class Color extends Pt_1.Pt { this._mode = mode; return this; } - /** - * Get this Color's mode - */ get mode() { return this._mode; } - // rgb get r() { return this[0]; } set r(n) { this[0] = n; } get g() { return this[1]; } set g(n) { this[1] = n; } get b() { return this[1]; } set b(n) { this[2] = n; } - // hsl, hsb get h() { return (this._mode == "lch") ? this[2] : this[0]; } set h(n) { let i = (this._mode == "lch") ? 2 : 0; @@ -6562,7 +4127,6 @@ class Color extends Pt_1.Pt { let i = (this._mode == "hsl") ? 2 : 0; this[i] = n; } - // lab, lch, luv get a() { return this[1]; } set a(n) { this[1] = n; } get c() { return this[1]; } @@ -6571,14 +4135,7 @@ class Color extends Pt_1.Pt { set u(n) { this[1] = n; } get v() { return this[1]; } set v(n) { this[2] = n; } - /** - * Get alpha value - */ get alpha() { return (this.length > 3) ? this[3] : 1; } - /** - * Normalize the color values to between 0 to 1, or revert it back to the min/max values in current color mode - * @param toNorm a boolean value specifying whether to normalize (`true`) or revert (`false`) - */ normalize(toNorm = true) { if (this._isNorm == toNorm) return this; @@ -6591,16 +4148,7 @@ class Color extends Pt_1.Pt { this._isNorm = toNorm; return this; } - /** - * Like `normalize()` but returns as a new Color - * @param toNorm a boolean value specifying whether to normalize (`true`) or revert (`false`) - * @returns new Color - */ $normalize(toNorm = true) { return this.clone().normalize(toNorm); } - /** - * Convert this Color to a string. It can be used to get a hex or rgb string for use in rendering - * @param format "hex", "rgb", "rgba", or "mode" which means using current color mode label. Default is "mode". - */ toString(format = "mode") { if (format == "hex") { let _hex = (n) => { @@ -6619,13 +4167,6 @@ class Color extends Pt_1.Pt { return `${this._mode}(${this[0]},${this[1]},${this[2]},${this.alpha})`; } } - /** - * Convert RGB to HSL - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new HSL Color - */ static RGBtoHSL(rgb, normalizedInput = false, normalizedOutput = false) { let [r, g, b] = (!normalizedInput) ? rgb.$normalize() : rgb; let max = Math.max(r, g, b); @@ -6635,7 +4176,7 @@ class Color extends Pt_1.Pt { let l = h; if (max == min) { h = 0; - s = 0; // achromatic + s = 0; } else { let d = max - min; @@ -6653,13 +4194,6 @@ class Color extends Pt_1.Pt { } return Color.hsl(((normalizedOutput) ? h / 60 : h * 60), s, l, rgb.alpha); } - /** - * Convert HSL to RGB - * @param hsl a HSL Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static HSLtoRGB(hsl, normalizedInput = false, normalizedOutput = false) { let [h, s, l] = hsl; if (!normalizedInput) @@ -6686,13 +4220,6 @@ class Color extends Pt_1.Pt { let sc = (normalizedOutput) ? 1 : 255; return Color.rgb(sc * convert((h + 1 / 3)), sc * convert(h), sc * convert((h - 1 / 3)), hsl.alpha); } - /** - * Convert RGB to HSB - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new HSB Color - */ static RGBtoHSB(rgb, normalizedInput = false, normalizedOutput = false) { let [r, g, b] = (!normalizedInput) ? rgb.$normalize() : rgb; let max = Math.max(r, g, b); @@ -6714,13 +4241,6 @@ class Color extends Pt_1.Pt { } return Color.hsb(((normalizedOutput) ? h / 60 : h * 60), s, v, rgb.alpha); } - /** - * Convert HSB to RGB - * @param hsb a HSB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static HSBtoRGB(hsb, normalizedInput = false, normalizedOutput = false) { let [h, s, v] = hsb; if (!normalizedInput) @@ -6738,79 +4258,30 @@ class Color extends Pt_1.Pt { let sc = (normalizedOutput) ? 1 : 255; return Color.rgb(sc * c[0], sc * c[1], sc * c[2], hsb.alpha); } - /** - * Convert RGB to LAB - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LAB Color - */ static RGBtoLAB(rgb, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? rgb.$normalize(false) : rgb; return Color.XYZtoLAB(Color.RGBtoXYZ(c), false, normalizedOutput); } - /** - * Convert LAB to RGB - * @param lab a LAB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static LABtoRGB(lab, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lab.$normalize(false) : lab; return Color.XYZtoRGB(Color.LABtoXYZ(c), false, normalizedOutput); } - /** - * Convert RGB to LCH - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LCH Color - */ static RGBtoLCH(rgb, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? rgb.$normalize(false) : rgb; return Color.LABtoLCH(Color.RGBtoLAB(c), false, normalizedOutput); } - /** - * Convert LCH to RGB - * @param lch a LCH Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static LCHtoRGB(lch, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lch.$normalize(false) : lch; return Color.LABtoRGB(Color.LCHtoLAB(c), false, normalizedOutput); } - /** - * Convert RGB to LUV - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LUV Color - */ static RGBtoLUV(rgb, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? rgb.$normalize(false) : rgb; return Color.XYZtoLUV(Color.RGBtoXYZ(c), false, normalizedOutput); } - /** - * Convert LUV to RGB - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static LUVtoRGB(luv, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? luv.$normalize(false) : luv; return Color.XYZtoRGB(Color.LUVtoXYZ(c), false, normalizedOutput); } - /** - * Convert RGB to XYZ - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new XYZ Color - */ static RGBtoXYZ(rgb, normalizedInput = false, normalizedOutput = false) { let c = (!normalizedInput) ? rgb.$normalize() : rgb.clone(); for (let i = 0; i < 3; i++) { @@ -6821,13 +4292,6 @@ class Color extends Pt_1.Pt { let cc = Color.xyz(c[0] * 0.4124564 + c[1] * 0.3575761 + c[2] * 0.1804375, c[0] * 0.2126729 + c[1] * 0.7151522 + c[2] * 0.0721750, c[0] * 0.0193339 + c[1] * 0.1191920 + c[2] * 0.9503041, rgb.alpha); return (normalizedOutput) ? cc.normalize() : cc; } - /** - * Convert XYZ to RGB - * @param xyz a XYZ Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static XYZtoRGB(xyz, normalizedInput = false, normalizedOutput = false) { let [x, y, z] = (!normalizedInput) ? xyz.$normalize() : xyz; let rgb = [ @@ -6835,7 +4299,6 @@ class Color extends Pt_1.Pt { x * -0.9692660 + y * 1.8760108 + z * 0.0415560, x * 0.0556434 + y * -0.2040259 + z * 1.0572252 ]; - // convert xyz to rgb. Note that not all colors are visible in rgb, so here we bound rgb between 0 to 1 for (let i = 0; i < 3; i++) { rgb[i] = (rgb[i] < 0) ? 0 : (rgb[i] > 0.0031308) ? (1.055 * Math.pow(rgb[i], 1 / 2.4) - 0.055) : (12.92 * rgb[i]); rgb[i] = Math.max(0, Math.min(1, rgb[i])); @@ -6845,29 +4308,14 @@ class Color extends Pt_1.Pt { let cc = Color.rgb(rgb[0], rgb[1], rgb[2], xyz.alpha); return (normalizedOutput) ? cc.normalize() : cc; } - /** - * Convert XYZ to LAB - * @param xyz a XYZ Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LAB Color - */ static XYZtoLAB(xyz, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? xyz.$normalize(false) : xyz.clone(); - // adjust for D65 c.divide(Color.D65); let fn = (n) => (n > 0.008856) ? Math.pow(n, 1 / 3) : (7.787 * n) + 16 / 116; let cy = fn(c[1]); let cc = Color.lab((116 * cy) - 16, 500 * (fn(c[0]) - cy), 200 * (cy - fn(c[2])), xyz.alpha); return (normalizedOutput) ? cc.normalize() : cc; } - /** - * Convert LAB to XYZ - * @param lab a LAB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new XYZ Color - */ static LABtoXYZ(lab, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lab.$normalize(false) : lab; let y = (c[0] + 16) / 116; @@ -6878,21 +4326,9 @@ class Color extends Pt_1.Pt { return (nnn > 0.008856) ? nnn : (n - 16 / 116) / 7.787; }; let d = Color.D65; - // adjusted - let cc = Color.xyz( - // Math.max(0, Math.min( 100, d[0] * fn(x) )), - // Math.max(0, Math.min( 100, d[1] * fn(y) )), - // Math.max(0, Math.min( 100, d[2] * fn(z) )), - Math.max(0, d[0] * fn(x)), Math.max(0, d[1] * fn(y)), Math.max(0, d[2] * fn(z)), lab.alpha); + let cc = Color.xyz(Math.max(0, d[0] * fn(x)), Math.max(0, d[1] * fn(y)), Math.max(0, d[2] * fn(z)), lab.alpha); return (normalizedOutput) ? cc.normalize() : cc; } - /** - * Convert XYZ to LUV - * @param xyz a XYZ Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LUV Color - */ static XYZtoLUV(xyz, normalizedInput = false, normalizedOutput = false) { let [x, y, z] = (normalizedInput) ? xyz.$normalize(false) : xyz; let u = (4 * x) / (x + (15 * y) + (3 * z)); @@ -6904,13 +4340,6 @@ class Color extends Pt_1.Pt { let L = (116 * y) - 16; return Color.luv(L, 13 * L * (u - refU), 13 * L * (v - refV), xyz.alpha); } - /** - * Convert LUV to XYZ - * @param luv a LUV Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new XYZ Color - */ static LUVtoXYZ(luv, normalizedInput = false, normalizedOutput = false) { let [l, u, v] = (normalizedInput) ? luv.$normalize(false) : luv; let y = (l + 16) / 116; @@ -6925,36 +4354,18 @@ class Color extends Pt_1.Pt { let z = (9 * y - (15 * v * y) - (v * x)) / (3 * v); return Color.xyz(x, y, z, luv.alpha); } - /** - * Convert LAB to LCH - * @param lab a LAB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LCH Color - */ static LABtoLCH(lab, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lab.$normalize(false) : lab; - let h = Num_1.Geom.toDegree(Num_1.Geom.boundRadian(Math.atan2(c[2], c[1]))); // 0 to 360 degrees + let h = Num_1.Geom.toDegree(Num_1.Geom.boundRadian(Math.atan2(c[2], c[1]))); return Color.lch(c[0], Math.sqrt(c[1] * c[1] + c[2] * c[2]), h, lab.alpha); } - /** - * Convert LCH to LAB - * @param lch a LCH Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LAB Color - */ static LCHtoLAB(lch, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lch.$normalize(false) : lch; let rad = Num_1.Geom.toRadian(c[2]); return Color.lab(c[0], Math.cos(rad) * c[1], Math.sin(rad) * c[1], lch.alpha); } } -// XYZ property for Standard Observer 2deg, Daylight/sRGB illuminant D65 Color.D65 = new Pt_1.Pt(95.047, 100, 108.883, 1); -/** - * Value range for each color space - */ Color.ranges = { rgb: new Pt_1.Group(new Pt_1.Pt(0, 255), new Pt_1.Pt(0, 255), new Pt_1.Pt(0, 255)), hsl: new Pt_1.Group(new Pt_1.Pt(0, 360), new Pt_1.Pt(0, 1), new Pt_1.Pt(0, 1)), @@ -6973,8 +4384,6 @@ exports.Color = Color; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Form_1 = __webpack_require__(6); const Num_1 = __webpack_require__(3); @@ -6982,16 +4391,7 @@ const Util_1 = __webpack_require__(1); const Pt_1 = __webpack_require__(0); const Op_1 = __webpack_require__(2); const Dom_1 = __webpack_require__(9); -/** - * A Space for SVG elements - */ class SVGSpace extends Dom_1.DOMSpace { - /** - * Create a SVGSpace which represents a Space for SVG elements - * @param elem Specify an element by its "id" attribute as string, or by the element object itself. An element can be an existing ``, or a `
` container in which a new `` will be created. If left empty, a `
` will be added to DOM. Use css to customize its appearance if needed. - * @param callback an optional callback `function(boundingBox, spaceElement)` to be called when canvas is appended and ready. Alternatively, a "ready" event will also be fired from the `` element when it's appended, which can be traced with `spaceInstance.canvas.addEventListener("ready")` - * @example `new SVGSpace( "#myElementID" )` - */ constructor(elem, callback) { super(elem, callback); this.id = "svgspace"; @@ -7002,22 +4402,10 @@ class SVGSpace extends Dom_1.DOMSpace { this._canvas = s; } } - /** - * Get a new `SVGForm` for drawing - * @see `SVGForm` - */ getForm() { return new SVGForm(this); } - /** - * Get the html element - */ get element() { return this._canvas; } - /** - * This overrides Space's `resize` function. It's used as a callback function for window's resize event and not usually called directly. You can keep track of resize events with `resize: (bound ,evt)` callback in your player objects (See `Space`'s `add()` function). - * @param b a Bound object to resize to - * @param evt Optionally pass a resize event - */ resize(b, evt) { super.resize(b, evt); SVGSpace.setAttr(this.element, { @@ -7029,12 +4417,6 @@ class SVGSpace extends Dom_1.DOMSpace { }); return this; } - /** - * A static function to add a svg element inside a node. Usually you don't need to use this directly. See methods in `SVGForm` instead. - * @param parent the parent element, or `null` to use current `` as parent. - * @param name a string of element name, such as `rect` or `circle` - * @param id id attribute of the new element - */ static svgElement(parent, name, id) { if (!parent || !parent.appendChild) throw new Error("parent is not a valid DOM element"); @@ -7046,10 +4428,6 @@ class SVGSpace extends Dom_1.DOMSpace { } return elem; } - /** - * Remove an item from this Space - * @param item a player item with an auto-assigned `animateID` property - */ remove(player) { let temp = this._container.querySelectorAll("." + SVGForm.scopeID(player)); temp.forEach((el) => { @@ -7057,24 +4435,13 @@ class SVGSpace extends Dom_1.DOMSpace { }); return super.remove(player); } - /** - * Remove all items from this Space - */ removeAll() { this._container.innerHTML = ""; return super.removeAll(); } } exports.SVGSpace = SVGSpace; -/** -* SVGForm is an implementation of abstract class VisualForm. It provide methods to express Pts on SVGSpace. -* You may extend SVGForm to implement your own expressions for SVGSpace. -*/ class SVGForm extends Form_1.VisualForm { - /** - * Create a new SVGForm. You may also use `space.getForm()` to get the default form. - * @param space an instance of SVGSpace - */ constructor(space) { super(); this._ctx = { @@ -7104,25 +4471,12 @@ class SVGForm extends Form_1.VisualForm { this._ready = true; } }); } - /** - * get the SVGSpace instance that this form is associated with - */ get space() { return this._space; } - /** - * Update a style in _ctx context or throw an Erorr if the style doesn't exist - * @param k style key - * @param v style value - */ styleTo(k, v) { if (this._ctx.style[k] === undefined) throw new Error(`${k} style property doesn't exist`); this._ctx.style[k] = v; } - /** - * Set current fill style. Provide a valid color string or `false` to specify no fill color. - * @example `form.fill("#F90")`, `form.fill("rgba(0,0,0,.5")`, `form.fill(false)` - * @param c fill color - */ fill(c) { if (typeof c == "boolean") { this.styleTo("filled", c); @@ -7133,14 +4487,6 @@ class SVGForm extends Form_1.VisualForm { } return this; } - /** - * Set current stroke style. Provide a valid color string or `false` to specify no stroke color. - * @example `form.stroke("#F90")`, `form.stroke("rgba(0,0,0,.5")`, `form.stroke(false)`, `form.stroke("#000", 0.5, 'round', 'square')` - * @param c stroke color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle)) - * @param width Optional value (can be floating point) to set line width - * @param linejoin Optional string to set line joint style. Can be "miter", "bevel", or "round". - * @param linecap Optional string to set line cap style. Can be "butt", "round", or "square". - */ stroke(c, width, linejoin, linecap) { if (typeof c == "boolean") { this.styleTo("stroked", c); @@ -7157,11 +4503,6 @@ class SVGForm extends Form_1.VisualForm { } return this; } - /** - * Add custom class to the created element - * @param c custom class name or `false` to reset it - * @example `form.fill("#f00").cls("myClass").rects(r)` `form.cls(false).circles(c)` - */ cls(c) { if (typeof c == "boolean") { this._ctx.currentClass = ""; @@ -7171,15 +4512,6 @@ class SVGForm extends Form_1.VisualForm { } return this; } - /** - * Set the current font - * @param sizeOrFont either a number to specify font-size, or a `Font` object to specify all font properties - * @param weight Optional font-weight string such as "bold" - * @param style Optional font-style string such as "italic" - * @param lineHeight Optional line-height number suchas 1.5 - * @param family Optional font-family such as "Helvetica, sans-serif" - * @example `form.font( myFont )`, `form.font(14, "bold")` - */ font(sizeOrFont, weight, style, lineHeight, family) { if (typeof sizeOrFont == "number") { this._font.size = sizeOrFont; @@ -7198,9 +4530,6 @@ class SVGForm extends Form_1.VisualForm { } return this; } - /** - * Reset the context's common styles to this form's styles. This supports using multiple forms on the same canvas context. - */ reset() { this._ctx.style = { "filled": true, "stroked": true, @@ -7213,12 +4542,6 @@ class SVGForm extends Form_1.VisualForm { this._ctx.font = this._font.value; return this; } - /** - * Set this form's group scope by an ID, and optionally define the group's parent element. A group scope keeps track of elements by their generated IDs, and updates their properties as needed. See also `scope()`. - * @param group_id a string to use as prefix for the group's id. For example, group_id "hello" will create elements with id like "hello-1", "hello-2", etc - * @param group Optional DOM or SVG element to define this group's parent element - * @returns this form's context - */ updateScope(group_id, group) { this._ctx.group = group; this._ctx.groupID = group_id; @@ -7226,46 +4549,22 @@ class SVGForm extends Form_1.VisualForm { this.nextID(); return this._ctx; } - /** - * Set the current group scope to an item added into space, in order to keep track of any point, circle, etc created within it. The item must have an `animateID` property, so that elements created within the item will have generated IDs like "item-{animateID}-{count}". - * @param item a "player" item that's added to space (see `space.add(...)`) and has an `animateID` property - * @returns this form's context - */ scope(item) { if (!item || item.animateID == null) throw new Error("item not defined or not yet added to Space"); return this.updateScope(SVGForm.scopeID(item), this.space.element); } - /** - * Get next available id in the current group - * @returns an id string - */ nextID() { this._ctx.groupCount++; this._ctx.currentID = `${this._ctx.groupID}-${this._ctx.groupCount}`; return this._ctx.currentID; } - /** - * A static function to generate an ID string based on a context object - * @param ctx a context object for an SVGForm - */ static getID(ctx) { return ctx.currentID || `p-${SVGForm.domID++}`; } - /** - * A static function to generate an ID string for a scope, based on a "player" item in the Space - * @param item a "player" item that's added to space (see `space.add(...)`) and has an `animateID` property - */ static scopeID(item) { return `item-${item.animateID}`; } - /** - * A static function to help adding style object to an element. This put all styles into `style` attribute instead of individual attributes, so that the styles can be parsed by Adobe Illustrator. - * @param elem A DOM element to add to - * @param styles an object of style properties - * @example `SVGForm.style(elem, {fill: "#f90", stroke: false})` - * @returns this DOM element - */ static style(elem, styles) { let st = []; if (!styles["filled"]) @@ -7290,14 +4589,6 @@ class SVGForm extends Form_1.VisualForm { } return Dom_1.DOMSpace.setAttr(elem, { style: st.join(";") }); } - /** - * Draws a point - * @param ctx a context object of SVGForm - * @param pt a Pt object or numeric array - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `SVGForm.point( p )`, `SVGForm.point( p, 10, "circle" )` - */ static point(ctx, pt, radius = 5, shape = "square") { if (shape === "circle") { return SVGForm.circle(ctx, pt, radius); @@ -7306,24 +4597,11 @@ class SVGForm extends Form_1.VisualForm { return SVGForm.square(ctx, pt, radius); } } - /** - * Draws a point - * @param p a Pt object - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `form.point( p )`, `form.point( p, 10, "circle" )` - */ point(pt, radius = 5, shape = "square") { this.nextID(); SVGForm.point(this._ctx, pt, radius, shape); return this; } - /** - * A static function to draw a circle - * @param ctx a context object of SVGForm - * @param pt center position of the circle - * @param radius radius of the circle - */ static circle(ctx, pt, radius = 10) { let elem = SVGSpace.svgElement(ctx.group, "circle", SVGForm.getID(ctx)); Dom_1.DOMSpace.setAttr(elem, { @@ -7335,25 +4613,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw a circle - * @param pts usually a Group of 2 Pts, but it can also take an array of two numeric arrays [ [position], [size] ] - * @see [`Circle.fromCenter`](./_op_.circle.html#frompt) - */ circle(pts) { this.nextID(); SVGForm.circle(this._ctx, pts[0], pts[1][0]); return this; } - /** - * A static function to draw an arc. - * @param ctx a context object of SVGForm - * @param pt center position - * @param radius radius of the arc circle - * @param startAngle start angle of the arc - * @param endAngle end angle of the arc - * @param cc an optional boolean value to specify if it should be drawn clockwise (`false`) or counter-clockwise (`true`). Default is clockwise. - */ static arc(ctx, pt, radius, startAngle, endAngle, cc) { let elem = SVGSpace.svgElement(ctx.group, "path", SVGForm.getID(ctx)); const start = new Pt_1.Pt(pt).toAngle(startAngle, radius, true); @@ -7371,25 +4635,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw an arc. - * @param pt center position - * @param radius radius of the arc circle - * @param startAngle start angle of the arc - * @param endAngle end angle of the arc - * @param cc an optional boolean value to specify if it should be drawn clockwise (`false`) or counter-clockwise (`true`). Default is clockwise. - */ arc(pt, radius, startAngle, endAngle, cc) { this.nextID(); SVGForm.arc(this._ctx, pt, radius, startAngle, endAngle, cc); return this; } - /** - * A static function to draw a square - * @param ctx a context object of SVGForm - * @param pt center position of the square - * @param halfsize half size of the square - */ static square(ctx, pt, halfsize) { let elem = SVGSpace.svgElement(ctx.group, "rect", SVGForm.getID(ctx)); Dom_1.DOMSpace.setAttr(elem, { @@ -7402,21 +4652,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw a square, given a center and its half-size - * @param pt center Pt - * @param halfsize half-size - */ square(pt, halfsize) { this.nextID(); SVGForm.square(this._ctx, pt, halfsize); return this; } - /** - * A static function to draw a line - * @param ctx a context object of SVGForm - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ static line(ctx, pts) { if (!this._checkSize(pts)) return; @@ -7433,21 +4673,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw a line or polyline - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ line(pts) { this.nextID(); SVGForm.line(this._ctx, pts); return this; } - /** - * A static helper function to draw polyline or polygon - * @param ctx a context object of SVGForm - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - * @param closePath a boolean to specify if the polygon path should be closed - */ static _poly(ctx, pts, closePath = true) { if (!this._checkSize(pts)) return; @@ -7460,28 +4690,14 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * A static function to draw polygon - * @param ctx a context object of SVGForm - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ static polygon(ctx, pts) { return SVGForm._poly(ctx, pts, true); } - /** - * Draw a polygon - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ polygon(pts) { this.nextID(); SVGForm.polygon(this._ctx, pts); return this; } - /** - * A static function to draw a rectangle - * @param ctx a context object of SVGForm - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ static rect(ctx, pts) { if (!this._checkSize(pts)) return; @@ -7498,22 +4714,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw a rectangle - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ rect(pts) { this.nextID(); SVGForm.rect(this._ctx, pts); return this; } - /** - * A static function to draw text - * @param ctx a context object of SVGForm - * @param `pt` a Point object to specify the anchor point - * @param `txt` a string of text to draw - * @param `maxWidth` specify a maximum width per line - */ static text(ctx, pt, txt) { let elem = SVGSpace.svgElement(ctx.group, "text", SVGForm.getID(ctx)); Dom_1.DOMSpace.setAttr(elem, { @@ -7527,21 +4732,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw text on canvas - * @param `pt` a Pt or numeric array to specify the anchor point - * @param `txt` text - * @param `maxWidth` specify a maximum width per line - */ text(pt, txt) { this.nextID(); SVGForm.text(this._ctx, pt, txt); return this; } - /** - * A convenient way to draw some text on canvas for logging or debugging. It'll be draw on the top-left of the canvas as an overlay. - * @param txt text - */ log(txt) { this.fill("#000").stroke("#fff", 0.5).text([10, 14], txt); return this; @@ -7552,6 +4747,358 @@ SVGForm.domID = 0; exports.SVGForm = SVGForm; +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const Pt_1 = __webpack_require__(0); +const Bound_1 = __webpack_require__(4); +const Op_1 = __webpack_require__(2); +class World { + constructor(bound, friction = 1, gravity = 0) { + this._lastTime = null; + this._gravity = new Pt_1.Pt(); + this._friction = 1; + this._damping = 0.75; + this._particles = []; + this._bodies = []; + this._names = { p: {}, b: {} }; + this._bound = Bound_1.Bound.fromGroup(bound); + this._friction = friction; + this._gravity = (typeof gravity === "number") ? new Pt_1.Pt(0, gravity) : new Pt_1.Pt(gravity); + return this; + } + get gravity() { return this._gravity; } + set gravity(g) { this._gravity = g; } + get friction() { return this._friction; } + set friction(f) { this._friction = f; } + get damping() { return this._damping; } + set damping(f) { this._damping = f; } + get bodyCount() { return this._bodies.length; } + get particleCount() { return this._particles.length; } + body(id) { return this._bodies[(typeof id === "string") ? this._names.b[id] : id]; } + particle(id) { return this._particles[(typeof id === "string") ? this._names.p[id] : id]; } + update(ms) { + let dt = ms / 1000; + this._updateParticles(dt); + this._updateBodies(dt); + } + drawParticles(fn) { + this._drawParticles = fn; + } + drawBodies(fn) { + this._drawBodies = fn; + } + add(p, name) { + if (p instanceof Body) { + this._bodies.push(p); + if (name) + this._names.b[name] = this._bodies.length - 1; + } + else { + this._particles.push(p); + if (name) + this._names.p[name] = this._particles.length - 1; + } + return this; + } + remove(which, index, count = 1) { + let param = (index < 0) ? [index * -1 - 1, count] : [index, count]; + if (which == "body") { + this._bodies.splice(param[0], param[1]); + } + else { + this._particles.splice(param[0], param[1]); + } + return this; + } + static edgeConstraint(p1, p2, dist, stiff = 1, precise = false) { + const m1 = 1 / (p1.mass || 1); + const m2 = 1 / (p2.mass || 1); + const mm = m1 + m2; + let delta = p2.$subtract(p1); + let distSq = dist * dist; + let d = (precise) ? (dist / delta.magnitude() - 1) : (distSq / (delta.dot(delta) + distSq) - 0.5); + let f = delta.$multiply(d * stiff); + p1.subtract(f.$multiply(m1 / mm)); + p2.add(f.$multiply(m2 / mm)); + return p1; + } + static boundConstraint(p, rect, damping = 0.75) { + let bound = rect.boundingBox(); + let np = p.$min(bound[1].subtract(p.radius)).$max(bound[0].add(p.radius)); + if (np[0] === bound[0][0] || np[0] === bound[1][0]) { + let c = p.changed.$multiply(damping); + p.previous = np.$subtract(new Pt_1.Pt(-c[0], c[1])); + } + else if (np[1] === bound[0][1] || np[1] === bound[1][1]) { + let c = p.changed.$multiply(damping); + p.previous = np.$subtract(new Pt_1.Pt(c[0], -c[1])); + } + p.to(np); + } + integrate(p, dt, prevDt) { + p.addForce(this._gravity); + p.verlet(dt, this._friction, prevDt); + return p; + } + _updateParticles(dt) { + for (let i = 0, len = this._particles.length; i < len; i++) { + let p = this._particles[i]; + this.integrate(p, dt, this._lastTime); + World.boundConstraint(p, this._bound, this._damping); + for (let k = i + 1; k < len; k++) { + if (i !== k) { + let p2 = this._particles[k]; + p.collide(p2, this._damping); + } + } + if (this._drawParticles) + this._drawParticles(p, i); + } + this._lastTime = dt; + } + _updateBodies(dt) { + for (let i = 0, len = this._bodies.length; i < len; i++) { + let b = this._bodies[i]; + for (let k = 0, klen = b.length; k < klen; k++) { + let bk = b[k]; + World.boundConstraint(bk, this._bound, this._damping); + this.integrate(bk, dt, this._lastTime); + } + for (let k = i + 1; k < len; k++) { + b.processBody(this._bodies[k]); + } + for (let m = 0, mlen = this._particles.length; m < mlen; m++) { + b.processParticle(this._particles[m]); + } + b.processEdges(); + if (this._drawBodies) + this._drawBodies(b, i); + } + } +} +exports.World = World; +class Particle extends Pt_1.Pt { + constructor(...args) { + super(...args); + this._mass = 1; + this._radius = 0; + this._force = new Pt_1.Pt(); + this._prev = new Pt_1.Pt(); + this._lock = false; + this._prev = this.clone(); + } + get mass() { return this._mass; } + set mass(m) { this._mass = m; } + get radius() { return this._radius; } + set radius(f) { this._radius = f; } + get previous() { return this._prev; } + set previous(p) { this._prev = p; } + get force() { return this._force; } + set force(g) { this._force = g; } + get body() { return this._body; } + set body(b) { this._body = b; } + get lock() { return this._lock; } + set lock(b) { + this._lock = b; + this._lockPt = new Pt_1.Pt(this); + } + get changed() { return this.$subtract(this._prev); } + set position(p) { + this.previous.to(this); + if (this._lock) + this._lockPt = p; + this.to(p); + } + size(r) { + this._mass = r; + this._radius = r; + return this; + } + addForce(...args) { + this._force.add(...args); + return this._force; + } + verlet(dt, friction, lastDt) { + if (this._lock) { + this.to(this._lockPt); + } + else { + let lt = (lastDt) ? lastDt : dt; + let a = this._force.multiply(dt * (dt + lt) / 2); + let v = this.changed.multiply(friction * dt / lt).add(a); + this._prev = this.clone(); + this.add(v); + this._force = new Pt_1.Pt(); + } + return this; + } + hit(...args) { + this._prev.subtract(new Pt_1.Pt(...args).$divide(Math.sqrt(this._mass))); + return this; + } + collide(p2, damp = 1) { + let p1 = this; + let dp = p1.$subtract(p2); + let distSq = dp.magnitudeSq(); + let dr = p1.radius + p2.radius; + if (distSq < dr * dr) { + let c1 = p1.changed; + let c2 = p2.changed; + let dist = Math.sqrt(distSq); + let d = dp.$multiply(((dist - dr) / dist) / 2); + let np1 = p1.$subtract(d); + let np2 = p2.$add(d); + p1.to(np1); + p2.to(np2); + let f1 = damp * dp.dot(c1) / distSq; + let f2 = damp * dp.dot(c2) / distSq; + let dm1 = p1.mass / (p1.mass + p2.mass); + let dm2 = p2.mass / (p1.mass + p2.mass); + c1.add(new Pt_1.Pt(f2 * dp[0] - f1 * dp[0], f2 * dp[1] - f1 * dp[1]).$multiply(dm2)); + c2.add(new Pt_1.Pt(f1 * dp[0] - f2 * dp[0], f1 * dp[1] - f2 * dp[1]).$multiply(dm1)); + p1.previous = p1.$subtract(c1); + p2.previous = p2.$subtract(c2); + } + } + toString() { + return `Particle: ${this[0]} ${this[1]} | previous ${this._prev[0]} ${this._prev[1]} | mass ${this._mass}`; + } +} +exports.Particle = Particle; +class Body extends Pt_1.Group { + constructor() { + super(); + this._cs = []; + this._stiff = 1; + this._locks = {}; + this._mass = 1; + } + static fromGroup(list, stiff = 1, autoLink = true, autoMass = true) { + let b = new Body().init(list); + if (autoLink) + b.linkAll(stiff); + if (autoMass) + b.autoMass(); + return b; + } + init(list, stiff = 1) { + let c = new Pt_1.Pt(); + for (let i = 0, len = list.length; i < len; i++) { + let p = new Particle(list[i]); + p.body = this; + c.add(list[i]); + this.push(p); + } + this._stiff = stiff; + return this; + } + get mass() { return this._mass; } + set mass(m) { + this._mass = m; + for (let i = 0, len = this.length; i < len; i++) { + this[i].mass = this._mass; + } + } + autoMass() { + this.mass = Math.sqrt(Op_1.Polygon.area(this)) / 10; + return this; + } + link(index1, index2, stiff) { + if (index1 < 0 || index1 >= this.length) + throw new Error("index1 is not in the Group's indices"); + if (index2 < 0 || index2 >= this.length) + throw new Error("index1 is not in the Group's indices"); + let d = this[index1].$subtract(this[index2]).magnitude(); + this._cs.push([index1, index2, d, stiff || this._stiff]); + return this; + } + linkAll(stiff) { + let half = this.length / 2; + for (let i = 0, len = this.length; i < len; i++) { + let n = (i >= len - 1) ? 0 : i + 1; + this.link(i, n, stiff); + if (len > 4) { + let nd = (Math.floor(half / 2)) + 1; + let n2 = (i >= len - nd) ? i % len : i + nd; + this.link(i, n2, stiff); + } + if (i <= half - 1) { + this.link(i, Math.min(this.length - 1, i + Math.floor(half))); + } + } + } + linksToLines() { + let gs = []; + for (let i = 0, len = this._cs.length; i < len; i++) { + let ln = this._cs[i]; + gs.push(new Pt_1.Group(this[ln[0]], this[ln[1]])); + } + return gs; + } + processEdges() { + for (let i = 0, len = this._cs.length; i < len; i++) { + let [m, n, d, s] = this._cs[i]; + World.edgeConstraint(this[m], this[n], d, s); + } + } + processBody(b) { + let b1 = this; + let b2 = b; + let hit = Op_1.Polygon.hasIntersectPolygon(b1, b2); + if (hit) { + let cv = hit.normal.$multiply(hit.dist); + let t; + let eg = hit.edge; + if (Math.abs(eg[0][0] - eg[1][0]) > Math.abs(eg[0][1] - eg[1][1])) { + t = (hit.vertex[0] - cv[0] - eg[0][0]) / (eg[1][0] - eg[0][0]); + } + else { + t = (hit.vertex[1] - cv[1] - eg[0][1]) / (eg[1][1] - eg[0][1]); + } + let lambda = 1 / (t * t + (1 - t) * (1 - t)); + let m0 = hit.vertex.body.mass || 1; + let m1 = hit.edge[0].body.mass || 1; + let mr0 = m0 / (m0 + m1); + let mr1 = m1 / (m0 + m1); + eg[0].subtract(cv.$multiply(mr0 * (1 - t) * lambda / 2)); + eg[1].subtract(cv.$multiply(mr0 * t * lambda / 2)); + hit.vertex.add(cv.$multiply(mr1)); + } + } + processParticle(b) { + let b1 = this; + let b2 = b; + let hit = Op_1.Polygon.hasIntersectCircle(b1, Op_1.Circle.fromCenter(b, b.radius)); + if (hit) { + let cv = hit.normal.$multiply(hit.dist); + let t; + let eg = hit.edge; + if (Math.abs(eg[0][0] - eg[1][0]) > Math.abs(eg[0][1] - eg[1][1])) { + t = (hit.vertex[0] - cv[0] - eg[0][0]) / (eg[1][0] - eg[0][0]); + } + else { + t = (hit.vertex[1] - cv[1] - eg[0][1]) / (eg[1][1] - eg[0][1]); + } + let lambda = 1 / (t * t + (1 - t) * (1 - t)); + let m0 = hit.vertex.mass || b2.mass || 1; + let m1 = hit.edge[0].body.mass || 1; + let mr0 = m0 / (m0 + m1); + let mr1 = m1 / (m0 + m1); + eg[0].subtract(cv.$multiply(mr0 * (1 - t) * lambda / 2)); + eg[1].subtract(cv.$multiply(mr0 * t * lambda / 2)); + let c1 = b.changed.add(cv.$multiply(mr1)); + b.previous = b.$subtract(c1); + } + } +} +exports.Body = Body; + + /***/ }) /******/ ]); }); \ No newline at end of file diff --git a/dist/pts.d.ts b/dist/pts.d.ts index 71f8d6a2..3404e3a2 100644 --- a/dist/pts.d.ts +++ b/dist/pts.d.ts @@ -1,6 +1,8 @@ // Generated by dts-bundle v0.7.3 +export {}; +export {}; /** * Bound is a subclass of Group that represents a rectangular boundary. @@ -1992,6 +1994,14 @@ export class Range { ticks(count: number): Group; } +export type IntersectContext = { + which: number; + dist: number; + normal: Pt; + vertex: Pt; + edge: Group; + other?: any; +}; /** * Line class provides static functions to create and operate on lines. A line is usually represented as a Group of 2 Pts. * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. @@ -2090,7 +2100,14 @@ export class Line { * @param poly a Group of Pts representing a polygon * @param sourceIsRay a boolean value to treat the line as a ray (infinite line). Default is `false`. */ - static intersectPolygon2D(lineOrRay: GroupLike, poly: GroupLike[], sourceIsRay?: boolean): Group; + static intersectPolygon2D(lineOrRay: GroupLike, poly: GroupLike, sourceIsRay?: boolean): Group; + /** + * Find intersection points of 2 polygons. This checks all line segments in the two lists. Consider using a bounding-box check before calling this. + * @param lines1 an array of line segments + * @param lines2 an array of line segments + * @param isRay a boolean value to treat the line as a ray (infinite line). Default is `false`. + */ + static intersectLines2D(lines1: GroupLike[], lines2: GroupLike[], isRay?: boolean): Group; /** * Get two intersection Pts of a ray with a 2D grid point * @param ray a ray specified by 2 Pts @@ -2107,9 +2124,10 @@ export class Line { static intersectGridWithLine2D(line: GroupLike, gridPt: PtLike | number[]): Group; /** * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. + * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. * @param line a Group representing a line * @param rect a Group representing a rectangle + * @returns a Group of intersecting Pts */ static intersectRect2D(line: GroupLike, rect: GroupLike): Group; /** @@ -2118,6 +2136,24 @@ export class Line { * @param num number of points to get */ static subpoints(line: GroupLike | number[][], num: number): Group; + /** + * Crop this line by a circle or rectangle at end point. + * @param line line to crop + * @param size size of circle or rectangle as Pt + * @param index line's end point index, ie, 0 = start and 1 = end. + * @param cropAsCircle a boolean to specify whether the `size` parameter should be treated as circle. Default is `true`. + * @return an intersecting point on the line that can be used for cropping. + */ + static crop(line: GroupLike, size: PtLike, index?: number, cropAsCircle?: boolean): Pt; + /** + * Create an marker arrow or line, placed at an end point of this line + * @param line line to place marker + * @param size size of the marker as Pt + * @param graphic either "arrow" or "line" + * @param atTail a boolean, if `true`, the marker will be positioned at tail of the line (ie, index = 1). Default is `true`. + * @returns a Group that defines the marker's shape + */ + static marker(line: GroupLike, size: PtLike, graphic?: string, atTail?: boolean): Group; /** * Convert this line to a rectangle representation * @param line a Group representing a line @@ -2220,11 +2256,12 @@ export class Rectangle { * Check if a rectangle is within the bounds of another rectangle * @param rect1 a Group of 2 Pts representing a rectangle * @param rect2 a Group of 2 Pts representing a rectangle + * @param resetBoundingBox if `true`, reset the bounding box. Default is `false` which assumes the rect's first Pt at is its top-left corner. */ - static hasIntersectRect2D(rect1: GroupLike, rect2: GroupLike): boolean; + static hasIntersectRect2D(rect1: GroupLike, rect2: GroupLike, resetBoundingBox?: boolean): boolean; /** * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. + * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. * @param rect1 a Group of 2 Pts representing a rectangle * @param rect2 a Group of 2 Pts representing a rectangle */ @@ -2278,7 +2315,7 @@ export class Circle { static intersectCircle2D(pts: GroupLike, circle: GroupLike): Group; /** * Quick way to check rectangle intersection with a circle. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersect2D()`. + * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. * @param pts a Group of 2 Pts representing a circle * @param rect a Group of 2 Pts representing a rectangle * @returns a Group of intersection points, or an empty Group if no intersection is found @@ -2384,6 +2421,20 @@ export class Polygon { * @param pts a Group of Pts representing a polygon */ static centroid(pts: GroupLike): Pt; + /** + * Create a rectangular polygon + * @param center center point of the rectangle + * @param widthOrSize width as number, or a Pt representing the size of the rectangle + * @param height optional height + */ + static rectangle(center: PtLike, widthOrSize: number | PtLike, height?: number): Group; + static fromCenter(center: PtLike, radius: number, sides: number): Group; + /** + * Given a Group of Pts that defines a polygon, get one edge using an index + * @param pts a Group + * @param idx index of a Pt in the Group + */ + static lineAt(pts: GroupLike, idx: number): Group; /** * Get the line segments in this polygon * @param pts a Group of Pts @@ -2436,13 +2487,6 @@ export class Polygon { * @returns a group of Pt that defines the convex hull polygon */ static convexHull(pts: GroupLike, sorted?: boolean): Group; - /** - * Find intersection points of 2 polygons - * @param poly a Group representing a polygon - * @param linesOrRays an array of Groups representing lines - * @param sourceIsRay a boolean value to treat the line as a ray (infinite line). Default is `false`. - */ - static intersect2D(poly: GroupLike[], linesOrRays: GroupLike[], sourceIsRay?: boolean): Group[]; /** * Given a point in the polygon as an origin, get an array of lines that connect all the remaining points to the origin point. * @param pts a Group representing a polygon @@ -2456,11 +2500,50 @@ export class Polygon { * @returns an index in the pts indicating the nearest Pt, or -1 if none found */ static nearestPt(pts: GroupLike, pt: PtLike): number; + /** + * Project axis (eg, for use in Separation Axis Theorem) + * @param poly + * @param unitAxis + */ + static projectAxis(poly: GroupLike, unitAxis: Pt): Pt; + /** + * Check overlap dist from projected axis + * @param poly1 first polygon + * @param poly2 second polygon + * @param unitAxis unit axis + */ + protected static _axisOverlap(poly1: any, poly2: any, unitAxis: any): number; + /** + * Check if a Pt is inside a convex polygon + * @param poly a Group of Pt defining a convex polygon + * @param pt the Pt to check + */ + static hasIntersectPoint(poly: GroupLike, pt: PtLike): boolean; + /** + * Check if a convex polygon and a circle has intersections using Separating Axis Theorem. + * @param poly a Group representing a convex polygon + * @param circle a Group representing a circle + * @returns an `IntersectContext` object that stores the intersection info, or undefined if there's no intersection + */ + static hasIntersectCircle(poly: GroupLike, circle: GroupLike): IntersectContext; + /** + * Check if two convex polygons has intersections using Separating Axis Theorem. + * @param poly1 a Group representing a convex polygon + * @param poly2 a Group representing a convex polygon + * @return an `IntersectContext` object that stores the intersection info, or undefined if there's no intersection + */ + static hasIntersectPolygon(poly1: GroupLike, poly2: GroupLike): IntersectContext; + /** + * Find intersection points of 2 polygons by checking every side of both polygons + * @param poly1 a Group representing a polygon + * @param poly2 another Group representing a polygon + */ + static intersectPolygon2D(poly1: GroupLike, poly2: GroupLike): Group; /** * Get a bounding box for each polygon group, as well as a union bounding-box for all groups * @param polys an array of Groups, or an array of Pt arrays */ - static toRects(poly: GroupLike[]): GroupLike[]; + static toRects(polys: GroupLike[]): GroupLike[]; } /** * Curve class provides static functions to interpolate curves. A curve is usually represented as a Group of 3 control points. @@ -2556,6 +2639,252 @@ export class Curve { static bsplineTensionStep(step: Pt, ctrls: GroupLike, tension?: number): Pt; } +/** + * A `World` stores and manages `Body` and `Particle` for 2D physics simulation + */ +export class World { + protected _gravity: Pt; + protected _friction: number; + protected _damping: number; + protected _bound: Bound; + protected _particles: Particle[]; + protected _bodies: Body[]; + protected _names: { + p: {}; + b: {}; + }; + protected _drawParticles: (p: Particle, i: number) => void; + protected _drawBodies: (p: Body, i: number) => void; + /** + * Create a `World` for 2D physics simulation + * @param bound a rectangular bounding box defined by a Group + * @param friction a value between 0 to 1 where 1 means no friction. Default is 1 + * @param gravity a number of a Pt to define gravitational force. Using a number is a shorthand to set `new Pt(0, n)`. Default is 0. + */ + constructor(bound: Group, friction?: number, gravity?: PtLike | number); + gravity: Pt; + friction: number; + damping: number; + /** + * Get the number of bodies + */ + readonly bodyCount: number; + /** + * Get the number of particles + */ + readonly particleCount: number; + /** + * Get a body in this world by index or string id + * @param id numeric index of the body, or a string id that associates with it. + */ + body(id: number | string): Body; + /** + * Get a particle in this world by index or string id + * @param id numeric index of the particle, or a string id that associates with it. + */ + particle(id: number): Particle; + /** + * Update this world one time step + * @param ms change in time in milliseconds + */ + update(ms: number): void; + /** + * Draw particles using the provided function + * @param fn a function that draws particles passed in the parameters `(particles, index)`. + */ + drawParticles(fn: (p: Particle, i: number) => void): void; + /** + * Draw bodies using the provided function + * @param fn a function that draws bodies passed in the parameters `(bodies, index)`. + */ + drawBodies(fn: (p: Body, i: number) => void): void; + /** + * Add a particle or body to this world. + * @param p `Particle` or `Body` instance + * @param name optional name, which can be referenced in `body()` or `particle()` function to retrieve this back. + */ + add(p: Particle | Body, name?: string): this; + /** + * Remove either body or particle from this world. Support removing a range and negative index. + * @param which Either "body" or "particle" + * @param index Start index, which can be negative (where -1 is at index 0, -2 at index 1, etc) + * @param count Number of items to remove. Default is 1. + */ + remove(which: "body" | "particle", index: number, count?: number): this; + /** + * Static function to calculate edge constraints between 2 particles. + * @param p1 particle 1 + * @param p2 particle 1 + * @param dist distance between particles + * @param stiff stiffness between 0 to 1. + * @param precise use precise distance calculation. Default is `false`. + */ + static edgeConstraint(p1: Particle, p2: Particle, dist: number, stiff?: number, precise?: boolean): Particle; + /** + * Static function to calculate bounding box constraints. + * @param p particle + * @param rect bounding box defined by a Group + * @param damping damping between 0 to 1, where 1 means no damping. Default is 0.75. + */ + static boundConstraint(p: Particle, rect: Group, damping?: number): void; + /** + * Internal integrate function + * @param p particle + * @param dt time changed + * @param prevDt previous change in time, optional + */ + protected integrate(p: Particle, dt: number, prevDt?: number): Particle; + /** + * Internal function to update particles + */ + protected _updateParticles(dt: number): void; + /** + * Internal function to update bodies + */ + protected _updateBodies(dt: number): void; +} +/** + * Particle is a Pt that has radius and mass. It's usually added into `World` to create physics simulations. + */ +export class Particle extends Pt { + protected _mass: number; + protected _radius: number; + protected _force: Pt; + protected _prev: Pt; + protected _body: Body; + protected _lock: boolean; + protected _lockPt: Pt; + /** + * Create a particle + * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties + */ + constructor(...args: any[]); + mass: number; + radius: number; + /** + * Get previous position + */ + previous: Pt; + /** + * Get current accumulated force + */ + force: Pt; + /** + * Get the body of this particle, if any. + */ + body: Body; + /** + * + */ + lock: boolean; + /** + * Get the change in position since last time step + */ + readonly changed: Pt; + /** + * Set a new position, and update previous and lock states if needed. + */ + position: Pt; + /** + * Set the size of this particle. This sets both the radius and the mass. + * @param r `radius` value, and also set `mass` to the same value. + */ + size(r: number): this; + /** + * Add to the accumulated force + * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties + */ + addForce(...args: any[]): Pt; + /** + * Verlet integration + * @param dt change in time + * @param friction friction from 0 to 1, where 1 means no friction + * @param lastDt optional last change in time + */ + verlet(dt: number, friction: number, lastDt?: number): this; + /** + * Hit this particle with an impulse + * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties + * @example `hit(10, 20)`, `hit( new Pt(5, 9) )` + */ + hit(...args: any[]): this; + /** + * Check and respoond to collisions between two particles + * @param p2 another particle + * @param damp damping value between 0 to 1, where 1 means no damping. + */ + collide(p2: Particle, damp?: number): void; + toString(): string; +} +/** + * Body consists of a group of `Particles` and edge constraints. It is usually added into a `World` to create physics simulations + */ +export class Body extends Group { + protected _cs: Array; + protected _stiff: number; + protected _locks: { + [index: string]: Particle; + }; + protected _mass: number; + /** + * Create an empty Body, this is usually followed by `init` to populate the Body. Alternatively, use static function `fromGroup` to create and initate a body directly. + */ + constructor(); + /** + * Create and populate a body with a group of Pts. + * @param list a group of Pts + * @param stiff stiffness value from 0 to 1, where 1 is the most stiff. Default is 1. + * @param autoLink Automatically create links between the Pts. This usually works for regular convex polygons. Default is true. + * @param autoMass Automatically calculate the mass based on the area of the polygon. Default is true. + */ + static fromGroup(list: GroupLike, stiff?: number, autoLink?: boolean, autoMass?: boolean): Body; + /** + * Initiate a body + * @param list a group of Pts + * @param stiff stiffness value from 0 to 1, where 1 is the most stiff. Default is 1. + */ + init(list: GroupLike, stiff?: number): this; + /** + * Get mass of this body. + */ + mass: number; + /** + * Automatically calculate `mass` to body's polygon area. + */ + autoMass(): this; + /** + * Create a linked edge between two points + * @param index1 first point by index + * @param index2 first point by index + * @param stiff optionally stiffness value between 0 to 1, where 1 is the most stiff. + */ + link(index1: number, index2: number, stiff?: number): this; + /** + * Automatically create links for all the points to preserve the initial body shape. This usually works for regular convex polygon. + * @param stiff optionally stiffness value between 0 to 1, where 1 is the most stiff. + */ + linkAll(stiff: number): void; + /** + * Return a list of all the linked edges as line segments. + * @returns an array of Groups, each of which represents an edge + */ + linksToLines(): Group[]; + /** + * Recalculate all edge constraints + */ + processEdges(): void; + /** + * Check and respond to collisions between two bodies + * @param b another body + */ + processBody(b: Body): void; + /** + * Check and respond to collisions between this body and a particle + * @param b a particle + */ + processParticle(b: Particle): void; +} + export interface IPt { x?: number; y?: number; @@ -2705,10 +3034,16 @@ export class Pt extends PtBaseArray implements IPt, Iterable { */ $cross(...args: any[]): Pt; /** - * Calculate vector projection of this Pt on another Pt. Returns result as a new Pt. + * Calculate vector projection of this Pt on another Pt. * @param p a list of numbers, an array of number, or an object with {x,y,z,w} properties + * @returns the projection vector as a Pt */ - $project(p: Pt): Pt; + $project(...args: any[]): Pt; + /** + * Calculate scalar projection + * @param p a list of numbers, an array of number, or an object with {x,y,z,w} properties + */ + projectScalar(...args: any[]): number; /** * Absolute values for all values in this pt */ @@ -3538,7 +3873,15 @@ export class Typography { * @param ratio font-size change ratio. Default is 1. * @returns a function where input parameter is a new box, and returns the new font size value */ - static fontSizeToBox(box: GroupLike, ratio?: number): (b: GroupLike) => number; + static fontSizeToBox(box: GroupLike, ratio?: number, byHeight?: boolean): (GroupLike) => number; + /** + * Get a function to scale font size based on a threshold value + * @param defaultSize default font size to base on + * @param threshold threshold value + * @param direction if negative, get a font size <= defaultSize; if positive, get a font size >= defaultSize; Default is 0 which will scale font without min or max limits. + * @returns a function where input parameter is the default font size and a value to compare with threshold, and returns new font size value + */ + static fontSizeToThreshold(threshold: number, direction?: number): (a: number, b: number) => number; } /** diff --git a/dist/pts.js b/dist/pts.js index 9550c7f0..9c7b27de 100644 --- a/dist/pts.js +++ b/dist/pts.js @@ -84,28 +84,15 @@ return /******/ (function(modules) { // webpackBootstrap "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = __webpack_require__(1); const Num_1 = __webpack_require__(3); const LinearAlgebra_1 = __webpack_require__(5); exports.PtBaseArray = Float32Array; -/** - * Pt is a subclass of Float32Array with additional properties and functions to support vector and geometric calculations. - * See [Pt guide](../../guide/Pt-0200.html) for details - */ class Pt extends exports.PtBaseArray { - /** - * Create a Pt. If no parameter is provided, this will instantiate a Pt with 2 dimensions [0, 0]. - * - * Note that `new Pt(3)` will only instantiate Pt with length of 3 (ie, same as `new Float32Array(3)` ). If you need a Pt with 1 dimension of value 3, use `new Pt([3])`. - * @example `new Pt()`, `new Pt(1,2,3,4,5)`, `new Pt([1,2])`, `new Pt({x:0, y:1})`, `new Pt(pt)` - * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ constructor(...args) { if (args.length === 1 && typeof args[0] == "number") { - super(args[0]); // init with the TypedArray's length. Needed this in order to make ".map", ".slice" etc work. + super(args[0]); } else { super((args.length > 0) ? Util_1.Util.getArgs(args) : [0, 0]); @@ -132,17 +119,9 @@ class Pt extends exports.PtBaseArray { set y(n) { this[1] = n; } set z(n) { this[2] = n; } set w(n) { this[3] = n; } - /** - * Clone this Pt - */ clone() { return new Pt(this); } - /** - * Check if another Pt is equal to this Pt, within a threshold - * @param p another Pt to compare with - * @param threshold a threshold value within which the two Pts are considered equal. Default is 0.000001. - */ equals(p, threshold = 0.000001) { for (let i = 0, len = this.length; i < len; i++) { if (Math.abs(this[i] - p[i]) > threshold) @@ -150,10 +129,6 @@ class Pt extends exports.PtBaseArray { } return true; } - /** - * Update the values of this Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ to(...args) { let p = Util_1.Util.getArgs(args); for (let i = 0, len = Math.min(this.length, p.length); i < len; i++) { @@ -161,42 +136,20 @@ class Pt extends exports.PtBaseArray { } return this; } - /** - * Like `to()` but returns a new Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $to(...args) { return this.clone().to(...args); } - /** - * Update the values of this Pt to point at a specific angle - * @param radian target angle in radian - * @param magnitude Optional magnitude if known. If not provided, it'll calculate and use this Pt's magnitude. - * @param anchorFromPt If `true`, translate to new position from current position. Default is `false` which update the position from origin (0,0); - */ toAngle(radian, magnitude, anchorFromPt = false) { let m = (magnitude != undefined) ? magnitude : this.magnitude(); let change = [Math.cos(radian) * m, Math.sin(radian) * m]; return (anchorFromPt) ? this.add(change) : this.to(change); } - /** - * Create an operation using this Pt, passing this Pt into a custom function's first parameter. See the [Op guide](../../guide/Op-0400.html) for details. - * For example: `let myOp = pt.op( fn ); let result = myOp( [1,2,3] );` - * @param fn any function that takes a Pt as its first parameter - * @returns a resulting function that takes other parameters required in `fn` - */ op(fn) { let self = this; return (...params) => { return fn(self, ...params); }; } - /** - * This combines a series of operations into an array. See `op()` for details. - * For example: `let myOps = pt.ops([fn1, fn2, fn3]); let results = myOps.map( (op) => op([1,2,3]) );` - * @param fns an array of functions for `op` - * @returns an array of resulting functions - */ ops(fns) { let _ops = []; for (let i = 0, len = fns.length; i < len; i++) { @@ -204,10 +157,6 @@ class Pt extends exports.PtBaseArray { } return _ops; } - /** - * Take specific dimensional values from this Pt and create a new Pt - * @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions - */ $take(axis) { let p = []; for (let i = 0, len = axis.length; i < len; i++) { @@ -215,181 +164,79 @@ class Pt extends exports.PtBaseArray { } return new Pt(p); } - /** - * Concatenate this Pt with addition dimensional values and return as a new Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $concat(...args) { return new Pt(this.toArray().concat(Util_1.Util.getArgs(args))); } - /** - * Add scalar or vector values to this Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ add(...args) { (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.add(this, args[0]) : LinearAlgebra_1.Vec.add(this, Util_1.Util.getArgs(args)); return this; } - /** - * Like `add`, but returns result as a new Pt - */ $add(...args) { return this.clone().add(...args); } - /** - * Subtract scalar or vector values from this Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ subtract(...args) { (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.subtract(this, args[0]) : LinearAlgebra_1.Vec.subtract(this, Util_1.Util.getArgs(args)); return this; } - /** - * Like `subtract`, but returns result as a new Pt - */ $subtract(...args) { return this.clone().subtract(...args); } - /** - * Multiply scalar or vector values (as element-wise) with this Pt. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ multiply(...args) { (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.multiply(this, args[0]) : LinearAlgebra_1.Vec.multiply(this, Util_1.Util.getArgs(args)); return this; } - /** - * Like `multiply`, but returns result as a new Pt - */ $multiply(...args) { return this.clone().multiply(...args); } - /** - * Divide this Pt over scalar or vector values (as element-wise) - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ divide(...args) { (args.length === 1 && typeof args[0] == "number") ? LinearAlgebra_1.Vec.divide(this, args[0]) : LinearAlgebra_1.Vec.divide(this, Util_1.Util.getArgs(args)); return this; } - /** - * Like `divide`, but returns result as a new Pt - */ $divide(...args) { return this.clone().divide(...args); } - /** - * Get the sqaured distance (magnitude) of this Pt from origin - */ magnitudeSq() { return LinearAlgebra_1.Vec.dot(this, this); } - /** - * Get the distance (magnitude) of this Pt from origin - */ magnitude() { return LinearAlgebra_1.Vec.magnitude(this); } - /** - * Convert to a unit vector, which is a normalized vector whose magnitude equals 1. - * @param magnitude Optional: if the magnitude is known, pass it as a parameter to avoid duplicate calculation. - */ unit(magnitude = undefined) { LinearAlgebra_1.Vec.unit(this, magnitude); return this; } - /** - * Get a unit vector from this Pt - */ $unit(magnitude = undefined) { return this.clone().unit(magnitude); } - /** - * Dot product of this Pt and another Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ dot(...args) { return LinearAlgebra_1.Vec.dot(this, Util_1.Util.getArgs(args)); } - /** - * 2D Cross product of this Pt and another Pt. Return results as a new Pt. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ cross2D(...args) { return LinearAlgebra_1.Vec.cross2D(this, Util_1.Util.getArgs(args)); } - /** - * 3D Cross product of this Pt and another Pt. Return results as a new Pt. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $cross(...args) { return LinearAlgebra_1.Vec.cross(this, Util_1.Util.getArgs(args)); } - /** - * Calculate vector projection of this Pt on another Pt. - * @param p a list of numbers, an array of number, or an object with {x,y,z,w} properties - * @returns the projection vector as a Pt - */ $project(...args) { return this.$multiply(this.dot(...args) / this.magnitudeSq()); } - /** - * Calculate scalar projection - * @param p a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ projectScalar(...args) { return this.dot(...args) / this.magnitude(); } - /** - * Absolute values for all values in this pt - */ abs() { LinearAlgebra_1.Vec.abs(this); return this; } - /** - * Get a new Pt with absolute values of this Pt - */ $abs() { return this.clone().abs(); } - /** - * Floor values for all values in this pt - */ floor() { LinearAlgebra_1.Vec.floor(this); return this; } - /** - * Get a new Pt with floor values of this Pt - */ $floor() { return this.clone().floor(); } - /** - * Ceil values for all values in this pt - */ ceil() { LinearAlgebra_1.Vec.ceil(this); return this; } - /** - * Get a new Pt with ceil values of this Pt - */ $ceil() { return this.clone().ceil(); } - /** - * Round values for all values in this pt - */ round() { LinearAlgebra_1.Vec.round(this); return this; } - /** - * Get a new Pt with round values of this Pt - */ $round() { return this.clone().round(); } - /** - * Find the minimum value across all dimensions in this Pt - * @returns an object with `value` and `index` which returns the minimum value and its dimensional index - */ minValue() { return LinearAlgebra_1.Vec.min(this); } - /** - * Find the maximum value across all dimensions in this Pt - * @returns an object with `value` and `index` which returns the maximum value and its dimensional index - */ maxValue() { return LinearAlgebra_1.Vec.max(this); } - /** - * Get a new Pt that has the minimum dimensional values of this Pt and another Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $min(...args) { let p = Util_1.Util.getArgs(args); let m = this.clone(); @@ -398,10 +245,6 @@ class Pt extends exports.PtBaseArray { } return m; } - /** - * Get a new Pt that has the maximum dimensional values of this Pt and another Pt - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ $max(...args) { let p = Util_1.Util.getArgs(args); let m = this.clone(); @@ -410,102 +253,50 @@ class Pt extends exports.PtBaseArray { } return m; } - /** - * Get angle of this vector from origin - * @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions - */ angle(axis = Util_1.Const.xy) { return Math.atan2(this[axis[1]], this[axis[0]]); } - /** - * Get the angle between this and another Pt - * @param p the other Pt - * @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions - */ angleBetween(p, axis = Util_1.Const.xy) { return Num_1.Geom.boundRadian(this.angle(axis)) - Num_1.Geom.boundRadian(p.angle(axis)); } - /** - * Scale this Pt from origin or from an anchor point - * @param scale scale ratio - * @param anchor optional anchor point to scale from - */ scale(scale, anchor) { Num_1.Geom.scale(this, scale, anchor || Pt.make(this.length, 0)); return this; } - /** - * Rotate this Pt from origin or from an anchor point in 2D - * @param angle rotate angle - * @param anchor optional anchor point to scale from - * @param axis optional string such as "yz" to specify a 2D plane - */ rotate2D(angle, anchor, axis) { Num_1.Geom.rotate2D(this, angle, anchor || Pt.make(this.length, 0), axis); return this; } - /** - * Shear this Pt from origin or from an anchor point in 2D - * @param shear shearing value which can be a number or an array of 2 numbers - * @param anchor optional anchor point to scale from - * @param axis optional string such as "yz" to specify a 2D plane - */ shear2D(scale, anchor, axis) { Num_1.Geom.shear2D(this, scale, anchor || Pt.make(this.length, 0), axis); return this; } - /** - * Reflect this Pt along a 2D line - * @param line a Group of 2 Pts that defines a line for reflection - * @param axis optional axis such as "yz" to define a 2D plane of reflection - */ reflect2D(line, axis) { Num_1.Geom.reflect2D(this, line, axis); return this; } - /** - * A string representation of this Pt: "Pt(1, 2, 3)" - */ toString() { return `Pt(${this.join(", ")})`; } - /** - * Convert this Pt to a javascript Array - */ toArray() { return [].slice.call(this); } } exports.Pt = Pt; -/** - * A Group is a subclass of Array. It should onnly contain Pt instances. You can think of it as an array of arrays (Float32Arrays to be specific). - * See [Group guide](../../guide/Group-0300.html) for details - */ class Group extends Array { constructor(...args) { super(...args); } get id() { return this._id; } set id(s) { this._id = s; } - /** The first Pt in this group */ get p1() { return this[0]; } - /** The second Pt in this group */ get p2() { return this[1]; } - /** The third Pt in this group */ get p3() { return this[2]; } - /** The forth Pt in this group */ get p4() { return this[3]; } - /** The last Pt in this group */ get q1() { return this[this.length - 1]; } - /** The second-last Pt in this group */ get q2() { return this[this.length - 2]; } - /** The third-last Pt in this group */ get q3() { return this[this.length - 3]; } - /** The forth-last Pt in this group */ get q4() { return this[this.length - 4]; } - /** - * Depp clone this group and its Pts - */ clone() { let group = new Group(); for (let i = 0, len = this.length; i < len; i++) { @@ -513,11 +304,6 @@ class Group extends Array { } return group; } - /** - * Convert an array of numeric arrays into a Group of Pts - * @param list an array of numeric arrays - * @example `Group.fromArray( [[1,2], [3,4], [5,6]] )` - */ static fromArray(list) { let g = new Group(); for (let i = 0, len = list.length; i < len; i++) { @@ -526,96 +312,39 @@ class Group extends Array { } return g; } - /** - * Convert an array of Pts into a Group. - * @param list an array of Pts - */ static fromPtArray(list) { return Group.from(list); } - /** - * Split this Group into an array of sub-groups - * @param chunkSize number of items per sub-group - * @param stride forward-steps after each sub-group - * @param loopBack if `true`, always go through the array till the end and loop back to the beginning to complete the segments if needed - */ split(chunkSize, stride, loopBack = false) { let sp = Util_1.Util.split(this, chunkSize, stride, loopBack); return sp; } - /** - * Insert a Pt into this group - * @param pts Another group of Pts - * @param index the index position to insert into - */ insert(pts, index = 0) { Group.prototype.splice.apply(this, [index, 0, ...pts]); return this; } - /** - * Like Array's splice function, with support for negative index and a friendlier name. - * @param index start index, which can be negative (where -1 is at index 0, -2 at index 1, etc) - * @param count number of items to remove - * @returns The items that are removed. - */ remove(index = 0, count = 1) { let param = (index < 0) ? [index * -1 - 1, count] : [index, count]; return Group.prototype.splice.apply(this, param); } - /** - * Split this group into an array of sub-group segments - * @param pts_per_segment number of Pts in each segment - * @param stride forward-step to take - * @param loopBack if `true`, always go through the array till the end and loop back to the beginning to complete the segments if needed - */ segments(pts_per_segment = 2, stride = 1, loopBack = false) { return this.split(pts_per_segment, stride, loopBack); } - /** - * Get all the line segments (ie, edges in a graph) of this group - */ lines() { return this.segments(2, 1); } - /** - * Find the centroid of this group's Pts, which is the average middle point. - */ centroid() { return Num_1.Geom.centroid(this); } - /** - * Find the rectangular bounding box of this group's Pts. - * @returns a Group of 2 Pts representing the top-left and bottom-right of the rectangle - */ boundingBox() { return Num_1.Geom.boundingBox(this); } - /** - * Anchor all the Pts in this Group using a target Pt as origin. (ie, subtract all Pt with the target anchor to get a relative position). All the Pts' values will be updated. - * @param ptOrIndex a Pt, or a numeric index to target a specific Pt in this Group - */ anchorTo(ptOrIndex = 0) { Num_1.Geom.anchor(this, ptOrIndex, "to"); } - /** - * Anchor all the Pts in this Group by its absolute position from a target Pt. (ie, add all Pt with the target anchor to get an absolute position). All the Pts' values will be updated. - * @param ptOrIndex a Pt, or a numeric index to target a specific Pt in this Group - */ anchorFrom(ptOrIndex = 0) { Num_1.Geom.anchor(this, ptOrIndex, "from"); } - /** - * Create an operation using this Group, passing this Group into a custom function's first parameter. See the [Op guide](../../guide/Op-0400.html) for details. - * For example: `let myOp = group.op( fn ); let result = myOp( [1,2,3] );` - * @param fn any function that takes a Group as its first parameter - * @returns a resulting function that takes other parameters required in `fn` - */ op(fn) { let self = this; return (...params) => { return fn(self, ...params); }; } - /** - * This combines a series of operations into an array. See `op()` for details. - * For example: `let myOps = pt.ops([fn1, fn2, fn3]); let results = myOps.map( (op) => op([1,2,3]) );` - * @param fns an array of functions for `op` - * @returns an array of resulting functions - */ ops(fns) { let _ops = []; for (let i = 0, len = fns.length; i < len; i++) { @@ -623,10 +352,6 @@ class Group extends Array { } return _ops; } - /** - * Get an interpolated point on the line segments defined by this Group - * @param t a value between 0 to 1 usually - */ interpolate(t) { t = Num_1.Num.clamp(t, 0, 1); let chunk = this.length - 1; @@ -634,81 +359,41 @@ class Group extends Array { let idx = Math.floor(t / tc); return Num_1.Geom.interpolate(this[idx], this[Math.min(this.length - 1, idx + 1)], (t - idx * tc) * chunk); } - /** - * Move every Pt's position by a specific amount. Same as `add`. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ moveBy(...args) { return this.add(...args); } - /** - * Move the first Pt in this group to a specific position, and move all the other Pts correspondingly - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ moveTo(...args) { let d = new Pt(Util_1.Util.getArgs(args)).subtract(this[0]); this.moveBy(d); return this; } - /** - * Scale this group's Pts from an anchor point. Default anchor point is the first Pt in this group. - * @param scale scale ratio - * @param anchor optional anchor point to scale from - */ scale(scale, anchor) { for (let i = 0, len = this.length; i < len; i++) { Num_1.Geom.scale(this[i], scale, anchor || this[0]); } return this; } - /** - * Rotate this group's Pt from an anchor point in 2D. Default anchor point is the first Pt in this group. - * @param angle rotate angle - * @param anchor optional anchor point to scale from - * @param axis optional string such as "yz" to specify a 2D plane - */ rotate2D(angle, anchor, axis) { for (let i = 0, len = this.length; i < len; i++) { Num_1.Geom.rotate2D(this[i], angle, anchor || this[0], axis); } return this; } - /** - * Shear this group's Pt from an anchor point in 2D. Default anchor point is the first Pt in this group. - * @param shear shearing value which can be a number or an array of 2 numbers - * @param anchor optional anchor point to scale from - * @param axis optional string such as "yz" to specify a 2D plane - */ shear2D(scale, anchor, axis) { for (let i = 0, len = this.length; i < len; i++) { Num_1.Geom.shear2D(this[i], scale, anchor || this[0], axis); } return this; } - /** - * Reflect this group's Pts along a 2D line. Default anchor point is the first Pt in this group. - * @param line a Group of 2 Pts that defines a line for reflection - * @param axis optional axis such as "yz" to define a 2D plane of reflection - */ reflect2D(line, axis) { for (let i = 0, len = this.length; i < len; i++) { Num_1.Geom.reflect2D(this[i], line, axis); } return this; } - /** - * Sort this group's Pts by values in a specific dimension - * @param dim dimensional index - * @param desc if true, sort descending. Default is false (ascending) - */ sortByDimension(dim, desc = false) { return this.sort((a, b) => (desc) ? b[dim] - a[dim] : a[dim] - b[dim]); } - /** - * Update each Pt in this Group with a Pt function - * @param ptFn string name of an existing Pt function. Note that the function must return Pt. - * @param args arguments for the function specified in ptFn - */ forEachPt(ptFn, ...args) { if (!this[0][ptFn]) { Util_1.Util.warn(`${ptFn} is not a function of Pt`); @@ -719,71 +404,30 @@ class Group extends Array { } return this; } - /** - * Add scalar or vector values to this group's Pts. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ add(...args) { return this.forEachPt("add", ...args); } - /** - * Subtract scalar or vector values from this group's Pts. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ subtract(...args) { return this.forEachPt("subtract", ...args); } - /** - * Multiply scalar or vector values (as element-wise) with this group's Pts. - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ multiply(...args) { return this.forEachPt("multiply", ...args); } - /** - * Divide this group's Pts over scalar or vector values (as element-wise) - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ divide(...args) { return this.forEachPt("divide", ...args); } - /** - * Apply this group as a matrix and calculate matrix addition - * @param g a scalar number, an array of numeric arrays, or a group of Pt - * @returns a new Group - */ $matrixAdd(g) { return LinearAlgebra_1.Mat.add(this, g); } - /** - * Apply this group as a matrix and calculate matrix multiplication - * @param g a scalar number, an array of numeric arrays, or a Group of K Pts, each with N dimensions (K-rows, N-columns) -- or if transposed is true, then N Pts with K dimensions - * @param transposed (Only applicable if it's not elementwise multiplication) If true, then a and b's columns should match (ie, each Pt should have the same dimensions). Default is `false`. - * @param elementwise if true, then the multiplication is done element-wise. Default is `false`. - * @returns If not elementwise, this will return a new Group with M Pt, each with N dimensions (M-rows, N-columns). - */ $matrixMultiply(g, transposed = false, elementwise = false) { return LinearAlgebra_1.Mat.multiply(this, g, transposed, elementwise); } - /** - * Zip one slice of an array of Pt. Imagine the Pts are organized in rows, then this function will take the values in a specific column. - * @param idx index to zip at - * @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead. - */ zipSlice(index, defaultValue = false) { return LinearAlgebra_1.Mat.zipSlice(this, index, defaultValue); } - /** - * Zip a group of Pt. eg, [[1,2],[3,4],[5,6]] => [[1,3,5],[2,4,6]] - * @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead. - * @param useLongest If true, find the longest list of values in a Pt and use its length for zipping. Default is false, which uses the first item's length for zipping. - */ $zip(defaultValue = undefined, useLongest = false) { return LinearAlgebra_1.Mat.zip(this, defaultValue, useLongest); } - /** - * Get a string representation of this group - */ toString() { return "Group[ " + this.reduce((p, c) => p + c.toString() + " ", "") + " ]"; } @@ -797,13 +441,8 @@ exports.Group = Group; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); -/** - * Various constant values for enumerations and calculations - */ exports.Const = { xy: "xy", yz: "yz", @@ -811,89 +450,52 @@ exports.Const = { xyz: "xyz", horizontal: 0, vertical: 1, - /* represents identical point or value */ identical: 0, - /* represents right position or direction */ right: 4, - /* represents bottom right position or direction */ bottom_right: 5, - /* represents bottom position or direction */ bottom: 6, - /* represents bottom left position or direction */ bottom_left: 7, - /* represents left position or direction */ left: 8, - /* represents top left position or direction */ top_left: 1, - /* represents top position or direction */ top: 2, - /* represents top right position or direction */ top_right: 3, - /* represents an arbitrary very small number. It is set as 0.0001 here. */ epsilon: 0.0001, - /* represents Number.MAX_VALUE */ max: Number.MAX_VALUE, - /* represents Number.MIN_VALUE */ min: Number.MIN_VALUE, - /* pi radian (180 deg) */ pi: Math.PI, - /* two pi radian (360deg) */ two_pi: 6.283185307179586, - /* half pi radian (90deg) */ half_pi: 1.5707963267948966, - /* pi/4 radian (45deg) */ quarter_pi: 0.7853981633974483, - /* pi/180: 1 degree in radian */ one_degree: 0.017453292519943295, - /* multiply this constant with a radian to get a degree */ rad_to_deg: 57.29577951308232, - /* multiply this constant with a degree to get a radian */ deg_to_rad: 0.017453292519943295, - /* Gravity acceleration (unit: m/s^2) and gravity force (unit: Newton) on 1kg of mass. */ gravity: 9.81, - /* 1 Newton: 0.10197 Kilogram-force */ newton: 0.10197, - /* Gaussian constant (1 / Math.sqrt(2 * Math.PI)) */ gaussian: 0.3989422804014327 }; -/** - * Util provides various helper functions - */ class Util { - /** - * Convert different kinds of parameters (arguments, array, object) into an array of numbers - * @param args a list of numbers, an array of number, or an object with {x,y,z,w} properties - */ static getArgs(args) { if (args.length < 1) return []; let pos = []; let isArray = Array.isArray(args[0]) || ArrayBuffer.isView(args[0]); - // positional arguments: x,y,z,w,... if (typeof args[0] === 'number') { pos = Array.prototype.slice.call(args); - // as an object of {x, y?, z?, w?} } else if (typeof args[0] === 'object' && !isArray) { let a = ["x", "y", "z", "w"]; let p = args[0]; for (let i = 0; i < a.length; i++) { if ((p.length && i >= p.length) || !(a[i] in p)) - break; // check for length and key exist + break; pos.push(p[a[i]]); } - // as an array of values } else if (isArray) { pos = [].slice.call(args[0]); } return pos; } - /** - * Send a warning message based on Util.warnLevel global setting. This allows you to dynamically set whether minor errors should be thrown or printed in console or muted. - * @param message any error or warning message - * @param defaultReturn optional return value - */ static warn(message = "error", defaultReturn = undefined) { if (Util.warnLevel == "error") { throw new Error(message); @@ -906,13 +508,6 @@ class Util { static randomInt(range, start = 0) { return Math.floor(Math.random() * range) + start; } - /** - * Split an array into chunks of sub-array - * @param pts an array - * @param size chunk size, ie, number of items in a chunk - * @param stride optional parameter to "walk through" the array in steps - * @param loopBack if `true`, always go through the array till the end and loop back to the beginning to complete the segments if needed - */ static split(pts, size, stride, loopBack = false) { let st = stride || size; let chunks = []; @@ -933,21 +528,10 @@ class Util { } return chunks; } - /** - * Flatten an array of arrays such as Group[] to a flat Array or Group - * @param pts an array, usually an array of Groups - * @param flattenAsGroup a boolean to specify whether the return type should be a Group or Array. Default is `true` which returns a Group. - */ static flatten(pts, flattenAsGroup = true) { let arr = (flattenAsGroup) ? new Pt_1.Group() : new Array(); return arr.concat.apply(arr, pts); } - /** - * Given two arrays of object, and a function that operate on two object, return an array of T - * @param a an array of object, eg [ Group, Group, ... ] - * @param b another array of object - * @param op a function that takes two parameters (a, b) and returns a T - */ static combine(a, b, op) { let result = []; for (let i = 0, len = a.length; i < len; i++) { @@ -957,10 +541,6 @@ class Util { } return result; } - /** - * Zip arrays. eg, [[1,2],[3,4],[5,6]] => [[1,3,5],[2,4,6]] - * @param arrays an array of arrays - */ static zip(...arrays) { let z = []; for (let i = 0, len = arrays[0].length; i < len; i++) { @@ -972,15 +552,6 @@ class Util { } return z; } - /** - * Create a convenient stepper. This returns a function which you can call repeatedly to step a counter. - * @param max Maximum of the stepper range. The resulting stepper will return (min to max-1) values. - * @param min Minimum of the stepper range. Default is 0. - * @param stride Stride of the step. Default is 1. - * @param callback An optional callback function( step ), which will be called each tiem when stepper function is called. - * @example `let counter = stepper(100); let c = counter(); c = counter(); ...` - * @returns a function which will increment the stepper and return its value at each call. - */ static stepper(max, min = 0, stride = 1, callback) { let c = min; return function () { @@ -993,12 +564,6 @@ class Util { return c; }; } - /** - * A convenient way to step through a range. Same as `for (i=0; i Util_1.Util.warn("Group's length is less than " + param, obj); let _errorOutofBound = (obj, param = "") => Util_1.Util.warn(`Index ${param} is out of bound in Group`, obj); -/** - * Line class provides static functions to create and operate on lines. A line is usually represented as a Group of 2 Pts. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Line { - /** - * Create a line by "drawing" from an anchor point, given an angle and a magnitude - * @param anchor an anchor Pt - * @param angle an angle in radian - * @param magnitude magnitude of the line - * @return a Group of 2 Pts representing a line segement - */ static fromAngle(anchor, angle, magnitude) { let g = new Pt_1.Group(new Pt_1.Pt(anchor), new Pt_1.Pt(anchor)); g[1].toAngle(angle, magnitude, true); return g; } - /** - * Calculate the slope of a line - * @param p1 line's first end point - * @param p2 line's second end point - */ static slope(p1, p2) { return (p2[0] - p1[0] === 0) ? undefined : (p2[1] - p1[1]) / (p2[0] - p1[0]); } - /** - * Calculate the slope and xy intercepts of a line - * @param p1 line's first end point - * @param p2 line's second end point - * @returns an object with `slope`, `xi`, `yi` properties - */ static intercept(p1, p2) { if (p2[0] - p1[0] === 0) { return undefined; @@ -1068,49 +608,20 @@ class Line { return { slope: m, yi: c, xi: (m === 0) ? undefined : -c / m }; } } - /** - * Given a 2D path and a point, find whether the point is on left or right side of the line - * @param line a Group of at least 2 Pts - * @param pt a Pt - * @returns a negative value if on left and a positive value if on right. If collinear, then the return value is 0. - */ static sideOfPt2D(line, pt) { return (line[1][0] - line[0][0]) * (pt[1] - line[0][1]) - (pt[0] - line[0][0]) * (line[1][1] - line[0][1]); } - /** - * Check if three Pts are collinear, ie, on the same straight path. - * @param p1 first Pt - * @param p2 second Pt - * @param p3 third Pt - * @param threshold a threshold where a smaller value means higher precision threshold for the straight line. Default is 0.01. - */ static collinear(p1, p2, p3, threshold = 0.01) { - // Use cross product method let a = new Pt_1.Pt(0, 0, 0).to(p1).$subtract(p2); let b = new Pt_1.Pt(0, 0, 0).to(p1).$subtract(p3); return a.$cross(b).divide(1000).equals(new Pt_1.Pt(0, 0, 0), threshold); } - /** - * Get magnitude of a line segment - * @param line a Group of at least 2 Pts - */ static magnitude(line) { return (line.length >= 2) ? line[1].$subtract(line[0]).magnitude() : 0; } - /** - * Get squared magnitude of a line segment - * @param line a Group of at least 2 Pts - */ static magnitudeSq(line) { return (line.length >= 2) ? line[1].$subtract(line[0]).magnitudeSq() : 0; } - /** - * Find a point on a line that is perpendicular (shortest distance) to a target point - * @param pt a target Pt - * @param ln a group of Pts that defines a line - * @param asProjection if true, this returns the projection vector instead. Default is false. - * @returns a Pt on the line that is perpendicular to the target Pt, or a projection vector if `asProjection` is true. - */ static perpendicularFromPt(line, pt, asProjection = false) { if (line[0].equals(line[1])) return undefined; @@ -1119,21 +630,9 @@ class Line { let proj = b.$subtract(a.$project(b)); return (asProjection) ? proj : proj.$add(pt); } - /** - * Given a line and a point, find the shortest distance from the point to the line - * @param line a Group of 2 Pts - * @param pt a Pt - * @see `Line.perpendicularFromPt` - */ static distanceFromPt(line, pt) { return Line.perpendicularFromPt(line, pt, true).magnitude(); } - /** - * Given two lines as rays (infinite lines), find their intersection point if any. - * @param la a Group of 2 Pts representing a ray - * @param lb a Group of 2 Pts representing a ray - * @returns an intersection Pt or undefined if no intersection - */ static intersectRay2D(la, lb) { let a = Line.intercept(la[0], la[1]); let b = Line.intercept(lb[0], lb[1]); @@ -1142,12 +641,10 @@ class Line { if (a == undefined) { if (b == undefined) return undefined; - // one of them is vertical line, while the other is not, so they will intersect - let y1 = -b.slope * (pb[0] - pa[0]) + pb[1]; // -slope * x + y + let y1 = -b.slope * (pb[0] - pa[0]) + pb[1]; return new Pt_1.Pt(pa[0], y1); } else { - // diff slope, or b slope is vertical line if (b == undefined) { let y1 = -a.slope * (pa[0] - pb[0]) + pa[1]; return new Pt_1.Pt(pb[0], y1); @@ -1167,32 +664,14 @@ class Line { } } } - /** - * Given two line segemnts, find their intersection point if any. - * @param la a Group of 2 Pts representing a line segment - * @param lb a Group of 2 Pts representing a line segment - * @returns an intersection Pt or undefined if no intersection - */ static intersectLine2D(la, lb) { let pt = Line.intersectRay2D(la, lb); return (pt && Num_1.Geom.withinBound(pt, la[0], la[1]) && Num_1.Geom.withinBound(pt, lb[0], lb[1])) ? pt : undefined; } - /** - * Given a line segemnt and a ray (infinite line), find their intersection point if any. - * @param line a Group of 2 Pts representing a line segment - * @param ray a Group of 2 Pts representing a ray - * @returns an intersection Pt or undefined if no intersection - */ static intersectLineWithRay2D(line, ray) { let pt = Line.intersectRay2D(line, ray); return (pt && Num_1.Geom.withinBound(pt, line[0], line[1])) ? pt : undefined; } - /** - * Given a line segemnt and a ray (infinite line), find its intersection point(s) with a polygon. - * @param lineOrRay a Group of 2 Pts representing a line or ray - * @param poly a Group of Pts representing a polygon - * @param sourceIsRay a boolean value to treat the line as a ray (infinite line). Default is `false`. - */ static intersectPolygon2D(lineOrRay, poly, sourceIsRay = false) { let fn = sourceIsRay ? Line.intersectLineWithRay2D : Line.intersectLine2D; let pts = new Pt_1.Group(); @@ -1204,12 +683,6 @@ class Line { } return (pts.length > 0) ? pts : undefined; } - /** - * Find intersection points of 2 polygons. This checks all line segments in the two lists. Consider using a bounding-box check before calling this. - * @param lines1 an array of line segments - * @param lines2 an array of line segments - * @param isRay a boolean value to treat the line as a ray (infinite line). Default is `false`. - */ static intersectLines2D(lines1, lines2, isRay = false) { let group = new Pt_1.Group(); let fn = isRay ? Line.intersectLineWithRay2D : Line.intersectLine2D; @@ -1222,12 +695,6 @@ class Line { } return group; } - /** - * Get two intersection Pts of a ray with a 2D grid point - * @param ray a ray specified by 2 Pts - * @param gridPt a Pt on the grid - * @returns a group of two intersecting Pts. The first one is horizontal intersection and the second one is vertical intersection. - */ static intersectGridWithRay2D(ray, gridPt) { let t = Line.intercept(new Pt_1.Pt(ray[0]).subtract(gridPt), new Pt_1.Pt(ray[1]).subtract(gridPt)); let g = new Pt_1.Group(); @@ -1237,12 +704,6 @@ class Line { g.push(new Pt_1.Pt(gridPt[0], gridPt[1] + t.yi)); return g; } - /** - * Get two intersection Pts of a line segment with a 2D grid point - * @param ray a ray specified by 2 Pts - * @param gridPt a Pt on the grid - * @returns a group of two intersecting Pts. The first one is horizontal intersection and the second one is vertical intersection. - */ static intersectGridWithLine2D(line, gridPt) { let g = Line.intersectGridWithRay2D(line, gridPt); let gg = new Pt_1.Group(); @@ -1252,24 +713,12 @@ class Line { } return gg; } - /** - * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. - * @param line a Group representing a line - * @param rect a Group representing a rectangle - * @returns a Group of intersecting Pts - */ static intersectRect2D(line, rect) { let box = Num_1.Geom.boundingBox(Pt_1.Group.fromPtArray(line)); if (!Rectangle.hasIntersectRect2D(box, rect)) return new Pt_1.Group(); return Line.intersectLines2D([line], Rectangle.sides(rect)); } - /** - * Get evenly distributed points on a line - * @param line a Group representing a line - * @param num number of points to get - */ static subpoints(line, num) { let pts = new Pt_1.Group(); for (let i = 1; i <= num; i++) { @@ -1277,14 +726,6 @@ class Line { } return pts; } - /** - * Crop this line by a circle or rectangle at end point. - * @param line line to crop - * @param size size of circle or rectangle as Pt - * @param index line's end point index, ie, 0 = start and 1 = end. - * @param cropAsCircle a boolean to specify whether the `size` parameter should be treated as circle. Default is `true`. - * @return an intersecting point on the line that can be used for cropping. - */ static crop(line, size, index = 0, cropAsCircle = true) { let tdx = (index === 0) ? 1 : 0; let ls = line[tdx].$subtract(line[index]); @@ -1307,14 +748,6 @@ class Line { return Line.intersectRay2D(sides[sideIdx], line); } } - /** - * Create an marker arrow or line, placed at an end point of this line - * @param line line to place marker - * @param size size of the marker as Pt - * @param graphic either "arrow" or "line" - * @param atTail a boolean, if `true`, the marker will be positioned at tail of the line (ie, index = 1). Default is `true`. - * @returns a Group that defines the marker's shape - */ static marker(line, size, graphic = ("arrow" || "line"), atTail = true) { let h = atTail ? 0 : 1; let t = atTail ? 1 : 0; @@ -1331,95 +764,44 @@ class Line { return new Pt_1.Group(ps[0], ps[1]); } } - /** - * Convert this line to a rectangle representation - * @param line a Group representing a line - */ static toRect(line) { return new Pt_1.Group(line[0].$min(line[1]), line[0].$max(line[1])); } } exports.Line = Line; -/** - * Rectangle class provides static functions to create and operate on rectangles. A rectangle is usually represented as a Group of 2 Pts, marking the top-left and bottom-right corners of the rectangle. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Rectangle { - /** - * Same as `Rectangle.fromTopLeft` - */ static from(topLeft, widthOrSize, height) { return Rectangle.fromTopLeft(topLeft, widthOrSize, height); } - /** - * Create a rectangle given a top-left position and a size - * @param topLeft top-left point - * @param widthOrSize width as a number, or a Pt that defines its size - * @param height optional height as a number - */ static fromTopLeft(topLeft, widthOrSize, height) { let size = (typeof widthOrSize == "number") ? [widthOrSize, (height || widthOrSize)] : widthOrSize; return new Pt_1.Group(new Pt_1.Pt(topLeft), new Pt_1.Pt(topLeft).add(size)); } - /** - * Create a rectangle given a center position and a size - * @param topLeft top-left point - * @param widthOrSize width as a number, or a Pt that defines its size - * @param height optional height as a number - */ static fromCenter(center, widthOrSize, height) { let half = (typeof widthOrSize == "number") ? [widthOrSize / 2, (height || widthOrSize) / 2] : new Pt_1.Pt(widthOrSize).divide(2); return new Pt_1.Group(new Pt_1.Pt(center).subtract(half), new Pt_1.Pt(center).add(half)); } - /** - * Convert this rectangle to a circle that fits within the rectangle - * @returns a Group that represents a circle - * @see `Circle` - */ static toCircle(pts) { return Circle.fromRect(pts); } - /** - * Create a square that either fits within or encloses a rectangle - * @param pts a Group of 2 Pts representing a rectangle - * @param enclose if `true`, the square will enclose the rectangle. Default is `false`, which will fit the square inside the rectangle. - */ static toSquare(pts, enclose = false) { let s = Rectangle.size(pts); let m = (enclose) ? s.maxValue().value : s.minValue().value; return Rectangle.fromCenter(Rectangle.center(pts), m, m); } - /** - * Get the size of this rectangle as a Pt - * @param pts a Group of 2 Pts representing a Rectangle - */ static size(pts) { return pts[0].$max(pts[1]).subtract(pts[0].$min(pts[1])); } - /** - * Get the center of this rectangle - * @param pts a Group of 2 Pts representing a Rectangle - */ static center(pts) { let min = pts[0].$min(pts[1]); let max = pts[0].$max(pts[1]); return min.add(max.$subtract(min).divide(2)); } - /** - * Get the 4 corners of this rectangle as a Group - * @param rect a Group of 2 Pts representing a Rectangle - */ static corners(rect) { let p0 = rect[0].$min(rect[1]); let p2 = rect[0].$max(rect[1]); return new Pt_1.Group(p0, new Pt_1.Pt(p2.x, p0.y), p2, new Pt_1.Pt(p0.x, p2.y)); } - /** - * Get the 4 sides of this rectangle as an array of 4 Groups - * @param rect a Group of 2 Pts representing a Rectangle - * @returns an array of 4 Groups, each of which represents a line segment - */ static sides(rect) { let [p0, p1, p2, p3] = Rectangle.corners(rect); return [ @@ -1427,22 +809,13 @@ class Rectangle { new Pt_1.Group(p2, p3), new Pt_1.Group(p3, p0) ]; } - /** - * Same as `Rectangle.sides` - */ static lines(rect) { return Rectangle.sides(rect); } - /** - * Given an array of rectangles, get a rectangle that bounds all of them - * @param rects an array of Groups that represent rectangles - * @returns the bounding rectangle as a Group - */ static boundingBox(rects) { let merged = Util_1.Util.flatten(rects, false); let min = Pt_1.Pt.make(2, Number.MAX_VALUE); let max = Pt_1.Pt.make(2, Number.MIN_VALUE); - // calculate min max in a single pass for (let i = 0, len = merged.length; i < len; i++) { for (let k = 0; k < 2; k++) { min[k] = Math.min(min[k], merged[i][k]); @@ -1451,30 +824,14 @@ class Rectangle { } return new Pt_1.Group(min, max); } - /** - * Convert this rectangle into a Group representing a polygon - * @param rect a Group of 2 Pts representing a Rectangle - */ static polygon(rect) { return Rectangle.corners(rect); } - /** - * Subdivide a rectangle into 4 rectangles, one for each quadrant - * @param rect a Group of 2 Pts representing a Rectangle - * @returns an array of 4 Groups of rectangles - */ static quadrants(rect, center) { let corners = Rectangle.corners(rect); let _center = (center != undefined) ? new Pt_1.Pt(center) : Rectangle.center(rect); return corners.map((c) => new Pt_1.Group(c, _center).boundingBox()); } - /** - * Subdivde a rectangle into 2 rectangles, by row or by column - * @param rect Group of 2 Pts representing a Rectangle - * @param ratio a value between 0 to 1 to indicate the split ratio - * @param asRows if `true`, split into 2 rows. Default is `false` which splits into 2 columns. - * @returns an array of 2 Groups of rectangles - */ static halves(rect, ratio = 0.5, asRows = false) { let min = rect[0].$min(rect[1]); let max = rect[0].$max(rect[1]); @@ -1483,20 +840,9 @@ class Rectangle { ? [new Pt_1.Group(min, new Pt_1.Pt(max[0], mid)), new Pt_1.Group(new Pt_1.Pt(min[0], mid), max)] : [new Pt_1.Group(min, new Pt_1.Pt(mid, max[1])), new Pt_1.Group(new Pt_1.Pt(mid, min[1]), max)]; } - /** - * Check if a point is within a rectangle - * @param rect a Group of 2 Pts representing a Rectangle - * @param pt the point to check - */ static withinBound(rect, pt) { return Num_1.Geom.withinBound(pt, rect[0], rect[1]); } - /** - * Check if a rectangle is within the bounds of another rectangle - * @param rect1 a Group of 2 Pts representing a rectangle - * @param rect2 a Group of 2 Pts representing a rectangle - * @param resetBoundingBox if `true`, reset the bounding box. Default is `false` which assumes the rect's first Pt at is its top-left corner. - */ static hasIntersectRect2D(rect1, rect2, resetBoundingBox = false) { if (resetBoundingBox) { rect1 = Num_1.Geom.boundingBox(rect1); @@ -1508,12 +854,6 @@ class Rectangle { return false; return true; } - /** - * Quick way to check rectangle intersection. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. - * @param rect1 a Group of 2 Pts representing a rectangle - * @param rect2 a Group of 2 Pts representing a rectangle - */ static intersectRect2D(rect1, rect2) { if (!Rectangle.hasIntersectRect2D(rect1, rect2)) return new Pt_1.Group(); @@ -1521,17 +861,7 @@ class Rectangle { } } exports.Rectangle = Rectangle; -/** - * Circle class provides static functions to create and operate on circles. A circle is usually represented as a Group of 2 Pts, where the first Pt specifies the center, and the second Pt specifies the radius. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Circle { - /** - * Create a circle that either fits within or encloses a rectangle - * @param pts a Group of 2 Pts representing a rectangle - * @param enclose if `true`, the circle will enclose the rectangle. Default is `false`, which will fit the circle inside the rectangle. - */ static fromRect(pts, enclose = false) { let r = 0; let min = r = Rectangle.size(pts).minValue().value / 2; @@ -1544,30 +874,13 @@ class Circle { } return new Pt_1.Group(Rectangle.center(pts), new Pt_1.Pt(r, r)); } - /** - * Create a circle based on a center point and a radius - * @param pt center point of circle - * @param radius radius of circle - */ static fromCenter(pt, radius) { return new Pt_1.Group(new Pt_1.Pt(pt), new Pt_1.Pt(radius, radius)); } - /** - * Check if a point is within a circle - * @param pts a Group of 2 Pts representing a circle - * @param pt the point to checks - * @param threshold an optional small number to set threshold. Default is 0. - */ static withinBound(pts, pt, threshold = 0) { let d = pts[0].$subtract(pt); return d.dot(d) + threshold < pts[1].x * pts[1].x; } - /** - * Get the intersection points between a circle and a ray (infinite line) - * @param pts a Group of 2 Pts representing a circle - * @param ray a Group of 2 Pts representing a ray - * @returns a Group of intersection points, or an empty Group if no intersection is found - */ static intersectRay2D(pts, ray) { let d = ray[0].$subtract(ray[1]); let f = pts[0].$subtract(ray[0]); @@ -1576,7 +889,7 @@ class Circle { let c = f.dot(f) - pts[1].x * pts[1].x; let p = b / a; let q = c / a; - let disc = p * p - q; // discriminant + let disc = p * p - q; if (disc < 0) { return new Pt_1.Group(); } @@ -1591,12 +904,6 @@ class Circle { return new Pt_1.Group(p1, p2); } } - /** - * Get the intersection points between a circle and a line segment - * @param pts a Group of 2 Pts representing a circle - * @param ray a Group of 2 Pts representing a line - * @returns a Group of intersection points, or an empty Group if no intersection is found - */ static intersectLine2D(pts, line) { let ps = Circle.intersectRay2D(pts, line); let g = new Pt_1.Group(); @@ -1608,12 +915,6 @@ class Circle { } return g; } - /** - * Get the intersection points between two circles - * @param pts a Group of 2 Pts representing a circle - * @param circle a Group of 2 Pts representing a circle - * @returns a Group of intersection points, or an empty Group if no intersection is found - */ static intersectCircle2D(pts, circle) { let dv = circle[0].$subtract(pts[0]); let dr2 = dv.magnitudeSq(); @@ -1635,13 +936,6 @@ class Circle { return new Pt_1.Group(new Pt_1.Pt(p.x + h * dv.y / dr, p.y - h * dv.x / dr), new Pt_1.Pt(p.x - h * dv.y / dr, p.y + h * dv.x / dr)); } } - /** - * Quick way to check rectangle intersection with a circle. - * For more optimized implementation, store the rectangle's sides separately (eg, `Rectangle.sides()`) and use `Polygon.intersectPolygon2D()`. - * @param pts a Group of 2 Pts representing a circle - * @param rect a Group of 2 Pts representing a rectangle - * @returns a Group of intersection points, or an empty Group if no intersection is found - */ static intersectRect2D(pts, rect) { let sides = Rectangle.sides(rect); let g = []; @@ -1652,27 +946,15 @@ class Circle { } return Util_1.Util.flatten(g); } - /** - * Convert this cirlce to a rectangle that encloses this circle - * @param pts a Group of 2 Pts representing a circle - */ static toRect(pts) { let r = pts[1][0]; return new Pt_1.Group(pts[0].$subtract(r), pts[0].$add(r)); } - /** - * Convert this cirlce to a rectangle that fits within this circle - * @param pts a Group of 2 Pts representing a circle - */ static toInnerRect(pts) { let r = pts[1][0]; let half = Math.sqrt(r * r) / 2; return new Pt_1.Group(pts[0].$subtract(half), pts[0].$add(half)); } - /** - * Convert this cirlce to a triangle that fits within this circle - * @param pts a Group of 2 Pts representing a circle - */ static toInnerTriangle(pts) { let ang = -Math.PI / 2; let inc = Math.PI * 2 / 3; @@ -1685,16 +967,7 @@ class Circle { } } exports.Circle = Circle; -/** - * Triangle class provides static functions to create and operate on trianges. A triange is usually represented as a Group of 3 Pts. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Triangle { - /** - * Create a triangle from a rectangle. The triangle will be isosceles, with the bottom of the rectangle as its base. - * @param rect a Group of 2 Pts representing a rectangle - */ static fromRect(rect) { let top = rect[0].$add(rect[1]).divide(2); top.y = rect[0][1]; @@ -1702,37 +975,17 @@ class Triangle { left.x = rect[0][0]; return new Pt_1.Group(top, rect[1].clone(), left); } - /** - * Create a triangle that fits within a circle - * @param circle a Group of 2 Pts representing a circle - */ static fromCircle(circle) { return Circle.toInnerTriangle(circle); } - /** - * Create an equilateral triangle based on a center point and a size - * @param pt the center point - * @param size size is the magnitude of lines from center to the triangle's vertices, like a "radius". - */ static fromCenter(pt, size) { return Triangle.fromCircle(Circle.fromCenter(pt, size)); } - /** - * Get the medial, which is an inner triangle formed by connecting the midpoints of this triangle's sides - * @param pts a Group of Pts - * @returns a Group representing a medial triangle - */ static medial(pts) { if (pts.length < 3) return _errorLength(new Pt_1.Group(), 3); return Polygon.midpoints(pts, true); } - /** - * Given a point of the triangle, the opposite side is the side which the point doesn't touch. - * @param pts a Group of Pts - * @param index a Pt on the triangle group - * @returns a Group that represents a line of the opposite side - */ static oppositeSide(pts, index) { if (pts.length < 3) return _errorLength(new Pt_1.Group(), 3); @@ -1746,12 +999,6 @@ class Triangle { return Pt_1.Group.fromPtArray([pts[0], pts[1]]); } } - /** - * Get a triangle's altitude, which is a line from a triangle's point to its opposite side, and perpendicular to its opposite side. - * @param pts a Group of Pts - * @param index a Pt on the triangle group - * @returns a Group that represents the altitude line - */ static altitude(pts, index) { let opp = Triangle.oppositeSide(pts, index); if (opp.length > 1) { @@ -1761,11 +1008,6 @@ class Triangle { return new Pt_1.Group(); } } - /** - * Get orthocenter, which is the intersection point of a triangle's 3 altitudes (the 3 lines that are perpendicular to its 3 opposite sides). - * @param pts a Group of Pts - * @returns the orthocenter as a Pt - */ static orthocenter(pts) { if (pts.length < 3) return _errorLength(undefined, 3); @@ -1773,11 +1015,6 @@ class Triangle { let b = Triangle.altitude(pts, 1); return Line.intersectRay2D(a, b); } - /** - * Get incenter, which is the center point of its inner circle, and also the intersection point of its 3 angle bisector lines (each of which cuts one of the 3 angles in half). - * @param pts a Group of Pts - * @returns the incenter as a Pt - */ static incenter(pts) { if (pts.length < 3) return _errorLength(undefined, 3); @@ -1785,11 +1022,6 @@ class Triangle { let b = Polygon.bisector(pts, 1).add(pts[1]); return Line.intersectRay2D(new Pt_1.Group(pts[0], a), new Pt_1.Group(pts[1], b)); } - /** - * Get an interior circle, which is the largest circle completed enclosed by this triangle - * @param pts a Group of Pts - * @param center Optional parameter if the incenter is already known. Otherwise, leave it empty and the incenter will be calculated - */ static incircle(pts, center) { let c = (center) ? center : Triangle.incenter(pts); let area = Polygon.area(pts); @@ -1797,22 +1029,12 @@ class Triangle { let r = 2 * area / perim.total; return Circle.fromCenter(c, r); } - /** - * Get circumcenter, which is the intersection point of its 3 perpendicular bisectors lines ( each of which divides a side in half and is perpendicular to the side) - * @param pts a Group of Pts - * @returns the circumcenter as a Pt - */ static circumcenter(pts) { let md = Triangle.medial(pts); let a = [md[0], Num_1.Geom.perpendicular(pts[0].$subtract(md[0])).p1.$add(md[0])]; let b = [md[1], Num_1.Geom.perpendicular(pts[1].$subtract(md[1])).p1.$add(md[1])]; return Line.intersectRay2D(a, b); } - /** - * Get circumcenter, which is the intersection point of its 3 perpendicular bisectors lines ( each of which divides a side in half and is perpendicular to the side) - * @param pts a Group of Pts - * @param center Optional parameter if the circumcenter is already known. Otherwise, leave it empty and the circumcenter will be calculated - */ static circumcircle(pts, center) { let c = (center) ? center : Triangle.circumcenter(pts); let r = pts[0].$subtract(c).magnitude(); @@ -1820,25 +1042,10 @@ class Triangle { } } exports.Triangle = Triangle; -/** - * Polygon class provides static functions to create and operate on polygons. A polygon is usually represented as a Group of 3 or more Pts. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Polygon { - /** - * Get the centroid of a polygon, which is the average of all its points. - * @param pts a Group of Pts representing a polygon - */ static centroid(pts) { return Num_1.Geom.centroid(pts); } - /** - * Create a rectangular polygon - * @param center center point of the rectangle - * @param widthOrSize width as number, or a Pt representing the size of the rectangle - * @param height optional height - */ static rectangle(center, widthOrSize, height) { return Rectangle.corners(Rectangle.fromCenter(center, widthOrSize, height)); } @@ -1850,22 +1057,11 @@ class Polygon { } return g; } - /** - * Given a Group of Pts that defines a polygon, get one edge using an index - * @param pts a Group - * @param idx index of a Pt in the Group - */ static lineAt(pts, idx) { if (idx < 0 || idx >= pts.length) throw new Error("index out of the Polygon's range"); return new Pt_1.Group(pts[idx], (idx === pts.length - 1) ? pts[0] : pts[idx + 1]); } - /** - * Get the line segments in this polygon - * @param pts a Group of Pts - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - * @returns an array of Groups which has 2 Pts in each group - */ static lines(pts, closePath = true) { if (pts.length < 2) return _errorLength(new Pt_1.Group(), 2); @@ -1874,12 +1070,6 @@ class Polygon { sp.push(new Pt_1.Group(pts[pts.length - 1], pts[0])); return sp.map((g) => g); } - /** - * Get a new polygon group that is derived from midpoints in this polygon - * @param pts a Group of Pts - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - * @param t a value between 0 to 1 for interpolation. Default to 0.5 which will get the middle point. - */ static midpoints(pts, closePath = false, t = 0.5) { if (pts.length < 2) return _errorLength(new Pt_1.Group(), 2); @@ -1887,12 +1077,6 @@ class Polygon { let mids = sides.map((s) => Num_1.Geom.interpolate(s[0], s[1], t)); return mids; } - /** - * Given a Pt in the polygon group, the adjacent sides are the two sides which the Pt touches. - * @param pts a group of Pts - * @param index the target Pt - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - */ static adjacentSides(pts, index, closePath = false) { if (pts.length < 2) return _errorLength(new Pt_1.Group(), 2); @@ -1911,13 +1095,6 @@ class Polygon { gs.push(new Pt_1.Group(pts[index], pts[right])); return gs; } - /** - * Get a bisector which is a line that split between two sides of a polygon equally. - * @param pts a group of Pts - * @param index the Pt in the polygon to bisect from - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - * @returns a bisector Pt that's a normalized unit vector - */ static bisector(pts, index) { let sides = Polygon.adjacentSides(pts, index, true); if (sides.length >= 2) { @@ -1929,12 +1106,6 @@ class Polygon { return undefined; } } - /** - * Find the perimeter of this polygon, ie, the lengths of its sides. - * @param pts a group of Pts - * @param closePath a boolean to specify whether the polygon should be closed (ie, whether the final segment should be counted). - * @returns an object with `total` length, and `segments` which is a Pt that stores each segment's length - */ static perimeter(pts, closePath = false) { if (pts.length < 2) return _errorLength(new Pt_1.Group(), 2); @@ -1951,14 +1122,9 @@ class Polygon { segments: p }; } - /** - * Find the area of a *convex* polygon. - * @param pts a group of Pts - */ static area(pts) { if (pts.length < 3) return _errorLength(new Pt_1.Group(), 3); - // determinant let det = (a, b) => a[0] * b[1] - a[1] * b[0]; let area = 0; for (let i = 0, len = pts.length; i < len; i++) { @@ -1971,13 +1137,6 @@ class Polygon { } return Math.abs(area / 2); } - /** - * Get a convex hull of the point set using Melkman's algorithm - * (Reference: http://geomalgorithms.com/a12-_hull-3.html) - * @param pts a group of Pt - * @param sorted a boolean value to indicate if the group is pre-sorted by x position. Default is false. - * @returns a group of Pt that defines the convex hull polygon - */ static convexHull(pts, sorted = false) { if (pts.length < 3) return _errorLength(new Pt_1.Group(), 3); @@ -1985,17 +1144,14 @@ class Polygon { pts = pts.slice(); pts.sort((a, b) => a[0] - b[0]); } - // check if is on left of ray a-b let left = (a, b, c) => { return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]) > 0; }; - // double end queue let dq = []; let bot = pts.length - 2; let top = bot + 3; dq[bot] = pts[2]; dq[top] = pts[2]; - // first 3 pt as counter-clockwise triangle if (left(pts[0], pts[1], pts[2])) { dq[bot + 1] = pts[0]; dq[bot + 2] = pts[1]; @@ -2004,20 +1160,16 @@ class Polygon { dq[bot + 1] = pts[1]; dq[bot + 2] = pts[0]; } - // remaining pts for (let i = 3, len = pts.length; i < len; i++) { let pt = pts[i]; - // if inside the hull if (left(dq[bot], dq[bot + 1], pt) && left(dq[top - 1], dq[top], pt)) { continue; } - // rightmost tangent while (!left(dq[bot], dq[bot + 1], pt)) { bot += 1; } bot -= 1; dq[bot] = pt; - // leftmost tangent while (!left(dq[top - 1], dq[top], pt)) { top -= 1; } @@ -2030,11 +1182,6 @@ class Polygon { } return hull; } - /** - * Given a point in the polygon as an origin, get an array of lines that connect all the remaining points to the origin point. - * @param pts a Group representing a polygon - * @param originIndex the origin point's index in the polygon - */ static network(pts, originIndex = 0) { let g = []; for (let i = 0, len = pts.length; i < len; i++) { @@ -2043,12 +1190,6 @@ class Polygon { } return g; } - /** - * Given a target Pt, find a Pt in a Group that's nearest to it. - * @param pts a Group of Pt - * @param pt Pt to check - * @returns an index in the pts indicating the nearest Pt, or -1 if none found - */ static nearestPt(pts, pt) { let _near = Number.MAX_VALUE; let _item = -1; @@ -2061,11 +1202,6 @@ class Polygon { } return _item; } - /** - * Project axis (eg, for use in Separation Axis Theorem) - * @param poly - * @param unitAxis - */ static projectAxis(poly, unitAxis) { let dot = unitAxis.dot(poly[0]); let d = new Pt_1.Pt(dot, dot); @@ -2075,22 +1211,11 @@ class Polygon { } return d; } - /** - * Check overlap dist from projected axis - * @param poly1 first polygon - * @param poly2 second polygon - * @param unitAxis unit axis - */ static _axisOverlap(poly1, poly2, unitAxis) { let pa = Polygon.projectAxis(poly1, unitAxis); let pb = Polygon.projectAxis(poly2, unitAxis); return (pa[0] < pb[0]) ? pb[0] - pa[1] : pa[0] - pb[1]; } - /** - * Check if a Pt is inside a convex polygon - * @param poly a Group of Pt defining a convex polygon - * @param pt the Pt to check - */ static hasIntersectPoint(poly, pt) { let c = false; for (let i = 0, len = poly.length; i < len; i++) { @@ -2102,12 +1227,6 @@ class Polygon { } return c; } - /** - * Check if a convex polygon and a circle has intersections using Separating Axis Theorem. - * @param poly a Group representing a convex polygon - * @param circle a Group representing a circle - * @returns an `IntersectContext` object that stores the intersection info, or undefined if there's no intersection - */ static hasIntersectCircle(poly, circle) { let info = { which: -1, @@ -2128,7 +1247,6 @@ class Polygon { return null; } else if (Math.abs(dist) < minDist) { - // Fix edge case and make sure the circle is intersecting. To be improved. let check = Rectangle.withinBound(edge, Line.perpendicularFromPt(edge, c)) || Circle.intersectLine2D(circle, edge).length > 0; if (check) { info.edge = edge; @@ -2140,7 +1258,6 @@ class Polygon { } if (!info.edge) return null; - // direction let dir = c.$subtract(Polygon.centroid(poly)).dot(info.normal); if (dir < 0) info.normal.multiply(-1); @@ -2148,31 +1265,23 @@ class Polygon { info.vertex = c; return info; } - /** - * Check if two convex polygons has intersections using Separating Axis Theorem. - * @param poly1 a Group representing a convex polygon - * @param poly2 a Group representing a convex polygon - * @return an `IntersectContext` object that stores the intersection info, or undefined if there's no intersection - */ static hasIntersectPolygon(poly1, poly2) { - // Reference: https://www.gamedev.net/articles/programming/math-and-physics/a-verlet-based-approach-for-2d-game-physics-r2714/ let info = { which: -1, dist: 0, normal: new Pt_1.Pt(), edge: new Pt_1.Group(), - vertex: new Pt_1.Pt() // the vertex on a polygon that has intersected + vertex: new Pt_1.Pt() }; let minDist = Number.MAX_SAFE_INTEGER; for (let i = 0, plen = (poly1.length + poly2.length); i < plen; i++) { let edge = (i < poly1.length) ? Polygon.lineAt(poly1, i) : Polygon.lineAt(poly2, i - poly1.length); - let axis = new Pt_1.Pt(edge[0].y - edge[1].y, edge[1].x - edge[0].x).unit(); // unit of a perpendicular vector + let axis = new Pt_1.Pt(edge[0].y - edge[1].y, edge[1].x - edge[0].x).unit(); let dist = Polygon._axisOverlap(poly1, poly2, axis); if (dist > 0) { return null; } else if (Math.abs(dist) < minDist) { - // store intersected edge and a normal vector info.edge = edge; info.normal = axis; minDist = Math.abs(dist); @@ -2180,16 +1289,13 @@ class Polygon { } } info.dist = minDist; - // flip if neded to make sure vertex and edge are in corresponding polygons let b1 = (info.which === 0) ? poly2 : poly1; let b2 = (info.which === 0) ? poly1 : poly2; let c1 = Polygon.centroid(b1); let c2 = Polygon.centroid(b2); - // direction let dir = c1.$subtract(c2).dot(info.normal); if (dir < 0) info.normal.multiply(-1); - // find vertex at smallest distance let smallest = Number.MAX_SAFE_INTEGER; for (let i = 0, len = b1.length; i < len; i++) { let d = info.normal.dot(b1[i].$subtract(c2)); @@ -2200,11 +1306,6 @@ class Polygon { } return info; } - /** - * Find intersection points of 2 polygons by checking every side of both polygons - * @param poly1 a Group representing a polygon - * @param poly2 another Group representing a polygon - */ static intersectPolygon2D(poly1, poly2) { let lp = Polygon.lines(poly1); let g = []; @@ -2215,10 +1316,6 @@ class Polygon { } return Util_1.Util.flatten(g, true); } - /** - * Get a bounding box for each polygon group, as well as a union bounding-box for all groups - * @param polys an array of Groups, or an array of Pt arrays - */ static toRects(polys) { let boxes = polys.map((g) => Num_1.Geom.boundingBox(g)); let merged = Util_1.Util.flatten(boxes, false); @@ -2227,16 +1324,7 @@ class Polygon { } } exports.Polygon = Polygon; -/** - * Curve class provides static functions to interpolate curves. A curve is usually represented as a Group of 3 control points. - * You can use the static function as-is, or apply the `op` method in Group or Pt to many of these functions. - * See [Op guide](../../guide/Op-0400.html) for details. - */ class Curve { - /** - * Get a precalculated coefficients per step - * @param steps number of steps - */ static getSteps(steps) { let ts = new Pt_1.Group(); for (let i = 0; i <= steps; i++) { @@ -2245,27 +1333,14 @@ class Curve { } return ts; } - /** - * Given an index for the starting position in a Pt group, get the control and/or end points of a curve segment - * @param pts a group of Pt - * @param index start index in `pts` array. Default is 0. - * @param copyStart an optional boolean value to indicate if the start index should be used twice. Default is false. - * @returns a group of 4 Pts - */ static controlPoints(pts, index = 0, copyStart = false) { if (index > pts.length - 1) return new Pt_1.Group(); let _index = (i) => (i < pts.length - 1) ? i : pts.length - 1; let p0 = pts[index]; index = (copyStart) ? index : index + 1; - // get points based on index return new Pt_1.Group(p0, pts[_index(index++)], pts[_index(index++)], pts[_index(index++)]); } - /** - * Calulcate weighted sum to get the interpolated points - * @param ctrls anchors - * @param params parameters - */ static _calcPt(ctrls, params) { let x = ctrls.reduce((a, c, i) => a + c.x * params[i], 0); let y = ctrls.reduce((a, c, i) => a + c.y * params[i], 0); @@ -2275,18 +1350,11 @@ class Curve { } return new Pt_1.Pt(x, y); } - /** - * Create a Catmull-Rom curve. Catmull-Rom is a kind of Cardinal curve with smooth-looking curve. - * @param pts a group of anchor Pt - * @param steps the number of line segments per curve. Defaults to 10 steps. - * @returns a curve as a group of interpolated Pt - */ static catmullRom(pts, steps = 10) { if (pts.length < 2) return new Pt_1.Group(); let ps = new Pt_1.Group(); let ts = Curve.getSteps(steps); - // use first point twice let c = Curve.controlPoints(pts, 0, true); for (let i = 0; i <= steps; i++) { ps.push(Curve.catmullRomStep(ts[i], c)); @@ -2303,36 +1371,15 @@ class Curve { } return ps; } - /** - * Interpolate to get a point on Catmull-Rom curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @return an interpolated Pt on the curve - */ static catmullRomStep(step, ctrls) { - /* - * Basis Matrix (http://mrl.nyu.edu/~perlin/courses/fall2002/hw/12.html) - * [-0.5, 1.5, -1.5, 0.5], - * [ 1 , -2.5, 2 ,-0.5], - * [-0.5, 0 , 0.5, 0 ], - * [ 0 , 1 , 0 , 0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-0.5, 1, -0.5, 0), new Pt_1.Pt(1.5, -2.5, 0, 1), new Pt_1.Pt(-1.5, 2, 0.5, 0), new Pt_1.Pt(0.5, -0.5, 0, 0)); return Curve._calcPt(ctrls, LinearAlgebra_1.Mat.multiply([step], m, true)[0]); } - /** - * Create a Cardinal spline curve - * @param pts a group of anchor Pt - * @param steps the number of line segments per curve. Defaults to 10 steps. - * @param tension optional value between 0 to 1 to specify a "tension". Default to 0.5 which is the tension for Catmull-Rom curve. - * @returns a curve as a group of interpolated Pt - */ static cardinal(pts, steps = 10, tension = 0.5) { if (pts.length < 2) return new Pt_1.Group(); let ps = new Pt_1.Group(); let ts = Curve.getSteps(steps); - // use first point twice let c = Curve.controlPoints(pts, 0, true); for (let i = 0; i <= steps; i++) { ps.push(Curve.cardinalStep(ts[i], c, tension)); @@ -2349,21 +1396,7 @@ class Curve { } return ps; } - /** - * Interpolate to get a point on Catmull-Rom curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @param tension optional value between 0 to 1 to specify a "tension". Default to 0.5 which is the tension for Catmull-Rom curve - * @return an interpolated Pt on the curve - */ static cardinalStep(step, ctrls, tension = 0.5) { - /* - * Basis Matrix (http://algorithmist.wordpress.com/2009/10/06/cardinal-splines-part-4/) - * [ -s 2-s s-2 s ] - * [ 2s s-3 3-2s -s ] - * [ -s 0 s 0 ] - * [ 0 1 0 0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-1, 2, -1, 0), new Pt_1.Pt(-1, 1, 0, 0), new Pt_1.Pt(1, -2, 1, 0), new Pt_1.Pt(1, -1, 0, 0)); let h = LinearAlgebra_1.Mat.multiply([step], m, true)[0].multiply(tension); let h2 = (2 * step[0] - 3 * step[1] + 1); @@ -2375,12 +1408,6 @@ class Curve { pt.z += h2 * ctrls[1].z + h3 * ctrls[2].z; return pt; } - /** - * Create a Bezier curve. In a cubic bezier curve, the first and 4th anchors are end-points, and 2nd and 3rd anchors are control-points. - * @param pts a group of anchor Pt - * @param steps the number of line segments per curve. Defaults to 10 steps. - * @returns a curve as a group of interpolated Pt - */ static bezier(pts, steps = 10) { if (pts.length < 4) return new Pt_1.Group(); @@ -2393,36 +1420,15 @@ class Curve { for (let i = 0; i <= steps; i++) { ps.push(Curve.bezierStep(ts[i], c)); } - // go to the next set of point, but assume current end pt is next start pt k += 3; } } return ps; } - /** - * Interpolate to get a point on a cubic Bezier curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @return an interpolated Pt on the curve - */ static bezierStep(step, ctrls) { - /* - * Bezier basis matrix - * [ -1, 3, -3, 1 ] - * [ 3, -6, 3, 0 ] - * [ -3, 3, 0, 0 ] - * [ 1, 0, 0, 0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-1, 3, -3, 1), new Pt_1.Pt(3, -6, 3, 0), new Pt_1.Pt(-3, 3, 0, 0), new Pt_1.Pt(1, 0, 0, 0)); return Curve._calcPt(ctrls, LinearAlgebra_1.Mat.multiply([step], m, true)[0]); } - /** - * Create a B-spline curve - * @param pts a group of anchor Pt - * @param steps the number of line segments per curve. Defaults to 10 steps. - * @param tension optional value between 0 to n to specify a "tension". Default is 1 which is the usual tension. - * @returns a curve as a group of interpolated Pt - */ static bspline(pts, steps = 10, tension = 1) { if (pts.length < 2) return new Pt_1.Group(); @@ -2447,38 +1453,11 @@ class Curve { } return ps; } - /** - * Interpolate to get a point on a B-spline curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @return an interpolated Pt on the curve - */ static bsplineStep(step, ctrls) { - /* - * Basis matrix: - * [ -1.0/6.0, 3.0/6.0, -3.0/6.0, 1.0/6.0 ], - * [ 3.0/6.0, -6.0/6.0, 3.0/6.0, 0.0 ], - * [ -3.0/6.0, 0.0, 3.0/6.0, 0.0 ], - * [ 1.0/6.0, 4.0/6.0, 1.0/6.0, 0.0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-0.16666666666666666, 0.5, -0.5, 0.16666666666666666), new Pt_1.Pt(0.5, -1, 0, 0.6666666666666666), new Pt_1.Pt(-0.5, 0.5, 0.5, 0.16666666666666666), new Pt_1.Pt(0.16666666666666666, 0, 0, 0)); return Curve._calcPt(ctrls, LinearAlgebra_1.Mat.multiply([step], m, true)[0]); } - /** - * Interpolate to get a point on a B-spline curve - * @param step the coefficients [t*t*t, t*t, t, 1] - * @param ctrls a group of anchor Pts - * @param tension optional value between 0 to n to specify a "tension". Default to 1 which is the usual tension. - * @return an interpolated Pt on the curve - */ static bsplineTensionStep(step, ctrls, tension = 1) { - /* - * Basis matrix: - * [ -1/6a, 2 - 1.5a, 1.5a - 2, 1/6a ] - * [ 0.5a, 2a-3, 3-2.5a 0 ] - * [ -0.5a, 0, 0.5a, 0 ] - * [ 1/6a, 1 - 1/3a, 1/6a, 0 ] - */ let m = new Pt_1.Group(new Pt_1.Pt(-0.16666666666666666, 0.5, -0.5, 0.16666666666666666), new Pt_1.Pt(-1.5, 2, 0, -0.3333333333333333), new Pt_1.Pt(1.5, -2.5, 0.5, 0.16666666666666666), new Pt_1.Pt(0.16666666666666666, 0, 0, 0)); let h = LinearAlgebra_1.Mat.multiply([step], m, true)[0].multiply(tension); let h2 = (2 * step[0] - 3 * step[1] + 1); @@ -2500,51 +1479,21 @@ exports.Curve = Curve; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = __webpack_require__(1); const Op_1 = __webpack_require__(2); const Pt_1 = __webpack_require__(0); const LinearAlgebra_1 = __webpack_require__(5); -/** - * Num class provides various helper functions for basic numeric operations - */ class Num { - /** - * Check if two numbers are equal or almost equal within a threshold - * @param a number a - * @param b number b - * @param threshold a threshold within which the two numbers are considered equal - */ static equals(a, b, threshold = 0.00001) { return Math.abs(a - b) < threshold; } - /** - * Linear interpolation - * @param a start value - * @param b end value - * @param t usually a value between 0 to 1 - */ static lerp(a, b, t) { return (1 - t) * a + t * b; } - /** - * Clamp values between min and max - * @param val value to clamp - * @param min min value - * @param max max value - */ static clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } - /** - * Different from Num.clamp in that the value out-of-bound will be "looped back" to the other end. - * @param val value to bound - * @param min min value - * @param max max value - * @example `boundValue(361, 0, 360)` will return 1 - */ static boundValue(val, min, max) { let len = Math.abs(max - min); let a = val % len; @@ -2554,40 +1503,18 @@ class Num { a += len; return a; } - /** - * Check if a value is within - * @param p - * @param a - * @param b - */ static within(p, a, b) { return p >= Math.min(a, b) && p <= Math.max(a, b); } - /** - * Get a random number within a range - * @param a range value 1 - * @param b range value 2 - */ static randomRange(a, b = 0) { let r = (a > b) ? (a - b) : (b - a); return a + Math.random() * r; } - /** - * Normalize a value within a range - * @param n the value to normalize - * @param a range value 1 - * @param b range value 1 - */ static normalizeValue(n, a, b) { let min = Math.min(a, b); let max = Math.max(a, b); return (n - min) / (max - min); } - /** - * Sum a group of numeric arrays - * @param pts an array of numeric arrays - * @returns a array of sums - */ static sum(pts) { let c = new Pt_1.Pt(pts[0]); for (let i = 1, len = pts.length; i < len; i++) { @@ -2595,31 +1522,12 @@ class Num { } return c; } - /** - * Sum a group of numeric arrays - * @param pts an array of numeric arrays - * @returns a array of sums - */ static average(pts) { return Num.sum(pts).divide(pts.length); } - /** - * Given a value between 0 to 1, returns a value that cycles between 0 -> 1 -> 0 using sine method. - * @param t a value between 0 to 1 - * @return a value between 0 to 1 - */ static cycle(t) { return (Math.sin(Math.PI * 2 * t) + 1) / 2; } - /** - * Map a value from one range to another - * @param n a value in the first range - * @param currMin lower bound of the first range - * @param currMax upper bound of the first range - * @param targetMin lower bound of the second range - * @param targetMax upper bound of the second range - * @returns a remapped value in the second range - */ static mapToRange(n, currA, currB, targetA, targetB) { if (currA == currB) throw new Error("[currMin, currMax] must define a range that is not zero"); @@ -2629,58 +1537,27 @@ class Num { } } exports.Num = Num; -/** - * Geom class provides various helper functions for basic geometric operations - */ class Geom { - /** - * Bound an angle between 0 to 360 degrees - */ static boundAngle(angle) { return Num.boundValue(angle, 0, 360); } - /** - * Bound a radian between 0 to 2-PI - */ static boundRadian(angle) { return Num.boundValue(angle, 0, Util_1.Const.two_pi); } - /** - * Convert an angle in degree to radian - */ static toRadian(angle) { return angle * Util_1.Const.deg_to_rad; } - /** - * Convert an angle in radian to degree - */ static toDegree(radian) { return radian * Util_1.Const.rad_to_deg; } - /** - * Get a bounding box for a set of Pts - * @param pts a Group or an array of Pts - * @return a Group of two Pts, representing the top-left and bottom-right corners. - */ static boundingBox(pts) { let minPt = pts.reduce((a, p) => a.$min(p)); let maxPt = pts.reduce((a, p) => a.$max(p)); return new Pt_1.Group(minPt, maxPt); } - /** - * Get a centroid (the average middle point) for a set of Pts - * @param pts a Group or an array of Pts - * @return a centroid Pt - */ static centroid(pts) { return Num.average(pts); } - /** - * Given an anchor Pt, rebase all Pts in this group either to or from this anchor base. - * @param pts a Group or array of Pt - * @param ptOrIndex an index for the Pt array, or an external Pt - * @param direction "to" (subtract all Pt with this anchor base) or "from" (add all Pt from this anchor base) - */ static anchor(pts, ptOrIndex = 0, direction = "to") { let method = (direction == "to") ? "subtract" : "add"; for (let i = 0, len = pts.length; i < len; i++) { @@ -2693,13 +1570,6 @@ class Geom { } } } - /** - * Get an interpolated (or extrapolated) value between two Pts - * @param a first Pt - * @param b second Pt - * @param t a value between 0 to 1 to interpolate, or any other value to extrapolate - * @returns interpolated point as a new Pt - */ static interpolate(a, b, t = 0.5) { let len = Math.min(a.length, b.length); let d = Pt_1.Pt.make(len); @@ -2708,11 +1578,6 @@ class Geom { } return d; } - /** - * Find two Pt that are perpendicular to this Pt (2D) - * @param axis a string such as "xy" (use Const.xy) or an array to specify index for two dimensions - * @returns an array of two Pt that are perpendicular to this Pt - */ static perpendicular(pt, axis = Util_1.Const.xy) { let y = axis[1]; let x = axis[0]; @@ -2725,18 +1590,9 @@ class Geom { pb[y] = -p[x]; return new Pt_1.Group(pa, pb); } - /** - * Check if two Pts (vectors) are perpendicular to each other - */ static isPerpendicular(p1, p2) { return new Pt_1.Pt(p1).dot(p2) === 0; } - /** - * Check if a Pt is within the rectangular boundary defined by two Pts - * @param pt the Pt to check - * @param boundPt1 boundary Pt 1 - * @param boundPt2 boundary Pt 2 - */ static withinBound(pt, boundPt1, boundPt2) { for (let i = 0, len = Math.min(pt.length, boundPt1.length, boundPt2.length); i < len; i++) { if (!Num.within(pt[i], boundPt1[i], boundPt2[i])) @@ -2744,11 +1600,6 @@ class Geom { } return true; } - /** - * Sort the Pts so that their edges will form a non-overlapping polygon - * Ref: https://stackoverflow.com/questions/6989100/sort-points-in-clockwise-order - * @param pts an array of Pts - */ static sortEdges(pts) { let bounds = Geom.boundingBox(pts); let center = bounds[1].add(bounds[0]).divide(2); @@ -2766,24 +1617,15 @@ class Geom { return (da[1] > db[1]) ? 1 : -1; return (db[1] > da[1]) ? 1 : -1; } - // compute the cross product of vectors (center -> a) x (center -> b) let det = da.cross2D(db); if (det < 0) return 1; if (det > 0) return -1; - // points a and b are on the same line from the center - // check which point is closer to the center return (da[0] * da[0] + da[1] * da[1] > db[0] * db[0] + db[1] * db[1]) ? 1 : -1; }; return pts.sort(fn); } - /** - * Scale a Pt or a Group of Pts - * @param ps a Pt or a Group of Pts - * @param scale scale value - * @param anchor optional anchor point to scale from - */ static scale(ps, scale, anchor) { let pts = (!Array.isArray(ps)) ? [ps] : ps; let scs = (typeof scale == "number") ? Pt_1.Pt.make(pts[0].length, scale) : scale; @@ -2797,13 +1639,6 @@ class Geom { } return Geom; } - /** - * Rotate a Pt or a Group of Pts in 2D space - * @param ps a Pt or a Group of Pts - * @param angle rotate angle - * @param anchor optional anchor point to rotate from - * @param axis optional axis such as "yz" to define a 2D plane of rotation - */ static rotate2D(ps, angle, anchor, axis) { let pts = (!Array.isArray(ps)) ? [ps] : ps; let fn = (anchor) ? LinearAlgebra_1.Mat.rotateAt2DMatrix : LinearAlgebra_1.Mat.rotate2DMatrix; @@ -2817,13 +1652,6 @@ class Geom { } return Geom; } - /** - * Shear a Pt or a Group of Pts in 2D space - * @param ps a Pt or a Group of Pts - * @param scale shearing value which can be a number or an array of 2 numbers - * @param anchor optional anchor point to shear from - * @param axis optional axis such as "yz" to define a 2D plane of shearing - */ static shear2D(ps, scale, anchor, axis) { let pts = (!Array.isArray(ps)) ? [ps] : ps; let s = (typeof scale == "number") ? [scale, scale] : scale; @@ -2838,12 +1666,6 @@ class Geom { } return Geom; } - /** - * Reflect a Pt or a Group of Pts along a 2D line - * @param ps a Pt or a Group of Pts - * @param line a Group of 2 Pts that defines a line for reflection - * @param axis optional axis such as "yz" to define a 2D plane of reflection - */ static reflect2D(ps, line, axis) { let pts = (!Array.isArray(ps)) ? [ps] : ps; let mat = LinearAlgebra_1.Mat.reflectAt2DMatrix(line[0], line[1]); @@ -2853,10 +1675,6 @@ class Geom { } return Geom; } - /** - * Generate a sine and cosine lookup table - * @returns an object with 2 tables (array of 360 values) and 2 functions to get sin/cos given a radian parameter. { sinTable:Float64Array, cosTable:Float64Array, sin:(rad)=>number, cos:(rad)=>number } - */ static cosTable() { let cos = new Float64Array(360); for (let i = 0; i < 360; i++) @@ -2864,10 +1682,6 @@ class Geom { let find = (rad) => cos[Math.floor(Geom.boundAngle(Geom.toDegree(rad)))]; return { table: cos, cos: find }; } - /** - * Generate a sine and cosine lookup table - * @returns an object with 2 tables (array of 360 values) and 2 functions to get sin/cos given a radian parameter. { sinTable:Float64Array, cosTable:Float64Array, sin:(rad)=>number, cos:(rad)=>number } - */ static sinTable() { let sin = new Float64Array(360); for (let i = 0; i < 360; i++) @@ -2877,175 +1691,72 @@ class Geom { } } exports.Geom = Geom; -/** - * Shaping provides various shaping/easing functions to interpolate a value non-linearly. - */ class Shaping { - /** - * Linear mapping - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static linear(t, c = 1) { return c * t; } - /** - * Quadratic in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static quadraticIn(t, c = 1) { return c * t * t; } - /** - * Quadratic out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static quadraticOut(t, c = 1) { return -c * t * (t - 2); } - /** - * Quadratic in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static quadraticInOut(t, c = 1) { let dt = t * 2; return (t < 0.5) ? c / 2 * t * t * 4 : -c / 2 * ((dt - 1) * (dt - 3) - 1); } - /** - * Cubic in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static cubicIn(t, c = 1) { return c * t * t * t; } - /** - * Cubic out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static cubicOut(t, c = 1) { let dt = t - 1; return c * (dt * dt * dt + 1); } - /** - * Cubic in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static cubicInOut(t, c = 1) { let dt = t * 2; return (t < 0.5) ? c / 2 * dt * dt * dt : c / 2 * ((dt - 2) * (dt - 2) * (dt - 2) + 2); } - /** - * Exponential ease In, adapted from Golan Levin's [polynomial shapers](http://www.flong.com/texts/code/shapers_poly/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p a value between 0 to 1 to control the curve. Default is 0.25. - */ static exponentialIn(t, c = 1, p = 0.25) { return c * Math.pow(t, 1 / p); } - /** - * Exponential ease out, adapted from Golan Levin's [polynomial shapers](http://www.flong.com/texts/code/shapers_poly/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p a value between 0 to 1 to control the curve. Default is 0.25. - */ static exponentialOut(t, c = 1, p = 0.25) { return c * Math.pow(t, p); } - /** - * Sinuous in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static sineIn(t, c = 1) { return -c * Math.cos(t * Util_1.Const.half_pi) + c; } - /** - * Sinuous out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static sineOut(t, c = 1) { return c * Math.sin(t * Util_1.Const.half_pi); } - /** - * Sinuous in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static sineInOut(t, c = 1) { return -c / 2 * (Math.cos(Math.PI * t) - 1); } - /** - * A faster way to approximate cosine ease in-out using Blinn-Wyvill Approximation. Adapated from Golan Levin's [polynomial shaping](http://www.flong.com/texts/code/shapers_poly/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static cosineApprox(t, c = 1) { let t2 = t * t; let t4 = t2 * t2; let t6 = t4 * t2; return c * (4 * t6 / 9 - 17 * t4 / 9 + 22 * t2 / 9); } - /** - * Circular in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static circularIn(t, c = 1) { return -c * (Math.sqrt(1 - t * t) - 1); } - /** - * Circular out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static circularOut(t, c = 1) { let dt = t - 1; return c * Math.sqrt(1 - dt * dt); } - /** - * Circular in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static circularInOut(t, c = 1) { let dt = t * 2; return (t < 0.5) ? -c / 2 * (Math.sqrt(1 - dt * dt) - 1) : c / 2 * (Math.sqrt(1 - (dt - 2) * (dt - 2)) + 1); } - /** - * Elastic in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p elastic parmeter between 0 to 1. The lower the number, the more elastic it will be. Default is 0.7. - */ static elasticIn(t, c = 1, p = 0.7) { let dt = t - 1; let s = (p / Util_1.Const.two_pi) * 1.5707963267948966; return c * (-Math.pow(2, 10 * dt) * Math.sin((dt - s) * Util_1.Const.two_pi / p)); } - /** - * Elastic out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p elastic parmeter between 0 to 1. The lower the number, the more elastic it will be. Default is 0.7. - */ static elasticOut(t, c = 1, p = 0.7) { let s = (p / Util_1.Const.two_pi) * 1.5707963267948966; return c * (Math.pow(2, -10 * t) * Math.sin((t - s) * Util_1.Const.two_pi / p)) + c; } - /** - * Elastic in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p elastic parmeter between 0 to 1. The lower the number, the more elastic it will be. Default is 0.6. - */ static elasticInOut(t, c = 1, p = 0.6) { let dt = t * 2; let s = (p / Util_1.Const.two_pi) * 1.5707963267948966; @@ -3058,19 +1769,9 @@ class Shaping { return c * (0.5 * (Math.pow(2, -10 * dt) * Math.sin((dt - s) * Util_1.Const.two_pi / p))) + c; } } - /** - * Bounce in, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static bounceIn(t, c = 1) { return c - Shaping.bounceOut((1 - t), c); } - /** - * Bounce out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static bounceOut(t, c = 1) { if (t < (1 / 2.75)) { return c * (7.5625 * t * t); @@ -3088,30 +1789,13 @@ class Shaping { return c * (7.5625 * t * t + 0.984375); } } - /** - * Bounce in-out, adapted from Robert Penner's [easing functions](http://robertpenner.com/easing/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - */ static bounceInOut(t, c = 1) { return (t < 0.5) ? Shaping.bounceIn(t * 2, c) / 2 : Shaping.bounceOut(t * 2 - 1, c) / 2 + c / 2; } - /** - * Sigmoid curve changes its shape adapted from the input value, but always returns a value between 0 to 1. - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p the larger the value, the "steeper" the curve will be. Default is 10. - */ static sigmoid(t, c = 1, p = 10) { let d = p * (t - 0.5); return c / (1 + Math.exp(-d)); } - /** - * The Logistic Sigmoid is a useful curve. Adapted from Golan Levin's [shaping function](http://www.flong.com/texts/code/shapers_exp/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p a parameter between 0 to 1 to control the steepness of the curve. Higher is steeper. Default is 0.7. - */ static logSigmoid(t, c = 1, p = 0.7) { p = Math.max(Util_1.Const.epsilon, Math.min(1 - Util_1.Const.epsilon, p)); p = 1 / (1 - p); @@ -3120,12 +1804,6 @@ class Shaping { let C = 1 / (1 + Math.exp(-p)); return c * (A - B) / (C - B); } - /** - * An exponential seat curve. Adapted from Golan Levin's [shaping functions](http://www.flong.com/texts/code/shapers_exp/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p a parameter between 0 to 1 to control the steepness of the curve. Higher is steeper. Default is 0.5. - */ static seat(t, c = 1, p = 0.5) { if ((t < 0.5)) { return c * (Math.pow(2 * t, 1 - p)) / 2; @@ -3134,12 +1812,6 @@ class Shaping { return c * (1 - (Math.pow(2 * (1 - t), 1 - p)) / 2); } } - /** - * Quadratic bezier curve. Adapted from Golan Levin's [shaping functions](http://www.flong.com/texts/code/shapers_exp/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p1 a Pt object specifying the first control Pt, or a value specifying the control Pt's x position (its y position will default to 0.5). Default is `Pt(0.95, 0.95) - */ static quadraticBezier(t, c = 1, p = [0.05, 0.95]) { let a = (typeof p != "number") ? p[0] : p; let b = (typeof p != "number") ? p[1] : 0.5; @@ -3150,23 +1822,10 @@ class Shaping { let d = (Math.sqrt(a * a + om2a * t) - a) / om2a; return c * ((1 - 2 * b) * (d * d) + (2 * b) * d); } - /** - * Cubic bezier curve. This reuses the bezier functions in Curve class. - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p1` a Pt object specifying the first control Pt. Default is `Pt(0.1, 0.7). - * @parma p2` a Pt object specifying the second control Pt. Default is `Pt(0.9, 0.2). - */ static cubicBezier(t, c = 1, p1 = [0.1, 0.7], p2 = [0.9, 0.2]) { let curve = new Pt_1.Group(new Pt_1.Pt(0, 0), new Pt_1.Pt(p1), new Pt_1.Pt(p2), new Pt_1.Pt(1, 1)); return c * Op_1.Curve.bezierStep(new Pt_1.Pt(t * t * t, t * t, t, 1), Op_1.Curve.controlPoints(curve)).y; } - /** - * Give a Pt, draw a quadratic curve that will pass through that Pt as closely as possible. Adapted from Golan Levin's [shaping functions](http://www.flong.com/texts/code/shapers_poly/) - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p1` a Pt object specifying the Pt to pass through. Default is `Pt(0.2, 0.35) - */ static quadraticTarget(t, c = 1, p1 = [0.2, 0.35]) { let a = Math.min(1 - Util_1.Const.epsilon, Math.max(Util_1.Const.epsilon, p1[0])); let b = Math.min(1, Math.max(0, p1[1])); @@ -3175,23 +1834,9 @@ class Shaping { let y = A * (t * t) - B * t; return c * Math.min(1, Math.max(0, y)); } - /** - * Step function is a simple jump from 0 to 1 at a specific Pt in time - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma p usually a value between 0 to 1, which specify the Pt to "jump". Default is 0.5 which is in the middle. - */ static cliff(t, c = 1, p = 0.5) { return (t > p) ? c : 0; } - /** - * Convert any shaping functions into a series of steps - * @parma fn the original shaping function - * @parma steps the number of steps - * @parma t a value between 0 to 1 - * @parma c the value to shape, default is 1 - * @parma args optional paramters to pass to original function - */ static step(fn, steps, t, c, ...args) { let s = 1 / steps; let tt = Math.floor(t / s) * s; @@ -3199,36 +1844,15 @@ class Shaping { } } exports.Shaping = Shaping; -/** - * Range object keeps track of a Group of n-dimensional Pts to provide its minimum, maximum, and magnitude in each dimension. - * It also provides convenient functions such as mapping the Group to another range. - */ class Range { - /** - * Construct a Range instance for a Group of Pts, - * @param g a Group or an array of Pts - */ constructor(g) { this._dims = 0; this._source = Pt_1.Group.fromPtArray(g); this.calc(); } - /** - * Get this Range's maximum values per dimension - */ get max() { return this._max.clone(); } - /** - * Get this Range's minimum values per dimension - */ get min() { return this._min.clone(); } - /** - * Get this Range's magnitude in each dimension - */ get magnitude() { return this._mag.clone(); } - /** - * Go through the group and find its min and max values. - * Usually you don't need to call this function directly. - */ calc() { if (!this._source) return; @@ -3253,12 +1877,6 @@ class Range { this._mag = mag; return this; } - /** - * Map this Range to another range of values - * @param min target range's minimum value - * @param max target range's maximum value - * @param exclude Optional boolean array where `true` means excluding the conversion in that specific dimension. - */ mapTo(min, max, exclude) { let target = new Pt_1.Group(); for (let i = 0, len = this._source.length; i < len; i++) { @@ -3271,11 +1889,6 @@ class Range { } return target; } - /** - * Add more Pts to this Range and recalculate its min and max values - * @param g a Group or an array of Pts to append to this Range - * @param update Optional. Set the parameter to `false` if you want to append without immediately updating this Range's min and max values. Default is `true`. - */ append(g, update = true) { if (g[0].length !== this._dims) throw new Error(`Dimensions don't match. ${this._dims} dimensions in Range and ${g[0].length} provided in parameter. `); @@ -3284,10 +1897,6 @@ class Range { this.calc(); return this; } - /** - * Create a number of evenly spaced "ticks" that span this Range's min and max value. - * @param count number of subdivision. For example, 10 subdivision will return 11 tick values, which include first(min) and last(max) values. - */ ticks(count) { let g = new Pt_1.Group(); for (let i = 0; i <= count; i++) { @@ -3309,19 +1918,9 @@ exports.Range = Range; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); -/** - * Bound is a subclass of Group that represents a rectangular boundary. - * It includes some convenient properties such as `x`, `y`, bottomRight`, `center`, and `size`. - */ class Bound extends Pt_1.Group { - /** - * Create a Bound. This is similar to the Group constructor. - * @param args a list of Pt as parameters - */ constructor(...args) { super(...args); this._center = new Pt_1.Pt(); @@ -3331,11 +1930,6 @@ class Bound extends Pt_1.Group { this._inited = false; this.init(); } - /** - * Create a Bound from a [ClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) object. - * @param rect an object has top/left/bottom/right/width/height properties - * @returns a Bound object - */ static fromBoundingRect(rect) { let b = new Bound(new Pt_1.Pt(rect.left || 0, rect.top || 0), new Pt_1.Pt(rect.right || 0, rect.bottom || 0)); if (rect.width && rect.height) @@ -3347,9 +1941,6 @@ class Bound extends Pt_1.Group { throw new Error("Cannot create a Bound from a group that has less than 2 Pt"); return new Bound(g[0], g[g.length - 1]); } - /** - * Initiate the bound's properties. - */ init() { if (this.p1) { this._size = this.p1.clone(); @@ -3364,42 +1955,24 @@ class Bound extends Pt_1.Group { this._inited = true; } } - /** - * Clone this bound and return a new one - */ clone() { return new Bound(this._topLeft.clone(), this._bottomRight.clone()); } - /** - * Recalculte size and center - */ _updateSize() { this._size = this._bottomRight.$subtract(this._topLeft).abs(); this._updateCenter(); } - /** - * Recalculate center - */ _updateCenter() { this._center = this._size.$multiply(0.5).add(this._topLeft); } - /** - * Recalculate based on top-left position and size - */ _updatePosFromTop() { this._bottomRight = this._topLeft.$add(this._size); this._updateCenter(); } - /** - * Recalculate based on bottom-right position and size - */ _updatePosFromBottom() { this._topLeft = this._bottomRight.$subtract(this._size); this._updateCenter(); } - /** - * Recalculate based on center position and size - */ _updatePosFromCenter() { let half = this._size.$multiply(0.5); this._topLeft = this._center.$subtract(half); @@ -3446,10 +2019,6 @@ class Bound extends Pt_1.Group { get y() { return this.topLeft.y; } get z() { return this.topLeft.z; } get inited() { return this._inited; } - /** - * If the Group elements are changed, call this function to update the Bound's properties. - * It's preferable to change the topLeft/bottomRight etc properties instead of changing the Group array directly. - */ update() { this._topLeft = this[0]; this._bottomRight = this[1]; @@ -3466,19 +2035,10 @@ exports.Bound = Bound; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); const Op_1 = __webpack_require__(2); -/** - * Vec provides static function for vector operations. It's not yet optimized but good enough to use. - */ class Vec { - /** - * Add b to vector `a` - * @returns vector `a` - */ static add(a, b) { if (typeof b == "number") { for (let i = 0, len = a.length; i < len; i++) @@ -3490,10 +2050,6 @@ class Vec { } return a; } - /** - * Subtract `b` from vector `a` - * @returns vector `a` - */ static subtract(a, b) { if (typeof b == "number") { for (let i = 0, len = a.length; i < len; i++) @@ -3505,10 +2061,6 @@ class Vec { } return a; } - /** - * Multiply `b` with vector `a` - * @returns vector `a` - */ static multiply(a, b) { if (typeof b == "number") { for (let i = 0, len = a.length; i < len; i++) @@ -3523,10 +2075,6 @@ class Vec { } return a; } - /** - * Divide `a` over `b` - * @returns vector `a` - */ static divide(a, b) { if (typeof b == "number") { if (b === 0) @@ -3543,9 +2091,6 @@ class Vec { } return a; } - /** - * Dot product of `a` and `b` - */ static dot(a, b) { if (a.length != b.length) throw new Error("Array lengths don't match"); @@ -3555,65 +2100,33 @@ class Vec { } return d; } - /** - * 2D cross product of `a` and `b` - */ static cross2D(a, b) { return a[0] * b[1] - a[1] * b[0]; } - /** - * 3D Cross product of `a` and `b` - */ static cross(a, b) { return new Pt_1.Pt((a[1] * b[2] - a[2] * b[1]), (a[2] * b[0] - a[0] * b[2]), (a[0] * b[1] - a[1] * b[0])); } - /** - * Magnitude of `a` - */ static magnitude(a) { return Math.sqrt(Vec.dot(a, a)); } - /** - * Unit vector of `a`. If magnitude of `a` is already known, pass it in the second paramter to optimize calculation. - */ static unit(a, magnitude = undefined) { let m = (magnitude === undefined) ? Vec.magnitude(a) : magnitude; if (m === 0) throw new Error("Cannot calculate unit vector because magnitude is 0"); return Vec.divide(a, m); } - /** - * Set `a` to its absolute value in each dimension - * @returns vector `a` - */ static abs(a) { return Vec.map(a, Math.abs); } - /** - * Set `a` to its floor value in each dimension - * @returns vector `a` - */ static floor(a) { return Vec.map(a, Math.floor); } - /** - * Set `a` to its ceiling value in each dimension - * @returns vector `a` - */ static ceil(a) { return Vec.map(a, Math.ceil); } - /** - * Set `a` to its rounded value in each dimension - * @returns vector `a` - */ static round(a) { return Vec.map(a, Math.round); } - /** - * Find the max value within a vector's dimensions - * @returns an object with `value` and `index` that specifies the max value and its corresponding dimension. - */ static max(a) { let m = Number.MIN_VALUE; let index = 0; @@ -3624,10 +2137,6 @@ class Vec { } return { value: m, index: index }; } - /** - * Find the min value within a vector's dimensions - * @returns an object with `value` and `index` that specifies the min value and its corresponding dimension. - */ static min(a) { let m = Number.MAX_VALUE; let index = 0; @@ -3638,19 +2147,12 @@ class Vec { } return { value: m, index: index }; } - /** - * Sum all the dimensions' values - */ static sum(a) { let s = 0; for (let i = 0, len = a.length; i < len; i++) s += a[i]; return s; } - /** - * Given a mapping function, update `a`'s value in each dimension - * @returns vector `a` - */ static map(a, fn) { for (let i = 0, len = a.length; i < len; i++) { a[i] = fn(a[i], i, a); @@ -3659,16 +2161,7 @@ class Vec { } } exports.Vec = Vec; -/** - * Mat provides static function for matrix operations. It's not yet optimized but good enough to use. - */ class Mat { - /** - * Matrix additions. Matrices should have the same rows and columns. - * @param a a group of Pt - * @param b a scalar number, an array of numeric arrays, or a group of Pt - * @returns a group with the same rows and columns as a and b - */ static add(a, b) { if (typeof b != "number") { if (a[0].length != b[0].length) @@ -3683,14 +2176,6 @@ class Mat { } return g; } - /** - * Matrix multiplication - * @param a a Group of M Pts, each with K dimensions (M-rows, K-columns) - * @param b a scalar number, an array of numeric arrays, or a Group of K Pts, each with N dimensions (K-rows, N-columns) -- or if transposed is true, then N Pts with K dimensions - * @param transposed (Only applicable if it's not elementwise multiplication) If true, then a and b's columns should match (ie, each Pt should have the same dimensions). Default is `false`. - * @param elementwise if true, then the multiplication is done element-wise. Default is `false`. - * @returns If not elementwise, this will return a group with M Pt, each with N dimensions (M-rows, N-columns). - */ static multiply(a, b, transposed = false, elementwise = false) { let g = new Pt_1.Group(); if (typeof b != "number") { @@ -3724,12 +2209,6 @@ class Mat { } return g; } - /** - * Zip one slice of an array of Pt. Imagine the Pts are organized in rows, then this function will take the values in a specific column. - * @param g a group of Pt - * @param idx index to zip at - * @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead. - */ static zipSlice(g, index, defaultValue = false) { let z = []; for (let i = 0, len = g.length; i < len; i++) { @@ -3739,12 +2218,6 @@ class Mat { } return new Pt_1.Pt(z); } - /** - * Zip a group of Pt. eg, [[1,2],[3,4],[5,6]] => [[1,3,5],[2,4,6]] - * @param g a group of Pt - * @param defaultValue a default value to fill if index out of bound. If not provided, it will throw an error instead. - * @param useLongest If true, find the longest list of values in a Pt and use its length for zipping. Default is false, which uses the first item's length for zipping. - */ static zip(g, defaultValue = false, useLongest = false) { let ps = new Pt_1.Group(); let len = (useLongest) ? g.reduce((a, b) => Math.max(a, b.length), 0) : g[0].length; @@ -3753,79 +2226,44 @@ class Mat { } return ps; } - /** - * Same as `zip` function - */ static transpose(g, defaultValue = false, useLongest = false) { return Mat.zip(g, defaultValue, useLongest); } - /** - * Transform a 2D point given a 2x3 or 3x3 matrix - * @param pt a Pt to be transformed - * @param m 2x3 or 3x3 matrix - * @returns a new transformed Pt - */ static transform2D(pt, m) { let x = pt[0] * m[0][0] + pt[1] * m[1][0] + m[2][0]; let y = pt[0] * m[0][1] + pt[1] * m[1][1] + m[2][1]; return new Pt_1.Pt(x, y); } - /** - * Get a scale matrix for use in `transform2D` - */ static scale2DMatrix(x, y) { return new Pt_1.Group(new Pt_1.Pt(x, 0, 0), new Pt_1.Pt(0, y, 0), new Pt_1.Pt(0, 0, 1)); } - /** - * Get a rotate matrix for use in `transform2D` - */ static rotate2DMatrix(cosA, sinA) { return new Pt_1.Group(new Pt_1.Pt(cosA, sinA, 0), new Pt_1.Pt(-sinA, cosA, 0), new Pt_1.Pt(0, 0, 1)); } - /** - * Get a shear matrix for use in `transform2D` - */ static shear2DMatrix(tanX, tanY) { return new Pt_1.Group(new Pt_1.Pt(1, tanX, 0), new Pt_1.Pt(tanY, 1, 0), new Pt_1.Pt(0, 0, 1)); } - /** - * Get a translate matrix for use in `transform2D` - */ static translate2DMatrix(x, y) { return new Pt_1.Group(new Pt_1.Pt(1, 0, 0), new Pt_1.Pt(0, 1, 0), new Pt_1.Pt(x, y, 1)); } - /** - * Get a matrix to scale a point from an origin point. For use in `transform2D` - */ static scaleAt2DMatrix(sx, sy, at) { let m = Mat.scale2DMatrix(sx, sy); m[2][0] = -at[0] * sx + at[0]; m[2][1] = -at[1] * sy + at[1]; return m; } - /** - * Get a matrix to rotate a point from an origin point. For use in `transform2D` - */ static rotateAt2DMatrix(cosA, sinA, at) { let m = Mat.rotate2DMatrix(cosA, sinA); m[2][0] = at[0] * (1 - cosA) + at[1] * sinA; m[2][1] = at[1] * (1 - cosA) - at[0] * sinA; return m; } - /** - * Get a matrix to shear a point from an origin point. For use in `transform2D` - */ static shearAt2DMatrix(tanX, tanY, at) { let m = Mat.shear2DMatrix(tanX, tanY); m[2][0] = -at[1] * tanY; m[2][1] = -at[0] * tanX; return m; } - /** - * Get a matrix to reflect a point along a line. For use in `transform2D` - * @param p1 first end point to define the reflection line - * @param p1 second end point to define the reflection line - */ static reflectAt2DMatrix(p1, p2) { let intercept = Op_1.Line.intercept(p1, p2); if (intercept == undefined) { @@ -3857,25 +2295,13 @@ exports.Mat = Mat; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Util_1 = __webpack_require__(1); -/** -* Form is an abstract class that represents a form that's used in a Space for expressions. -*/ class Form { constructor() { this._ready = false; } - /** - * get whether the Form has received the Space's rendering context - */ get ready() { return this._ready; } - /** - * Check number of items in a Group against a required number - * @param pts - */ static _checkSize(pts, required = 2) { if (pts.length < required) { Util_1.Util.warn("Requires 2 or more Pts in this Group."); @@ -3885,10 +2311,6 @@ class Form { } } exports.Form = Form; -/** -* VisualForm is an abstract class that represents a form that can be used to express Pts visually. -* For example, CanvasForm is an implementation of VisualForm that draws on CanvasSpace which represents a html canvas. -*/ class VisualForm extends Form { constructor() { super(...arguments); @@ -3909,42 +2331,20 @@ class VisualForm extends Form { } return this; } - /** - * Set fill color (not implemented) - */ fill(c) { return this; } - /** - * Set current fill style and without stroke. - * @example `form.fillOnly("#F90")`, `form.fillOnly("rgba(0,0,0,.5")` - * @param c fill color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle)) - */ fillOnly(c) { this.stroke(false); return this.fill(c); } - /** - * Set stroke style (not implemented) - */ stroke(c, width, linejoin, linecap) { return this; } - /** - * Set current stroke style and without fill. - * @example `form.strokeOnly("#F90")`, `form.strokeOnly("#000", 0.5, 'round', 'square')` - * @param c stroke color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle) - */ strokeOnly(c, width, linejoin, linecap) { this.fill(false); return this.stroke(c, width, linejoin, linecap); } - /** - * Draw multiple points at once - * @param pts an array of Pt or an array of number arrays - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - */ points(pts, radius, shape) { if (!pts) return; @@ -3953,56 +2353,24 @@ class VisualForm extends Form { } return this; } - /** - * Draw multiple circles at once - * @param groups an array of Groups that defines multiple circles - */ circles(groups) { return this._multiple(groups, "circle"); } - /** - * Draw multiple squares at once - * @param groups an array of Groups that defines multiple circles - */ squares(groups) { return this._multiple(groups, "square"); } - /** - * Draw multiple lines at once - * @param groups An array of Groups of Pts - */ lines(groups) { return this._multiple(groups, "line"); } - /** - * Draw multiple polygons at once - * @param groups An array of Groups of Pts - */ polygons(groups) { return this._multiple(groups, "polygon"); } - /** - * Draw multiple rectangles at once - * @param groups An array of Groups of Pts - */ rects(groups) { return this._multiple(groups, "rect"); } } exports.VisualForm = VisualForm; -/** -* Font class lets you create a specific font style with properties for its size and style -*/ class Font { - /** - * Create a font style - * @param size font size. Defaults is 12px. - * @param face Optional font-family, use css-like string such as "Helvetica" or "Helvetica, sans-serif". Default is "sans-serif". - * @param weight Optional font weight such as "bold". Default is "" (none). - * @param style Optional font style such as "italic". Default is "" (none). - * @param lineHeight Optional line height. Default is 1.5. - * @example `new Font(12, "Frutiger, sans-serif", "bold", "underline", 1.5)` - */ constructor(size = 12, face = "sans-serif", weight = "", style = "", lineHeight = 1.5) { this.size = size; this.face = face; @@ -4010,13 +2378,7 @@ class Font { this.weight = weight; this.lineHeight = lineHeight; } - /** - * Get a string representing the font style, in css-like string such as "italic bold 12px/1.5 sans-serif" - */ get value() { return `${this.style} ${this.weight} ${this.size}px/${this.lineHeight} ${this.face}`; } - /** - * Get a string representing the font style, in css-like string such as "italic bold 12px/1.5 sans-serif" - */ toString() { return this.value; } } exports.Font = Font; @@ -4028,16 +2390,10 @@ exports.Font = Font; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Bound_1 = __webpack_require__(4); const Pt_1 = __webpack_require__(0); const UI_1 = __webpack_require__(12); -/** -* Space is an abstract class that represents a general context for expressing Pts. -* See [Space guide](../../guide/Space-0500.html) for details. -*/ class Space { constructor() { this.id = "space"; @@ -4052,23 +2408,10 @@ class Space { this._isReady = false; this._playing = false; } - /** - * Set whether the rendering should be repainted on each frame - * @param b a boolean value to set whether to repaint each frame - */ refresh(b) { this._refresh = b; return this; } - /** - * Add an IPlayer to this space. An IPlayer can define the following callback functions: - * - `animate( time, ftime, space )` - * - `start(bound, space)` - * - `resize( size, event )` - * - `action( type, x, y, event )` - * Subclasses of Space may define other callback functions. - * @param player an IPlayer object with animate function, or simply a function(time, ftime){} - */ add(p) { let player = (typeof p == "function") ? { animate: p } : p; let k = this.playerCount++; @@ -4077,31 +2420,18 @@ class Space { player.animateID = pid; if (player.resize && this.bound.inited) player.resize(this.bound); - // if _refresh is not set, set it to true if (this._refresh === undefined) this._refresh = true; return this; } - /** - * Remove a player from this Space - * @param player an IPlayer that has an `animateID` property - */ remove(player) { delete this.players[player.animateID]; return this; } - /** - * Remove all players from this Space - */ removeAll() { this.players = {}; return this; } - /** - * Main play loop. This implements window.requestAnimationFrame and calls it recursively. - * Override this `play()` function to implemenet your own animation loop. - * @param time current time - */ play(time = 0) { this._animID = requestAnimationFrame(this.play.bind(this)); if (this._pause) @@ -4118,150 +2448,77 @@ class Space { } return this; } - /** - * Replay the animation after `stop()`. This resets the end-time counter. - * You may also use `pause()` and `resume()` for temporary pause. - */ replay() { this._time.end = -1; this.play(); } - /** - * Main animate function. This calls all the items to perform - * @param time current time - */ playItems(time) { this._playing = true; - // clear before draw if refresh is true if (this._refresh) this.clear(); - // animate all players if (this._isReady) { for (let k in this.players) { if (this.players[k].animate) this.players[k].animate(time, this._time.diff, this); } } - // stop if time ended if (this._time.end >= 0 && time > this._time.end) { cancelAnimationFrame(this._animID); this._playing = false; } } - /** - * Pause the animation - * @param toggle a boolean value to set if this function call should be a toggle (between pause and resume) - */ pause(toggle = false) { this._pause = (toggle) ? !this._pause : true; return this; } - /** - * Resume the pause animation - */ resume() { this._pause = false; return this; } - /** - * Specify when the animation should stop: immediately, after a time period, or never stops. - * @param t a value in millisecond to specify a time period to play before stopping, or `-1` to play forever, or `0` to end immediately. Default is 0 which will stop the animation immediately. - */ stop(t = 0) { this._time.end = t; return this; } - /** - * Play animation loop, and then stop after `duration` time has passed. - * @param duration a value in millisecond to specify a time period to play before stopping, or `-1` to play forever - */ playOnce(duration = 5000) { this.play(); this.stop(duration); return this; } - /** - * Custom rendering - * @param context rendering context - */ render(context) { if (this._renderFunc) this._renderFunc(context, this); return this; } - /** - * Set a custom rendering `function(graphics_context, canvas_space)` if needed - */ set customRendering(f) { this._renderFunc = f; } get customRendering() { return this._renderFunc; } - /** - * Get a boolean to indicate whether the animation is playing - */ get isPlaying() { return this._playing; } - /** - * Get this space's bounding box - */ get outerBound() { return this.bound.clone(); } - /** - * The bounding box of the canvas - */ get innerBound() { return new Bound_1.Bound(Pt_1.Pt.make(this.size.length, 0), this.size.clone()); } - /** - * Get the size of this bounding box as a Pt - */ get size() { return this.bound.size.clone(); } - /** - * Get the size of this bounding box as a Pt - */ get center() { return this.size.divide(2); } - /** - * Get width of canvas - */ get width() { return this.bound.width; } - /** - * Get height of canvas - */ get height() { return this.bound.height; } } exports.Space = Space; class MultiTouchSpace extends Space { constructor() { super(...arguments); - // track mouse dragging this._pressed = false; this._dragged = false; this._hasMouse = false; this._hasTouch = false; } - /** - * Get the mouse or touch pointer that stores the last action - */ get pointer() { let p = this._pointer.clone(); p.id = this._pointer.id; return p; } - /** - * Bind event listener in canvas element. You can also use `bindMouse` or `bindTouch` to bind mouse or touch events conveniently. - * @param evt an event string such as "mousedown" - * @param callback callback function for this event - */ bindCanvas(evt, callback) { this._canvas.addEventListener(evt, callback); } - /** - * Unbind a callback from the event listener - * @param evt an event string such as "mousedown" - * @param callback callback function to unbind - */ unbindCanvas(evt, callback) { this._canvas.removeEventListener(evt, callback); } - /** - * A convenient method to bind (or unbind) all mouse events in canvas element. All "players" added to this space that implements an `action` callback property will receive mouse event callbacks. The types of mouse actions are defined by UIPointerActions constants: "up", "down", "move", "drag", "drop", "over", and "out". See `Space`'s `add()` function for more details. - * @param _bind a boolean value to bind mouse events if set to `true`. If `false`, all mouse events will be unbound. Default is true. - * @see Space`'s [`add`](./_space_.space.html#add) function - */ bindMouse(_bind = true) { if (_bind) { this.bindCanvas("mousedown", this._mouseDown.bind(this)); @@ -4281,11 +2538,6 @@ class MultiTouchSpace extends Space { } return this; } - /** - * A convenient method to bind (or unbind) all touch events in canvas element. All "players" added to this space that implements an `action` callback property will receive mouse event callbacks. The types of mouse actions are: "up", "down", "move", "drag", "drop", "over", and "out". - * @param _bind a boolean value to bind touch events if set to `true`. If `false`, all mouse events will be unbound. Default is true. - * @see Space`'s [`add`](./_space_.space.html#add) function - */ bindTouch(_bind = true) { if (_bind) { this.bindCanvas("touchstart", this._mouseDown.bind(this)); @@ -4303,12 +2555,6 @@ class MultiTouchSpace extends Space { } return this; } - /** - * A convenient method to convert the touch points in a touch event to an array of `Pt`. - * @param evt a touch event which contains touches, changedTouches, and targetTouches list - * @param which a string to select a touches list: "touches", "changedTouches", or "targetTouches". Default is "touches" - * @return an array of Pt, whose origin position (0,0) is offset to the top-left of this space - */ touchesToPoints(evt, which = "touches") { if (!evt || !evt[which]) return []; @@ -4319,11 +2565,6 @@ class MultiTouchSpace extends Space { } return ts; } - /** - * Go through all the `players` and call its `action` callback function - * @param type an UIPointerActions constant or string: "up", "down", "move", "drag", "drop", "over", and "out" - * @param evt mouse or touch event - */ _mouseAction(type, evt) { let px = 0, py = 0; if (evt instanceof MouseEvent) { @@ -4355,19 +2596,11 @@ class MultiTouchSpace extends Space { this._pointer.id = type; } } - /** - * MouseDown handler - * @param evt - */ _mouseDown(evt) { this._mouseAction(UI_1.UIPointerActions.down, evt); this._pressed = true; return false; } - /** - * MouseUp handler - * @param evt - */ _mouseUp(evt) { this._mouseAction(UI_1.UIPointerActions.up, evt); if (this._dragged) @@ -4376,10 +2609,6 @@ class MultiTouchSpace extends Space { this._dragged = false; return false; } - /** - * MouseMove handler - * @param evt - */ _mouseMove(evt) { this._mouseAction(UI_1.UIPointerActions.move, evt); if (this._pressed) { @@ -4388,18 +2617,10 @@ class MultiTouchSpace extends Space { } return false; } - /** - * MouseOver handler - * @param evt - */ _mouseOver(evt) { this._mouseAction(UI_1.UIPointerActions.over, evt); return false; } - /** - * MouseOut handler - * @param evt - */ _mouseOut(evt) { this._mouseAction(UI_1.UIPointerActions.out, evt); if (this._dragged) @@ -4407,10 +2628,6 @@ class MultiTouchSpace extends Space { this._dragged = false; return false; } - /** - * TouchMove handler - * @param evt - */ _touchMove(evt) { this._mouseMove(evt); evt.preventDefault(); @@ -4426,31 +2643,14 @@ exports.MultiTouchSpace = MultiTouchSpace; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); -/** Various functions to support typography */ class Typography { - /** - * Create a heuristic text width estimate function. It will be less accurate but faster. - * @param fn a reference function that can measure text width accurately - * @param samples a list of string samples. Default is ["M", "n", "."] - * @param distribution a list of the samples' probability distribution. Default is [0.06, 0.8, 0.14]. - * @return a function that can estimate text width - */ static textWidthEstimator(fn, samples = ["M", "n", "."], distribution = [0.06, 0.8, 0.14]) { let m = samples.map(fn); let avg = new Pt_1.Pt(distribution).dot(m); return (str) => str.length * avg; } - /** - * Truncate text to fit width - * @param fn a function that can measure text width - * @param str text to truncate - * @param width width to fit - * @param tail text to indicate overflow such as "...". Default is empty "". - */ static truncate(fn, str, width, tail = "") { let trim = Math.floor(str.length * Math.min(1, width / fn(str))); if (trim < str.length) { @@ -4461,12 +2661,6 @@ class Typography { return [str, str.length]; } } - /** - * Get a function to scale font size proportionally to text box size changes. - * @param box Initial box as a Group - * @param ratio font-size change ratio. Default is 1. - * @returns a function where input parameter is a new box, and returns the new font size value - */ static fontSizeToBox(box, ratio = 1, byHeight = true) { let i = byHeight ? 1 : 0; let h = (box[1][i] - box[0][i]); @@ -4476,13 +2670,6 @@ class Typography { return f * nh; }; } - /** - * Get a function to scale font size based on a threshold value - * @param defaultSize default font size to base on - * @param threshold threshold value - * @param direction if negative, get a font size <= defaultSize; if positive, get a font size >= defaultSize; Default is 0 which will scale font without min or max limits. - * @returns a function where input parameter is the default font size and a value to compare with threshold, and returns new font size value - */ static fontSizeToThreshold(threshold, direction = 0) { return function (defaultSize, val) { let d = defaultSize * val / threshold; @@ -4503,24 +2690,13 @@ exports.Typography = Typography; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Space_1 = __webpack_require__(7); const Form_1 = __webpack_require__(6); const Bound_1 = __webpack_require__(4); const Util_1 = __webpack_require__(1); const Pt_1 = __webpack_require__(0); -/** - * A Space for DOM elements - */ class DOMSpace extends Space_1.MultiTouchSpace { - /** - * Create a DOMSpace which represents a Space for DOM elements - * @param elem Specify an element by its "id" attribute as string, or by the element object itself. Use css to customize its appearance if needed. - * @param callback an optional callback `function(boundingBox, spaceElement)` to be called when canvas is appended and ready. Alternatively, a "ready" event will also be fired from the element when it's appended, which can be traced with `spaceInstance.canvas.addEventListener("ready")` - * @example `new DOMSpace( "#myElementID" )` - */ constructor(elem, callback) { super(); this.id = "domspace"; @@ -4530,7 +2706,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { var _selector = null; var _existed = false; this.id = "pts"; - // check element or element id string if (elem instanceof Element) { _selector = elem; this.id = "pts_existing_space"; @@ -4540,7 +2715,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { _existed = true; this.id = elem.substr(1); } - // if selector is not defined, create a canvas if (!_selector) { this._container = DOMSpace.createElement("div", "pts_container"); this._canvas = DOMSpace.createElement("div", "pts_element"); @@ -4552,15 +2726,8 @@ class DOMSpace extends Space_1.MultiTouchSpace { this._canvas = _selector; this._container = _selector.parentElement; } - // no mutation observer, so we set a timeout for ready event setTimeout(this._ready.bind(this, callback), 50); } - /** - * Helper function to create a DOM element - * @param elem element tag name - * @param id element id attribute - * @param appendTo Optional, if specified, the created element will be appended to this element - */ static createElement(elem = "div", id, appendTo) { let d = document.createElement(elem); if (id) @@ -4569,10 +2736,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { appendTo.appendChild(d); return d; } - /** - * Handle callbacks after element is mounted in DOM - * @param callback - */ _ready(callback) { if (!this._container) throw new Error(`Cannot initiate #${this.id} element`); @@ -4587,17 +2750,10 @@ class DOMSpace extends Space_1.MultiTouchSpace { } } this._pointer = this.center; - this.refresh(false); // No need to clear and redraw for every frame in DOM + this.refresh(false); if (callback) callback(this.bound, this._canvas); } - /** - * Set up various options for DOMSpace. The `opt` parameter is an object with the following fields. This is usually set during instantiation, eg `new DOMSpace(...).setup( { opt } )` - * @param opt an object with optional settings, as follows. - * @param opt.bgcolor a hex or rgba string to set initial background color of the canvas, or use `false` or "transparent" to set a transparent background. You may also change it later with `clear()` - * @param opt.resize a boolean to set whether `` size should auto resize to match its container's size. You can also set it manually with `autoSize()` - * @example `space.setup({ bgcolor: "#f00", resize: true })` - */ setup(opt) { if (opt.bgcolor) { this._bgcolor = opt.bgcolor; @@ -4605,16 +2761,9 @@ class DOMSpace extends Space_1.MultiTouchSpace { this.autoResize = (opt.resize != undefined) ? opt.resize : false; return this; } - /** - * Not implemented. See SVGSpace and HTMLSpace for implementation - */ getForm() { return null; } - /** - * Set whether the canvas element should resize when its container is resized. - * @param auto a boolean value indicating if auto size is set - */ set autoResize(auto) { this._autoResize = auto; if (auto) { @@ -4627,11 +2776,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { } } get autoResize() { return this._autoResize; } - /** - * This overrides Space's `resize` function. It's used as a callback function for window's resize event and not usually called directly. You can keep track of resize events with `resize: (bound ,evt)` callback in your player objects (See `Space`'s `add()` function). - * @param b a Bound object to resize to - * @param evt Optionally pass a resize event - */ resize(b, evt) { this.bound = b; this.styles({ width: `${b.width}px`, height: `${b.height}px` }, true); @@ -4644,10 +2788,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { } return this; } - /** - * Window resize handling - * @param evt - */ _resizeHandler(evt) { let b = Bound_1.Bound.fromBoundingRect(this._container.getBoundingClientRect()); if (this._autoResize) { @@ -4658,59 +2798,30 @@ class DOMSpace extends Space_1.MultiTouchSpace { } this.resize(b, evt); } - /** - * Get this DOM element - */ get element() { return this._canvas; } - /** - * Get the parent DOM element that contains this DOM element - */ get parent() { return this._container; } - /** - * A property to indicate if the Space is ready - */ get ready() { return this._isReady; } - /** - * Clear the element's contents, and ptionally set a new backgrounc color. Overrides Space's `clear` function. - * @param bg Optionally specify a custom background color in hex or rgba string, or "transparent". If not defined, it will use its `bgcolor` property as background color to clear the canvas. - */ clear(bg) { if (bg) this.background = bg; this._canvas.innerHTML = ""; return this; } - /** - * Set a background color on the container element - @param bg background color as hex or rgba string - */ set background(bg) { this._bgcolor = bg; this._container.style.backgroundColor = this._bgcolor; } get background() { return this._bgcolor; } - /** - * Add or update a style definition, and optionally update that style in the Element - * @param key style name - * @param val style value - * @param update a boolean to update the element's style immediately if set to `true`. Default is `false`. - */ style(key, val, update = false) { this._css[key] = val; if (update) this._canvas.style[key] = val; return this; } - /** - * Add of update a list of style definitions, and optionally update those styles in the Element - * @param styles a key-value objects of style definitions - * @param update a boolean to update the element's style immediately if set to `true`. Default is `false`. - * @return this - */ styles(styles, update = false) { for (let k in styles) { if (styles.hasOwnProperty(k)) @@ -4718,12 +2829,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { } return this; } - /** - * A static helper function to add or update Element attributes - * @param elem Element to update - * @param data an object with key-value pairs - * @returns this DOM element - */ static setAttr(elem, data) { for (let k in data) { if (data.hasOwnProperty(k)) { @@ -4732,12 +2837,6 @@ class DOMSpace extends Space_1.MultiTouchSpace { } return elem; } - /** - * A static helper function to compose an inline style string from a object of styles - * @param elem Element to update - * @param data an object with key-value pairs - * @exmaple DOMSpace.getInlineStyles( {width: "100px", "font-size": "10px"} ); // returns "width: 100px; font-size: 10px" - */ static getInlineStyles(data) { let str = ""; for (let k in data) { @@ -4750,24 +2849,10 @@ class DOMSpace extends Space_1.MultiTouchSpace { } } exports.DOMSpace = DOMSpace; -/** - * HTMLSpace. Note that this is currently experimental and may change in future. - */ class HTMLSpace extends DOMSpace { - /** - * Get a new `HTMLForm` for drawing - * @see `HTMLForm` - */ getForm() { return new HTMLForm(this); } - /** - * A static function to add a DOM element inside a node. Usually you don't need to use this directly. See methods in `DOMForm` instead. - * @param parent the parent element, or `null` to use current `` as parent. - * @param name a string of element name, such as `rect` or `circle` - * @param id id attribute of the new element - * @param autoClass add a class based on the id (from char 0 to index of "-"). Default is true. - */ static htmlElement(parent, name, id, autoClass = true) { if (!parent || !parent.appendChild) throw new Error("parent is not a valid DOM element"); @@ -4781,10 +2866,6 @@ class HTMLSpace extends DOMSpace { } return elem; } - /** - * Remove an item from this Space - * @param item a player item with an auto-assigned `animateID` property - */ remove(player) { let temp = this._container.querySelectorAll("." + HTMLForm.scopeID(player)); temp.forEach((el) => { @@ -4792,18 +2873,12 @@ class HTMLSpace extends DOMSpace { }); return super.remove(player); } - /** - * Remove all items from this Space - */ removeAll() { this._container.innerHTML = ""; return super.removeAll(); } } exports.HTMLSpace = HTMLSpace; -/** - * Form for HTMLSpace. Note that this is currently experimental and may change in future. - */ class HTMLForm extends Form_1.VisualForm { constructor(space) { super(); @@ -4841,22 +2916,11 @@ class HTMLForm extends Form_1.VisualForm { } }); } get space() { return this._space; } - /** - * Update a style in _ctx context or throw an Erorr if the style doesn't exist - * @param k style key - * @param v style value - * @param unit Optional unit like 'px' to append to value - */ styleTo(k, v, unit = '') { if (this._ctx.style[k] === undefined) throw new Error(`${k} style property doesn't exist`); this._ctx.style[k] = `${v}${unit}`; } - /** - * Set current fill style. Provide a valid color string or `false` to specify no fill color. - * @example `form.fill("#F90")`, `form.fill("rgba(0,0,0,.5")`, `form.fill(false)` - * @param c fill color - */ fill(c) { if (typeof c == "boolean") { this.styleTo("filled", c); @@ -4869,14 +2933,6 @@ class HTMLForm extends Form_1.VisualForm { } return this; } - /** - * Set current stroke style. Provide a valid color string or `false` to specify no stroke color. - * @example `form.stroke("#F90")`, `form.stroke("rgba(0,0,0,.5")`, `form.stroke(false)`, `form.stroke("#000", 0.5, 'round', 'square')` - * @param c stroke color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle)) - * @param width Optional value (can be floating point) to set line width - * @param linejoin not implemented in HTMLForm - * @param linecap not implemented in HTMLForm - */ stroke(c, width, linejoin, linecap) { if (typeof c == "boolean") { this.styleTo("stroked", c); @@ -4890,20 +2946,10 @@ class HTMLForm extends Form_1.VisualForm { } return this; } - /** - * Set current text color style. Provide a valid color string. - * @example `form.fill("#F90")`, `form.fill("rgba(0,0,0,.5")`, `form.fill(false)` - * @param c fill color - */ fillText(c) { this.styleTo("color", c); return this; } - /** - * Add custom class to the created element - * @param c custom class name or `false` to reset it - * @example `form.fill("#f00").cls("myClass").rects(r)` `form.cls(false).circles(c)` - */ cls(c) { if (typeof c == "boolean") { this._ctx.currentClass = ""; @@ -4913,15 +2959,6 @@ class HTMLForm extends Form_1.VisualForm { } return this; } - /** - * Set the current font - * @param sizeOrFont either a number to specify font-size, or a `Font` object to specify all font properties - * @param weight Optional font-weight string such as "bold" - * @param style Optional font-style string such as "italic" - * @param lineHeight Optional line-height number suchas 1.5 - * @param family Optional font-family such as "Helvetica, sans-serif" - * @example `form.font( myFont )`, `form.font(14, "bold")` - */ font(sizeOrFont, weight, style, lineHeight, family) { if (typeof sizeOrFont == "number") { this._font.size = sizeOrFont; @@ -4940,9 +2977,6 @@ class HTMLForm extends Form_1.VisualForm { } return this; } - /** - * Reset the context's common styles to this form's styles. This supports using multiple forms on the same canvas context. - */ reset() { this._ctx.style = { "filled": true, "stroked": true, @@ -4953,12 +2987,6 @@ class HTMLForm extends Form_1.VisualForm { this._ctx.font = this._font.value; return this; } - /** - * Set this form's group scope by an ID, and optionally define the group's parent element. A group scope keeps track of elements by their generated IDs, and updates their properties as needed. See also `scope()`. - * @param group_id a string to use as prefix for the group's id. For example, group_id "hello" will create elements with id like "hello-1", "hello-2", etc - * @param group Optional DOM element to define this group's parent element - * @returns this form's context - */ updateScope(group_id, group) { this._ctx.group = group; this._ctx.groupID = group_id; @@ -4966,46 +2994,22 @@ class HTMLForm extends Form_1.VisualForm { this.nextID(); return this._ctx; } - /** - * Set the current group scope to an item added into space, in order to keep track of any point, circle, etc created within it. The item must have an `animateID` property, so that elements created within the item will have generated IDs like "item-{animateID}-{count}". - * @param item a "player" item that's added to space (see `space.add(...)`) and has an `animateID` property - * @returns this form's context - */ scope(item) { if (!item || item.animateID == null) throw new Error("item not defined or not yet added to Space"); return this.updateScope(HTMLForm.scopeID(item), this.space.element); } - /** - * Get next available id in the current group - * @returns an id string - */ nextID() { this._ctx.groupCount++; this._ctx.currentID = `${this._ctx.groupID}-${this._ctx.groupCount}`; return this._ctx.currentID; } - /** - * A static function to generate an ID string based on a context object - * @param ctx a context object for an HTMLForm - */ static getID(ctx) { return ctx.currentID || `p-${HTMLForm.domID++}`; } - /** - * A static function to generate an ID string for a scope, based on a "player" item in the Space - * @param item a "player" item that's added to space (see `space.add(...)`) and has an `animateID` property - */ static scopeID(item) { return `item-${item.animateID}`; } - /** - * A static function to help adding style object to an element. This put all styles into `style` attribute instead of individual attributes, so that the styles can be parsed by Adobe Illustrator. - * @param elem A DOM element to add to - * @param styles an object of style properties - * @example `HTMLForm.style(elem, {fill: "#f90", stroke: false})` - * @returns DOM element - */ static style(elem, styles) { let st = []; if (!styles["filled"]) @@ -5030,13 +3034,6 @@ class HTMLForm extends Form_1.VisualForm { } return HTMLSpace.setAttr(elem, { style: st.join(";") }); } - /** - * A helper function to set top, left, width, height of DOM element - * @param x left position - * @param y top position - * @param w width - * @param h height - */ static rectStyle(ctx, pt, size) { ctx.style["left"] = pt[0] + "px"; ctx.style["top"] = pt[1] + "px"; @@ -5044,14 +3041,6 @@ class HTMLForm extends Form_1.VisualForm { ctx.style["height"] = size[1] + "px"; return ctx; } - /** - * Draws a point - * @param ctx a context object of HTMLForm - * @param pt a Pt object or numeric array - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `HTMLForm.point( p )`, `HTMLForm.point( p, 10, "circle" )` - */ static point(ctx, pt, radius = 5, shape = "square") { if (shape === "circle") { return HTMLForm.circle(ctx, pt, radius); @@ -5060,13 +3049,6 @@ class HTMLForm extends Form_1.VisualForm { return HTMLForm.square(ctx, pt, radius); } } - /** - * Draws a point - * @param p a Pt object - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `form.point( p )`, `form.point( p, 10, "circle" )` - */ point(pt, radius = 5, shape = "square") { this.nextID(); if (shape == "circle") @@ -5074,12 +3056,6 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.point(this._ctx, pt, radius, shape); return this; } - /** - * A static function to draw a circle - * @param ctx a context object of HTMLForm - * @param pt center position of the circle - * @param radius radius of the circle - */ static circle(ctx, pt, radius = 10) { let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx)); HTMLSpace.setAttr(elem, { class: `pts-form pts-circle ${ctx.currentClass}` }); @@ -5087,23 +3063,12 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.style(elem, ctx.style); return elem; } - /** - * Draw a circle - * @param pts usually a Group of 2 Pts, but it can also take an array of two numeric arrays [ [position], [size] ] - * @see [`Circle.fromCenter`](./_op_.circle.html#frompt) - */ circle(pts) { this.nextID(); this.styleTo("border-radius", "100%"); HTMLForm.circle(this._ctx, pts[0], pts[1][0]); return this; } - /** - * A static function to draw a square - * @param ctx a context object of HTMLForm - * @param pt center position of the square - * @param halfsize half size of the square - */ static square(ctx, pt, halfsize) { let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx)); HTMLSpace.setAttr(elem, { class: `pts-form pts-square ${ctx.currentClass}` }); @@ -5111,21 +3076,11 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.style(elem, ctx.style); return elem; } - /** - * Draw a square, given a center and its half-size - * @param pt center Pt - * @param halfsize half-size - */ square(pt, halfsize) { this.nextID(); HTMLForm.square(this._ctx, pt, halfsize); return this; } - /** - * A static function to draw a rectangle - * @param ctx a context object of HTMLForm - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ static rect(ctx, pts) { if (!this._checkSize(pts)) return; @@ -5135,23 +3090,12 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.style(elem, ctx.style); return elem; } - /** - * Draw a rectangle - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ rect(pts) { this.nextID(); this.styleTo("border-radius", "0"); HTMLForm.rect(this._ctx, pts); return this; } - /** - * A static function to draw text - * @param ctx a context object of HTMLForm - * @param `pt` a Point object to specify the anchor point - * @param `txt` a string of text to draw - * @param `maxWidth` specify a maximum width per line - */ static text(ctx, pt, txt) { let elem = HTMLSpace.htmlElement(ctx.group, "div", HTMLForm.getID(ctx)); HTMLSpace.setAttr(elem, { @@ -5164,43 +3108,23 @@ class HTMLForm extends Form_1.VisualForm { HTMLForm.style(elem, ctx.style); return elem; } - /** - * Draw text on canvas - * @param `pt` a Pt or numeric array to specify the anchor point - * @param `txt` text - * @param `maxWidth` specify a maximum width per line - */ text(pt, txt) { this.nextID(); HTMLForm.text(this._ctx, pt, txt); return this; } - /** - * A convenient way to draw some text on canvas for logging or debugging. It'll be draw on the top-left of the canvas as an overlay. - * @param txt text - */ log(txt) { this.fill("#000").stroke("#fff", 0.5).text([10, 14], txt); return this; } - /** - * Arc is not implemented in HTMLForm - */ arc(pt, radius, startAngle, endAngle, cc) { Util_1.Util.warn("arc is not implemented in HTMLForm"); return this; } - /** - * Line is not implemented in HTMLForm - */ line(pts) { Util_1.Util.warn("line is not implemented in HTMLForm"); return this; } - /** - * Polygon is not implemented in HTMLForm - * @param pts - */ polygon(pts) { Util_1.Util.warn("polygon is not implemented in HTMLForm"); return this; @@ -5233,7 +3157,6 @@ const _Dom = __webpack_require__(9); const _Svg = __webpack_require__(15); const _Typography = __webpack_require__(8); const _Physics = __webpack_require__(16); -// A function to switch scope for Pts library. eg, Pts.scope( Pts, window ); let namespace = (sc) => { let lib = module.exports; for (let k in lib) { @@ -5251,8 +3174,6 @@ module.exports = Object.assign({ namespace }, _Bound, _Canvas, _Create, _Form, _ "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Space_1 = __webpack_require__(7); const Form_1 = __webpack_require__(6); @@ -5261,17 +3182,7 @@ const Pt_1 = __webpack_require__(0); const Util_1 = __webpack_require__(1); const Typography_1 = __webpack_require__(8); const Op_1 = __webpack_require__(2); -/** -* CanvasSpace is an implementation of the abstract class Space. It represents a space for HTML Canvas. -* Learn more about the concept of Space in [this guide](..guide/Space-0500.html) -*/ class CanvasSpace extends Space_1.MultiTouchSpace { - /** - * Create a CanvasSpace which represents a HTML Canvas Space - * @param elem Specify an element by its "id" attribute as string, or by the element object itself. An element can be an existing ``, or a `
` container in which a new `` will be created. If left empty, a `
` will be added to DOM. Use css to customize its appearance if needed. - * @param callback an optional callback `function(boundingBox, spaceElement)` to be called when canvas is appended and ready. Alternatively, a "ready" event will also be fired from the `` element when it's appended, which can be traced with `spaceInstance.canvas.addEventListener("ready")` - * @example `new CanvasSpace( "#myElementID" )` - */ constructor(elem, callback) { super(); this._pixelScale = 1; @@ -5282,7 +3193,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { var _selector = null; var _existed = false; this.id = "pt"; - // check element or element id string if (elem instanceof Element) { _selector = elem; this.id = "pts_existing_space"; @@ -5292,51 +3202,32 @@ class CanvasSpace extends Space_1.MultiTouchSpace { _existed = true; this.id = elem; } - // if selector is not defined, create a canvas if (!_selector) { this._container = this._createElement("div", this.id + "_container"); this._canvas = this._createElement("canvas", this.id); this._container.appendChild(this._canvas); document.body.appendChild(this._container); _existed = false; - // if selector is element but not canvas, create a canvas inside it } else if (_selector.nodeName.toLowerCase() != "canvas") { this._container = _selector; this._canvas = this._createElement("canvas", this.id + "_canvas"); this._container.appendChild(this._canvas); this._initialResize = true; - // if selector is an existing canvas } else { this._canvas = _selector; this._container = _selector.parentElement; this._autoResize = false; } - // if size is known then set it immediately - // if (_existed) { - // let b = this._container.getBoundingClientRect(); - // this.resize( Bound.fromBoundingRect(b) ); - // } - // no mutation observer, so we set a timeout for ready event setTimeout(this._ready.bind(this, callback), 100); - // store canvas 2d rendering context this._ctx = this._canvas.getContext('2d'); } - /** - * Helper function to create a DOM element - * @param elem element tag name - * @param id element id attribute - */ _createElement(elem = "div", id) { let d = document.createElement(elem); d.setAttribute("id", id); return d; } - /** - * Handle callbacks after element is mounted in DOM - * @param callback - */ _ready(callback) { if (!this._container) throw new Error(`Cannot initiate #${this.id} element`); @@ -5351,19 +3242,10 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } } this._pointer = this.center; - this._initialResize = false; // unset + this._initialResize = false; if (callback) callback(this.bound, this._canvas); } - /** - * Set up various options for CanvasSpace. The `opt` parameter is an object with the following fields. This is usually set during instantiation, eg `new CanvasSpace(...).setup( { opt } )` - * @param opt an object with optional settings, as follows. - * @param opt.bgcolor a hex or rgba string to set initial background color of the canvas, or use `false` or "transparent" to set a transparent background. You may also change it later with `clear()` - * @param opt.resize a boolean to set whether `` size should auto resize to match its container's size. You can also set it manually with `autoSize()` - * @param opt.retina a boolean to set if device pixel scaling should be used. This may make drawings on retina displays look sharper but may reduce performance slightly. Default is `true`. - * @param opt.offscreen a boolean to set if a duplicate canvas should be created for offscreen rendering. Default is `false`. - * @example `space.setup({ bgcolor: "#f00", retina: true, resize: true })` - */ setup(opt) { if (opt.bgcolor) this._bgcolor = opt.bgcolor; @@ -5383,10 +3265,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } return this; } - /** - * Set whether the canvas element should resize when its container is resized. - * @param auto a boolean value indicating if auto size is set - */ set autoResize(auto) { this._autoResize = auto; if (auto) { @@ -5397,11 +3275,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } } get autoResize() { return this._autoResize; } - /** - * This overrides Space's `resize` function. It's used as a callback function for window's resize event and not usually called directly. You can keep track of resize events with `resize: (bound ,evt)` callback in your player objects (See `Space`'s `add()` function). - * @param b a Bound object to resize to - * @param evt Optionally pass a resize event - */ resize(b, evt) { this.bound = b; this._canvas.width = this.bound.size.x * this._pixelScale; @@ -5411,8 +3284,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { if (this._offscreen) { this._offCanvas.width = this.bound.size.x * this._pixelScale; this._offCanvas.height = this.bound.size.y * this._pixelScale; - // this._offCanvas.style.width = Math.floor(this.bound.size.x) + "px"; - // this._offCanvas.style.height = Math.floor(this.bound.size.y) + "px"; } if (this._pixelScale != 1) { this._ctx.scale(this._pixelScale, this._pixelScale); @@ -5430,81 +3301,39 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } } this.render(this._ctx); - // if it's a valid resize event and space is not playing, repaint the canvas once if (evt && !this.isPlaying) this.playOnce(0); return this; } - /** - * Window resize handling - * @param evt - */ _resizeHandler(evt) { let b = (this._autoResize || this._initialResize) ? this._container.getBoundingClientRect() : this._canvas.getBoundingClientRect(); if (b) { let box = Bound_1.Bound.fromBoundingRect(b); - // Need to compute offset from window scroll. See outerBound calculation in Space's _mouseAction box.center = box.center.add(window.pageXOffset, window.pageYOffset); this.resize(box, evt); } } - /** - * Set a background color for this canvas. Alternatively, you may use `clear()` function. - @param bg background color as hex or rgba string - */ set background(bg) { this._bgcolor = bg; } get background() { return this._bgcolor; } - /** - * `pixelScale` property returns a number that let you determine if the screen is "retina" (when value >= 2) - */ get pixelScale() { return this._pixelScale; } - /** - * Check if an offscreen canvas is created - */ get hasOffscreen() { return this._offscreen; } - /** - * Get the rendering context of offscreen canvas (if created via `setup()`) - */ get offscreenCtx() { return this._offCtx; } - /** - * Get the offscreen canvas element - */ get offscreenCanvas() { return this._offCanvas; } - /** - * Get a new `CanvasForm` for drawing - * @see `CanvasForm` - */ getForm() { return new CanvasForm(this); } - /** - * Get the html canvas element - */ get element() { return this._canvas; } - /** - * Get the parent element that contains the canvas element - */ get parent() { return this._container; } - /** - * A property to indicate if the Space is ready - */ get ready() { return this._isReady; } - /** - * Get the rendering context of canvas - */ get ctx() { return this._ctx; } - /** - * Clear the canvas with its background color. Overrides Space's `clear` function. - * @param bg Optionally specify a custom background color in hex or rgba string, or "transparent". If not defined, it will use its `bgcolor` property as background color to clear the canvas. - */ clear(bg) { if (bg) this._bgcolor = bg; @@ -5519,10 +3348,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { this._ctx.fillStyle = lastColor; return this; } - /** - * Similiar to `clear()` but clear the offscreen canvas instead - * @param bg Optionally specify a custom background color in hex or rgba string, or "transparent". If not defined, it will use its `bgcolor` property as background color to clear the canvas. - */ clearOffscreen(bg) { if (this._offscreen) { if (bg) { @@ -5535,10 +3360,6 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } return this; } - /** - * Main animation function. Call `Space.playItems`. - * @param time current time - */ playItems(time) { if (this._isReady) { this._ctx.save(); @@ -5553,20 +3374,9 @@ class CanvasSpace extends Space_1.MultiTouchSpace { } } exports.CanvasSpace = CanvasSpace; -/** -* CanvasForm is an implementation of abstract class VisualForm. It provide methods to express Pts on CanvasSpace. -* You may extend CanvasForm to implement your own expressions for CanvasSpace. -*/ class CanvasForm extends Form_1.VisualForm { - /** - * Create a new CanvasForm. You may also use `space.getForm()` to get the default form. - * @param space an instance of CanvasSpace - */ constructor(space) { super(); - /** - * store common styles so that they can be restored to canvas context when using multiple forms. See `reset()`. - */ this._style = { fillStyle: "#f03", strokeStyle: "#fff", lineWidth: 1, lineJoin: "bevel", lineCap: "butt", @@ -5581,35 +3391,18 @@ class CanvasForm extends Form_1.VisualForm { this._ready = true; } }); } - /** - * get the CanvasSpace instance that this form is associated with - */ get space() { return this._space; } - /** - * Toggle whether to draw on offscreen canvas (if offscreen is set in CanvasSpace) - * @param off if `true`, draw on offscreen canvas instead of the visible canvas. Default is `true` - * @param clear optionally provide a valid color string to fill a bg color. see CanvasSpace's `clearOffscreen` function. - */ useOffscreen(off = true, clear = false) { if (clear) this._space.clearOffscreen((typeof clear == "string") ? clear : null); this._ctx = (this._space.hasOffscreen && off) ? this._space.offscreenCtx : this._space.ctx; return this; } - /** - * Render the offscreen canvas's content on the visible canvas - * @param offset Optional offset on the top-left position when drawing on the visible canvas - */ renderOffscreen(offset = [0, 0]) { if (this._space.hasOffscreen) { this._space.ctx.drawImage(this._space.offscreenCanvas, offset[0], offset[1], this._space.width, this._space.height); } } - /** - * Set current fill style. Provide a valid color string or `false` to specify no fill color. - * @example `form.fill("#F90")`, `form.fill("rgba(0,0,0,.5")`, `form.fill(false)` - * @param c fill color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle)) - */ fill(c) { if (typeof c == "boolean") { this.filled = c; @@ -5621,14 +3414,6 @@ class CanvasForm extends Form_1.VisualForm { } return this; } - /** - * Set current stroke style. Provide a valid color string or `false` to specify no stroke color. - * @example `form.stroke("#F90")`, `form.stroke("rgba(0,0,0,.5")`, `form.stroke(false)`, `form.stroke("#000", 0.5, 'round', 'square')` - * @param c stroke color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle)) - * @param width Optional value (can be floating point) to set line width - * @param linejoin Optional string to set line joint style. Can be "miter", "bevel", or "round". - * @param linecap Optional string to set line cap style. Can be "butt", "round", or "square". - */ stroke(c, width, linejoin, linecap) { if (typeof c == "boolean") { this.stroked = c; @@ -5652,15 +3437,6 @@ class CanvasForm extends Form_1.VisualForm { } return this; } - /** - * Set the current font - * @param sizeOrFont either a number to specify font-size, or a `Font` object to specify all font properties - * @param weight Optional font-weight string such as "bold" - * @param style Optional font-style string such as "italic" - * @param lineHeight Optional line-height number suchas 1.5 - * @param family Optional font-family such as "Helvetica, sans-serif" - * @example `form.font( myFont )`, `form.font(14, "bold")` - */ font(sizeOrFont, weight, style, lineHeight, family) { if (typeof sizeOrFont == "number") { this._font.size = sizeOrFont; @@ -5677,42 +3453,20 @@ class CanvasForm extends Form_1.VisualForm { else { this._font = sizeOrFont; } - // If using estimate, reapply it when font changes. if (this._estimateTextWidth) this.fontWidthEstimate(true); return this; } - /** - * Set whether to use ctx.measureText or a faster but less accurate heuristic function. - * @param estimate `true` to use heuristic function, or `false` to use ctx.measureText - */ fontWidthEstimate(estimate = true) { this._estimateTextWidth = (estimate) ? Typography_1.Typography.textWidthEstimator(((c) => this._ctx.measureText(c).width)) : undefined; return this; } - /** - * Get the width of this text. It will return an actual measurement or an estimate based on `fontWidthEstimate` setting. Default is an actual measurement using canvas context's measureText. - * @param c a string of text contents - */ getTextWidth(c) { return (!this._estimateTextWidth) ? this._ctx.measureText(c + " .").width : this._estimateTextWidth(c); } - /** - * Truncate text to fit width - * @param str text to truncate - * @param width width to fit - * @param tail text to indicate overflow such as "...". Default is empty "". - */ _textTruncate(str, width, tail = "") { return Typography_1.Typography.truncate(this.getTextWidth.bind(this), str, width, tail); } - /** - * Align text within a rectangle box - * @param box a Group that defines a rectangular box - * @param vertical a string that specifies the vertical alignment in the box: "top", "bottom", "middle", "start", "end" - * @param offset Optional offset from the edge (like padding) - * @param center Optional center position - */ _textAlign(box, vertical, offset, center) { if (!center) center = Op_1.Rectangle.center(box); @@ -5732,9 +3486,6 @@ class CanvasForm extends Form_1.VisualForm { } return (offset) ? new Pt_1.Pt(px + offset[0], py + offset[1]) : new Pt_1.Pt(px, py); } - /** - * Reset the rendering context's common styles to this form's styles. This supports using multiple forms on the same canvas context. - */ reset() { for (let k in this._style) { if (this._style.hasOwnProperty(k)) { @@ -5751,13 +3502,6 @@ class CanvasForm extends Form_1.VisualForm { if (this._stroked) this._ctx.stroke(); } - /** - * Draws a point - * @param p a Pt object - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `form.point( p )`, `form.point( p, 10, "circle" )` - */ point(p, radius = 5, shape = "square") { if (!p) return; @@ -5767,12 +3511,6 @@ class CanvasForm extends Form_1.VisualForm { this._paint(); return this; } - /** - * A static function to draw a circle - * @param ctx canvas rendering context - * @param pt center position of the circle - * @param radius radius of the circle - */ static circle(ctx, pt, radius = 10) { if (!pt) return; @@ -5780,50 +3518,22 @@ class CanvasForm extends Form_1.VisualForm { ctx.arc(pt[0], pt[1], radius, 0, Util_1.Const.two_pi, false); ctx.closePath(); } - /** - * Draw a circle - * @param pts usually a Group of 2 Pts, but it can also take an array of two numeric arrays [ [position], [size] ] - * @see [`Circle.fromCenter`](./_op_.circle.html#frompt) - */ circle(pts) { CanvasForm.circle(this._ctx, pts[0], pts[1][0]); this._paint(); return this; } - /** - * A static function to draw an arc. - * @param ctx canvas rendering context - * @param pt center position - * @param radius radius of the arc circle - * @param startAngle start angle of the arc - * @param endAngle end angle of the arc - * @param cc an optional boolean value to specify if it should be drawn clockwise (`false`) or counter-clockwise (`true`). Default is clockwise. - */ static arc(ctx, pt, radius, startAngle, endAngle, cc) { if (!pt) return; ctx.beginPath(); ctx.arc(pt[0], pt[1], radius, startAngle, endAngle, cc); } - /** - * Draw an arc. - * @param pt center position - * @param radius radius of the arc circle - * @param startAngle start angle of the arc - * @param endAngle end angle of the arc - * @param cc an optional boolean value to specify if it should be drawn clockwise (`false`) or counter-clockwise (`true`). Default is clockwise. - */ arc(pt, radius, startAngle, endAngle, cc) { CanvasForm.arc(this._ctx, pt, radius, startAngle, endAngle, cc); this._paint(); return this; } - /** - * A static function to draw a square - * @param ctx canvas rendering context - * @param pt center position of the square - * @param halfsize half size of the square - */ static square(ctx, pt, halfsize) { if (!pt) return; @@ -5831,7 +3541,6 @@ class CanvasForm extends Form_1.VisualForm { let y1 = pt[1] - halfsize; let x2 = pt[0] + halfsize; let y2 = pt[1] + halfsize; - // faster than using `rect` ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x1, y2); @@ -5839,21 +3548,11 @@ class CanvasForm extends Form_1.VisualForm { ctx.lineTo(x2, y1); ctx.closePath(); } - /** - * Draw a square, given a center and its half-size - * @param pt center Pt - * @param halfsize half-size - */ square(pt, halfsize) { CanvasForm.square(this._ctx, pt, halfsize); this._paint(); return this; } - /** - * A static function to draw a line - * @param ctx canvas rendering context - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ static line(ctx, pts) { if (pts.length < 2) return; @@ -5864,20 +3563,11 @@ class CanvasForm extends Form_1.VisualForm { ctx.lineTo(pts[i][0], pts[i][1]); } } - /** - * Draw a line or polyline - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ line(pts) { CanvasForm.line(this._ctx, pts); this._paint(); return this; } - /** - * A static function to draw polygon - * @param ctx canvas rendering context - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ static polygon(ctx, pts) { if (pts.length < 2) return; @@ -5889,20 +3579,11 @@ class CanvasForm extends Form_1.VisualForm { } ctx.closePath(); } - /** - * Draw a polygon - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ polygon(pts) { CanvasForm.polygon(this._ctx, pts); this._paint(); return this; } - /** - * A static function to draw a rectangle - * @param ctx canvas rendering context - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ static rect(ctx, pts) { if (pts.length < 2) return; @@ -5913,45 +3594,20 @@ class CanvasForm extends Form_1.VisualForm { ctx.lineTo(pts[1][0], pts[0][1]); ctx.closePath(); } - /** - * Draw a rectangle - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ rect(pts) { CanvasForm.rect(this._ctx, pts); this._paint(); return this; } - /** - * A static function to draw text - * @param ctx canvas rendering context - * @param `pt` a Point object to specify the anchor point - * @param `txt` a string of text to draw - * @param `maxWidth` specify a maximum width per line - */ static text(ctx, pt, txt, maxWidth) { if (!pt) return; ctx.fillText(txt, pt[0], pt[1], maxWidth); } - /** - * Draw text on canvas - * @param `pt` a Pt or numeric array to specify the anchor point - * @param `txt` text - * @param `maxWidth` specify a maximum width per line - */ text(pt, txt, maxWidth) { CanvasForm.text(this._ctx, pt, txt, maxWidth); return this; } - /** - * Fit a single-line text in a rectangular box - * @param box a rectangle box defined by a Group - * @param txt string of text - * @param tail text to indicate overflow such as "...". Default is empty "". - * @param verticalAlign "top", "middle", or "bottom" to specify vertical alignment inside the box - * @param overrideBaseline If `true`, use the corresponding baseline as verticalAlign. If `false`, use the current canvas context's textBaseline setting. Default is `true`. - */ textBox(box, txt, verticalAlign = "middle", tail = "", overrideBaseline = true) { if (overrideBaseline) this._ctx.textBaseline = verticalAlign; @@ -5960,19 +3616,10 @@ class CanvasForm extends Form_1.VisualForm { this.text(this._textAlign(box, verticalAlign), t[0]); return this; } - /** - * Fit multi-line text in a rectangular box. Note that this will also set canvas context's textBaseline to "top". - * @param box a rectangle box defined by a Group - * @param txt string of text - * @param lineHeight line height as a ratio of font size. Default is 1.2. - * @param verticalAlign "top", "middle", or "bottom" to specify vertical alignment inside the box - * @param crop a boolean to specify whether to crop text when overflowing - */ paragraphBox(box, txt, lineHeight = 1.2, verticalAlign = "top", crop = true) { let size = Op_1.Rectangle.size(box); - this._ctx.textBaseline = "top"; // override textBaseline + this._ctx.textBaseline = "top"; let lstep = this._font.size * lineHeight; - // find next lines recursively let nextLine = (sub, buffer = [], cc = 0) => { if (!sub) return buffer; @@ -5981,13 +3628,11 @@ class CanvasForm extends Form_1.VisualForm { if (cc > 10000) throw new Error("max recursion reached (10000)"); let t = this._textTruncate(sub, size[0], ""); - // new line let newln = t[0].indexOf("\n"); if (newln >= 0) { buffer.push(t[0].substr(0, newln)); return nextLine(sub.substr(newln + 1), buffer, cc + 1); } - // word wrap let dt = t[0].lastIndexOf(" ") + 1; if (dt <= 0 || t[1] === sub.length) dt = undefined; @@ -5995,8 +3640,8 @@ class CanvasForm extends Form_1.VisualForm { buffer.push(line); return (t[1] <= 0 || t[1] === sub.length) ? buffer : nextLine(sub.substr((dt || t[1])), buffer, cc + 1); }; - let lines = nextLine(txt); // go through all lines - let lsize = lines.length * lstep; // total height + let lines = nextLine(txt); + let lsize = lines.length * lstep; let lbox = box; if (verticalAlign == "middle" || verticalAlign == "center") { let lpad = (size[1] - lsize) / 2; @@ -6016,11 +3661,6 @@ class CanvasForm extends Form_1.VisualForm { } return this; } - /** - * Set text alignment and baseline (eg, vertical-align) - * @param alignment Canvas' textAlign option: "left", "right", "center", "start", or "end" - * @param baseline Canvas' textBaseline option: "top", "hanging", "middle", "alphabetic", "ideographic", "bottom". For convenience, you can also use "center" (same as "middle"), and "baseline" (same as "alphabetic") - */ alignText(alignment = "left", baseline = "alphabetic") { if (baseline == "center") baseline = "middle"; @@ -6030,10 +3670,6 @@ class CanvasForm extends Form_1.VisualForm { this._ctx.textBaseline = baseline; return this; } - /** - * A convenient way to draw some text on canvas for logging or debugging. It'll be draw on the top-left of the canvas as an overlay. - * @param txt text - */ log(txt) { let w = this._ctx.measureText(txt).width + 20; this.stroke(false).fill("rgba(0,0,0,.4)").rect([[0, 0], [w, 20]]); @@ -6050,13 +3686,8 @@ exports.CanvasForm = CanvasForm; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Op_1 = __webpack_require__(2); -/** - * An enumeration of different UI types - */ var UIShape; (function (UIShape) { UIShape[UIShape["Rectangle"] = 0] = "Rectangle"; @@ -6069,9 +3700,6 @@ exports.UIPointerActions = { up: "up", down: "down", move: "move", drag: "drag", drop: "drop", over: "over", out: "out" }; class UI { - /** - * Wrap an UI insider a group - */ constructor(group, shape, states, id) { this.group = group; this.shape = shape; @@ -6079,41 +3707,19 @@ class UI { this._states = states; this._actions = {}; } - /** - * Get and set uique id - */ get id() { return this._id; } set id(d) { this._id = d; } - /** - * Get a state - * @param key state's name - */ state(key) { return this._states[key] || false; } - /** - * Add an event handler - * @param key event key - * @param fn handler function - */ on(key, fn) { this._actions[key] = fn; return this; } - /** - * Remove an event handler - * @param key even key - * @param fn - */ off(key) { delete this._actions[key]; return this; } - /** - * Listen for interactions and trigger action handlers - * @param key action key - * @param p point to check - */ listen(key, p) { if (this._actions[key] !== undefined) { if (this._trigger(p)) { @@ -6123,17 +3729,9 @@ class UI { } return false; } - /** - * Take a custom render function to render this UI - * @param fn render function - */ render(fn) { fn(this.group, this._states); } - /** - * Check intersection using a specific function based on UIShape - * @param p a point to check - */ _trigger(p) { let fn = null; if (this.shape === UIShape.Rectangle) { @@ -6152,31 +3750,16 @@ class UI { } } exports.UI = UI; -/** - * A simple UI button that can track clicks and hovers - */ class UIButton extends UI { constructor(group, shape, states, id) { super(group, shape, states, id); this._clicks = 0; } - /** - * Get the total number of clicks on this UIButton - */ get clicks() { return this._clicks; } - /** - * Add a click handler - * @param fn a function to handle clicks - */ onClick(fn) { this._clicks++; this.on(exports.UIPointerActions.up, fn); } - /** - * Add hover handler - * @param over a function to handle when pointer enters hover - * @param out a function to handle when pointer exits hover - */ onHover(over, out) { this.on(exports.UIPointerActions.over, over); this.on(exports.UIPointerActions.out, out); @@ -6191,24 +3774,13 @@ exports.UIButton = UIButton; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); const Op_1 = __webpack_require__(2); const Util_1 = __webpack_require__(1); const Num_1 = __webpack_require__(3); const LinearAlgebra_1 = __webpack_require__(5); -/** - * The `Create` class provides various convenient functions to create structures or shapes. - */ class Create { - /** - * Create a set of random points inside a bounday - * @param bound the rectangular boundary - * @param count number of random points to create - * @param dimensions number of dimensions in each point - */ static distributeRandom(bound, count, dimensions = 2) { let pts = new Pt_1.Group(); for (let i = 0; i < count; i++) { @@ -6221,25 +3793,12 @@ class Create { } return pts; } - /** - * Create a set of points that distribute evenly on a line - * @param line a Group representing a line - * @param count number of points to create - */ static distributeLinear(line, count) { let ln = Op_1.Line.subpoints(line, count - 2); ln.unshift(line[0]); ln.push(line[line.length - 1]); return ln; } - /** - * Create an evenly distributed set of points (like a grid of points) inside a boundary. - * @param bound the rectangular boundary - * @param columns number of columns - * @param rows number of rows - * @param orientation a Pt or number array to specify where the point should be inside a cell. Default is [0.5, 0.5] which places the point in the middle. - * @returns a Group of Pts - */ static gridPts(bound, columns, rows, orientation = [0.5, 0.5]) { if (columns === 0 || rows === 0) throw new Error("grid columns and rows cannot be 0"); @@ -6253,17 +3812,10 @@ class Create { } return g; } - /** - * Create a grid inside a boundary - * @param bound the rectangular boundary - * @param columns number of columns - * @param rows number of rows - * @returns an array of Groups, where each group represents a rectangular cell - */ static gridCells(bound, columns, rows) { if (columns === 0 || rows === 0) throw new Error("grid columns and rows cannot be 0"); - let unit = bound.size.$subtract(1).divide(columns, rows); // subtract 1 to fill whole border of rectangles + let unit = bound.size.$subtract(1).divide(columns, rows); let g = []; for (let r = 0; r < rows; r++) { for (let c = 0; c < columns; c++) { @@ -6272,12 +3824,6 @@ class Create { } return g; } - /** - * Create a set of Pts around a circular path - * @param center circle center - * @param radius circle radius - * @param count number of Pts to create - */ static radialPts(center, radius, count) { let g = new Pt_1.Group(); let a = Util_1.Const.two_pi / count; @@ -6286,14 +3832,6 @@ class Create { } return g; } - /** - * Given a group of Pts, return a new group of `Noise` Pts. - * @param pts a Group or an array of Pts - * @param dx small increment value in x dimension - * @param dy small increment value in y dimension - * @param rows Optional row count to generate 2D noise - * @param columns Optional column count to generate 2D noise - */ static noisePts(pts, dx = 0.01, dy = 0.01, rows = 0, columns = 0) { let seed = Math.random(); let g = new Pt_1.Group(); @@ -6307,27 +3845,16 @@ class Create { } return g; } - /** - * Create a Delaunay Group. Use the `.delaunay()` and `.voronoi()` functions in the returned group to generate tessellations. - * @param pts a Group or an array of Pts - * @returns an instance of the Delaunay class - */ static delaunay(pts) { return Delaunay.from(pts); } } exports.Create = Create; -/** - * Perlin noise gradient indices - */ const grad3 = [ [1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0], [1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1], [0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1] ]; -/** - * Perlin noise permutation table - */ const permTable = [151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, @@ -6342,42 +3869,19 @@ const permTable = [151, 160, 137, 91, 90, 15, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 ]; -/** - * A class to generate Perlin noise. Currently it implements a basic 2D noise. More to follow. - * Based on https://gist.github.com/banksean/304522 - */ class Noise extends Pt_1.Pt { - /** - * Create a Noise Pt that's capable of generating noise - * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ constructor(...args) { super(...args); this.perm = []; this._n = new Pt_1.Pt(0.01, 0.01); - // For easier index wrapping, double the permutation table length this.perm = permTable.concat(permTable); } - /** - * Set the initial noise values - * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - * @example `noise.initNoise( 0.01, 0.1 )` - */ initNoise(...args) { this._n = new Pt_1.Pt(...args); } - /** - * Add a small increment to the noise values - * @param x step in x dimension - * @param y step in y dimension - */ step(x = 0, y = 0) { this._n.add(x, y); } - /** - * Specify a seed for this Noise - * @param s seed value - */ seed(s) { if (s > 0 && s < 1) s *= 65536; @@ -6389,9 +3893,6 @@ class Noise extends Pt_1.Pt { this.perm[i] = this.perm[i + 256] = v; } } - /** - * Generate a 2D Perlin noise value - */ noise2D() { let i = Math.floor(this._n[0]) % 255; let j = Math.floor(this._n[1]) % 255; @@ -6407,69 +3908,49 @@ class Noise extends Pt_1.Pt { } } exports.Noise = Noise; -/** - * Delaunay is a Group of Pts that can generate Delaunay and Voronoi tessellations. The triangulation algorithm is ported from [Pt](https://github.com/williamngan/pt) - * This implementation is based on [Paul Bourke's algorithm](http://paulbourke.net/papers/triangulate/) - * with reference to its [javascript implementation by ironwallaby](https://github.com/ironwallaby/delaunay) - */ class Delaunay extends Pt_1.Group { constructor() { super(...arguments); this._mesh = []; } - /** - * Generate Delaunay triangles. This function also caches the mesh that is used to generate Voronoi tessellation in `voronoi()`. - * @param triangleOnly if true, returns an array of triangles in Groups, otherwise return the whole DelaunayShape - * @returns an array of Groups or an array of DelaunayShapes `{i, j, k, triangle, circle}` which records the indices of the vertices, and the calculated triangles and circumcircles - */ delaunay(triangleOnly = true) { if (this.length < 3) return []; this._mesh = []; let n = this.length; - // sort the points and store the sorted index let indices = []; for (let i = 0; i < n; i++) indices[i] = i; indices.sort((i, j) => this[j][0] - this[i][0]); - // duplicate the points list and add super triangle's points to it let pts = this.slice(); let st = this._superTriangle(); pts = pts.concat(st); - // arrays to store edge buffer and opened triangles let opened = [this._circum(n, n + 1, n + 2, st)]; let closed = []; let tris = []; - // Go through each point using the sorted indices for (let i = 0, len = indices.length; i < len; i++) { let c = indices[i]; let edges = []; let j = opened.length; if (!this._mesh[c]) this._mesh[c] = {}; - // Go through each opened triangles while (j--) { let circum = opened[j]; let radius = circum.circle[1][0]; let d = pts[c].$subtract(circum.circle[0]); - // if point is to the right of circumcircle, add it to closed list and don't check again if (d[0] > 0 && d[0] * d[0] > radius * radius) { closed.push(circum); tris.push(circum.triangle); opened.splice(j, 1); continue; } - // if it's outside the circumcircle, skip if (d[0] * d[0] + d[1] * d[1] - radius * radius > Util_1.Const.epsilon) { continue; } - // otherwise it's inside the circumcircle, so we add to edge buffer and remove it from the opened list edges.push(circum.i, circum.j, circum.j, circum.k, circum.k, circum.i); opened.splice(j, 1); } - // dedup edges Delaunay._dedupe(edges); - // Go through the edge buffer and create a triangle for each edge j = edges.length; while (j > 1) { opened.push(this._circum(edges[--j], edges[--j], c, false, pts)); @@ -6485,10 +3966,6 @@ class Delaunay extends Pt_1.Group { } return (triangleOnly) ? tris : closed; } - /** - * Generate Voronoi cells. `delaunay()` must be called before calling this function. - * @returns an array of Groups, each of which represents a Voronoi cell - */ voronoi() { let vs = []; let n = this._mesh; @@ -6497,19 +3974,9 @@ class Delaunay extends Pt_1.Group { } return vs; } - /** - * Get the cached mesh. The mesh is an array of objects, each of which representing the enclosing triangles around a Pt in this Delaunay group - * @return an array of objects that store a series of DelaunayShapes - */ mesh() { return this._mesh; } - /** - * Given an index of a Pt in this Delaunay Group, returns its neighboring Pts in the network - * @param i index of a Pt - * @param sort if true, sort the neighbors so that their edges will form a polygon - * @returns an array of Pts - */ neighborPts(i, sort = false) { let cs = new Pt_1.Group(); let n = this._mesh; @@ -6519,11 +3986,6 @@ class Delaunay extends Pt_1.Group { } return (sort) ? Num_1.Geom.sortEdges(cs) : cs; } - /** - * Given an index of a Pt in this Delaunay Group, returns its neighboring DelaunayShapes - * @param i index of a Pt - * @returns an array of DelaunayShapes `{i, j, k, triangle, circle}` - */ neighbors(i) { let cs = []; let n = this._mesh; @@ -6533,19 +3995,11 @@ class Delaunay extends Pt_1.Group { } return cs; } - /** - * Record a DelaunayShape in the mesh - * @param o DelaunayShape instance - */ _cache(o) { this._mesh[o.i][`${Math.min(o.j, o.k)}-${Math.max(o.j, o.k)}`] = o; this._mesh[o.j][`${Math.min(o.i, o.k)}-${Math.max(o.i, o.k)}`] = o; this._mesh[o.k][`${Math.min(o.i, o.j)}-${Math.max(o.i, o.j)}`] = o; } - /** - * Get the initial "super triangle" that contains all the points in this set - * @returns a Group representing a triangle - */ _superTriangle() { let minPt = this[0]; let maxPt = this[0]; @@ -6558,24 +4012,9 @@ class Delaunay extends Pt_1.Group { let dmax = Math.max(d[0], d[1]); return new Pt_1.Group(mid.$subtract(20 * dmax, dmax), mid.$add(0, 20 * dmax), mid.$add(20 * dmax, -dmax)); } - /** - * Get a triangle from 3 points in a list of points - * @param i index 1 - * @param j index 2 - * @param k index 3 - * @param pts a Group of Pts - */ _triangle(i, j, k, pts = this) { return new Pt_1.Group(pts[i], pts[j], pts[k]); } - /** - * Get a circumcircle and triangle from 3 points in a list of points - * @param i index 1 - * @param j index 2 - * @param k index 3 - * @param tri a Group representing a triangle, or `false` to create it from indices - * @param pts a Group of Pts - */ _circum(i, j, k, tri, pts = this) { let t = tri || this._triangle(i, j, k, pts); return { @@ -6586,10 +4025,6 @@ class Delaunay extends Pt_1.Group { circle: Op_1.Triangle.circumcircle(t) }; } - /** - * Dedupe the edges array - * @param edges - */ static _dedupe(edges) { let j = edges.length; while (j > 1) { @@ -6618,29 +4053,16 @@ exports.Delaunay = Delaunay; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); const Util_1 = __webpack_require__(1); const Num_1 = __webpack_require__(3); -/** - * Color is a subclass of Pt. You can think of a color as a point in a color space. The Color class provides support for many color spaces. - */ class Color extends Pt_1.Pt { - /** - * Create a Color. Same as creating a Pt. - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ constructor(...args) { super(...args); this._mode = "rgb"; this._isNorm = false; } - /** - * Create a Color object with defaults to 4 dimensions - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ static from(...args) { let p = [1, 1, 1, 1]; let c = Util_1.Util.getArgs(args); @@ -6650,13 +4072,9 @@ class Color extends Pt_1.Pt { } return new Color(p); } - /** - * Convert a rgb hex string like #FF0000 or #F00 to a Color object - * @param hex a hex string, with optional '#' prefix - */ static fromHex(hex) { if (hex[0] == "#") - hex = hex.substr(1); // remove '#' if needed + hex = hex.substr(1); if (hex.length <= 3) { let fn = (i) => hex[i] || "F"; hex = `${fn(0)}${fn(0)}${fn(1)}${fn(1)}${fn(2)}${fn(2)}`; @@ -6669,72 +4087,22 @@ class Color extends Pt_1.Pt { let hexVal = parseInt(hex, 16); return new Color(hexVal >> 16, hexVal >> 8 & 0xFF, hexVal & 0xFF, alpha); } - /** - * Create RGB Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static rgb(...args) { return Color.from(...args).toMode("rgb"); } - /** - * Create HSL Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static hsl(...args) { return Color.from(...args).toMode("hsl"); } - /** - * Create HSB Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static hsb(...args) { return Color.from(...args).toMode("hsb"); } - /** - * Create LAB Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static lab(...args) { return Color.from(...args).toMode("lab"); } - /** - * Create LCH Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static lch(...args) { return Color.from(...args).toMode("lch"); } - /** - * Create LUV Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static luv(...args) { return Color.from(...args).toMode("luv"); } - /** - * Create XYZ Color - * @param args Pt-like parameters which can be a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties. - */ static xyz(...args) { return Color.from(...args).toMode("xyz"); } - /** - * Get a Color object whose values are the maximum of its mode - * @param mode a mode string such as "rgb" or "lab" - * @example Color.maxValue("rgb") will return a rgb Color object with values (255,255,255) - */ static maxValues(mode) { return Color.ranges[mode].zipSlice(1).$take([0, 1, 2]); } - /** - * Get a hex string such as "#FF0000". Same as `toString("hex")` - */ get hex() { return this.toString("hex"); } - /** - * Get a rgb string such as "rgb(255,0,0)". Same as `toString("rgb")` - */ get rgb() { return this.toString("rgb"); } - /** - * Get a rgba string such as "rgb(255,0,0,0.5)". Same as `toString("rgba")` - */ get rgba() { return this.toString("rgba"); } - /** - * Clone this Color - */ clone() { let c = new Color(this); c.toMode(this._mode); return c; } - /** - * Convert this color from current color space to another color space - * @param mode a ColorType string: "rgb" "hsl" "hsb" "lab" "lch" "luv" "xyz"; - * @param convert if `true`, convert this Color to the new color space specified in `mode`. Default is `false`, which only sets the color mode without converting color values. - */ toMode(mode, convert = false) { if (convert) { let fname = this._mode.toUpperCase() + "to" + mode.toUpperCase(); @@ -6748,18 +4116,13 @@ class Color extends Pt_1.Pt { this._mode = mode; return this; } - /** - * Get this Color's mode - */ get mode() { return this._mode; } - // rgb get r() { return this[0]; } set r(n) { this[0] = n; } get g() { return this[1]; } set g(n) { this[1] = n; } get b() { return this[1]; } set b(n) { this[2] = n; } - // hsl, hsb get h() { return (this._mode == "lch") ? this[2] : this[0]; } set h(n) { let i = (this._mode == "lch") ? 2 : 0; @@ -6772,7 +4135,6 @@ class Color extends Pt_1.Pt { let i = (this._mode == "hsl") ? 2 : 0; this[i] = n; } - // lab, lch, luv get a() { return this[1]; } set a(n) { this[1] = n; } get c() { return this[1]; } @@ -6781,14 +4143,7 @@ class Color extends Pt_1.Pt { set u(n) { this[1] = n; } get v() { return this[1]; } set v(n) { this[2] = n; } - /** - * Get alpha value - */ get alpha() { return (this.length > 3) ? this[3] : 1; } - /** - * Normalize the color values to between 0 to 1, or revert it back to the min/max values in current color mode - * @param toNorm a boolean value specifying whether to normalize (`true`) or revert (`false`) - */ normalize(toNorm = true) { if (this._isNorm == toNorm) return this; @@ -6801,16 +4156,7 @@ class Color extends Pt_1.Pt { this._isNorm = toNorm; return this; } - /** - * Like `normalize()` but returns as a new Color - * @param toNorm a boolean value specifying whether to normalize (`true`) or revert (`false`) - * @returns new Color - */ $normalize(toNorm = true) { return this.clone().normalize(toNorm); } - /** - * Convert this Color to a string. It can be used to get a hex or rgb string for use in rendering - * @param format "hex", "rgb", "rgba", or "mode" which means using current color mode label. Default is "mode". - */ toString(format = "mode") { if (format == "hex") { let _hex = (n) => { @@ -6829,13 +4175,6 @@ class Color extends Pt_1.Pt { return `${this._mode}(${this[0]},${this[1]},${this[2]},${this.alpha})`; } } - /** - * Convert RGB to HSL - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new HSL Color - */ static RGBtoHSL(rgb, normalizedInput = false, normalizedOutput = false) { let [r, g, b] = (!normalizedInput) ? rgb.$normalize() : rgb; let max = Math.max(r, g, b); @@ -6845,7 +4184,7 @@ class Color extends Pt_1.Pt { let l = h; if (max == min) { h = 0; - s = 0; // achromatic + s = 0; } else { let d = max - min; @@ -6863,13 +4202,6 @@ class Color extends Pt_1.Pt { } return Color.hsl(((normalizedOutput) ? h / 60 : h * 60), s, l, rgb.alpha); } - /** - * Convert HSL to RGB - * @param hsl a HSL Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static HSLtoRGB(hsl, normalizedInput = false, normalizedOutput = false) { let [h, s, l] = hsl; if (!normalizedInput) @@ -6896,13 +4228,6 @@ class Color extends Pt_1.Pt { let sc = (normalizedOutput) ? 1 : 255; return Color.rgb(sc * convert((h + 1 / 3)), sc * convert(h), sc * convert((h - 1 / 3)), hsl.alpha); } - /** - * Convert RGB to HSB - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new HSB Color - */ static RGBtoHSB(rgb, normalizedInput = false, normalizedOutput = false) { let [r, g, b] = (!normalizedInput) ? rgb.$normalize() : rgb; let max = Math.max(r, g, b); @@ -6924,13 +4249,6 @@ class Color extends Pt_1.Pt { } return Color.hsb(((normalizedOutput) ? h / 60 : h * 60), s, v, rgb.alpha); } - /** - * Convert HSB to RGB - * @param hsb a HSB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static HSBtoRGB(hsb, normalizedInput = false, normalizedOutput = false) { let [h, s, v] = hsb; if (!normalizedInput) @@ -6948,79 +4266,30 @@ class Color extends Pt_1.Pt { let sc = (normalizedOutput) ? 1 : 255; return Color.rgb(sc * c[0], sc * c[1], sc * c[2], hsb.alpha); } - /** - * Convert RGB to LAB - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LAB Color - */ static RGBtoLAB(rgb, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? rgb.$normalize(false) : rgb; return Color.XYZtoLAB(Color.RGBtoXYZ(c), false, normalizedOutput); } - /** - * Convert LAB to RGB - * @param lab a LAB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static LABtoRGB(lab, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lab.$normalize(false) : lab; return Color.XYZtoRGB(Color.LABtoXYZ(c), false, normalizedOutput); } - /** - * Convert RGB to LCH - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LCH Color - */ static RGBtoLCH(rgb, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? rgb.$normalize(false) : rgb; return Color.LABtoLCH(Color.RGBtoLAB(c), false, normalizedOutput); } - /** - * Convert LCH to RGB - * @param lch a LCH Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static LCHtoRGB(lch, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lch.$normalize(false) : lch; return Color.LABtoRGB(Color.LCHtoLAB(c), false, normalizedOutput); } - /** - * Convert RGB to LUV - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LUV Color - */ static RGBtoLUV(rgb, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? rgb.$normalize(false) : rgb; return Color.XYZtoLUV(Color.RGBtoXYZ(c), false, normalizedOutput); } - /** - * Convert LUV to RGB - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static LUVtoRGB(luv, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? luv.$normalize(false) : luv; return Color.XYZtoRGB(Color.LUVtoXYZ(c), false, normalizedOutput); } - /** - * Convert RGB to XYZ - * @param rgb a RGB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new XYZ Color - */ static RGBtoXYZ(rgb, normalizedInput = false, normalizedOutput = false) { let c = (!normalizedInput) ? rgb.$normalize() : rgb.clone(); for (let i = 0; i < 3; i++) { @@ -7031,13 +4300,6 @@ class Color extends Pt_1.Pt { let cc = Color.xyz(c[0] * 0.4124564 + c[1] * 0.3575761 + c[2] * 0.1804375, c[0] * 0.2126729 + c[1] * 0.7151522 + c[2] * 0.0721750, c[0] * 0.0193339 + c[1] * 0.1191920 + c[2] * 0.9503041, rgb.alpha); return (normalizedOutput) ? cc.normalize() : cc; } - /** - * Convert XYZ to RGB - * @param xyz a XYZ Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new RGB Color - */ static XYZtoRGB(xyz, normalizedInput = false, normalizedOutput = false) { let [x, y, z] = (!normalizedInput) ? xyz.$normalize() : xyz; let rgb = [ @@ -7045,7 +4307,6 @@ class Color extends Pt_1.Pt { x * -0.9692660 + y * 1.8760108 + z * 0.0415560, x * 0.0556434 + y * -0.2040259 + z * 1.0572252 ]; - // convert xyz to rgb. Note that not all colors are visible in rgb, so here we bound rgb between 0 to 1 for (let i = 0; i < 3; i++) { rgb[i] = (rgb[i] < 0) ? 0 : (rgb[i] > 0.0031308) ? (1.055 * Math.pow(rgb[i], 1 / 2.4) - 0.055) : (12.92 * rgb[i]); rgb[i] = Math.max(0, Math.min(1, rgb[i])); @@ -7055,29 +4316,14 @@ class Color extends Pt_1.Pt { let cc = Color.rgb(rgb[0], rgb[1], rgb[2], xyz.alpha); return (normalizedOutput) ? cc.normalize() : cc; } - /** - * Convert XYZ to LAB - * @param xyz a XYZ Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LAB Color - */ static XYZtoLAB(xyz, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? xyz.$normalize(false) : xyz.clone(); - // adjust for D65 c.divide(Color.D65); let fn = (n) => (n > 0.008856) ? Math.pow(n, 1 / 3) : (7.787 * n) + 16 / 116; let cy = fn(c[1]); let cc = Color.lab((116 * cy) - 16, 500 * (fn(c[0]) - cy), 200 * (cy - fn(c[2])), xyz.alpha); return (normalizedOutput) ? cc.normalize() : cc; } - /** - * Convert LAB to XYZ - * @param lab a LAB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new XYZ Color - */ static LABtoXYZ(lab, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lab.$normalize(false) : lab; let y = (c[0] + 16) / 116; @@ -7088,21 +4334,9 @@ class Color extends Pt_1.Pt { return (nnn > 0.008856) ? nnn : (n - 16 / 116) / 7.787; }; let d = Color.D65; - // adjusted - let cc = Color.xyz( - // Math.max(0, Math.min( 100, d[0] * fn(x) )), - // Math.max(0, Math.min( 100, d[1] * fn(y) )), - // Math.max(0, Math.min( 100, d[2] * fn(z) )), - Math.max(0, d[0] * fn(x)), Math.max(0, d[1] * fn(y)), Math.max(0, d[2] * fn(z)), lab.alpha); + let cc = Color.xyz(Math.max(0, d[0] * fn(x)), Math.max(0, d[1] * fn(y)), Math.max(0, d[2] * fn(z)), lab.alpha); return (normalizedOutput) ? cc.normalize() : cc; } - /** - * Convert XYZ to LUV - * @param xyz a XYZ Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LUV Color - */ static XYZtoLUV(xyz, normalizedInput = false, normalizedOutput = false) { let [x, y, z] = (normalizedInput) ? xyz.$normalize(false) : xyz; let u = (4 * x) / (x + (15 * y) + (3 * z)); @@ -7114,13 +4348,6 @@ class Color extends Pt_1.Pt { let L = (116 * y) - 16; return Color.luv(L, 13 * L * (u - refU), 13 * L * (v - refV), xyz.alpha); } - /** - * Convert LUV to XYZ - * @param luv a LUV Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new XYZ Color - */ static LUVtoXYZ(luv, normalizedInput = false, normalizedOutput = false) { let [l, u, v] = (normalizedInput) ? luv.$normalize(false) : luv; let y = (l + 16) / 116; @@ -7135,36 +4362,18 @@ class Color extends Pt_1.Pt { let z = (9 * y - (15 * v * y) - (v * x)) / (3 * v); return Color.xyz(x, y, z, luv.alpha); } - /** - * Convert LAB to LCH - * @param lab a LAB Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LCH Color - */ static LABtoLCH(lab, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lab.$normalize(false) : lab; - let h = Num_1.Geom.toDegree(Num_1.Geom.boundRadian(Math.atan2(c[2], c[1]))); // 0 to 360 degrees + let h = Num_1.Geom.toDegree(Num_1.Geom.boundRadian(Math.atan2(c[2], c[1]))); return Color.lch(c[0], Math.sqrt(c[1] * c[1] + c[2] * c[2]), h, lab.alpha); } - /** - * Convert LCH to LAB - * @param lch a LCH Color - * @param normalizedInput a boolean specifying whether input color is normalized. Default is not normalized: `false`. - * @param normalizedOutput a boolean specifying whether output color shoud be normalized. Default is not normalized: `false`. - * @returns a new LAB Color - */ static LCHtoLAB(lch, normalizedInput = false, normalizedOutput = false) { let c = (normalizedInput) ? lch.$normalize(false) : lch; let rad = Num_1.Geom.toRadian(c[2]); return Color.lab(c[0], Math.cos(rad) * c[1], Math.sin(rad) * c[1], lch.alpha); } } -// XYZ property for Standard Observer 2deg, Daylight/sRGB illuminant D65 Color.D65 = new Pt_1.Pt(95.047, 100, 108.883, 1); -/** - * Value range for each color space - */ Color.ranges = { rgb: new Pt_1.Group(new Pt_1.Pt(0, 255), new Pt_1.Pt(0, 255), new Pt_1.Pt(0, 255)), hsl: new Pt_1.Group(new Pt_1.Pt(0, 360), new Pt_1.Pt(0, 1), new Pt_1.Pt(0, 1)), @@ -7183,8 +4392,6 @@ exports.Color = Color; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Form_1 = __webpack_require__(6); const Num_1 = __webpack_require__(3); @@ -7192,16 +4399,7 @@ const Util_1 = __webpack_require__(1); const Pt_1 = __webpack_require__(0); const Op_1 = __webpack_require__(2); const Dom_1 = __webpack_require__(9); -/** - * A Space for SVG elements - */ class SVGSpace extends Dom_1.DOMSpace { - /** - * Create a SVGSpace which represents a Space for SVG elements - * @param elem Specify an element by its "id" attribute as string, or by the element object itself. An element can be an existing ``, or a `
` container in which a new `` will be created. If left empty, a `
` will be added to DOM. Use css to customize its appearance if needed. - * @param callback an optional callback `function(boundingBox, spaceElement)` to be called when canvas is appended and ready. Alternatively, a "ready" event will also be fired from the `` element when it's appended, which can be traced with `spaceInstance.canvas.addEventListener("ready")` - * @example `new SVGSpace( "#myElementID" )` - */ constructor(elem, callback) { super(elem, callback); this.id = "svgspace"; @@ -7212,22 +4410,10 @@ class SVGSpace extends Dom_1.DOMSpace { this._canvas = s; } } - /** - * Get a new `SVGForm` for drawing - * @see `SVGForm` - */ getForm() { return new SVGForm(this); } - /** - * Get the html element - */ get element() { return this._canvas; } - /** - * This overrides Space's `resize` function. It's used as a callback function for window's resize event and not usually called directly. You can keep track of resize events with `resize: (bound ,evt)` callback in your player objects (See `Space`'s `add()` function). - * @param b a Bound object to resize to - * @param evt Optionally pass a resize event - */ resize(b, evt) { super.resize(b, evt); SVGSpace.setAttr(this.element, { @@ -7239,12 +4425,6 @@ class SVGSpace extends Dom_1.DOMSpace { }); return this; } - /** - * A static function to add a svg element inside a node. Usually you don't need to use this directly. See methods in `SVGForm` instead. - * @param parent the parent element, or `null` to use current `` as parent. - * @param name a string of element name, such as `rect` or `circle` - * @param id id attribute of the new element - */ static svgElement(parent, name, id) { if (!parent || !parent.appendChild) throw new Error("parent is not a valid DOM element"); @@ -7256,10 +4436,6 @@ class SVGSpace extends Dom_1.DOMSpace { } return elem; } - /** - * Remove an item from this Space - * @param item a player item with an auto-assigned `animateID` property - */ remove(player) { let temp = this._container.querySelectorAll("." + SVGForm.scopeID(player)); temp.forEach((el) => { @@ -7267,24 +4443,13 @@ class SVGSpace extends Dom_1.DOMSpace { }); return super.remove(player); } - /** - * Remove all items from this Space - */ removeAll() { this._container.innerHTML = ""; return super.removeAll(); } } exports.SVGSpace = SVGSpace; -/** -* SVGForm is an implementation of abstract class VisualForm. It provide methods to express Pts on SVGSpace. -* You may extend SVGForm to implement your own expressions for SVGSpace. -*/ class SVGForm extends Form_1.VisualForm { - /** - * Create a new SVGForm. You may also use `space.getForm()` to get the default form. - * @param space an instance of SVGSpace - */ constructor(space) { super(); this._ctx = { @@ -7314,25 +4479,12 @@ class SVGForm extends Form_1.VisualForm { this._ready = true; } }); } - /** - * get the SVGSpace instance that this form is associated with - */ get space() { return this._space; } - /** - * Update a style in _ctx context or throw an Erorr if the style doesn't exist - * @param k style key - * @param v style value - */ styleTo(k, v) { if (this._ctx.style[k] === undefined) throw new Error(`${k} style property doesn't exist`); this._ctx.style[k] = v; } - /** - * Set current fill style. Provide a valid color string or `false` to specify no fill color. - * @example `form.fill("#F90")`, `form.fill("rgba(0,0,0,.5")`, `form.fill(false)` - * @param c fill color - */ fill(c) { if (typeof c == "boolean") { this.styleTo("filled", c); @@ -7343,14 +4495,6 @@ class SVGForm extends Form_1.VisualForm { } return this; } - /** - * Set current stroke style. Provide a valid color string or `false` to specify no stroke color. - * @example `form.stroke("#F90")`, `form.stroke("rgba(0,0,0,.5")`, `form.stroke(false)`, `form.stroke("#000", 0.5, 'round', 'square')` - * @param c stroke color which can be as color, gradient, or pattern. (See [canvas documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle)) - * @param width Optional value (can be floating point) to set line width - * @param linejoin Optional string to set line joint style. Can be "miter", "bevel", or "round". - * @param linecap Optional string to set line cap style. Can be "butt", "round", or "square". - */ stroke(c, width, linejoin, linecap) { if (typeof c == "boolean") { this.styleTo("stroked", c); @@ -7367,11 +4511,6 @@ class SVGForm extends Form_1.VisualForm { } return this; } - /** - * Add custom class to the created element - * @param c custom class name or `false` to reset it - * @example `form.fill("#f00").cls("myClass").rects(r)` `form.cls(false).circles(c)` - */ cls(c) { if (typeof c == "boolean") { this._ctx.currentClass = ""; @@ -7381,15 +4520,6 @@ class SVGForm extends Form_1.VisualForm { } return this; } - /** - * Set the current font - * @param sizeOrFont either a number to specify font-size, or a `Font` object to specify all font properties - * @param weight Optional font-weight string such as "bold" - * @param style Optional font-style string such as "italic" - * @param lineHeight Optional line-height number suchas 1.5 - * @param family Optional font-family such as "Helvetica, sans-serif" - * @example `form.font( myFont )`, `form.font(14, "bold")` - */ font(sizeOrFont, weight, style, lineHeight, family) { if (typeof sizeOrFont == "number") { this._font.size = sizeOrFont; @@ -7408,9 +4538,6 @@ class SVGForm extends Form_1.VisualForm { } return this; } - /** - * Reset the context's common styles to this form's styles. This supports using multiple forms on the same canvas context. - */ reset() { this._ctx.style = { "filled": true, "stroked": true, @@ -7423,12 +4550,6 @@ class SVGForm extends Form_1.VisualForm { this._ctx.font = this._font.value; return this; } - /** - * Set this form's group scope by an ID, and optionally define the group's parent element. A group scope keeps track of elements by their generated IDs, and updates their properties as needed. See also `scope()`. - * @param group_id a string to use as prefix for the group's id. For example, group_id "hello" will create elements with id like "hello-1", "hello-2", etc - * @param group Optional DOM or SVG element to define this group's parent element - * @returns this form's context - */ updateScope(group_id, group) { this._ctx.group = group; this._ctx.groupID = group_id; @@ -7436,46 +4557,22 @@ class SVGForm extends Form_1.VisualForm { this.nextID(); return this._ctx; } - /** - * Set the current group scope to an item added into space, in order to keep track of any point, circle, etc created within it. The item must have an `animateID` property, so that elements created within the item will have generated IDs like "item-{animateID}-{count}". - * @param item a "player" item that's added to space (see `space.add(...)`) and has an `animateID` property - * @returns this form's context - */ scope(item) { if (!item || item.animateID == null) throw new Error("item not defined or not yet added to Space"); return this.updateScope(SVGForm.scopeID(item), this.space.element); } - /** - * Get next available id in the current group - * @returns an id string - */ nextID() { this._ctx.groupCount++; this._ctx.currentID = `${this._ctx.groupID}-${this._ctx.groupCount}`; return this._ctx.currentID; } - /** - * A static function to generate an ID string based on a context object - * @param ctx a context object for an SVGForm - */ static getID(ctx) { return ctx.currentID || `p-${SVGForm.domID++}`; } - /** - * A static function to generate an ID string for a scope, based on a "player" item in the Space - * @param item a "player" item that's added to space (see `space.add(...)`) and has an `animateID` property - */ static scopeID(item) { return `item-${item.animateID}`; } - /** - * A static function to help adding style object to an element. This put all styles into `style` attribute instead of individual attributes, so that the styles can be parsed by Adobe Illustrator. - * @param elem A DOM element to add to - * @param styles an object of style properties - * @example `SVGForm.style(elem, {fill: "#f90", stroke: false})` - * @returns this DOM element - */ static style(elem, styles) { let st = []; if (!styles["filled"]) @@ -7500,14 +4597,6 @@ class SVGForm extends Form_1.VisualForm { } return Dom_1.DOMSpace.setAttr(elem, { style: st.join(";") }); } - /** - * Draws a point - * @param ctx a context object of SVGForm - * @param pt a Pt object or numeric array - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `SVGForm.point( p )`, `SVGForm.point( p, 10, "circle" )` - */ static point(ctx, pt, radius = 5, shape = "square") { if (shape === "circle") { return SVGForm.circle(ctx, pt, radius); @@ -7516,24 +4605,11 @@ class SVGForm extends Form_1.VisualForm { return SVGForm.square(ctx, pt, radius); } } - /** - * Draws a point - * @param p a Pt object - * @param radius radius of the point. Default is 5. - * @param shape The shape of the point. Defaults to "square", but it can be "circle" or a custom shape function in your own implementation. - * @example `form.point( p )`, `form.point( p, 10, "circle" )` - */ point(pt, radius = 5, shape = "square") { this.nextID(); SVGForm.point(this._ctx, pt, radius, shape); return this; } - /** - * A static function to draw a circle - * @param ctx a context object of SVGForm - * @param pt center position of the circle - * @param radius radius of the circle - */ static circle(ctx, pt, radius = 10) { let elem = SVGSpace.svgElement(ctx.group, "circle", SVGForm.getID(ctx)); Dom_1.DOMSpace.setAttr(elem, { @@ -7545,25 +4621,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw a circle - * @param pts usually a Group of 2 Pts, but it can also take an array of two numeric arrays [ [position], [size] ] - * @see [`Circle.fromCenter`](./_op_.circle.html#frompt) - */ circle(pts) { this.nextID(); SVGForm.circle(this._ctx, pts[0], pts[1][0]); return this; } - /** - * A static function to draw an arc. - * @param ctx a context object of SVGForm - * @param pt center position - * @param radius radius of the arc circle - * @param startAngle start angle of the arc - * @param endAngle end angle of the arc - * @param cc an optional boolean value to specify if it should be drawn clockwise (`false`) or counter-clockwise (`true`). Default is clockwise. - */ static arc(ctx, pt, radius, startAngle, endAngle, cc) { let elem = SVGSpace.svgElement(ctx.group, "path", SVGForm.getID(ctx)); const start = new Pt_1.Pt(pt).toAngle(startAngle, radius, true); @@ -7581,25 +4643,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw an arc. - * @param pt center position - * @param radius radius of the arc circle - * @param startAngle start angle of the arc - * @param endAngle end angle of the arc - * @param cc an optional boolean value to specify if it should be drawn clockwise (`false`) or counter-clockwise (`true`). Default is clockwise. - */ arc(pt, radius, startAngle, endAngle, cc) { this.nextID(); SVGForm.arc(this._ctx, pt, radius, startAngle, endAngle, cc); return this; } - /** - * A static function to draw a square - * @param ctx a context object of SVGForm - * @param pt center position of the square - * @param halfsize half size of the square - */ static square(ctx, pt, halfsize) { let elem = SVGSpace.svgElement(ctx.group, "rect", SVGForm.getID(ctx)); Dom_1.DOMSpace.setAttr(elem, { @@ -7612,21 +4660,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw a square, given a center and its half-size - * @param pt center Pt - * @param halfsize half-size - */ square(pt, halfsize) { this.nextID(); SVGForm.square(this._ctx, pt, halfsize); return this; } - /** - * A static function to draw a line - * @param ctx a context object of SVGForm - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ static line(ctx, pts) { if (!this._checkSize(pts)) return; @@ -7643,21 +4681,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw a line or polyline - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ line(pts) { this.nextID(); SVGForm.line(this._ctx, pts); return this; } - /** - * A static helper function to draw polyline or polygon - * @param ctx a context object of SVGForm - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - * @param closePath a boolean to specify if the polygon path should be closed - */ static _poly(ctx, pts, closePath = true) { if (!this._checkSize(pts)) return; @@ -7670,28 +4698,14 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * A static function to draw polygon - * @param ctx a context object of SVGForm - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ static polygon(ctx, pts) { return SVGForm._poly(ctx, pts, true); } - /** - * Draw a polygon - * @param pts a Group of multiple Pts, or an array of multiple numeric arrays - */ polygon(pts) { this.nextID(); SVGForm.polygon(this._ctx, pts); return this; } - /** - * A static function to draw a rectangle - * @param ctx a context object of SVGForm - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ static rect(ctx, pts) { if (!this._checkSize(pts)) return; @@ -7708,22 +4722,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw a rectangle - * @param pts usually a Group of 2 Pts specifying the top-left and bottom-right positions. Alternatively it can be an array of numeric arrays. - */ rect(pts) { this.nextID(); SVGForm.rect(this._ctx, pts); return this; } - /** - * A static function to draw text - * @param ctx a context object of SVGForm - * @param `pt` a Point object to specify the anchor point - * @param `txt` a string of text to draw - * @param `maxWidth` specify a maximum width per line - */ static text(ctx, pt, txt) { let elem = SVGSpace.svgElement(ctx.group, "text", SVGForm.getID(ctx)); Dom_1.DOMSpace.setAttr(elem, { @@ -7737,21 +4740,11 @@ class SVGForm extends Form_1.VisualForm { SVGForm.style(elem, ctx.style); return elem; } - /** - * Draw text on canvas - * @param `pt` a Pt or numeric array to specify the anchor point - * @param `txt` text - * @param `maxWidth` specify a maximum width per line - */ text(pt, txt) { this.nextID(); SVGForm.text(this._ctx, pt, txt); return this; } - /** - * A convenient way to draw some text on canvas for logging or debugging. It'll be draw on the top-left of the canvas as an overlay. - * @param txt text - */ log(txt) { this.fill("#000").stroke("#fff", 0.5).text([10, 14], txt); return this; @@ -7768,27 +4761,16 @@ exports.SVGForm = SVGForm; "use strict"; -// Source code licensed under Apache License 2.0. -// Copyright © 2017 William Ngan. (https://github.com/williamngan/pts) Object.defineProperty(exports, "__esModule", { value: true }); const Pt_1 = __webpack_require__(0); const Bound_1 = __webpack_require__(4); const Op_1 = __webpack_require__(2); -/** - * A `World` stores and manages `Body` and `Particle` for 2D physics simulation - */ class World { - /** - * Create a `World` for 2D physics simulation - * @param bound a rectangular bounding box defined by a Group - * @param friction a value between 0 to 1 where 1 means no friction. Default is 1 - * @param gravity a number of a Pt to define gravitational force. Using a number is a shorthand to set `new Pt(0, n)`. Default is 0. - */ constructor(bound, friction = 1, gravity = 0) { this._lastTime = null; this._gravity = new Pt_1.Pt(); - this._friction = 1; // general friction - this._damping = 0.75; // collision damping + this._friction = 1; + this._damping = 0.75; this._particles = []; this._bodies = []; this._names = { p: {}, b: {} }; @@ -7803,52 +4785,21 @@ class World { set friction(f) { this._friction = f; } get damping() { return this._damping; } set damping(f) { this._damping = f; } - /** - * Get the number of bodies - */ get bodyCount() { return this._bodies.length; } - /** - * Get the number of particles - */ get particleCount() { return this._particles.length; } - /** - * Get a body in this world by index or string id - * @param id numeric index of the body, or a string id that associates with it. - */ body(id) { return this._bodies[(typeof id === "string") ? this._names.b[id] : id]; } - /** - * Get a particle in this world by index or string id - * @param id numeric index of the particle, or a string id that associates with it. - */ particle(id) { return this._particles[(typeof id === "string") ? this._names.p[id] : id]; } - /** - * Update this world one time step - * @param ms change in time in milliseconds - */ update(ms) { let dt = ms / 1000; this._updateParticles(dt); this._updateBodies(dt); } - /** - * Draw particles using the provided function - * @param fn a function that draws particles passed in the parameters `(particles, index)`. - */ drawParticles(fn) { this._drawParticles = fn; } - /** - * Draw bodies using the provided function - * @param fn a function that draws bodies passed in the parameters `(bodies, index)`. - */ drawBodies(fn) { this._drawBodies = fn; } - /** - * Add a particle or body to this world. - * @param p `Particle` or `Body` instance - * @param name optional name, which can be referenced in `body()` or `particle()` function to retrieve this back. - */ add(p, name) { if (p instanceof Body) { this._bodies.push(p); @@ -7862,12 +4813,6 @@ class World { } return this; } - /** - * Remove either body or particle from this world. Support removing a range and negative index. - * @param which Either "body" or "particle" - * @param index Start index, which can be negative (where -1 is at index 0, -2 at index 1, etc) - * @param count Number of items to remove. Default is 1. - */ remove(which, index, count = 1) { let param = (index < 0) ? [index * -1 - 1, count] : [index, count]; if (which == "body") { @@ -7878,32 +4823,18 @@ class World { } return this; } - /** - * Static function to calculate edge constraints between 2 particles. - * @param p1 particle 1 - * @param p2 particle 1 - * @param dist distance between particles - * @param stiff stiffness between 0 to 1. - * @param precise use precise distance calculation. Default is `false`. - */ static edgeConstraint(p1, p2, dist, stiff = 1, precise = false) { const m1 = 1 / (p1.mass || 1); const m2 = 1 / (p2.mass || 1); const mm = m1 + m2; let delta = p2.$subtract(p1); let distSq = dist * dist; - let d = (precise) ? (dist / delta.magnitude() - 1) : (distSq / (delta.dot(delta) + distSq) - 0.5); // approx square root + let d = (precise) ? (dist / delta.magnitude() - 1) : (distSq / (delta.dot(delta) + distSq) - 0.5); let f = delta.$multiply(d * stiff); p1.subtract(f.$multiply(m1 / mm)); p2.add(f.$multiply(m2 / mm)); return p1; } - /** - * Static function to calculate bounding box constraints. - * @param p particle - * @param rect bounding box defined by a Group - * @param damping damping between 0 to 1, where 1 means no damping. Default is 0.75. - */ static boundConstraint(p, rect, damping = 0.75) { let bound = rect.boundingBox(); let np = p.$min(bound[1].subtract(p.radius)).$max(bound[0].add(p.radius)); @@ -7917,47 +4848,30 @@ class World { } p.to(np); } - /** - * Internal integrate function - * @param p particle - * @param dt time changed - * @param prevDt previous change in time, optional - */ integrate(p, dt, prevDt) { p.addForce(this._gravity); p.verlet(dt, this._friction, prevDt); return p; } - /** - * Internal function to update particles - */ _updateParticles(dt) { for (let i = 0, len = this._particles.length; i < len; i++) { let p = this._particles[i]; - // force and integrate this.integrate(p, dt, this._lastTime); - // constraints World.boundConstraint(p, this._bound, this._damping); - // collisions for (let k = i + 1; k < len; k++) { if (i !== k) { let p2 = this._particles[k]; p.collide(p2, this._damping); } } - // render if (this._drawParticles) this._drawParticles(p, i); } this._lastTime = dt; } - /** - * Internal function to update bodies - */ _updateBodies(dt) { for (let i = 0, len = this._bodies.length; i < len; i++) { let b = this._bodies[i]; - // integrate for (let k = 0, klen = b.length; k < klen; k++) { let bk = b[k]; World.boundConstraint(bk, this._bound, this._damping); @@ -7969,23 +4883,14 @@ class World { for (let m = 0, mlen = this._particles.length; m < mlen; m++) { b.processParticle(this._particles[m]); } - // constraints b.processEdges(); - // render if (this._drawBodies) this._drawBodies(b, i); } } } exports.World = World; -/** - * Particle is a Pt that has radius and mass. It's usually added into `World` to create physics simulations. - */ class Particle extends Pt_1.Pt { - /** - * Create a particle - * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ constructor(...args) { super(...args); this._mass = 1; @@ -7999,73 +4904,38 @@ class Particle extends Pt_1.Pt { set mass(m) { this._mass = m; } get radius() { return this._radius; } set radius(f) { this._radius = f; } - /** - * Get previous position - */ get previous() { return this._prev; } set previous(p) { this._prev = p; } - /** - * Get current accumulated force - */ get force() { return this._force; } set force(g) { this._force = g; } - /** - * Get the body of this particle, if any. - */ get body() { return this._body; } set body(b) { this._body = b; } - /** - * - */ get lock() { return this._lock; } set lock(b) { this._lock = b; this._lockPt = new Pt_1.Pt(this); } - /** - * Get the change in position since last time step - */ get changed() { return this.$subtract(this._prev); } - /** - * Set a new position, and update previous and lock states if needed. - */ set position(p) { this.previous.to(this); if (this._lock) this._lockPt = p; this.to(p); } - /** - * Set the size of this particle. This sets both the radius and the mass. - * @param r `radius` value, and also set `mass` to the same value. - */ size(r) { this._mass = r; this._radius = r; return this; } - /** - * Add to the accumulated force - * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - */ addForce(...args) { this._force.add(...args); return this._force; } - /** - * Verlet integration - * @param dt change in time - * @param friction friction from 0 to 1, where 1 means no friction - * @param lastDt optional last change in time - */ verlet(dt, friction, lastDt) { - // Positional verlet: curr + (curr - prev) + a * dt * dt if (this._lock) { this.to(this._lockPt); - // this._prev.to( this._lockPt ); } else { - // time corrected (https://en.wikipedia.org/wiki/Verlet_integration#Non-constant_time_differences) let lt = (lastDt) ? lastDt : dt; let a = this._force.multiply(dt * (dt + lt) / 2); let v = this.changed.multiply(friction * dt / lt).add(a); @@ -8075,23 +4945,11 @@ class Particle extends Pt_1.Pt { } return this; } - /** - * Hit this particle with an impulse - * @param args a list of numeric parameters, an array of numbers, or an object with {x,y,z,w} properties - * @example `hit(10, 20)`, `hit( new Pt(5, 9) )` - */ hit(...args) { this._prev.subtract(new Pt_1.Pt(...args).$divide(Math.sqrt(this._mass))); return this; } - /** - * Check and respoond to collisions between two particles - * @param p2 another particle - * @param damp damping value between 0 to 1, where 1 means no damping. - */ collide(p2, damp = 1) { - // reference: http://codeflow.org/entries/2010/nov/29/verlet-collision-with-impulse-preservation - // simultaneous collision not yet resolved. Possible solutions in this paper: https://www2.msm.ctw.utwente.nl/sluding/PAPERS/dem07.pdf let p1 = this; let dp = p1.$subtract(p2); let distSq = dp.magnitudeSq(); @@ -8120,13 +4978,7 @@ class Particle extends Pt_1.Pt { } } exports.Particle = Particle; -/** - * Body consists of a group of `Particles` and edge constraints. It is usually added into a `World` to create physics simulations - */ class Body extends Pt_1.Group { - /** - * Create an empty Body, this is usually followed by `init` to populate the Body. Alternatively, use static function `fromGroup` to create and initate a body directly. - */ constructor() { super(); this._cs = []; @@ -8134,13 +4986,6 @@ class Body extends Pt_1.Group { this._locks = {}; this._mass = 1; } - /** - * Create and populate a body with a group of Pts. - * @param list a group of Pts - * @param stiff stiffness value from 0 to 1, where 1 is the most stiff. Default is 1. - * @param autoLink Automatically create links between the Pts. This usually works for regular convex polygons. Default is true. - * @param autoMass Automatically calculate the mass based on the area of the polygon. Default is true. - */ static fromGroup(list, stiff = 1, autoLink = true, autoMass = true) { let b = new Body().init(list); if (autoLink) @@ -8149,11 +4994,6 @@ class Body extends Pt_1.Group { b.autoMass(); return b; } - /** - * Initiate a body - * @param list a group of Pts - * @param stiff stiffness value from 0 to 1, where 1 is the most stiff. Default is 1. - */ init(list, stiff = 1) { let c = new Pt_1.Pt(); for (let i = 0, len = list.length; i < len; i++) { @@ -8165,9 +5005,6 @@ class Body extends Pt_1.Group { this._stiff = stiff; return this; } - /** - * Get mass of this body. - */ get mass() { return this._mass; } set mass(m) { this._mass = m; @@ -8175,19 +5012,10 @@ class Body extends Pt_1.Group { this[i].mass = this._mass; } } - /** - * Automatically calculate `mass` to body's polygon area. - */ autoMass() { this.mass = Math.sqrt(Op_1.Polygon.area(this)) / 10; return this; } - /** - * Create a linked edge between two points - * @param index1 first point by index - * @param index2 first point by index - * @param stiff optionally stiffness value between 0 to 1, where 1 is the most stiff. - */ link(index1, index2, stiff) { if (index1 < 0 || index1 >= this.length) throw new Error("index1 is not in the Group's indices"); @@ -8197,10 +5025,6 @@ class Body extends Pt_1.Group { this._cs.push([index1, index2, d, stiff || this._stiff]); return this; } - /** - * Automatically create links for all the points to preserve the initial body shape. This usually works for regular convex polygon. - * @param stiff optionally stiffness value between 0 to 1, where 1 is the most stiff. - */ linkAll(stiff) { let half = this.length / 2; for (let i = 0, len = this.length; i < len; i++) { @@ -8216,10 +5040,6 @@ class Body extends Pt_1.Group { } } } - /** - * Return a list of all the linked edges as line segments. - * @returns an array of Groups, each of which represents an edge - */ linksToLines() { let gs = []; for (let i = 0, len = this._cs.length; i < len; i++) { @@ -8228,19 +5048,12 @@ class Body extends Pt_1.Group { } return gs; } - /** - * Recalculate all edge constraints - */ processEdges() { for (let i = 0, len = this._cs.length; i < len; i++) { let [m, n, d, s] = this._cs[i]; World.edgeConstraint(this[m], this[n], d, s); } } - /** - * Check and respond to collisions between two bodies - * @param b another body - */ processBody(b) { let b1 = this; let b2 = b; @@ -8265,10 +5078,6 @@ class Body extends Pt_1.Group { hit.vertex.add(cv.$multiply(mr1)); } } - /** - * Check and respond to collisions between this body and a particle - * @param b a particle - */ processParticle(b) { let b1 = this; let b2 = b; @@ -8292,8 +5101,6 @@ class Body extends Pt_1.Group { eg[1].subtract(cv.$multiply(mr0 * t * lambda / 2)); let c1 = b.changed.add(cv.$multiply(mr1)); b.previous = b.$subtract(c1); - // let c2 = b2.changed.add( cv.$multiply(mr0) ); - // b2.previous = b2.$subtract( c2 ); } } } diff --git a/dist/pts.min.js b/dist/pts.min.js index 4efb6af6..8e09e4b1 100644 --- a/dist/pts.min.js +++ b/dist/pts.min.js @@ -3,5 +3,5 @@ * Licensed under Apache 2.0 License. * See https://github.com/williamngan/pts for details. */ -(function(e,t){'object'==typeof exports&&'object'==typeof module?module.exports=t():'function'==typeof define&&define.amd?define([],t):'object'==typeof exports?exports.Pts=t():e.Pts=t()})('undefined'==typeof self?this:self,function(){return function(e){function t(o){if(n[o])return n[o].exports;var i=n[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e['default']}:function(){return e};return t.d(n,'a',n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p='',t(t.s=0)}([function(e,t){(function(t,n){e.exports=n()})('undefined'==typeof self?this:self,function(){var e=Math.pow,n=Math.sqrt,o=Math.PI,t=Number.MIN_VALUE,i=Number.MAX_VALUE,l=Math.floor,r=Math.atan2,u=Math.max,a=Math.sin,s=Math.cos,_=Math.min,d=Math.abs;return function(e){function t(o){if(n[o])return n[o].exports;var i=n[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e['default']}:function(){return e};return t.d(n,'a',n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p='',t(t.s=10)}([function(e,t,n){'use strict';Object.defineProperty(t,'__esModule',{value:!0});const o=n(1),c=n(3),i=n(4);t.PtBaseArray=Float32Array;class h extends t.PtBaseArray{constructor(...e){1===e.length&&'number'==typeof e[0]?super(e[0]):super(0t)return!1;return!0}to(...e){let t=o.Util.getArgs(e);for(let n=0,o=_(this.length,t.length);ne(t,...n)}ops(e){let t=[];for(let n=0,o=e.length;ne?[-1*e-1,t]:[e,t];return p.prototype.splice.apply(this,n)}segments(e=2,t=1,n=!1){return this.split(e,t,n)}lines(){return this.segments(2,1)}centroid(){return c.Geom.centroid(this)}boundingBox(){return c.Geom.boundingBox(this)}anchorTo(e=0){c.Geom.anchor(this,e,'to')}anchorFrom(e=0){c.Geom.anchor(this,e,'from')}op(e){let t=this;return(...n)=>e(t,...n)}ops(e){let t=[];for(let n=0,o=e.length;nt?o[e]-n[e]:n[e]-o[e])}forEachPt(e,...t){if(!this[0][e])return o.Util.warn(`${e} is not a function of Pt`),this;for(let n=0,o=this.length;ne+t.toString()+' ','')+' ]'}}t.Group=p},function(e,n,r){'use strict';Object.defineProperty(n,'__esModule',{value:!0});const a=r(0);n.Const={xy:'xy',yz:'yz',xz:'xz',xyz:'xyz',horizontal:0,vertical:1,identical:0,right:4,bottom_right:5,bottom:6,bottom_left:7,left:8,top_left:1,top:2,top_right:3,epsilon:1e-4,max:i,min:t,pi:o,two_pi:6.283185307179586,half_pi:1.5707963267948966,quarter_pi:.7853981633974483,one_degree:.017453292519943295,rad_to_deg:57.29577951308232,deg_to_rad:.017453292519943295,gravity:9.81,newton:.10197,gaussian:.3989422804014327};class s{static getArgs(e){if(1>e.length)return[];let t=[],n=Array.isArray(e[0])||ArrayBuffer.isView(e[0]);if('number'==typeof e[0])t=Array.prototype.slice.call(e);else if('object'==typeof e[0]&&!n){let n=['x','y','z','w'],o=e[0];for(let e=0;e=o.length||!(n[e]in o));e++)t.push(o[n[e]])}else n&&(t=[].slice.call(e[0]));return t}static warn(e='error',t=void 0){if('error'==s.warnLevel)throw new Error(e);else'warn'==s.warnLevel&&console.warn(e);return t}static randomInt(e,t=0){return l(Math.random()*e)+t}static split(e,t,n,o=!1){let r=n||t,l=[];for(let a=0;ae.length))l.push(e.slice(a*r,a*r+t));else if(o){let n=e.slice(a*r);n=n.concat(e.slice(0,(a*r+t)%t)),l.push(n)}else break;return l}static flatten(e,t=!0){let n=t?new a.Group:[];return n.concat.apply(n,e)}static combine(e,t,n){let o=[];for(let r=0,i=e.length;r=e&&(i=t+(i-e)),o&&o(i),i}}static forRange(e,t,n=0,o=1){let r=[];for(let l=n;ll.Util.warn('Group\'s length is less than '+t,e),p=(e,t='')=>l.Util.warn(`Index ${t} is out of bound in Group`,e);class f{static fromAngle(e,t,n){let o=new s.Group(new s.Pt(e),new s.Pt(e));return o[1].toAngle(t,n,!0),o}static slope(e,t){return 0==t[0]-e[0]?void 0:(t[1]-e[1])/(t[0]-e[0])}static intercept(e,t){if(0==t[0]-e[0])return;else{let n=(t[1]-e[1])/(t[0]-e[0]),o=e[1]-n*e[0];return{slope:n,yi:o,xi:0==n?void 0:-o/n}}}static sideOfPt2D(e,t){return(e[1][0]-e[0][0])*(t[1]-e[0][1])-(t[0]-e[0][0])*(e[1][1]-e[0][1])}static collinear(e,t,n,o=.01){let i=new s.Pt(0,0,0).to(e).$subtract(t),r=new s.Pt(0,0,0).to(e).$subtract(n);return i.$cross(r).divide(1e3).equals(new s.Pt(0,0,0),o)}static magnitude(e){return 2<=e.length?e[1].$subtract(e[0]).magnitude():0}static magnitudeSq(e){return 2<=e.length?e[1].$subtract(e[0]).magnitudeSq():0}static perpendicularFromPt(e,t,n=!1){if(!e[0].equals(e[1])){let o=e[0].$subtract(e[1]),i=e[1].$subtract(t),r=i.$subtract(o.$project(i));return n?r:r.$add(t)}}static distanceFromPt(e,t){return f.perpendicularFromPt(e,t,!0).magnitude()}static intersectRay2D(e,t){let n=f.intercept(e[0],e[1]),o=f.intercept(t[0],t[1]),i=e[0],r=t[0];if(n==void 0){if(o==void 0)return;let e=-o.slope*(r[0]-i[0])+r[1];return new s.Pt(i[0],e)}if(void 0==o){let e=-n.slope*(i[0]-r[0])+i[1];return new s.Pt(r[0],e)}if(o.slope!=n.slope){let e=(n.slope*i[0]-o.slope*r[0]+r[1]-i[1])/(n.slope-o.slope),t=n.slope*(e-i[0])+i[1];return new s.Pt(e,t)}return n.yi==o.yi?new s.Pt(i[0],i[1]):void 0}static intersectLine2D(e,t){let n=f.intersectRay2D(e,t);return n&&c.Geom.withinBound(n,e[0],e[1])&&c.Geom.withinBound(n,t[0],t[1])?n:void 0}static intersectLineWithRay2D(e,t){let n=f.intersectRay2D(e,t);return n&&c.Geom.withinBound(n,e[0],e[1])?n:void 0}static intersectPolygon2D(e,t,n=!1){let o=n?f.intersectLineWithRay2D:f.intersectLine2D,r=new s.Group;for(let l,a=0,i=t.length;ad(t[1]/t[0])?0>r[1]?0:2:0>r[0]?3:1,f.intersectRay2D(i[l],e)}}static marker(e,n,o='arrow',i=!0){let r=i?0:1,l=i?1:0,t=e[r].$subtract(e[l]);if(0===t.magnitudeSq())return new s.Group;t.unit();let a=c.Geom.perpendicular(t).multiply(n[0]).add(e[l]);return'arrow'==o?(a.add(t.$multiply(n[1])),new s.Group(e[l],a[0],a[1])):new s.Group(a[0],a[1])}static toRect(e){return new s.Group(e[0].$min(e[1]),e[0].$max(e[1]))}}t.Line=f;class y{static from(e,t,n){return y.fromTopLeft(e,t,n)}static fromTopLeft(e,t,n){let o='number'==typeof t?[t,n||t]:t;return new s.Group(new s.Pt(e),new s.Pt(e).add(o))}static fromCenter(e,t,n){let o='number'==typeof t?[t/2,(n||t)/2]:new s.Pt(t).divide(2);return new s.Group(new s.Pt(e).subtract(o),new s.Pt(e).add(o))}static toCircle(e){return m.fromRect(e)}static toSquare(e,t=!1){let n=y.size(e),o=t?n.maxValue().value:n.minValue().value;return y.fromCenter(y.center(e),o,o)}static size(e){return e[0].$max(e[1]).subtract(e[0].$min(e[1]))}static center(e){let t=e[0].$min(e[1]),n=e[0].$max(e[1]);return t.add(n.$subtract(t).divide(2))}static corners(e){let t=e[0].$min(e[1]),n=e[0].$max(e[1]);return new s.Group(t,new s.Pt(n.x,t.y),n,new s.Pt(t.x,n.y))}static sides(e){let[t,n,o,i]=y.corners(e);return[new s.Group(t,n),new s.Group(n,o),new s.Group(o,i),new s.Group(i,t)]}static lines(e){return y.sides(e)}static boundingBox(e){let t=l.Util.flatten(e,!1),n=s.Pt.make(2,Number.MAX_VALUE),o=s.Pt.make(2,Number.MIN_VALUE);for(let r=0,i=t.length;re;e++)n[e]=_(n[e],t[r][e]),o[e]=u(o[e],t[r][e]);return new s.Group(n,o)}static polygon(e){return y.corners(e)}static quadrants(e,t){let n=y.corners(e),o=t==void 0?y.center(e):new s.Pt(t);return n.map((e)=>new s.Group(e,o).boundingBox())}static halves(e,t=.5,n=!1){let o=e[0].$min(e[1]),i=e[0].$max(e[1]),r=n?c.Num.lerp(o[1],i[1],t):c.Num.lerp(o[0],i[0],t);return n?[new s.Group(o,new s.Pt(i[0],r)),new s.Group(new s.Pt(o[0],r),i)]:[new s.Group(o,new s.Pt(r,i[1])),new s.Group(new s.Pt(r,o[1]),i)]}static withinBound(e,t){return c.Geom.withinBound(t,e[0],e[1])}static hasIntersectRect2D(e,t){let n=y.corners(e);for(let o=0,i=n.length;oc)return new s.Group;else{let e=n(c),i=t[0].$subtract(o.$multiply(-d+e));if(0==c)return new s.Group(i);let r=t[0].$subtract(o.$multiply(-d-e));return new s.Group(i,r)}}static intersectLine2D(e,t){let n=m.intersectRay2D(e,t),o=new s.Group;if(0l+c)return new s.Group;if(rr;r++)n.push(e[0].clone().toAngle(t,e[1][0],!0)),t+=2*o/3;return n}}t.Circle=m;class g{static fromRect(e){let t=e[0].$add(e[1]).divide(2);t.y=e[0][1];let n=e[1].clone();return n.x=e[0][0],new s.Group(t,e[1].clone(),n)}static fromCircle(e){return m.toInnerTriangle(e)}static fromCenter(e,t){return g.fromCircle(m.fromCenter(e,t))}static medial(e){return 3>e.length?h(new s.Group,3):x.midpoints(e,!0)}static oppositeSide(e,t){return 3>e.length?h(new s.Group,3):0===t?s.Group.fromPtArray([e[1],e[2]]):1===t?s.Group.fromPtArray([e[0],e[2]]):s.Group.fromPtArray([e[0],e[1]])}static altitude(e,t){let n=g.oppositeSide(e,t);return 1e.length)return h(void 0,3);let t=g.altitude(e,0),n=g.altitude(e,1);return f.intersectRay2D(t,n)}static incenter(e){if(3>e.length)return h(void 0,3);let t=x.bisector(e,0).add(e[0]),n=x.bisector(e,1).add(e[1]);return f.intersectRay2D(new s.Group(e[0],t),new s.Group(e[1],n))}static incircle(e,t){let n=t?t:g.incenter(e),o=x.area(e),i=x.perimeter(e,!0),l=2*o/i.total;return m.fromCenter(n,l)}static circumcenter(e){let t=g.medial(e),n=[t[0],c.Geom.perpendicular(e[0].$subtract(t[0])).p1.$add(t[0])],o=[t[1],c.Geom.perpendicular(e[1].$subtract(t[1])).p1.$add(t[1])];return f.intersectRay2D(n,o)}static circumcircle(e,t){let n=t?t:g.circumcenter(e),o=e[0].$subtract(n).magnitude();return m.fromCenter(n,o)}}t.Triangle=g;class x{static centroid(e){return c.Geom.centroid(e)}static lines(e,t=!1){if(2>e.length)return h(new s.Group,2);let n=l.Util.split(e,2,1);return t&&n.push(new s.Group(e[e.length-1],e[0])),n.map((e)=>e)}static midpoints(e,n=!1,o=.5){if(2>e.length)return h(new s.Group,2);let t=x.lines(e,n),i=t.map((e)=>c.Geom.interpolate(e[0],e[1],o));return i}static adjacentSides(e,t,n=!1){if(2>e.length)return h(new s.Group,2);if(0>t||t>=e.length)return p(new s.Group,t);let o=[],i=t-1;n&&0>i&&(i=e.length-1),0<=i&&o.push(new s.Group(e[t],e[i]));let r=t+1;return n&&r>e.length-1&&(r=0),r<=e.length-1&&o.push(new s.Group(e[t],e[r])),o}static bisector(e,t){let n=x.adjacentSides(e,t,!0);if(2<=n.length){let e=n[0][1].$subtract(n[0][0]).unit(),t=n[1][1].$subtract(n[1][0]).unit();return e.add(t).divide(2)}}static perimeter(e,t=!1){if(2>e.length)return h(new s.Group,2);let n=x.lines(e,t),o=0,r=s.Pt.make(n.length,0);for(let l,a=0,i=n.length;ae.length)return h(new s.Group,3);let t=(e,t)=>e[0]*t[1]-e[1]*t[0],n=0;for(let o=0,i=e.length;oe.length)return h(new s.Group,3);t||(e=e.slice(),e.sort((e,t)=>e[0]-t[0]));let n=(e,t,n)=>0<(t[0]-e[0])*(n[1]-e[1])-(n[0]-e[0])*(t[1]-e[1]),o=[],r=e.length-2,l=r+3;o[r]=e[2],o[l]=e[2],n(e[0],e[1],e[2])?(o[r+1]=e[0],o[r+2]=e[1]):(o[r+1]=e[1],o[r+2]=e[0]);for(let a,s=3,i=e.length;sc.Geom.boundingBox(e)),n=l.Util.flatten(t,!1);return t.unshift(c.Geom.boundingBox(n)),t}}t.Polygon=x;class b{static getSteps(e){let n=new s.Group;for(let o,t=0;t<=e;t++)o=t/e,n.push(new s.Pt(o*o*o,o*o,o,1));return n}static controlPoints(e,t=0,n=!1){if(t>e.length-1)return new s.Group;let o=(t)=>te+n.x*t[o],0),o=e.reduce((e,n,o)=>e+n.y*t[o],0);if(2e+n.z*t[o],0);return new s.Pt(n,o,i)}return new s.Pt(n,o)}static catmullRom(e,t=10){if(2>e.length)return new s.Group;let n=new s.Group,o=b.getSteps(t),r=b.controlPoints(e,0,!0);for(let l=0;l<=t;l++)n.push(b.catmullRomStep(o[l],r));for(let i=0;ie.length)return new s.Group;let o=new s.Group,r=b.getSteps(t),l=b.controlPoints(e,0,!0);for(let a=0;a<=t;a++)o.push(b.cardinalStep(r[a],l,n));for(let i=0;ie.length)return new s.Group;let n=new s.Group,o=b.getSteps(t),i=0;for(;ie.length)return new s.Group;let o=new s.Group,r=b.getSteps(t),i=0;for(;in?i-=o:i=_(t,n)&&e<=u(t,n)}static randomRange(e,t=0){let n=e>t?e-t:t-e;return e+Math.random()*n}static normalizeValue(e,t,n){let o=_(t,n),i=u(t,n);return(e-o)/(i-o)}static sum(e){let t=new m.Pt(e[0]);for(let n=1,o=e.length;ne.$min(t)),n=e.reduce((e,t)=>e.$max(t));return new m.Group(t,n)}static centroid(e){return c.average(e)}static anchor(e,t=0,n='to'){let o='to'==n?'subtract':'add';for(let r=0,i=e.length;r{if(2>e.length||2>t.length)throw new Error('Pt dimension cannot be less than 2');let o=e.$subtract(n),i=t.$subtract(n);if(0<=o[0]&&0>i[0])return 1;if(0>o[0]&&0<=i[0])return-1;if(0==o[0]&&0==i[0])return 0<=o[1]||0<=i[1]?o[1]>i[1]?1:-1:i[1]>o[1]?1:-1;let r=o.cross2D(i);return 0>r?1:0i[0]*i[0]+i[1]*i[1]?1:-1})}static scale(e,t,n){let o=Array.isArray(e)?e:[e],i='number'==typeof t?m.Pt.make(o[0].length,t):t;n||(n=m.Pt.make(o[0].length,0));for(let r,l=0,a=o.length;lt;t++)e[t]=s(t*o/180);return{table:e,cos:(t)=>e[l(y.boundAngle(y.toDegree(t)))]}}static sinTable(){let e=new Float64Array(360);for(let t=0;360>t;t++)e[t]=a(t*o/180);return{table:e,sin:(t)=>e[l(y.boundAngle(y.toDegree(t)))]}}}i.Geom=y;class x{static linear(e,t=1){return t*e}static quadraticIn(e,t=1){return t*e*e}static quadraticOut(e,t=1){return-t*e*(e-2)}static quadraticInOut(e,t=1){let n=2*e;return .5>e?4*(t/2*e*e):-t/2*((n-1)*(n-3)-1)}static cubicIn(e,t=1){return t*e*e*e}static cubicOut(e,t=1){let n=e-1;return t*(n*n*n+1)}static cubicInOut(e,t=1){let n=2*e;return .5>e?t/2*n*n*n:t/2*((n-2)*(n-2)*(n-2)+2)}static exponentialIn(n,t=1,o=.25){return t*e(n,1/o)}static exponentialOut(n,t=1,o=.25){return t*e(n,o)}static sineIn(e,t=1){return-t*s(e*g.Const.half_pi)+t}static sineOut(e,t=1){return t*a(e*g.Const.half_pi)}static sineInOut(e,t=1){return-t/2*(s(o*e)-1)}static cosineApprox(e,t=1){let n=e*e,o=n*n;return t*(4*(o*n)/9-17*o/9+22*n/9)}static circularIn(e,t=1){return-t*(n(1-e*e)-1)}static circularOut(e,t=1){let o=e-1;return t*n(1-o*o)}static circularInOut(e,t=1){let o=2*e;return .5>e?-t/2*(n(1-o*o)-1):t/2*(n(1-(o-2)*(o-2))+1)}static elasticIn(n,t=1,o=.7){let i=n-1,r=1.5707963267948966*(o/g.Const.two_pi);return t*(-e(2,10*i)*a((i-r)*g.Const.two_pi/o))}static elasticOut(n,t=1,o=.7){let i=1.5707963267948966*(o/g.Const.two_pi);return t*(e(2,-10*n)*a((n-i)*g.Const.two_pi/o))+t}static elasticInOut(n,t=1,o=.6){let i=2*n,r=1.5707963267948966*(o/g.Const.two_pi);return .5>n?(i-=1,t*(-.5*(e(2,10*i)*a((i-r)*g.Const.two_pi/o)))):(i-=1,t*(.5*(e(2,-10*i)*a((i-r)*g.Const.two_pi/o)))+t)}static bounceIn(e,t=1){return t-x.bounceOut(1-e,t)}static bounceOut(e,t=1){return e<1/2.75?t*(7.5625*e*e):e<2/2.75?(e-=1.5/2.75,t*(7.5625*e*e+.75)):e<2.5/2.75?(e-=2.25/2.75,t*(7.5625*e*e+.9375)):(e-=2.625/2.75,t*(7.5625*e*e+.984375))}static bounceInOut(e,t=1){return .5>e?x.bounceIn(2*e,t)/2:x.bounceOut(2*e-1,t)/2+t/2}static sigmoid(e,t=1,n=10){return t/(1+h(-(n*(e-.5))))}static logSigmoid(e,t=1,n=.7){n=u(g.Const.epsilon,_(1-g.Const.epsilon,n)),n=1/(1-n);let o=1/(1+h(-2*((e-.5)*n))),i=1/(1+h(n)),r=1/(1+h(-n));return t*(o-i)/(r-i)}static seat(n,t=1,o=.5){return .5>n?t*e(2*n,1-o)/2:t*(1-e(2*(1-n),1-o)/2)}static quadraticBezier(e,t=1,o=[.05,.95]){let i='number'==typeof o?o:o[0],r='number'==typeof o?.5:o[1],l=1-2*i;0===l&&(l=g.Const.epsilon);let a=(n(i*i+l*e)-i)/l;return t*((1-2*r)*(a*a)+2*r*a)}static cubicBezier(e,t=1,n=[.1,.7],o=[.9,.2]){let i=new m.Group(new m.Pt(0,0),new m.Pt(n),new m.Pt(o),new m.Pt(1,1));return t*p.Curve.bezierStep(new m.Pt(e*e*e,e*e,e,1),p.Curve.controlPoints(i)).y}static quadraticTarget(e,t=1,n=[.2,.35]){let o=_(1-g.Const.epsilon,u(g.Const.epsilon,n[0])),i=_(1,u(0,n[1])),r=(1-i)/(1-o)-i/o;return t*_(1,u(0,r*(e*e)-(r*(o*o)-i)/o*e))}static cliff(e,t=1,n=.5){return e>n?t:0}static step(e,n,o,t,...i){let r=1/n,a=l(o/r)*r;return e(a,t,...i)}}i.Shaping=x;class b{constructor(e){this._dims=0,this._source=m.Group.fromPtArray(e),this.calc()}get max(){return this._max.clone()}get min(){return this._min.clone()}get magnitude(){return this._mag.clone()}calc(){if(this._source){let e=this._source[0].length;this._dims=e;let t=new m.Pt(e),n=new m.Pt(e),o=new m.Pt(e);for(let r=0;ru(e,t.length),0):e[0].length;for(let l=0;le.length)throw new Error('Cannot create a Bound from a group that has less than 2 Pt');return new i(e[0],e[e.length-1])}init(){if(this.p1&&(this._size=this.p1.clone(),this._inited=!0),this.p1&&this.p2){let e=this.p1,t=this.p2;this.topLeft=e.$min(t),this._bottomRight=e.$max(t),this._updateSize(),this._inited=!0}}clone(){return new i(this._topLeft.clone(),this._bottomRight.clone())}_updateSize(){this._size=this._bottomRight.$subtract(this._topLeft).abs(),this._updateCenter()}_updateCenter(){this._center=this._size.$multiply(.5).add(this._topLeft)}_updatePosFromTop(){this._bottomRight=this._topLeft.$add(this._size),this._updateCenter()}_updatePosFromBottom(){this._topLeft=this._bottomRight.$subtract(this._size),this._updateCenter()}_updatePosFromCenter(){let e=this._size.$multiply(.5);this._topLeft=this._center.$subtract(e),this._bottomRight=this._center.$add(e)}get size(){return new o.Pt(this._size)}set size(e){this._size=new o.Pt(e),this._updatePosFromTop()}get center(){return new o.Pt(this._center)}set center(e){this._center=new o.Pt(e),this._updatePosFromCenter()}get topLeft(){return new o.Pt(this._topLeft)}set topLeft(e){this._topLeft=new o.Pt(e),this[0]=this._topLeft,this._updateSize()}get bottomRight(){return new o.Pt(this._bottomRight)}set bottomRight(e){this._bottomRight=new o.Pt(e),this[1]=this._bottomRight,this._updateSize()}get width(){return 0this._time.end&&(cancelAnimationFrame(this._animID),this._playing=!1)}pause(e=!1){return this._pause=!e||!this._pause,this}resume(){return this._pause=!1,this}stop(e=0){return this._time.end=e,this}playOnce(e=5e3){return this.play(),this.stop(e),this}render(e){return this._renderFunc&&this._renderFunc(e,this),this}set customRendering(e){this._renderFunc=e}get customRendering(){return this._renderFunc}get isPlaying(){return this._playing}get outerBound(){return this.bound.clone()}get innerBound(){return new o.Bound(r.Pt.make(this.size.length,0),this.size.clone())}get size(){return this.bound.size.clone()}get center(){return this.size.divide(2)}get width(){return this.bound.width}get height(){return this.bound.height}}t.Space=l;class a extends l{constructor(){super(...arguments),this._pressed=!1,this._dragged=!1,this._hasMouse=!1,this._hasTouch=!1}get pointer(){let e=this._pointer.clone();return e.id=this._pointer.id,e}bindCanvas(e,t){this._canvas.addEventListener(e,t)}unbindCanvas(e,t){this._canvas.removeEventListener(e,t)}bindMouse(e=!0){return e?(this.bindCanvas('mousedown',this._mouseDown.bind(this)),this.bindCanvas('mouseup',this._mouseUp.bind(this)),this.bindCanvas('mouseover',this._mouseOver.bind(this)),this.bindCanvas('mouseout',this._mouseOut.bind(this)),this.bindCanvas('mousemove',this._mouseMove.bind(this)),this._hasMouse=!0):(this.unbindCanvas('mousedown',this._mouseDown.bind(this)),this.unbindCanvas('mouseup',this._mouseUp.bind(this)),this.unbindCanvas('mouseover',this._mouseOver.bind(this)),this.unbindCanvas('mouseout',this._mouseOut.bind(this)),this.unbindCanvas('mousemove',this._mouseMove.bind(this)),this._hasMouse=!1),this}bindTouch(e=!0){return e?(this.bindCanvas('touchstart',this._mouseDown.bind(this)),this.bindCanvas('touchend',this._mouseUp.bind(this)),this.bindCanvas('touchmove',this._touchMove.bind(this)),this.bindCanvas('touchcancel',this._mouseOut.bind(this)),this._hasTouch=!0):(this.unbindCanvas('touchstart',this._mouseDown.bind(this)),this.unbindCanvas('touchend',this._mouseUp.bind(this)),this.unbindCanvas('touchmove',this._touchMove.bind(this)),this.unbindCanvas('touchcancel',this._mouseOut.bind(this)),this._hasTouch=!1),this}touchesToPoints(e,n='touches'){if(!e||!e[n])return[];let o=[];for(var l=0;le.length*r}static truncate(e,t,n,o=''){let i=l(t.length*_(1,n/e(t)));return it?_(i,n):0{e.parentNode.removeChild(e)}),super.remove(e)}removeAll(){return this._container.innerHTML='',super.removeAll()}}t.HTMLSpace=d;class c extends i.VisualForm{constructor(e){super(),this._ctx={group:null,groupID:'pts',groupCount:0,currentID:'pts0',currentClass:'',style:{filled:!0,stroked:!0,background:'#f03',"border-color":'#fff',color:'#000',"border-width":'1px',"border-radius":'0',"border-style":'solid',position:'absolute',top:0,left:0,width:0,height:0},font:'11px sans-serif',fontSize:11,fontFamily:'sans-serif'},this._ready=!1,this._space=e,this._space.add({start:()=>{this._ctx.group=this._space.element,this._ctx.groupID='pts_dom_'+c.groupID++,this._ready=!0}})}get space(){return this._space}styleTo(e,t,n=''){if(this._ctx.style[e]===void 0)throw new Error(`${e} style property doesn't exist`);this._ctx.style[e]=`${t}${n}`}fill(e){return'boolean'==typeof e?(this.styleTo('filled',e),!e&&this.styleTo('background','transparent')):(this.styleTo('filled',!0),this.styleTo('background',e)),this}stroke(e,t){return'boolean'==typeof e?(this.styleTo('stroked',e),!e&&this.styleTo('border-width',0)):(this.styleTo('stroked',!0),this.styleTo('border-color',e),this.styleTo('border-width',(t||1)+'px')),this}fillText(e){return this.styleTo('color',e),this}cls(e){return this._ctx.currentClass='boolean'==typeof e?'':e,this}font(e,t,n,o,i){return'number'==typeof e?(this._font.size=e,i&&(this._font.face=i),t&&(this._font.weight=t),n&&(this._font.style=n),o&&(this._font.lineHeight=o),this._ctx.font=this._font.value):this._font=e,this}reset(){return this._ctx.style={filled:!0,stroked:!0,background:'#f03',"border-color":'#fff',"border-width":'1px'},this._font=new i.Font(14,'sans-serif'),this._ctx.font=this._font.value,this}updateScope(e,t){return this._ctx.group=t,this._ctx.groupID=e,this._ctx.groupCount=0,this.nextID(),this._ctx}scope(e){if(!e||null==e.animateID)throw new Error('item not defined or not yet added to Space');return this.updateScope(c.scopeID(e),this.space.element)}nextID(){return this._ctx.groupCount++,this._ctx.currentID=`${this._ctx.groupID}-${this._ctx.groupCount}`,this._ctx.currentID}static getID(e){return e.currentID||`p-${c.domID++}`}static scopeID(e){return`item-${e.animateID}`}static style(e,t){let n=[];for(let o in t.filled||n.push('background: none'),t.stroked||n.push('border: none'),t)if(t.hasOwnProperty(o)&&'filled'!=o&&'stroked'!=o){let e=t[o];if(e)if(!t.filled&&0===o.indexOf('background'))continue;else if(!t.stroked&&0===o.indexOf('border-width'))continue;else n.push(`${o}: ${e}`)}return d.setAttr(e,{style:n.join(';')})}static rectStyle(e,t,n){return e.style.left=t[0]+'px',e.style.top=t[1]+'px',e.style.width=n[0]+'px',e.style.height=n[1]+'px',e}static point(e,t,n=5,o='square'){return'circle'===o?c.circle(e,t,n):c.square(e,t,n)}point(e,t=5,n='square'){return this.nextID(),'circle'==n&&this.styleTo('border-radius','100%'),c.point(this._ctx,e,t,n),this}static circle(e,t,n=10){let o=d.htmlElement(e.group,'div',c.getID(e));return d.setAttr(o,{class:`pts-form pts-circle ${e.currentClass}`}),c.rectStyle(e,new a.Pt(t).$subtract(n),new a.Pt(2*n,2*n)),c.style(o,e.style),o}circle(e){return this.nextID(),this.styleTo('border-radius','100%'),c.circle(this._ctx,e[0],e[1][0]),this}static square(e,t,n){let o=d.htmlElement(e.group,'div',c.getID(e));return d.setAttr(o,{class:`pts-form pts-square ${e.currentClass}`}),c.rectStyle(e,new a.Pt(t).$subtract(n),new a.Pt(2*n,2*n)),c.style(o,e.style),o}square(e,t){return this.nextID(),c.square(this._ctx,e,t),this}static rect(e,t){if(this._checkSize(t)){let n=d.htmlElement(e.group,'div',c.getID(e));return d.setAttr(n,{class:`pts-form pts-rect ${e.currentClass}`}),c.rectStyle(e,t[0],t[1]),c.style(n,e.style),n}}rect(e){return this.nextID(),this.styleTo('border-radius','0'),c.rect(this._ctx,e),this}static text(e,t,n){let o=d.htmlElement(e.group,'div',c.getID(e));return d.setAttr(o,{position:'absolute',class:`pts-form pts-text ${e.currentClass}`,left:t[0],top:t[1]}),o.textContent=n,c.style(o,e.style),o}text(e,t){return this.nextID(),c.text(this._ctx,e,t),this}log(e){return this.fill('#000').stroke('#fff',.5).text([10,14],e),this}arc(){return l.Util.warn('arc is not implemented in HTMLForm'),this}line(){return l.Util.warn('line is not implemented in HTMLForm'),this}polygon(){return l.Util.warn('polygon is not implemented in HTMLForm'),this}}c.groupID=0,c.domID=0,t.HTMLForm=c},function(e,t,n){'use strict';Object.defineProperty(t,'__esModule',{value:!0});const o=n(5),i=n(11),r=n(13),l=n(6),a=n(4),s=n(3),d=n(2),c=n(0),u=n(7),p=n(14),_=n(1),h=n(9),g=n(15),m=n(8);e.exports=Object.assign({namespace:(t)=>{let n=e.exports;for(let e in n)'namespace'!=e&&(t[e]=n[e])}},o,i,r,l,a,d,s,c,u,_,p,h,g,m)},function(e,t,n){'use strict';Object.defineProperty(t,'__esModule',{value:!0});const o=n(7),i=n(6),r=n(5),a=n(0),s=n(1),d=n(8),c=n(2);class p extends o.MultiTouchSpace{constructor(e,t){super(),this._pixelScale=1,this._autoResize=!0,this._bgcolor='#e1e9f0',this._offscreen=!1,this._initialResize=!1;var n=null,o=!1;this.id='pt',e instanceof Element?(n=e,this.id='pts_existing_space'):(n=document.querySelector(e),o=!0,this.id=e),n?'canvas'==n.nodeName.toLowerCase()?(this._canvas=n,this._container=n.parentElement,this._autoResize=!1):(this._container=n,this._canvas=this._createElement('canvas',this.id+'_canvas'),this._container.appendChild(this._canvas),this._initialResize=!0):(this._container=this._createElement('div',this.id+'_container'),this._canvas=this._createElement('canvas',this.id),this._container.appendChild(this._canvas),document.body.appendChild(this._container),o=!1),setTimeout(this._ready.bind(this,t),100),this._ctx=this._canvas.getContext('2d')}_createElement(e='div',t){let n=document.createElement(e);return n.setAttribute('id',t),n}_ready(e){if(!this._container)throw new Error(`Cannot initiate #${this.id} element`);for(let t in this._isReady=!0,this._resizeHandler(null),this.clear(this._bgcolor),this._canvas.dispatchEvent(new Event('ready')),this.players)this.players.hasOwnProperty(t)&&this.players[t].start&&this.players[t].start(this.bound.clone(),this);this._pointer=this.center,this._initialResize=!1,e&&e(this.bound,this._canvas)}setup(e){if(e.bgcolor&&(this._bgcolor=e.bgcolor),this.autoResize=void 0!=e.resize&&e.resize,!1!==e.retina){let e=window.devicePixelRatio||1,t=this._ctx.webkitBackingStorePixelRatio||this._ctx.mozBackingStorePixelRatio||this._ctx.msBackingStorePixelRatio||this._ctx.oBackingStorePixelRatio||this._ctx.backingStorePixelRatio||1;this._pixelScale=e/t}return e.offscreen?(this._offscreen=!0,this._offCanvas=this._createElement('canvas',this.id+'_offscreen'),this._offCtx=this._offCanvas.getContext('2d')):this._offscreen=!1,this}set autoResize(e){this._autoResize=e,e?window.addEventListener('resize',this._resizeHandler.bind(this)):window.removeEventListener('resize',this._resizeHandler.bind(this))}get autoResize(){return this._autoResize}resize(e,t){for(let n in this.bound=e,this._canvas.width=this.bound.size.x*this._pixelScale,this._canvas.height=this.bound.size.y*this._pixelScale,this._canvas.style.width=l(this.bound.size.x)+'px',this._canvas.style.height=l(this.bound.size.y)+'px',this._offscreen&&(this._offCanvas.width=this.bound.size.x*this._pixelScale,this._offCanvas.height=this.bound.size.y*this._pixelScale),1!=this._pixelScale&&(this._ctx.scale(this._pixelScale,this._pixelScale),this._ctx.translate(.5,.5),this._offscreen&&(this._offCtx.scale(this._pixelScale,this._pixelScale),this._offCtx.translate(.5,.5))),this.players)if(this.players.hasOwnProperty(n)){let e=this.players[n];e.resize&&e.resize(this.bound,t)}return this.render(this._ctx),t&&!this.isPlaying&&this.playOnce(0),this}_resizeHandler(e){let t=this._autoResize||this._initialResize?this._container.getBoundingClientRect():this._canvas.getBoundingClientRect();if(t){let n=r.Bound.fromBoundingRect(t);n.center=n.center.add(window.pageXOffset,window.pageYOffset),this.resize(n,e)}}set background(e){this._bgcolor=e}get background(){return this._bgcolor}get pixelScale(){return this._pixelScale}get hasOffscreen(){return this._offscreen}get offscreenCtx(){return this._offCtx}get offscreenCanvas(){return this._offCanvas}getForm(){return new _(this)}get element(){return this._canvas}get parent(){return this._container}get ready(){return this._isReady}get ctx(){return this._ctx}clear(e){e&&(this._bgcolor=e);let t=this._ctx.fillStyle;return this._bgcolor&&'transparent'!=this._bgcolor?(this._ctx.fillStyle=this._bgcolor,this._ctx.fillRect(-1,-1,this._canvas.width+1,this._canvas.height+1)):this._ctx.clearRect(-1,-1,this._canvas.width+1,this._canvas.height+1),this._ctx.fillStyle=t,this}clearOffscreen(e){return this._offscreen&&(e?(this._offCtx.fillStyle=e,this._offCtx.fillRect(-1,-1,this._canvas.width+1,this._canvas.height+1)):this._offCtx.clearRect(-1,-1,this._offCanvas.width+1,this._offCanvas.height+1)),this}playItems(e){this._isReady&&(this._ctx.save(),this._offscreen&&this._offCtx.save(),super.playItems(e),this._ctx.restore(),this._offscreen&&this._offCtx.restore(),this.render(this._ctx))}}t.CanvasSpace=p;class _ extends i.VisualForm{constructor(e){super(),this._style={fillStyle:'#f03',strokeStyle:'#fff',lineWidth:1,lineJoin:'bevel',lineCap:'butt'},this._space=e,this._space.add({start:()=>{this._ctx=this._space.ctx,this._ctx.fillStyle=this._style.fillStyle,this._ctx.strokeStyle=this._style.strokeStyle,this._ctx.lineJoin='bevel',this._ctx.font=this._font.value,this._ready=!0}})}get space(){return this._space}useOffscreen(e=!0,t=!1){return t&&this._space.clearOffscreen('string'==typeof t?t:null),this._ctx=this._space.hasOffscreen&&e?this._space.offscreenCtx:this._space.ctx,this}renderOffscreen(e=[0,0]){this._space.hasOffscreen&&this._space.ctx.drawImage(this._space.offscreenCanvas,e[0],e[1],this._space.width,this._space.height)}fill(e){return'boolean'==typeof e?this.filled=e:(this.filled=!0,this._style.fillStyle=e,this._ctx.fillStyle=e),this}stroke(e,t,n,o){return'boolean'==typeof e?this.stroked=e:(this.stroked=!0,this._style.strokeStyle=e,this._ctx.strokeStyle=e,t&&(this._ctx.lineWidth=t,this._style.lineWidth=t),n&&(this._ctx.lineJoin=n,this._style.lineJoin=n),o&&(this._ctx.lineCap=o,this._style.lineCap=o)),this}font(e,t,n,o,i){return'number'==typeof e?(this._font.size=e,i&&(this._font.face=i),t&&(this._font.weight=t),n&&(this._font.style=n),o&&(this._font.lineHeight=o),this._ctx.font=this._font.value):this._font=e,this._estimateTextWidth&&this.fontWidthEstimate(!0),this}fontWidthEstimate(e=!0){return this._estimateTextWidth=e?d.Typography.textWidthEstimator((e)=>this._ctx.measureText(e).width):void 0,this}getTextWidth(e){return this._estimateTextWidth?this._estimateTextWidth(e):this._ctx.measureText(e+' .').width}_textTruncate(e,t,n=''){return d.Typography.truncate(this.getTextWidth.bind(this),e,t,n)}_textAlign(e,t,n,o){o||(o=c.Rectangle.center(e));var i=e[0][0];'end'==this._ctx.textAlign||'right'==this._ctx.textAlign?i=e[1][0]:('center'==this._ctx.textAlign||'middle'==this._ctx.textAlign)&&(i=o[0]);var r=o[1];return'top'==t||'start'==t?r=e[0][1]:('end'==t||'bottom'==t)&&(r=e[1][1]),n?new a.Pt(i+n[0],r+n[1]):new a.Pt(i,r)}reset(){for(let e in this._style)this._style.hasOwnProperty(e)&&(this._ctx[e]=this._style[e]);return this._font=new i.Font,this._ctx.font=this._font.value,this}_paint(){this._filled&&this._ctx.fill(),this._stroked&&this._ctx.stroke()}point(e,t=5,n='square'){if(e){if(!_[n])throw new Error(`${n} is not a static function of CanvasForm`);return _[n](this._ctx,e,t),this._paint(),this}}static circle(e,t,n=10){t&&(e.beginPath(),e.arc(t[0],t[1],n,0,s.Const.two_pi,!1),e.closePath())}circle(e){return _.circle(this._ctx,e[0],e[1][0]),this._paint(),this}static arc(e,t,n,o,i,r){t&&(e.beginPath(),e.arc(t[0],t[1],n,o,i,r))}arc(e,t,n,o,i){return _.arc(this._ctx,e,t,n,o,i),this._paint(),this}static square(e,t,n){if(t){let o=t[0]-n,i=t[1]-n,r=t[0]+n,l=t[1]+n;e.beginPath(),e.moveTo(o,i),e.lineTo(o,l),e.lineTo(r,l),e.lineTo(r,i),e.closePath()}}square(e,t){return _.square(this._ctx,e,t),this._paint(),this}static line(e,t){if(!(2>t.length)){e.beginPath(),e.moveTo(t[0][0],t[0][1]);for(let n=1,o=t.length;nt.length)){e.beginPath(),e.moveTo(t[0][0],t[0][1]);for(let n=1,o=t.length;nt.length||(e.beginPath(),e.moveTo(t[0][0],t[0][1]),e.lineTo(t[0][0],t[1][1]),e.lineTo(t[1][0],t[1][1]),e.lineTo(t[1][0],t[0][1]),e.closePath())}rect(e){return _.rect(this._ctx,e),this._paint(),this}static text(e,t,n,o){t&&e.fillText(n,t[0],t[1],o)}text(e,t,n){return _.text(this._ctx,e,t,n),this}textBox(e,n,o='middle',i='',r=!0){r&&(this._ctx.textBaseline=o);let l=c.Rectangle.size(e),a=this._textTruncate(n,l[0],i);return this.text(this._textAlign(e,o),a[0]),this}paragraphBox(e,t,n=1.2,o='top',i=!0){let r=c.Rectangle.size(e);this._ctx.textBaseline='top';let l=this._font.size*n,s=(e,n=[],o=0)=>{if(!e)return n;if(i&&o*l>r[1]-2*l)return n;if(1e4=d||a[1]===e.length)&&(d=void 0);let c=a[0].substr(0,d);return n.push(c),0>=a[1]||a[1]===e.length?n:s(e.substr(d||a[1]),n,o+1)},d=s(t),p=d.length*l,_=e;if('middle'==o||'center'==o){let t=(r[1]-p)/2;i&&(t=u(0,t)),_=new a.Group(e[0].$add(0,t),e[1].$subtract(0,t))}else _='bottom'==o?new a.Group(e[0].$add(0,r[1]-p),e[1]):new a.Group(e[0],e[0].$add(r[0],p));let h=c.Rectangle.center(_);for(let r=0,a=d.length;re&&(e*=65536),e=l(e),256>e&&(e|=e<<8);for(let t,n=0;255>n;n++)t=1&n?p[n]^255&e:p[n]^255&e>>8,this.perm[n]=this.perm[n+256]=t}noise2D(){let e=l(this._n[0])%255,t=l(this._n[1])%255,n=this._n[0]%255-e,o=this._n[1]%255-t,i=d.Vec.dot(c[(e+this.perm[t])%12],[n,o,0]),r=d.Vec.dot(c[(e+this.perm[t+1])%12],[n,o-1,0]),s=d.Vec.dot(c[(e+1+this.perm[t])%12],[n-1,o,0]),u=d.Vec.dot(c[(e+1+this.perm[t+1])%12],[n-1,o-1,0]),p=(e)=>e*e*e*(e*(6*e-15)+10),_=p(n);return a.Num.lerp(a.Num.lerp(i,s,_),a.Num.lerp(r,u,_),p(o))}}t.Noise=h;class i extends o.Group{constructor(){super(...arguments),this._mesh=[]}delaunay(e=!0){if(3>this.length)return[];this._mesh=[];let t=this.length,n=[];for(let o=0;othis[t][0]-this[e][0]);let o=this.slice(),r=this._superTriangle();o=o.concat(r);let l=[this._circum(t,t+1,t+2,r)],a=[],d=[];for(let t=0,r=n.length;tn*n){a.push(t),d.push(t.triangle),l.splice(c,1);continue}i[0]*i[0]+i[1]*i[1]-n*n>s.Const.epsilon||(r.push(t.i,t.j,t.j,t.k,t.k,t.i),l.splice(c,1))}for(i._dedupe(r),c=r.length;1=e.length){let t=(t)=>e[t]||'F';e=`${t(0)}${t(0)}${t(1)}${t(1)}${t(2)}${t(2)}`}let t=1;8===e.length&&(t=e.substr(6)&&1,e=e.substring(0,6));let n=parseInt(e,16);return new m(n>>16,255&n>>8,255&n,t)}static rgb(...e){return m.from(...e).toMode('rgb')}static hsl(...e){return m.from(...e).toMode('hsl')}static hsb(...e){return m.from(...e).toMode('hsb')}static lab(...e){return m.from(...e).toMode('lab')}static lch(...e){return m.from(...e).toMode('lch')}static luv(...e){return m.from(...e).toMode('luv')}static xyz(...e){return m.from(...e).toMode('xyz')}static maxValues(e){return m.ranges[e].zipSlice(1).$take([0,1,2])}get hex(){return this.toString('hex')}get rgb(){return this.toString('rgb')}get rgba(){return this.toString('rgba')}clone(){let e=new m(this);return e.toMode(this._mode),e}toMode(e,t=!1){if(t){let t=this._mode.toUpperCase()+'to'+e.toUpperCase();if(m[t])this.to(m[t](this,this._isNorm,this._isNorm));else throw new Error('Cannot convert color with '+t)}return this._mode=e,this}get mode(){return this._mode}get r(){return this[0]}set r(e){this[0]=e}get g(){return this[1]}set g(e){this[1]=e}get b(){return this[1]}set b(e){this[2]=e}get h(){return'lch'==this._mode?this[2]:this[0]}set h(e){let t='lch'==this._mode?2:0;this[t]=e}get s(){return this[1]}set s(e){this[1]=e}get l(){return'hsl'==this._mode?this[2]:this[0]}set l(e){let t='hsl'==this._mode?2:0;this[t]=e}get a(){return this[1]}set a(e){this[1]=e}get c(){return this[1]}set c(e){this[1]=e}get u(){return this[1]}set u(e){this[1]=e}get v(){return this[1]}set v(e){this[2]=e}get alpha(){return 3n;n++)this[n]=e?p.Num.mapToRange(this[n],t[n][0],t[n][1],0,1):p.Num.mapToRange(this[n],0,1,t[n][0],t[n][1]);return this._isNorm=e,this}$normalize(e=!0){return this.clone().normalize(e)}toString(e='mode'){if('hex'==e){let e=(e)=>{let t=l(e).toString(16);return 2>t.length?'0'+t:t};return`#${e(this[0])}${e(this[1])}${e(this[2])}`}return'rgba'==e?`rgba(${l(this[0])},${l(this[1])},${l(this[2])},${this.alpha}`:'rgb'==e?`rgb(${l(this[0])},${l(this[1])},${l(this[2])}`:`${this._mode}(${this[0]},${this[1]},${this[2]},${this.alpha})`}static RGBtoHSL(e,t=!1,n=!1){let[o,i,r]=t?e:e.$normalize(),a=u(o,i,r),c=_(o,i,r),p=(a+c)/2,h=p,s=p;if(a==c)p=0,h=0;else{let e=a-c;h=.5=r?r*(1+i):r+i-r*i,a=2*r-l,s=(e)=>(e=0>e?e+1:16*e?a+6*((l-a)*e):1>2*e?l:2>3*e?a+6*((l-a)*(2/3-e)):a),d=n?1:255;return m.rgb(d*s(o+1/3),d*s(o),d*s(o-1/3),e.alpha)}static RGBtoHSB(e,t=!1,n=!1){let[o,i,r]=t?e:e.$normalize(),l=u(o,i,r),a=_(o,i,r),c=l-a,d=0,p=0===l?0:c/l;return l!=a&&(l===o?d=(i-r)/c+(il;l++)r[l]=.04045r;r++)a[r]=0>a[r]?0:.0031308.008856{let t=e*e*e;return .008856{e.parentNode.removeChild(e)}),super.remove(e)}removeAll(){return this._container.innerHTML='',super.removeAll()}}t.SVGSpace=c;class u extends o.VisualForm{constructor(e){super(),this._ctx={group:null,groupID:'pts',groupCount:0,currentID:'pts0',currentClass:'',style:{filled:!0,stroked:!0,fill:'#f03',stroke:'#fff',"stroke-width":1,"stroke-linejoin":'bevel',"stroke-linecap":'sqaure'},font:'11px sans-serif',fontSize:11,fontFamily:'sans-serif'},this._ready=!1,this._space=e,this._space.add({start:()=>{this._ctx.group=this._space.element,this._ctx.groupID='pts_svg_'+u.groupID++,this._ready=!0}})}get space(){return this._space}styleTo(e,t){if(this._ctx.style[e]===void 0)throw new Error(`${e} style property doesn't exist`);this._ctx.style[e]=t}fill(e){return'boolean'==typeof e?this.styleTo('filled',e):(this.styleTo('filled',!0),this.styleTo('fill',e)),this}stroke(e,t,n,o){return'boolean'==typeof e?this.styleTo('stroked',e):(this.styleTo('stroked',!0),this.styleTo('stroke',e),t&&this.styleTo('stroke-width',t),n&&this.styleTo('stroke-linejoin',n),o&&this.styleTo('stroke-linecap',o)),this}cls(e){return this._ctx.currentClass='boolean'==typeof e?'':e,this}font(e,t,n,o,i){return'number'==typeof e?(this._font.size=e,i&&(this._font.face=i),t&&(this._font.weight=t),n&&(this._font.style=n),o&&(this._font.lineHeight=o),this._ctx.font=this._font.value):this._font=e,this}reset(){return this._ctx.style={filled:!0,stroked:!0,fill:'#f03',stroke:'#fff',"stroke-width":1,"stroke-linejoin":'bevel',"stroke-linecap":'sqaure'},this._font=new o.Font(14,'sans-serif'),this._ctx.font=this._font.value,this}updateScope(e,t){return this._ctx.group=t,this._ctx.groupID=e,this._ctx.groupCount=0,this.nextID(),this._ctx}scope(e){if(!e||null==e.animateID)throw new Error('item not defined or not yet added to Space');return this.updateScope(u.scopeID(e),this.space.element)}nextID(){return this._ctx.groupCount++,this._ctx.currentID=`${this._ctx.groupID}-${this._ctx.groupCount}`,this._ctx.currentID}static getID(e){return e.currentID||`p-${u.domID++}`}static scopeID(e){return`item-${e.animateID}`}static style(e,t){let n=[];for(let o in t.filled||n.push('fill: none'),t.stroked||n.push('stroke: none'),t)if(t.hasOwnProperty(o)&&'filled'!=o&&'stroked'!=o){let e=t[o];if(e)if(!t.filled&&0===o.indexOf('fill'))continue;else if(!t.stroked&&0===o.indexOf('stroke'))continue;else n.push(`${o}: ${e}`)}return s.DOMSpace.setAttr(e,{style:n.join(';')})}static point(e,t,n=5,o='square'){return'circle'===o?u.circle(e,t,n):u.square(e,t,n)}point(e,t=5,n='square'){return this.nextID(),u.point(this._ctx,e,t,n),this}static circle(e,t,n=10){let o=c.svgElement(e.group,'circle',u.getID(e));return s.DOMSpace.setAttr(o,{cx:t[0],cy:t[1],r:n,class:`pts-svgform pts-circle ${e.currentClass}`}),u.style(o,e.style),o}circle(e){return this.nextID(),u.circle(this._ctx,e[0],e[1][0]),this}static arc(e,t,n,o,a,p){let _=c.svgElement(e.group,'path',u.getID(e));const h=new l.Pt(t).toAngle(o,n,!0),g=new l.Pt(t).toAngle(a,n,!0),m=i.Geom.boundAngle(a)-i.Geom.boundAngle(o);let f=!!(m>r.Const.pi);p&&(f=!f);const y=p?'0':'1',x=`M ${h[0]} ${h[1]} A ${n} ${n} 0 ${f?'1':'0'} ${y} ${g[0]} ${g[1]}`;return s.DOMSpace.setAttr(_,{d:x,class:`pts-svgform pts-arc ${e.currentClass}`}),u.style(_,e.style),_}arc(e,t,n,o,i){return this.nextID(),u.arc(this._ctx,e,t,n,o,i),this}static square(e,t,n){let o=c.svgElement(e.group,'rect',u.getID(e));return s.DOMSpace.setAttr(o,{x:t[0]-n,y:t[1]-n,width:2*n,height:2*n,class:`pts-svgform pts-square ${e.currentClass}`}),u.style(o,e.style),o}square(e,t){return this.nextID(),u.square(this._ctx,e,t),this}static line(e,t){if(this._checkSize(t)){if(2e+`${t[0]},${t[1]} `,'');return s.DOMSpace.setAttr(o,{points:i,class:`pts-svgform pts-polygon ${e.currentClass}`}),u.style(o,e.style),o}}static polygon(e,t){return u._poly(e,t,!0)}polygon(e){return this.nextID(),u.polygon(this._ctx,e),this}static rect(e,t){if(this._checkSize(t)){let n=c.svgElement(e.group,'rect',u.getID(e)),o=l.Group.fromArray(t).boundingBox(),i=a.Rectangle.size(o);return s.DOMSpace.setAttr(n,{x:o[0][0],y:o[0][1],width:i[0],height:i[1],class:`pts-svgform pts-rect ${e.currentClass}`}),u.style(n,e.style),n}}rect(e){return this.nextID(),u.rect(this._ctx,e),this}static text(e,t,n){let o=c.svgElement(e.group,'text',u.getID(e));return s.DOMSpace.setAttr(o,{"pointer-events":'none',x:t[0],y:t[1],dx:0,dy:0,class:`pts-svgform pts-text ${e.currentClass}`}),o.textContent=n,u.style(o,e.style),o}text(e,t){return this.nextID(),u.text(this._ctx,e,t),this}log(e){return this.fill('#000').stroke('#fff',.5).text([10,14],e),this}}u.groupID=0,u.domID=0,t.SVGForm=u}])})}])}); +(function(e,t){'object'==typeof exports&&'object'==typeof module?module.exports=t():'function'==typeof define&&define.amd?define([],t):'object'==typeof exports?exports.Pts=t():e.Pts=t()})('undefined'==typeof self?this:self,function(){return function(e){function t(i){if(n[i])return n[i].exports;var o=n[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,i){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:i})},t.n=function(e){var n=e&&e.__esModule?function(){return e['default']}:function(){return e};return t.d(n,'a',n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p='',t(t.s=0)}([function(e,t){(function(t,n){e.exports=n()})('undefined'==typeof self?this:self,function(){var e=Math.pow,n=Math.sqrt,o=Math.PI,t=Number.MIN_VALUE,i=Number.MAX_VALUE,a=Math.floor,r=Math.atan2,u=Math.max,l=Math.sin,s=Math.cos,_=Math.min,d=Math.abs;return function(e){function t(i){if(n[i])return n[i].exports;var o=n[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,i){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:i})},t.n=function(e){var n=e&&e.__esModule?function(){return e['default']}:function(){return e};return t.d(n,'a',n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p='',t(t.s=10)}([function(e,t,n){'use strict';Object.defineProperty(t,'__esModule',{value:!0});const i=n(1),o=n(3),c=n(5);t.PtBaseArray=Float32Array;class h extends t.PtBaseArray{constructor(...e){1===e.length&&'number'==typeof e[0]?super(e[0]):super(0t)return!1;return!0}to(...e){let t=i.Util.getArgs(e);for(let n=0,i=_(this.length,t.length);ne(t,...n)}ops(e){let t=[];for(let n=0,i=e.length;ne?[-1*e-1,t]:[e,t];return p.prototype.splice.apply(this,n)}segments(e=2,t=1,n=!1){return this.split(e,t,n)}lines(){return this.segments(2,1)}centroid(){return o.Geom.centroid(this)}boundingBox(){return o.Geom.boundingBox(this)}anchorTo(e=0){o.Geom.anchor(this,e,'to')}anchorFrom(e=0){o.Geom.anchor(this,e,'from')}op(e){let t=this;return(...n)=>e(t,...n)}ops(e){let t=[];for(let n=0,i=e.length;nt?i[e]-n[e]:n[e]-i[e])}forEachPt(e,...t){if(!this[0][e])return i.Util.warn(`${e} is not a function of Pt`),this;for(let n=0,i=this.length;ne+t.toString()+' ','')+' ]'}}t.Group=p},function(e,n,r){'use strict';Object.defineProperty(n,'__esModule',{value:!0});const s=r(0);n.Const={xy:'xy',yz:'yz',xz:'xz',xyz:'xyz',horizontal:0,vertical:1,identical:0,right:4,bottom_right:5,bottom:6,bottom_left:7,left:8,top_left:1,top:2,top_right:3,epsilon:1e-4,max:i,min:t,pi:o,two_pi:6.283185307179586,half_pi:1.5707963267948966,quarter_pi:.7853981633974483,one_degree:.017453292519943295,rad_to_deg:57.29577951308232,deg_to_rad:.017453292519943295,gravity:9.81,newton:.10197,gaussian:.3989422804014327};class l{static getArgs(e){if(1>e.length)return[];let t=[],n=Array.isArray(e[0])||ArrayBuffer.isView(e[0]);if('number'==typeof e[0])t=Array.prototype.slice.call(e);else if('object'==typeof e[0]&&!n){let n=['x','y','z','w'],o=e[0];for(let e=0;e=o.length||!(n[e]in o));e++)t.push(o[n[e]])}else n&&(t=[].slice.call(e[0]));return t}static warn(e='error',t=void 0){if('error'==l.warnLevel)throw new Error(e);else'warn'==l.warnLevel&&console.warn(e);return t}static randomInt(e,t=0){return a(Math.random()*e)+t}static split(e,t,n,o=!1){let r=n||t,s=[];for(let a=0;ae.length))s.push(e.slice(a*r,a*r+t));else if(o){let n=e.slice(a*r);n=n.concat(e.slice(0,(a*r+t)%t)),s.push(n)}else break;return s}static flatten(e,t=!0){let n=t?new s.Group:[];return n.concat.apply(n,e)}static combine(e,t,n){let o=[];for(let r=0,i=e.length;r=e&&(o=t+(o-e)),i&&i(o),o}}static forRange(e,t,n=0,o=1){let r=[];for(let s=n;sc.Util.warn('Group\'s length is less than '+t,e),m=(e,t='')=>c.Util.warn(`Index ${t} is out of bound in Group`,e);class f{static fromAngle(e,t,n){let i=new P.Group(new P.Pt(e),new P.Pt(e));return i[1].toAngle(t,n,!0),i}static slope(e,t){return 0==t[0]-e[0]?void 0:(t[1]-e[1])/(t[0]-e[0])}static intercept(e,t){if(0==t[0]-e[0])return;else{let n=(t[1]-e[1])/(t[0]-e[0]),i=e[1]-n*e[0];return{slope:n,yi:i,xi:0==n?void 0:-i/n}}}static sideOfPt2D(e,t){return(e[1][0]-e[0][0])*(t[1]-e[0][1])-(t[0]-e[0][0])*(e[1][1]-e[0][1])}static collinear(e,t,n,i=.01){let o=new P.Pt(0,0,0).to(e).$subtract(t),r=new P.Pt(0,0,0).to(e).$subtract(n);return o.$cross(r).divide(1e3).equals(new P.Pt(0,0,0),i)}static magnitude(e){return 2<=e.length?e[1].$subtract(e[0]).magnitude():0}static magnitudeSq(e){return 2<=e.length?e[1].$subtract(e[0]).magnitudeSq():0}static perpendicularFromPt(e,t,n=!1){if(!e[0].equals(e[1])){let i=e[0].$subtract(e[1]),o=e[1].$subtract(t),r=o.$subtract(i.$project(o));return n?r:r.$add(t)}}static distanceFromPt(e,t){return f.perpendicularFromPt(e,t,!0).magnitude()}static intersectRay2D(e,t){let n=f.intercept(e[0],e[1]),i=f.intercept(t[0],t[1]),o=e[0],r=t[0];if(n==void 0){if(i==void 0)return;let e=-i.slope*(r[0]-o[0])+r[1];return new P.Pt(o[0],e)}if(void 0==i){let e=-n.slope*(o[0]-r[0])+o[1];return new P.Pt(r[0],e)}if(i.slope!=n.slope){let e=(n.slope*o[0]-i.slope*r[0]+r[1]-o[1])/(n.slope-i.slope),t=n.slope*(e-o[0])+o[1];return new P.Pt(e,t)}return n.yi==i.yi?new P.Pt(o[0],o[1]):void 0}static intersectLine2D(e,t){let n=f.intersectRay2D(e,t);return n&&p.Geom.withinBound(n,e[0],e[1])&&p.Geom.withinBound(n,t[0],t[1])?n:void 0}static intersectLineWithRay2D(e,t){let n=f.intersectRay2D(e,t);return n&&p.Geom.withinBound(n,e[0],e[1])?n:void 0}static intersectPolygon2D(e,t,n=!1){let o=n?f.intersectLineWithRay2D:f.intersectLine2D,r=new P.Group;for(let s=0,i=t.length;sd(t[1]/t[0])?0>r[1]?0:2:0>r[0]?3:1,f.intersectRay2D(o[s],e)}}static marker(e,n,i='arrow',o=!0){let r=o?0:1,s=o?1:0,t=e[r].$subtract(e[s]);if(0===t.magnitudeSq())return new P.Group;t.unit();let a=p.Geom.perpendicular(t).multiply(n[0]).add(e[s]);return'arrow'==i?(a.add(t.$multiply(n[1])),new P.Group(e[s],a[0],a[1])):new P.Group(a[0],a[1])}static toRect(e){return new P.Group(e[0].$min(e[1]),e[0].$max(e[1]))}}t.Line=f;class y{static from(e,t,n){return y.fromTopLeft(e,t,n)}static fromTopLeft(e,t,n){let i='number'==typeof t?[t,n||t]:t;return new P.Group(new P.Pt(e),new P.Pt(e).add(i))}static fromCenter(e,t,n){let i='number'==typeof t?[t/2,(n||t)/2]:new P.Pt(t).divide(2);return new P.Group(new P.Pt(e).subtract(i),new P.Pt(e).add(i))}static toCircle(e){return x.fromRect(e)}static toSquare(e,t=!1){let n=y.size(e),i=t?n.maxValue().value:n.minValue().value;return y.fromCenter(y.center(e),i,i)}static size(e){return e[0].$max(e[1]).subtract(e[0].$min(e[1]))}static center(e){let t=e[0].$min(e[1]),n=e[0].$max(e[1]);return t.add(n.$subtract(t).divide(2))}static corners(e){let t=e[0].$min(e[1]),n=e[0].$max(e[1]);return new P.Group(t,new P.Pt(n.x,t.y),n,new P.Pt(t.x,n.y))}static sides(e){let[t,n,i,o]=y.corners(e);return[new P.Group(t,n),new P.Group(n,i),new P.Group(i,o),new P.Group(o,t)]}static lines(e){return y.sides(e)}static boundingBox(e){let t=c.Util.flatten(e,!1),n=P.Pt.make(2,Number.MAX_VALUE),o=P.Pt.make(2,Number.MIN_VALUE);for(let r=0,i=t.length;re;e++)n[e]=_(n[e],t[r][e]),o[e]=u(o[e],t[r][e]);return new P.Group(n,o)}static polygon(e){return y.corners(e)}static quadrants(e,t){let n=y.corners(e),i=t==void 0?y.center(e):new P.Pt(t);return n.map((e)=>new P.Group(e,i).boundingBox())}static halves(e,t=.5,n=!1){let i=e[0].$min(e[1]),o=e[0].$max(e[1]),r=n?p.Num.lerp(i[1],o[1],t):p.Num.lerp(i[0],o[0],t);return n?[new P.Group(i,new P.Pt(o[0],r)),new P.Group(new P.Pt(i[0],r),o)]:[new P.Group(i,new P.Pt(r,o[1])),new P.Group(new P.Pt(r,i[1]),o)]}static withinBound(e,t){return p.Geom.withinBound(t,e[0],e[1])}static hasIntersectRect2D(e,t,n=!1){return n&&(e=p.Geom.boundingBox(e),t=p.Geom.boundingBox(t)),!(e[0][0]>t[1][0]||t[0][0]>e[1][0])&&!(e[0][1]>t[1][1]||t[0][1]>e[1][1])}static intersectRect2D(e,t){return y.hasIntersectRect2D(e,t)?f.intersectLines2D(y.sides(e),y.sides(t)):new P.Group}}t.Rectangle=y;class x{static fromRect(e,t=!1){let i=0,o=i=y.size(e).minValue().value/2;if(t){let t=y.size(e).maxValue().value/2;i=n(o*o+t*t)}else i=o;return new P.Group(y.center(e),new P.Pt(i,i))}static fromCenter(e,t){return new P.Group(new P.Pt(e),new P.Pt(t,t))}static withinBound(e,t,n=0){let i=e[0].$subtract(t);return i.dot(i)+nd)return new P.Group;else{let e=n(d),o=t[0].$subtract(i.$multiply(-l+e));if(0==d)return new P.Group(o);let r=t[0].$subtract(i.$multiply(-l-e));return new P.Group(o,r)}}static intersectLine2D(e,t){let n=x.intersectRay2D(e,t),o=new P.Group;if(0s+l)return new P.Group;if(rr;r++)n.push(e[0].clone().toAngle(t,e[1][0],!0)),t+=2*o/3;return n}}t.Circle=x;class v{static fromRect(e){let t=e[0].$add(e[1]).divide(2);t.y=e[0][1];let n=e[1].clone();return n.x=e[0][0],new P.Group(t,e[1].clone(),n)}static fromCircle(e){return x.toInnerTriangle(e)}static fromCenter(e,t){return v.fromCircle(x.fromCenter(e,t))}static medial(e){return 3>e.length?h(new P.Group,3):G.midpoints(e,!0)}static oppositeSide(e,t){return 3>e.length?h(new P.Group,3):0===t?P.Group.fromPtArray([e[1],e[2]]):1===t?P.Group.fromPtArray([e[0],e[2]]):P.Group.fromPtArray([e[0],e[1]])}static altitude(e,t){let n=v.oppositeSide(e,t);return 1e.length)return h(void 0,3);let t=v.altitude(e,0),n=v.altitude(e,1);return f.intersectRay2D(t,n)}static incenter(e){if(3>e.length)return h(void 0,3);let t=G.bisector(e,0).add(e[0]),n=G.bisector(e,1).add(e[1]);return f.intersectRay2D(new P.Group(e[0],t),new P.Group(e[1],n))}static incircle(e,t){let n=t?t:v.incenter(e),i=G.area(e),o=G.perimeter(e,!0),s=2*i/o.total;return x.fromCenter(n,s)}static circumcenter(e){let t=v.medial(e),n=[t[0],p.Geom.perpendicular(e[0].$subtract(t[0])).p1.$add(t[0])],i=[t[1],p.Geom.perpendicular(e[1].$subtract(t[1])).p1.$add(t[1])];return f.intersectRay2D(n,i)}static circumcircle(e,t){let n=t?t:v.circumcenter(e),i=e[0].$subtract(n).magnitude();return x.fromCenter(n,i)}}t.Triangle=v;class G{static centroid(e){return p.Geom.centroid(e)}static rectangle(e,t,n){return y.corners(y.fromCenter(e,t,n))}static fromCenter(e,t,n){let r=new P.Group;for(let a,d=0;dt||t>=e.length)throw new Error('index out of the Polygon\'s range');return new P.Group(e[t],t===e.length-1?e[0]:e[t+1])}static lines(e,t=!0){if(2>e.length)return h(new P.Group,2);let n=c.Util.split(e,2,1);return t&&n.push(new P.Group(e[e.length-1],e[0])),n.map((e)=>e)}static midpoints(e,n=!1,i=.5){if(2>e.length)return h(new P.Group,2);let t=G.lines(e,n),o=t.map((e)=>p.Geom.interpolate(e[0],e[1],i));return o}static adjacentSides(e,t,n=!1){if(2>e.length)return h(new P.Group,2);if(0>t||t>=e.length)return m(new P.Group,t);let i=[],o=t-1;n&&0>o&&(o=e.length-1),0<=o&&i.push(new P.Group(e[t],e[o]));let r=t+1;return n&&r>e.length-1&&(r=0),r<=e.length-1&&i.push(new P.Group(e[t],e[r])),i}static bisector(e,t){let n=G.adjacentSides(e,t,!0);if(2<=n.length){let e=n[0][1].$subtract(n[0][0]).unit(),t=n[1][1].$subtract(n[1][0]).unit();return e.add(t).divide(2)}}static perimeter(e,t=!1){if(2>e.length)return h(new P.Group,2);let n=G.lines(e,t),o=0,r=P.Pt.make(n.length,0);for(let s,a=0,i=n.length;ae.length)return h(new P.Group,3);let t=(e,t)=>e[0]*t[1]-e[1]*t[0],n=0;for(let o=0,i=e.length;oe.length)return h(new P.Group,3);t||(e=e.slice(),e.sort((e,t)=>e[0]-t[0]));let n=(e,t,n)=>0<(t[0]-e[0])*(n[1]-e[1])-(n[0]-e[0])*(t[1]-e[1]),o=[],r=e.length-2,s=r+3;o[r]=e[2],o[s]=e[2],n(e[0],e[1],e[2])?(o[r+1]=e[0],o[r+2]=e[1]):(o[r+1]=e[1],o[r+2]=e[0]);for(let a,l=3,i=e.length;lt[1]!=o[1][1]>t[1]&&t[0]<(o[1][0]-o[0][0])*(t[1]-o[0][1])/(o[1][1]-o[0][1])+o[0][0]&&(n=!n);return n}static hasIntersectCircle(e,t){let n={which:-1,dist:0,normal:null,edge:null,vertex:null},o=t[0],s=t[1][0],r=a;for(let a=0,i=e.length;ai&&n.normal.multiply(-1),n.dist=r,n.vertex=o,n}static hasIntersectPolygon(e,t){let n={which:-1,dist:0,normal:new P.Pt,edge:new P.Group,vertex:new P.Pt},o=a;for(let r=0,i=e.length+t.length;ru&&n.normal.multiply(-1);let c=a;for(let o,s=0,i=r.length;sp.Geom.boundingBox(e)),n=c.Util.flatten(t,!1);return t.unshift(p.Geom.boundingBox(n)),t}}t.Polygon=G;class b{static getSteps(e){let n=new P.Group;for(let o,t=0;t<=e;t++)o=t/e,n.push(new P.Pt(o*o*o,o*o,o,1));return n}static controlPoints(e,t=0,n=!1){if(t>e.length-1)return new P.Group;let i=(t)=>te+n.x*t[o],0),i=e.reduce((e,n,o)=>e+n.y*t[o],0);if(2e+n.z*t[o],0);return new P.Pt(n,i,o)}return new P.Pt(n,i)}static catmullRom(e,t=10){if(2>e.length)return new P.Group;let n=new P.Group,o=b.getSteps(t),r=b.controlPoints(e,0,!0);for(let s=0;s<=t;s++)n.push(b.catmullRomStep(o[s],r));for(let i=0;ie.length)return new P.Group;let o=new P.Group,r=b.getSteps(t),s=b.controlPoints(e,0,!0);for(let a=0;a<=t;a++)o.push(b.cardinalStep(r[a],s,n));for(let i=0;ie.length)return new P.Group;let n=new P.Group,o=b.getSteps(t),i=0;for(;ie.length)return new P.Group;let o=new P.Group,r=b.getSteps(t),i=0;for(;in?o-=i:o=_(t,n)&&e<=u(t,n)}static randomRange(e,t=0){let n=e>t?e-t:t-e;return e+Math.random()*n}static normalizeValue(e,t,n){let i=_(t,n),o=u(t,n);return(e-i)/(o-i)}static sum(e){let t=new m.Pt(e[0]);for(let n=1,i=e.length;ne.$min(t)),n=e.reduce((e,t)=>e.$max(t));return new m.Group(t,n)}static centroid(e){return c.average(e)}static anchor(e,t=0,n='to'){let o='to'==n?'subtract':'add';for(let r=0,i=e.length;r{if(2>e.length||2>t.length)throw new Error('Pt dimension cannot be less than 2');let i=e.$subtract(n),o=t.$subtract(n);if(0<=i[0]&&0>o[0])return 1;if(0>i[0]&&0<=o[0])return-1;if(0==i[0]&&0==o[0])return 0<=i[1]||0<=o[1]?i[1]>o[1]?1:-1:o[1]>i[1]?1:-1;let r=i.cross2D(o);return 0>r?1:0o[0]*o[0]+o[1]*o[1]?1:-1})}static scale(e,t,n){let o=Array.isArray(e)?e:[e],i='number'==typeof t?m.Pt.make(o[0].length,t):t;n||(n=m.Pt.make(o[0].length,0));for(let r,s=0,a=o.length;st;t++)e[t]=s(t*o/180);return{table:e,cos:(t)=>e[a(y.boundAngle(y.toDegree(t)))]}}static sinTable(){let e=new Float64Array(360);for(let t=0;360>t;t++)e[t]=l(t*o/180);return{table:e,sin:(t)=>e[a(y.boundAngle(y.toDegree(t)))]}}}i.Geom=y;class x{static linear(e,t=1){return t*e}static quadraticIn(e,t=1){return t*e*e}static quadraticOut(e,t=1){return-t*e*(e-2)}static quadraticInOut(e,t=1){let n=2*e;return .5>e?4*(t/2*e*e):-t/2*((n-1)*(n-3)-1)}static cubicIn(e,t=1){return t*e*e*e}static cubicOut(e,t=1){let n=e-1;return t*(n*n*n+1)}static cubicInOut(e,t=1){let n=2*e;return .5>e?t/2*n*n*n:t/2*((n-2)*(n-2)*(n-2)+2)}static exponentialIn(n,t=1,i=.25){return t*e(n,1/i)}static exponentialOut(n,t=1,i=.25){return t*e(n,i)}static sineIn(e,t=1){return-t*s(e*g.Const.half_pi)+t}static sineOut(e,t=1){return t*l(e*g.Const.half_pi)}static sineInOut(e,t=1){return-t/2*(s(o*e)-1)}static cosineApprox(e,t=1){let n=e*e,i=n*n;return t*(4*(i*n)/9-17*i/9+22*n/9)}static circularIn(e,t=1){return-t*(n(1-e*e)-1)}static circularOut(e,t=1){let i=e-1;return t*n(1-i*i)}static circularInOut(e,t=1){let i=2*e;return .5>e?-t/2*(n(1-i*i)-1):t/2*(n(1-(i-2)*(i-2))+1)}static elasticIn(n,t=1,i=.7){let o=n-1,r=1.5707963267948966*(i/g.Const.two_pi);return t*(-e(2,10*o)*l((o-r)*g.Const.two_pi/i))}static elasticOut(n,t=1,i=.7){let o=1.5707963267948966*(i/g.Const.two_pi);return t*(e(2,-10*n)*l((n-o)*g.Const.two_pi/i))+t}static elasticInOut(n,t=1,i=.6){let o=2*n,r=1.5707963267948966*(i/g.Const.two_pi);return .5>n?(o-=1,t*(-.5*(e(2,10*o)*l((o-r)*g.Const.two_pi/i)))):(o-=1,t*(.5*(e(2,-10*o)*l((o-r)*g.Const.two_pi/i)))+t)}static bounceIn(e,t=1){return t-x.bounceOut(1-e,t)}static bounceOut(e,t=1){return e<1/2.75?t*(7.5625*e*e):e<2/2.75?(e-=1.5/2.75,t*(7.5625*e*e+.75)):e<2.5/2.75?(e-=2.25/2.75,t*(7.5625*e*e+.9375)):(e-=2.625/2.75,t*(7.5625*e*e+.984375))}static bounceInOut(e,t=1){return .5>e?x.bounceIn(2*e,t)/2:x.bounceOut(2*e-1,t)/2+t/2}static sigmoid(e,t=1,n=10){return t/(1+h(-(n*(e-.5))))}static logSigmoid(e,t=1,n=.7){n=u(g.Const.epsilon,_(1-g.Const.epsilon,n)),n=1/(1-n);let i=1/(1+h(-2*((e-.5)*n))),o=1/(1+h(n)),r=1/(1+h(-n));return t*(i-o)/(r-o)}static seat(n,t=1,i=.5){return .5>n?t*e(2*n,1-i)/2:t*(1-e(2*(1-n),1-i)/2)}static quadraticBezier(e,t=1,i=[.05,.95]){let o='number'==typeof i?i:i[0],r='number'==typeof i?.5:i[1],s=1-2*o;0===s&&(s=g.Const.epsilon);let a=(n(o*o+s*e)-o)/s;return t*((1-2*r)*(a*a)+2*r*a)}static cubicBezier(e,t=1,n=[.1,.7],i=[.9,.2]){let o=new m.Group(new m.Pt(0,0),new m.Pt(n),new m.Pt(i),new m.Pt(1,1));return t*p.Curve.bezierStep(new m.Pt(e*e*e,e*e,e,1),p.Curve.controlPoints(o)).y}static quadraticTarget(e,t=1,n=[.2,.35]){let i=_(1-g.Const.epsilon,u(g.Const.epsilon,n[0])),o=_(1,u(0,n[1])),r=(1-o)/(1-i)-o/i;return t*_(1,u(0,r*(e*e)-(r*(i*i)-o)/i*e))}static cliff(e,t=1,n=.5){return e>n?t:0}static step(e,n,i,t,...o){let r=1/n,s=a(i/r)*r;return e(s,t,...o)}}i.Shaping=x;class b{constructor(e){this._dims=0,this._source=m.Group.fromPtArray(e),this.calc()}get max(){return this._max.clone()}get min(){return this._min.clone()}get magnitude(){return this._mag.clone()}calc(){if(this._source){let e=this._source[0].length;this._dims=e;let t=new m.Pt(e),n=new m.Pt(e),o=new m.Pt(e);for(let r=0;re.length)throw new Error('Cannot create a Bound from a group that has less than 2 Pt');return new o(e[0],e[e.length-1])}init(){if(this.p1&&(this._size=this.p1.clone(),this._inited=!0),this.p1&&this.p2){let e=this.p1,t=this.p2;this.topLeft=e.$min(t),this._bottomRight=e.$max(t),this._updateSize(),this._inited=!0}}clone(){return new o(this._topLeft.clone(),this._bottomRight.clone())}_updateSize(){this._size=this._bottomRight.$subtract(this._topLeft).abs(),this._updateCenter()}_updateCenter(){this._center=this._size.$multiply(.5).add(this._topLeft)}_updatePosFromTop(){this._bottomRight=this._topLeft.$add(this._size),this._updateCenter()}_updatePosFromBottom(){this._topLeft=this._bottomRight.$subtract(this._size),this._updateCenter()}_updatePosFromCenter(){let e=this._size.$multiply(.5);this._topLeft=this._center.$subtract(e),this._bottomRight=this._center.$add(e)}get size(){return new i.Pt(this._size)}set size(e){this._size=new i.Pt(e),this._updatePosFromTop()}get center(){return new i.Pt(this._center)}set center(e){this._center=new i.Pt(e),this._updatePosFromCenter()}get topLeft(){return new i.Pt(this._topLeft)}set topLeft(e){this._topLeft=new i.Pt(e),this[0]=this._topLeft,this._updateSize()}get bottomRight(){return new i.Pt(this._bottomRight)}set bottomRight(e){this._bottomRight=new i.Pt(e),this[1]=this._bottomRight,this._updateSize()}get width(){return 0u(e,t.length),0):e[0].length;for(let s=0;sthis._time.end&&(cancelAnimationFrame(this._animID),this._playing=!1)}pause(e=!1){return this._pause=!e||!this._pause,this}resume(){return this._pause=!1,this}stop(e=0){return this._time.end=e,this}playOnce(e=5e3){return this.play(),this.stop(e),this}render(e){return this._renderFunc&&this._renderFunc(e,this),this}set customRendering(e){this._renderFunc=e}get customRendering(){return this._renderFunc}get isPlaying(){return this._playing}get outerBound(){return this.bound.clone()}get innerBound(){return new i.Bound(o.Pt.make(this.size.length,0),this.size.clone())}get size(){return this.bound.size.clone()}get center(){return this.size.divide(2)}get width(){return this.bound.width}get height(){return this.bound.height}}t.Space=s;class a extends s{constructor(){super(...arguments),this._pressed=!1,this._dragged=!1,this._hasMouse=!1,this._hasTouch=!1}get pointer(){let e=this._pointer.clone();return e.id=this._pointer.id,e}bindCanvas(e,t){this._canvas.addEventListener(e,t)}unbindCanvas(e,t){this._canvas.removeEventListener(e,t)}bindMouse(e=!0){return e?(this.bindCanvas('mousedown',this._mouseDown.bind(this)),this.bindCanvas('mouseup',this._mouseUp.bind(this)),this.bindCanvas('mouseover',this._mouseOver.bind(this)),this.bindCanvas('mouseout',this._mouseOut.bind(this)),this.bindCanvas('mousemove',this._mouseMove.bind(this)),this._hasMouse=!0):(this.unbindCanvas('mousedown',this._mouseDown.bind(this)),this.unbindCanvas('mouseup',this._mouseUp.bind(this)),this.unbindCanvas('mouseover',this._mouseOver.bind(this)),this.unbindCanvas('mouseout',this._mouseOut.bind(this)),this.unbindCanvas('mousemove',this._mouseMove.bind(this)),this._hasMouse=!1),this}bindTouch(e=!0){return e?(this.bindCanvas('touchstart',this._mouseDown.bind(this)),this.bindCanvas('touchend',this._mouseUp.bind(this)),this.bindCanvas('touchmove',this._touchMove.bind(this)),this.bindCanvas('touchcancel',this._mouseOut.bind(this)),this._hasTouch=!0):(this.unbindCanvas('touchstart',this._mouseDown.bind(this)),this.unbindCanvas('touchend',this._mouseUp.bind(this)),this.unbindCanvas('touchmove',this._touchMove.bind(this)),this.unbindCanvas('touchcancel',this._mouseOut.bind(this)),this._hasTouch=!1),this}touchesToPoints(e,n='touches'){if(!e||!e[n])return[];let r=[];for(var s=0;se.length*r}static truncate(e,t,n,i=''){let o=a(t.length*_(1,n/e(t)));return ot?_(o,n):0{e.parentNode.removeChild(e)}),super.remove(e)}removeAll(){return this._container.innerHTML='',super.removeAll()}}t.HTMLSpace=d;class u extends o.VisualForm{constructor(e){super(),this._ctx={group:null,groupID:'pts',groupCount:0,currentID:'pts0',currentClass:'',style:{filled:!0,stroked:!0,background:'#f03',"border-color":'#fff',color:'#000',"border-width":'1px',"border-radius":'0',"border-style":'solid',position:'absolute',top:0,left:0,width:0,height:0},font:'11px sans-serif',fontSize:11,fontFamily:'sans-serif'},this._ready=!1,this._space=e,this._space.add({start:()=>{this._ctx.group=this._space.element,this._ctx.groupID='pts_dom_'+u.groupID++,this._ready=!0}})}get space(){return this._space}styleTo(e,t,n=''){if(this._ctx.style[e]===void 0)throw new Error(`${e} style property doesn't exist`);this._ctx.style[e]=`${t}${n}`}fill(e){return'boolean'==typeof e?(this.styleTo('filled',e),!e&&this.styleTo('background','transparent')):(this.styleTo('filled',!0),this.styleTo('background',e)),this}stroke(e,t){return'boolean'==typeof e?(this.styleTo('stroked',e),!e&&this.styleTo('border-width',0)):(this.styleTo('stroked',!0),this.styleTo('border-color',e),this.styleTo('border-width',(t||1)+'px')),this}fillText(e){return this.styleTo('color',e),this}cls(e){return this._ctx.currentClass='boolean'==typeof e?'':e,this}font(e,t,n,i,o){return'number'==typeof e?(this._font.size=e,o&&(this._font.face=o),t&&(this._font.weight=t),n&&(this._font.style=n),i&&(this._font.lineHeight=i),this._ctx.font=this._font.value):this._font=e,this}reset(){return this._ctx.style={filled:!0,stroked:!0,background:'#f03',"border-color":'#fff',"border-width":'1px'},this._font=new o.Font(14,'sans-serif'),this._ctx.font=this._font.value,this}updateScope(e,t){return this._ctx.group=t,this._ctx.groupID=e,this._ctx.groupCount=0,this.nextID(),this._ctx}scope(e){if(!e||null==e.animateID)throw new Error('item not defined or not yet added to Space');return this.updateScope(u.scopeID(e),this.space.element)}nextID(){return this._ctx.groupCount++,this._ctx.currentID=`${this._ctx.groupID}-${this._ctx.groupCount}`,this._ctx.currentID}static getID(e){return e.currentID||`p-${u.domID++}`}static scopeID(e){return`item-${e.animateID}`}static style(e,t){let n=[];for(let i in t.filled||n.push('background: none'),t.stroked||n.push('border: none'),t)if(t.hasOwnProperty(i)&&'filled'!=i&&'stroked'!=i){let e=t[i];if(e)if(!t.filled&&0===i.indexOf('background'))continue;else if(!t.stroked&&0===i.indexOf('border-width'))continue;else n.push(`${i}: ${e}`)}return d.setAttr(e,{style:n.join(';')})}static rectStyle(e,t,n){return e.style.left=t[0]+'px',e.style.top=t[1]+'px',e.style.width=n[0]+'px',e.style.height=n[1]+'px',e}static point(e,t,n=5,i='square'){return'circle'===i?u.circle(e,t,n):u.square(e,t,n)}point(e,t=5,n='square'){return this.nextID(),'circle'==n&&this.styleTo('border-radius','100%'),u.point(this._ctx,e,t,n),this}static circle(e,t,n=10){let i=d.htmlElement(e.group,'div',u.getID(e));return d.setAttr(i,{class:`pts-form pts-circle ${e.currentClass}`}),u.rectStyle(e,new a.Pt(t).$subtract(n),new a.Pt(2*n,2*n)),u.style(i,e.style),i}circle(e){return this.nextID(),this.styleTo('border-radius','100%'),u.circle(this._ctx,e[0],e[1][0]),this}static square(e,t,n){let i=d.htmlElement(e.group,'div',u.getID(e));return d.setAttr(i,{class:`pts-form pts-square ${e.currentClass}`}),u.rectStyle(e,new a.Pt(t).$subtract(n),new a.Pt(2*n,2*n)),u.style(i,e.style),i}square(e,t){return this.nextID(),u.square(this._ctx,e,t),this}static rect(e,t){if(this._checkSize(t)){let n=d.htmlElement(e.group,'div',u.getID(e));return d.setAttr(n,{class:`pts-form pts-rect ${e.currentClass}`}),u.rectStyle(e,t[0],t[1]),u.style(n,e.style),n}}rect(e){return this.nextID(),this.styleTo('border-radius','0'),u.rect(this._ctx,e),this}static text(e,t,n){let i=d.htmlElement(e.group,'div',u.getID(e));return d.setAttr(i,{position:'absolute',class:`pts-form pts-text ${e.currentClass}`,left:t[0],top:t[1]}),i.textContent=n,u.style(i,e.style),i}text(e,t){return this.nextID(),u.text(this._ctx,e,t),this}log(e){return this.fill('#000').stroke('#fff',.5).text([10,14],e),this}arc(){return s.Util.warn('arc is not implemented in HTMLForm'),this}line(){return s.Util.warn('line is not implemented in HTMLForm'),this}polygon(){return s.Util.warn('polygon is not implemented in HTMLForm'),this}}u.groupID=0,u.domID=0,t.HTMLForm=u},function(e,t,n){'use strict';Object.defineProperty(t,'__esModule',{value:!0});const i=n(4),o=n(11),r=n(13),s=n(6),a=n(5),l=n(3),d=n(2),u=n(0),c=n(7),p=n(14),_=n(1),h=n(9),g=n(15),m=n(8),f=n(16);e.exports=Object.assign({namespace:(t)=>{let n=e.exports;for(let e in n)'namespace'!=e&&(t[e]=n[e])}},i,o,r,s,a,d,l,u,c,_,p,h,g,m,f)},function(e,t,n){'use strict';Object.defineProperty(t,'__esModule',{value:!0});const i=n(7),o=n(6),r=n(4),s=n(0),l=n(1),d=n(8),c=n(2);class p extends i.MultiTouchSpace{constructor(e,t){super(),this._pixelScale=1,this._autoResize=!0,this._bgcolor='#e1e9f0',this._offscreen=!1,this._initialResize=!1;var n=null,i=!1;this.id='pt',e instanceof Element?(n=e,this.id='pts_existing_space'):(n=document.querySelector(e),i=!0,this.id=e),n?'canvas'==n.nodeName.toLowerCase()?(this._canvas=n,this._container=n.parentElement,this._autoResize=!1):(this._container=n,this._canvas=this._createElement('canvas',this.id+'_canvas'),this._container.appendChild(this._canvas),this._initialResize=!0):(this._container=this._createElement('div',this.id+'_container'),this._canvas=this._createElement('canvas',this.id),this._container.appendChild(this._canvas),document.body.appendChild(this._container),i=!1),setTimeout(this._ready.bind(this,t),100),this._ctx=this._canvas.getContext('2d')}_createElement(e='div',t){let n=document.createElement(e);return n.setAttribute('id',t),n}_ready(e){if(!this._container)throw new Error(`Cannot initiate #${this.id} element`);for(let t in this._isReady=!0,this._resizeHandler(null),this.clear(this._bgcolor),this._canvas.dispatchEvent(new Event('ready')),this.players)this.players.hasOwnProperty(t)&&this.players[t].start&&this.players[t].start(this.bound.clone(),this);this._pointer=this.center,this._initialResize=!1,e&&e(this.bound,this._canvas)}setup(e){if(e.bgcolor&&(this._bgcolor=e.bgcolor),this.autoResize=void 0!=e.resize&&e.resize,!1!==e.retina){let e=window.devicePixelRatio||1,t=this._ctx.webkitBackingStorePixelRatio||this._ctx.mozBackingStorePixelRatio||this._ctx.msBackingStorePixelRatio||this._ctx.oBackingStorePixelRatio||this._ctx.backingStorePixelRatio||1;this._pixelScale=e/t}return e.offscreen?(this._offscreen=!0,this._offCanvas=this._createElement('canvas',this.id+'_offscreen'),this._offCtx=this._offCanvas.getContext('2d')):this._offscreen=!1,this}set autoResize(e){this._autoResize=e,e?window.addEventListener('resize',this._resizeHandler.bind(this)):window.removeEventListener('resize',this._resizeHandler.bind(this))}get autoResize(){return this._autoResize}resize(e,t){for(let n in this.bound=e,this._canvas.width=this.bound.size.x*this._pixelScale,this._canvas.height=this.bound.size.y*this._pixelScale,this._canvas.style.width=a(this.bound.size.x)+'px',this._canvas.style.height=a(this.bound.size.y)+'px',this._offscreen&&(this._offCanvas.width=this.bound.size.x*this._pixelScale,this._offCanvas.height=this.bound.size.y*this._pixelScale),1!=this._pixelScale&&(this._ctx.scale(this._pixelScale,this._pixelScale),this._ctx.translate(.5,.5),this._offscreen&&(this._offCtx.scale(this._pixelScale,this._pixelScale),this._offCtx.translate(.5,.5))),this.players)if(this.players.hasOwnProperty(n)){let e=this.players[n];e.resize&&e.resize(this.bound,t)}return this.render(this._ctx),t&&!this.isPlaying&&this.playOnce(0),this}_resizeHandler(e){let t=this._autoResize||this._initialResize?this._container.getBoundingClientRect():this._canvas.getBoundingClientRect();if(t){let n=r.Bound.fromBoundingRect(t);n.center=n.center.add(window.pageXOffset,window.pageYOffset),this.resize(n,e)}}set background(e){this._bgcolor=e}get background(){return this._bgcolor}get pixelScale(){return this._pixelScale}get hasOffscreen(){return this._offscreen}get offscreenCtx(){return this._offCtx}get offscreenCanvas(){return this._offCanvas}getForm(){return new _(this)}get element(){return this._canvas}get parent(){return this._container}get ready(){return this._isReady}get ctx(){return this._ctx}clear(e){e&&(this._bgcolor=e);let t=this._ctx.fillStyle;return this._bgcolor&&'transparent'!=this._bgcolor?(this._ctx.fillStyle=this._bgcolor,this._ctx.fillRect(-1,-1,this._canvas.width+1,this._canvas.height+1)):this._ctx.clearRect(-1,-1,this._canvas.width+1,this._canvas.height+1),this._ctx.fillStyle=t,this}clearOffscreen(e){return this._offscreen&&(e?(this._offCtx.fillStyle=e,this._offCtx.fillRect(-1,-1,this._canvas.width+1,this._canvas.height+1)):this._offCtx.clearRect(-1,-1,this._offCanvas.width+1,this._offCanvas.height+1)),this}playItems(e){this._isReady&&(this._ctx.save(),this._offscreen&&this._offCtx.save(),super.playItems(e),this._ctx.restore(),this._offscreen&&this._offCtx.restore(),this.render(this._ctx))}}t.CanvasSpace=p;class _ extends o.VisualForm{constructor(e){super(),this._style={fillStyle:'#f03',strokeStyle:'#fff',lineWidth:1,lineJoin:'bevel',lineCap:'butt'},this._space=e,this._space.add({start:()=>{this._ctx=this._space.ctx,this._ctx.fillStyle=this._style.fillStyle,this._ctx.strokeStyle=this._style.strokeStyle,this._ctx.lineJoin='bevel',this._ctx.font=this._font.value,this._ready=!0}})}get space(){return this._space}useOffscreen(e=!0,t=!1){return t&&this._space.clearOffscreen('string'==typeof t?t:null),this._ctx=this._space.hasOffscreen&&e?this._space.offscreenCtx:this._space.ctx,this}renderOffscreen(e=[0,0]){this._space.hasOffscreen&&this._space.ctx.drawImage(this._space.offscreenCanvas,e[0],e[1],this._space.width,this._space.height)}fill(e){return'boolean'==typeof e?this.filled=e:(this.filled=!0,this._style.fillStyle=e,this._ctx.fillStyle=e),this}stroke(e,t,n,i){return'boolean'==typeof e?this.stroked=e:(this.stroked=!0,this._style.strokeStyle=e,this._ctx.strokeStyle=e,t&&(this._ctx.lineWidth=t,this._style.lineWidth=t),n&&(this._ctx.lineJoin=n,this._style.lineJoin=n),i&&(this._ctx.lineCap=i,this._style.lineCap=i)),this}font(e,t,n,i,o){return'number'==typeof e?(this._font.size=e,o&&(this._font.face=o),t&&(this._font.weight=t),n&&(this._font.style=n),i&&(this._font.lineHeight=i),this._ctx.font=this._font.value):this._font=e,this._estimateTextWidth&&this.fontWidthEstimate(!0),this}fontWidthEstimate(e=!0){return this._estimateTextWidth=e?d.Typography.textWidthEstimator((e)=>this._ctx.measureText(e).width):void 0,this}getTextWidth(e){return this._estimateTextWidth?this._estimateTextWidth(e):this._ctx.measureText(e+' .').width}_textTruncate(e,t,n=''){return d.Typography.truncate(this.getTextWidth.bind(this),e,t,n)}_textAlign(e,t,n,i){i||(i=c.Rectangle.center(e));var o=e[0][0];'end'==this._ctx.textAlign||'right'==this._ctx.textAlign?o=e[1][0]:('center'==this._ctx.textAlign||'middle'==this._ctx.textAlign)&&(o=i[0]);var r=i[1];return'top'==t||'start'==t?r=e[0][1]:('end'==t||'bottom'==t)&&(r=e[1][1]),n?new s.Pt(o+n[0],r+n[1]):new s.Pt(o,r)}reset(){for(let e in this._style)this._style.hasOwnProperty(e)&&(this._ctx[e]=this._style[e]);return this._font=new o.Font,this._ctx.font=this._font.value,this}_paint(){this._filled&&this._ctx.fill(),this._stroked&&this._ctx.stroke()}point(e,t=5,n='square'){if(e){if(!_[n])throw new Error(`${n} is not a static function of CanvasForm`);return _[n](this._ctx,e,t),this._paint(),this}}static circle(e,t,n=10){t&&(e.beginPath(),e.arc(t[0],t[1],n,0,l.Const.two_pi,!1),e.closePath())}circle(e){return _.circle(this._ctx,e[0],e[1][0]),this._paint(),this}static arc(e,t,n,i,o,r){t&&(e.beginPath(),e.arc(t[0],t[1],n,i,o,r))}arc(e,t,n,i,o){return _.arc(this._ctx,e,t,n,i,o),this._paint(),this}static square(e,t,n){if(t){let i=t[0]-n,o=t[1]-n,r=t[0]+n,s=t[1]+n;e.beginPath(),e.moveTo(i,o),e.lineTo(i,s),e.lineTo(r,s),e.lineTo(r,o),e.closePath()}}square(e,t){return _.square(this._ctx,e,t),this._paint(),this}static line(e,t){if(!(2>t.length)){e.beginPath(),e.moveTo(t[0][0],t[0][1]);for(let n=1,i=t.length;nt.length)){e.beginPath(),e.moveTo(t[0][0],t[0][1]);for(let n=1,i=t.length;nt.length||(e.beginPath(),e.moveTo(t[0][0],t[0][1]),e.lineTo(t[0][0],t[1][1]),e.lineTo(t[1][0],t[1][1]),e.lineTo(t[1][0],t[0][1]),e.closePath())}rect(e){return _.rect(this._ctx,e),this._paint(),this}static text(e,t,n,i){t&&e.fillText(n,t[0],t[1],i)}text(e,t,n){return _.text(this._ctx,e,t,n),this}textBox(e,n,i='middle',o='',r=!0){r&&(this._ctx.textBaseline=i);let s=c.Rectangle.size(e),a=this._textTruncate(n,s[0],o);return this.text(this._textAlign(e,i),a[0]),this}paragraphBox(e,t,n=1.2,i='top',o=!0){let r=c.Rectangle.size(e);this._ctx.textBaseline='top';let a=this._font.size*n,l=(e,n=[],i=0)=>{if(!e)return n;if(o&&i*a>r[1]-2*a)return n;if(1e4=d||s[1]===e.length)&&(d=void 0);let u=s[0].substr(0,d);return n.push(u),0>=s[1]||s[1]===e.length?n:l(e.substr(d||s[1]),n,i+1)},d=l(t),p=d.length*a,_=e;if('middle'==i||'center'==i){let t=(r[1]-p)/2;o&&(t=u(0,t)),_=new s.Group(e[0].$add(0,t),e[1].$subtract(0,t))}else _='bottom'==i?new s.Group(e[0].$add(0,r[1]-p),e[1]):new s.Group(e[0],e[0].$add(r[0],p));let h=c.Rectangle.center(_);for(let r=0,s=d.length;re&&(e*=65536),e=a(e),256>e&&(e|=e<<8);for(let t,n=0;255>n;n++)t=1&n?p[n]^255&e:p[n]^255&e>>8,this.perm[n]=this.perm[n+256]=t}noise2D(){let e=a(this._n[0])%255,t=a(this._n[1])%255,n=this._n[0]%255-e,i=this._n[1]%255-t,o=d.Vec.dot(c[(e+this.perm[t])%12],[n,i,0]),r=d.Vec.dot(c[(e+this.perm[t+1])%12],[n,i-1,0]),s=d.Vec.dot(c[(e+1+this.perm[t])%12],[n-1,i,0]),u=d.Vec.dot(c[(e+1+this.perm[t+1])%12],[n-1,i-1,0]),p=(e)=>e*e*e*(e*(6*e-15)+10),_=p(n);return l.Num.lerp(l.Num.lerp(o,s,_),l.Num.lerp(r,u,_),p(i))}}t.Noise=h;class i extends o.Group{constructor(){super(...arguments),this._mesh=[]}delaunay(e=!0){if(3>this.length)return[];this._mesh=[];let t=this.length,n=[];for(let o=0;othis[t][0]-this[e][0]);let o=this.slice(),r=this._superTriangle();o=o.concat(r);let a=[this._circum(t,t+1,t+2,r)],l=[],d=[];for(let t=0,r=n.length;tn*n){l.push(t),d.push(t.triangle),a.splice(u,1);continue}i[0]*i[0]+i[1]*i[1]-n*n>s.Const.epsilon||(r.push(t.i,t.j,t.j,t.k,t.k,t.i),a.splice(u,1))}for(i._dedupe(r),u=r.length;1=e.length){let t=(t)=>e[t]||'F';e=`${t(0)}${t(0)}${t(1)}${t(1)}${t(2)}${t(2)}`}let t=1;8===e.length&&(t=e.substr(6)&&1,e=e.substring(0,6));let n=parseInt(e,16);return new m(n>>16,255&n>>8,255&n,t)}static rgb(...e){return m.from(...e).toMode('rgb')}static hsl(...e){return m.from(...e).toMode('hsl')}static hsb(...e){return m.from(...e).toMode('hsb')}static lab(...e){return m.from(...e).toMode('lab')}static lch(...e){return m.from(...e).toMode('lch')}static luv(...e){return m.from(...e).toMode('luv')}static xyz(...e){return m.from(...e).toMode('xyz')}static maxValues(e){return m.ranges[e].zipSlice(1).$take([0,1,2])}get hex(){return this.toString('hex')}get rgb(){return this.toString('rgb')}get rgba(){return this.toString('rgba')}clone(){let e=new m(this);return e.toMode(this._mode),e}toMode(e,t=!1){if(t){let t=this._mode.toUpperCase()+'to'+e.toUpperCase();if(m[t])this.to(m[t](this,this._isNorm,this._isNorm));else throw new Error('Cannot convert color with '+t)}return this._mode=e,this}get mode(){return this._mode}get r(){return this[0]}set r(e){this[0]=e}get g(){return this[1]}set g(e){this[1]=e}get b(){return this[1]}set b(e){this[2]=e}get h(){return'lch'==this._mode?this[2]:this[0]}set h(e){let t='lch'==this._mode?2:0;this[t]=e}get s(){return this[1]}set s(e){this[1]=e}get l(){return'hsl'==this._mode?this[2]:this[0]}set l(e){let t='hsl'==this._mode?2:0;this[t]=e}get a(){return this[1]}set a(e){this[1]=e}get c(){return this[1]}set c(e){this[1]=e}get u(){return this[1]}set u(e){this[1]=e}get v(){return this[1]}set v(e){this[2]=e}get alpha(){return 3n;n++)this[n]=e?p.Num.mapToRange(this[n],t[n][0],t[n][1],0,1):p.Num.mapToRange(this[n],0,1,t[n][0],t[n][1]);return this._isNorm=e,this}$normalize(e=!0){return this.clone().normalize(e)}toString(e='mode'){if('hex'==e){let e=(e)=>{let t=a(e).toString(16);return 2>t.length?'0'+t:t};return`#${e(this[0])}${e(this[1])}${e(this[2])}`}return'rgba'==e?`rgba(${a(this[0])},${a(this[1])},${a(this[2])},${this.alpha}`:'rgb'==e?`rgb(${a(this[0])},${a(this[1])},${a(this[2])}`:`${this._mode}(${this[0]},${this[1]},${this[2]},${this.alpha})`}static RGBtoHSL(e,t=!1,n=!1){let[i,o,r]=t?e:e.$normalize(),a=u(i,o,r),c=_(i,o,r),p=(a+c)/2,h=p,s=p;if(a==c)p=0,h=0;else{let e=a-c;h=.5=r?r*(1+o):r+o-r*o,a=2*r-s,l=(e)=>(e=0>e?e+1:16*e?a+6*((s-a)*e):1>2*e?s:2>3*e?a+6*((s-a)*(2/3-e)):a),d=n?1:255;return m.rgb(d*l(i+1/3),d*l(i),d*l(i-1/3),e.alpha)}static RGBtoHSB(e,t=!1,n=!1){let[i,o,r]=t?e:e.$normalize(),a=u(i,o,r),l=_(i,o,r),c=a-l,d=0,p=0===a?0:c/a;return a!=l&&(a===i?d=(o-r)/c+(os;s++)r[s]=.04045r;r++)a[r]=0>a[r]?0:.0031308.008856{let t=e*e*e;return .008856{e.parentNode.removeChild(e)}),super.remove(e)}removeAll(){return this._container.innerHTML='',super.removeAll()}}t.SVGSpace=u;class c extends i.VisualForm{constructor(e){super(),this._ctx={group:null,groupID:'pts',groupCount:0,currentID:'pts0',currentClass:'',style:{filled:!0,stroked:!0,fill:'#f03',stroke:'#fff',"stroke-width":1,"stroke-linejoin":'bevel',"stroke-linecap":'sqaure'},font:'11px sans-serif',fontSize:11,fontFamily:'sans-serif'},this._ready=!1,this._space=e,this._space.add({start:()=>{this._ctx.group=this._space.element,this._ctx.groupID='pts_svg_'+c.groupID++,this._ready=!0}})}get space(){return this._space}styleTo(e,t){if(this._ctx.style[e]===void 0)throw new Error(`${e} style property doesn't exist`);this._ctx.style[e]=t}fill(e){return'boolean'==typeof e?this.styleTo('filled',e):(this.styleTo('filled',!0),this.styleTo('fill',e)),this}stroke(e,t,n,i){return'boolean'==typeof e?this.styleTo('stroked',e):(this.styleTo('stroked',!0),this.styleTo('stroke',e),t&&this.styleTo('stroke-width',t),n&&this.styleTo('stroke-linejoin',n),i&&this.styleTo('stroke-linecap',i)),this}cls(e){return this._ctx.currentClass='boolean'==typeof e?'':e,this}font(e,t,n,i,o){return'number'==typeof e?(this._font.size=e,o&&(this._font.face=o),t&&(this._font.weight=t),n&&(this._font.style=n),i&&(this._font.lineHeight=i),this._ctx.font=this._font.value):this._font=e,this}reset(){return this._ctx.style={filled:!0,stroked:!0,fill:'#f03',stroke:'#fff',"stroke-width":1,"stroke-linejoin":'bevel',"stroke-linecap":'sqaure'},this._font=new i.Font(14,'sans-serif'),this._ctx.font=this._font.value,this}updateScope(e,t){return this._ctx.group=t,this._ctx.groupID=e,this._ctx.groupCount=0,this.nextID(),this._ctx}scope(e){if(!e||null==e.animateID)throw new Error('item not defined or not yet added to Space');return this.updateScope(c.scopeID(e),this.space.element)}nextID(){return this._ctx.groupCount++,this._ctx.currentID=`${this._ctx.groupID}-${this._ctx.groupCount}`,this._ctx.currentID}static getID(e){return e.currentID||`p-${c.domID++}`}static scopeID(e){return`item-${e.animateID}`}static style(e,t){let n=[];for(let i in t.filled||n.push('fill: none'),t.stroked||n.push('stroke: none'),t)if(t.hasOwnProperty(i)&&'filled'!=i&&'stroked'!=i){let e=t[i];if(e)if(!t.filled&&0===i.indexOf('fill'))continue;else if(!t.stroked&&0===i.indexOf('stroke'))continue;else n.push(`${i}: ${e}`)}return l.DOMSpace.setAttr(e,{style:n.join(';')})}static point(e,t,n=5,i='square'){return'circle'===i?c.circle(e,t,n):c.square(e,t,n)}point(e,t=5,n='square'){return this.nextID(),c.point(this._ctx,e,t,n),this}static circle(e,t,n=10){let i=u.svgElement(e.group,'circle',c.getID(e));return l.DOMSpace.setAttr(i,{cx:t[0],cy:t[1],r:n,class:`pts-svgform pts-circle ${e.currentClass}`}),c.style(i,e.style),i}circle(e){return this.nextID(),c.circle(this._ctx,e[0],e[1][0]),this}static arc(e,t,n,i,a,p){let _=u.svgElement(e.group,'path',c.getID(e));const h=new s.Pt(t).toAngle(i,n,!0),g=new s.Pt(t).toAngle(a,n,!0),m=o.Geom.boundAngle(a)-o.Geom.boundAngle(i);let f=!!(m>r.Const.pi);p&&(f=!f);const y=p?'0':'1',x=`M ${h[0]} ${h[1]} A ${n} ${n} 0 ${f?'1':'0'} ${y} ${g[0]} ${g[1]}`;return l.DOMSpace.setAttr(_,{d:x,class:`pts-svgform pts-arc ${e.currentClass}`}),c.style(_,e.style),_}arc(e,t,n,i,o){return this.nextID(),c.arc(this._ctx,e,t,n,i,o),this}static square(e,t,n){let i=u.svgElement(e.group,'rect',c.getID(e));return l.DOMSpace.setAttr(i,{x:t[0]-n,y:t[1]-n,width:2*n,height:2*n,class:`pts-svgform pts-square ${e.currentClass}`}),c.style(i,e.style),i}square(e,t){return this.nextID(),c.square(this._ctx,e,t),this}static line(e,t){if(this._checkSize(t)){if(2e+`${t[0]},${t[1]} `,'');return l.DOMSpace.setAttr(i,{points:o,class:`pts-svgform pts-polygon ${e.currentClass}`}),c.style(i,e.style),i}}static polygon(e,t){return c._poly(e,t,!0)}polygon(e){return this.nextID(),c.polygon(this._ctx,e),this}static rect(e,t){if(this._checkSize(t)){let n=u.svgElement(e.group,'rect',c.getID(e)),i=s.Group.fromArray(t).boundingBox(),o=a.Rectangle.size(i);return l.DOMSpace.setAttr(n,{x:i[0][0],y:i[0][1],width:o[0],height:o[1],class:`pts-svgform pts-rect ${e.currentClass}`}),c.style(n,e.style),n}}rect(e){return this.nextID(),c.rect(this._ctx,e),this}static text(e,t,n){let i=u.svgElement(e.group,'text',c.getID(e));return l.DOMSpace.setAttr(i,{"pointer-events":'none',x:t[0],y:t[1],dx:0,dy:0,class:`pts-svgform pts-text ${e.currentClass}`}),i.textContent=n,c.style(i,e.style),i}text(e,t){return this.nextID(),c.text(this._ctx,e,t),this}log(e){return this.fill('#000').stroke('#fff',.5).text([10,14],e),this}}c.groupID=0,c.domID=0,t.SVGForm=c},function(e,t,i){'use strict';Object.defineProperty(t,'__esModule',{value:!0});const o=i(0),r=i(4),s=i(2);class l{constructor(e,t=1,n=0){return this._lastTime=null,this._gravity=new o.Pt,this._friction=1,this._damping=.75,this._particles=[],this._bodies=[],this._names={p:{},b:{}},this._bound=r.Bound.fromGroup(e),this._friction=t,this._gravity='number'==typeof n?new o.Pt(0,n):new o.Pt(n),this}get gravity(){return this._gravity}set gravity(e){this._gravity=e}get friction(){return this._friction}set friction(e){this._friction=e}get damping(){return this._damping}set damping(e){this._damping=e}get bodyCount(){return this._bodies.length}get particleCount(){return this._particles.length}body(e){return this._bodies['string'==typeof e?this._names.b[e]:e]}particle(e){return this._particles['string'==typeof e?this._names.p[e]:e]}update(e){let t=e/1e3;this._updateParticles(t),this._updateBodies(t)}drawParticles(e){this._drawParticles=e}drawBodies(e){this._drawBodies=e}add(e,t){return e instanceof c?(this._bodies.push(e),t&&(this._names.b[t]=this._bodies.length-1)):(this._particles.push(e),t&&(this._names.p[t]=this._particles.length-1)),this}remove(e,t,n=1){let i=0>t?[-1*t-1,n]:[t,n];return'body'==e?this._bodies.splice(i[0],i[1]):this._particles.splice(i[0],i[1]),this}static edgeConstraint(e,t,n,i=1,o=!1){const r=1/(e.mass||1),s=1/(t.mass||1),a=r+s;let l=t.$subtract(e),u=n*n,c=o?n/l.magnitude()-1:u/(l.dot(l)+u)-.5,d=l.$multiply(c*i);return e.subtract(d.$multiply(r/a)),t.add(d.$multiply(s/a)),e}static boundConstraint(e,t,n=.75){let i=t.boundingBox(),r=e.$min(i[1].subtract(e.radius)).$max(i[0].add(e.radius));if(r[0]===i[0][0]||r[0]===i[1][0]){let t=e.changed.$multiply(n);e.previous=r.$subtract(new o.Pt(-t[0],t[1]))}else if(r[1]===i[0][1]||r[1]===i[1][1]){let t=e.changed.$multiply(n);e.previous=r.$subtract(new o.Pt(t[0],-t[1]))}e.to(r)}integrate(e,t,n){return e.addForce(this._gravity),e.verlet(t,this._friction,n),e}_updateParticles(e){for(let t,n=0,i=this._particles.length;ne||e>=this.length)throw new Error('index1 is not in the Group\'s indices');if(0>t||t>=this.length)throw new Error('index1 is not in the Group\'s indices');let i=this[e].$subtract(this[t]).magnitude();return this._cs.push([e,t,i,n||this._stiff]),this}linkAll(e){let t=this.length/2;for(let o,n=0,i=this.length;n=i-1?0:n+1,this.link(n,o,e),4=i-o?n%i:n+o;this.link(n,r,e)}n<=t-1&&this.link(n,_(this.length-1,n+a(t)))}}linksToLines(){let e=[];for(let t,n=0,i=this._cs.length;nd(i[0][1]-i[1][1])?(n.vertex[0]-t[0]-i[0][0])/(i[1][0]-i[0][0]):(n.vertex[1]-t[1]-i[0][1])/(i[1][1]-i[0][1]);let o=1/(e*e+(1-e)*(1-e)),r=n.vertex.body.mass||1,s=n.edge[0].body.mass||1,a=r/(r+s);i[0].subtract(t.$multiply(a*(1-e)*o/2)),i[1].subtract(t.$multiply(a*e*o/2)),n.vertex.add(t.$multiply(s/(r+s)))}}processParticle(e){let t=this,n=s.Polygon.hasIntersectCircle(t,s.Circle.fromCenter(e,e.radius));if(n){let i,t=n.normal.$multiply(n.dist),o=n.edge;i=d(o[0][0]-o[1][0])>d(o[0][1]-o[1][1])?(n.vertex[0]-t[0]-o[0][0])/(o[1][0]-o[0][0]):(n.vertex[1]-t[1]-o[0][1])/(o[1][1]-o[0][1]);let r=1/(i*i+(1-i)*(1-i)),s=n.vertex.mass||e.mass||1,a=n.edge[0].body.mass||1,l=s/(s+a);o[0].subtract(t.$multiply(l*(1-i)*r/2)),o[1].subtract(t.$multiply(l*i*r/2));let u=e.changed.add(t.$multiply(a/(s+a)));e.previous=e.$subtract(u)}}}t.Body=c}])})}])}); //# sourceMappingURL=pts.min.js.map \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index eeb04f58..74fdf377 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "outDir": "./dist/files", "declaration": false, "rootDir": "src", - "downlevelIteration": true + "downlevelIteration": true, + "removeComments": true }, "include": [