@mathigon/euclid
Advanced tools
Comparing version 1.0.6 to 1.0.7
@@ -32,2 +32,3 @@ import { Arc } from './arc'; | ||
at(): Point; | ||
offset(): number; | ||
contains(): boolean; | ||
@@ -41,2 +42,3 @@ transform(m: TransformMatrix): Angle; | ||
equals(_a: Angle): boolean; | ||
toString(): string; | ||
} |
@@ -0,1 +1,2 @@ | ||
import { Circle } from './circle'; | ||
import { Line } from './line'; | ||
@@ -11,2 +12,3 @@ import { Point } from './point'; | ||
constructor(c: Point, start: Point, angle: number); | ||
get circle(): Circle; | ||
get radius(): number; | ||
@@ -21,2 +23,3 @@ get end(): Point; | ||
at(t: number): Point; | ||
offset(p: Point): number; | ||
contains(p: Point): boolean; | ||
@@ -31,2 +34,3 @@ transform(m: TransformMatrix): this; | ||
equals(): boolean; | ||
toString(): string; | ||
} | ||
@@ -36,2 +40,3 @@ export declare class Sector extends Arc { | ||
contains(p: Point): boolean; | ||
toString(): string; | ||
} |
@@ -21,2 +21,3 @@ import { Arc } from './arc'; | ||
at(t: number): Point; | ||
offset(p: Point): number; | ||
contains(p: Point): boolean; | ||
@@ -23,0 +24,0 @@ transform(m: TransformMatrix): Circle; |
@@ -22,2 +22,3 @@ import { Line } from './line'; | ||
at(t: number): Point; | ||
offset(p: Point): number; | ||
contains(_p: Point): boolean; | ||
@@ -31,2 +32,3 @@ transform(_m: TransformMatrix): this; | ||
equals(): boolean; | ||
toString(): string; | ||
} |
@@ -41,2 +41,3 @@ var __defProp = Object.defineProperty; | ||
Segment: () => Segment, | ||
TWO_PI: () => TWO_PI, | ||
Triangle: () => Triangle, | ||
@@ -67,5 +68,11 @@ angleSize: () => angleSize, | ||
// src/angle.ts | ||
var import_fermat6 = require("@mathigon/fermat"); | ||
// src/arc.ts | ||
var import_fermat5 = require("@mathigon/fermat"); | ||
// src/arc.ts | ||
// src/circle.ts | ||
var import_fermat4 = require("@mathigon/fermat"); | ||
// src/line.ts | ||
var import_fermat3 = require("@mathigon/fermat"); | ||
@@ -84,2 +91,17 @@ | ||
} | ||
function findClosest(p, items) { | ||
let q = void 0; | ||
let d = Infinity; | ||
let index = -1; | ||
for (const [i, e] of items.entries()) { | ||
const q1 = e.project(p); | ||
const d1 = Point.distance(p, q1); | ||
if (d1 < d) { | ||
q = q1; | ||
d = d1; | ||
index = i; | ||
} | ||
} | ||
return q ? [q, index] : void 0; | ||
} | ||
@@ -236,3 +258,3 @@ // src/point.ts | ||
toString() { | ||
return `Point(x: ${this.x}, y: ${this.y})`; | ||
return `point(${this.x},${this.y})`; | ||
} | ||
@@ -242,86 +264,3 @@ }; | ||
// src/arc.ts | ||
var Arc = class { | ||
constructor(c, start, angle) { | ||
this.c = c; | ||
this.start = start; | ||
this.angle = angle; | ||
this.type = "arc"; | ||
} | ||
get radius() { | ||
return Point.distance(this.c, this.start); | ||
} | ||
get end() { | ||
return this.start.rotate(this.angle, this.c); | ||
} | ||
get startAngle() { | ||
return rad(this.start, this.c); | ||
} | ||
contract(p) { | ||
return new this.constructor(this.c, this.at(p / 2), this.angle * (1 - p)); | ||
} | ||
get minor() { | ||
if (this.angle <= Math.PI) | ||
return this; | ||
return new this.constructor(this.c, this.end, TWO_PI - this.angle); | ||
} | ||
get major() { | ||
if (this.angle >= Math.PI) | ||
return this; | ||
return new this.constructor(this.c, this.end, TWO_PI - this.angle); | ||
} | ||
get center() { | ||
return this.at(0.5); | ||
} | ||
project(p) { | ||
const start = this.startAngle; | ||
const end = start + this.angle; | ||
let angle = rad(p, this.c); | ||
if (end > TWO_PI && angle < end - TWO_PI) | ||
angle += TWO_PI; | ||
angle = (0, import_fermat3.clamp)(angle, start, end); | ||
return this.c.shift(this.radius, 0).rotate(angle, this.c); | ||
} | ||
at(t) { | ||
return this.start.rotate(this.angle * t, this.c); | ||
} | ||
contains(p) { | ||
return p.equals(this.project(p)); | ||
} | ||
transform(m) { | ||
return new this.constructor(this.c.transform(m), this.start.transform(m), this.angle); | ||
} | ||
rotate(a, c = ORIGIN) { | ||
if ((0, import_fermat3.nearlyEquals)(a, 0)) | ||
return this; | ||
return new this.constructor(this.c.rotate(a, c), this.start.rotate(a, c), this.angle); | ||
} | ||
reflect(l) { | ||
return new this.constructor(this.c.reflect(l), this.start.reflect(l), this.angle); | ||
} | ||
scale(sx, sy = sx) { | ||
return new this.constructor(this.c.scale(sx, sy), this.start.scale(sx, sy), this.angle); | ||
} | ||
shift(x, y = x) { | ||
return new this.constructor(this.c.shift(x, y), this.start.shift(x, y), this.angle); | ||
} | ||
translate(p) { | ||
return this.shift(p.x, p.y); | ||
} | ||
equals() { | ||
return false; | ||
} | ||
}; | ||
var Sector = class extends Arc { | ||
constructor() { | ||
super(...arguments); | ||
this.type = "sector"; | ||
} | ||
contains(p) { | ||
return Point.distance(p, this.c) <= this.radius && new Angle(this.start, this.c, p).rad <= this.angle; | ||
} | ||
}; | ||
// src/line.ts | ||
var import_fermat4 = require("@mathigon/fermat"); | ||
var Line = class { | ||
@@ -386,3 +325,3 @@ constructor(p1, p2) { | ||
const d = b.x * a.y - b.y * a.x; | ||
return (0, import_fermat4.nearlyEquals)(d, 0, tolerance) ? 0 : Math.sign(d); | ||
return (0, import_fermat3.nearlyEquals)(d, 0, tolerance) ? 0 : Math.sign(d); | ||
} | ||
@@ -399,3 +338,3 @@ contains(p, tolerance) { | ||
rotate(a, c = ORIGIN) { | ||
if ((0, import_fermat4.nearlyEquals)(a, 0)) | ||
if ((0, import_fermat3.nearlyEquals)(a, 0)) | ||
return this; | ||
@@ -420,3 +359,3 @@ return new this.constructor(this.p1.rotate(a, c), this.p2.rotate(a, c)); | ||
toString() { | ||
return `Line(p1: ${this.p1}, p2: ${this.p2})`; | ||
return `line(${this.p1},${this.p2})`; | ||
} | ||
@@ -438,3 +377,3 @@ }; | ||
toString() { | ||
return `Ray(p1: ${this.p1}, p2: ${this.p2})`; | ||
return `ray(${this.p1},${this.p2})`; | ||
} | ||
@@ -452,6 +391,6 @@ }; | ||
return true; | ||
if ((0, import_fermat4.nearlyEquals)(this.p1.x, this.p2.x, tolerance)) { | ||
return (0, import_fermat4.isBetween)(p.y, this.p1.y, this.p2.y); | ||
if ((0, import_fermat3.nearlyEquals)(this.p1.x, this.p2.x, tolerance)) { | ||
return (0, import_fermat3.isBetween)(p.y, this.p1.y, this.p2.y); | ||
} else { | ||
return (0, import_fermat4.isBetween)(p.x, this.p1.x, this.p2.x); | ||
return (0, import_fermat3.isBetween)(p.x, this.p1.x, this.p2.x); | ||
} | ||
@@ -465,3 +404,3 @@ } | ||
const b = Point.difference(p, this.p1); | ||
const q = (0, import_fermat4.clamp)(Point.dot(a, b) / this.lengthSquared, 0, 1); | ||
const q = (0, import_fermat3.clamp)(Point.dot(a, b) / this.lengthSquared, 0, 1); | ||
return Point.sum(this.p1, a.scale(q)); | ||
@@ -478,6 +417,171 @@ } | ||
toString() { | ||
return `Segment(p1: ${this.p1}, p2: ${this.p2})`; | ||
return `segment(${this.p1},${this.p2})`; | ||
} | ||
}; | ||
// src/circle.ts | ||
var Circle = class { | ||
constructor(c = ORIGIN, r = 1) { | ||
this.c = c; | ||
this.r = r; | ||
this.type = "circle"; | ||
} | ||
get circumference() { | ||
return TWO_PI * this.r; | ||
} | ||
get area() { | ||
return Math.PI * this.r ** 2; | ||
} | ||
get arc() { | ||
const start = this.c.shift(this.r, 0); | ||
return new Arc(this.c, start, TWO_PI); | ||
} | ||
tangentAt(t) { | ||
const p1 = this.at(t); | ||
const p2 = this.c.rotate(Math.PI / 2, p1); | ||
return new Line(p1, p2); | ||
} | ||
collision(r) { | ||
const tX = this.c.x < r.p.x ? r.p.x : this.c.x > r.p.x + r.w ? r.p.x + r.w : this.c.x; | ||
const tY = this.c.y < r.p.y ? r.p.y : this.c.y > r.p.y + r.h ? r.p.y + r.h : this.c.y; | ||
const d = Point.distance(this.c, new Point(tX, tY)); | ||
return d <= this.r; | ||
} | ||
project(p) { | ||
const proj = p.subtract(this.c).unitVector.scale(this.r); | ||
return Point.sum(this.c, proj); | ||
} | ||
at(t) { | ||
const a = TWO_PI * t; | ||
return this.c.shift(this.r * Math.cos(a), this.r * Math.sin(a)); | ||
} | ||
offset(p) { | ||
return rad(p, this.c) / TWO_PI; | ||
} | ||
contains(p) { | ||
return Point.distance(p, this.c) <= this.r; | ||
} | ||
transform(m) { | ||
const scale = Math.abs(m[0][0]) + Math.abs(m[1][1]); | ||
return new Circle(this.c.transform(m), this.r * scale / 2); | ||
} | ||
rotate(a, c = ORIGIN) { | ||
if ((0, import_fermat4.nearlyEquals)(a, 0)) | ||
return this; | ||
return new Circle(this.c.rotate(a, c), this.r); | ||
} | ||
reflect(l) { | ||
return new Circle(this.c.reflect(l), this.r); | ||
} | ||
scale(sx, sy = sx) { | ||
return new Circle(this.c.scale(sx, sy), this.r * (sx + sy) / 2); | ||
} | ||
shift(x, y = x) { | ||
return new Circle(this.c.shift(x, y), this.r); | ||
} | ||
translate(p) { | ||
return this.shift(p.x, p.y); | ||
} | ||
equals(other, tolerance) { | ||
return (0, import_fermat4.nearlyEquals)(this.r, other.r, tolerance) && this.c.equals(other.c, tolerance); | ||
} | ||
toString() { | ||
return `circle(${this.c},${this.r})`; | ||
} | ||
}; | ||
// src/arc.ts | ||
var Arc = class { | ||
constructor(c, start, angle) { | ||
this.c = c; | ||
this.start = start; | ||
this.angle = angle; | ||
this.type = "arc"; | ||
} | ||
get circle() { | ||
return new Circle(this.c, this.radius); | ||
} | ||
get radius() { | ||
return Point.distance(this.c, this.start); | ||
} | ||
get end() { | ||
return this.start.rotate(this.angle, this.c); | ||
} | ||
get startAngle() { | ||
return rad(this.start, this.c); | ||
} | ||
contract(p) { | ||
return new this.constructor(this.c, this.at(p / 2), this.angle * (1 - p)); | ||
} | ||
get minor() { | ||
if (this.angle <= Math.PI) | ||
return this; | ||
return new this.constructor(this.c, this.end, TWO_PI - this.angle); | ||
} | ||
get major() { | ||
if (this.angle >= Math.PI) | ||
return this; | ||
return new this.constructor(this.c, this.end, TWO_PI - this.angle); | ||
} | ||
get center() { | ||
return this.at(0.5); | ||
} | ||
project(p) { | ||
const start = this.startAngle; | ||
const end = start + this.angle; | ||
let angle = rad(p, this.c); | ||
if (end > TWO_PI && angle < end - TWO_PI) | ||
angle += TWO_PI; | ||
angle = (0, import_fermat5.clamp)(angle, start, end); | ||
return this.c.shift(this.radius, 0).rotate(angle, this.c); | ||
} | ||
at(t) { | ||
return this.start.rotate(this.angle * t, this.c); | ||
} | ||
offset(p) { | ||
return new Angle(this.start, this.c, p).rad / this.angle; | ||
} | ||
contains(p) { | ||
return p.equals(this.project(p)); | ||
} | ||
transform(m) { | ||
return new this.constructor(this.c.transform(m), this.start.transform(m), this.angle); | ||
} | ||
rotate(a, c = ORIGIN) { | ||
if ((0, import_fermat5.nearlyEquals)(a, 0)) | ||
return this; | ||
return new this.constructor(this.c.rotate(a, c), this.start.rotate(a, c), this.angle); | ||
} | ||
reflect(l) { | ||
return new this.constructor(this.c.reflect(l), this.start.reflect(l), this.angle); | ||
} | ||
scale(sx, sy = sx) { | ||
return new this.constructor(this.c.scale(sx, sy), this.start.scale(sx, sy), this.angle); | ||
} | ||
shift(x, y = x) { | ||
return new this.constructor(this.c.shift(x, y), this.start.shift(x, y), this.angle); | ||
} | ||
translate(p) { | ||
return this.shift(p.x, p.y); | ||
} | ||
equals() { | ||
return false; | ||
} | ||
toString() { | ||
return `arc(${this.c},${this.start},${this.angle})`; | ||
} | ||
}; | ||
var Sector = class extends Arc { | ||
constructor() { | ||
super(...arguments); | ||
this.type = "sector"; | ||
} | ||
contains(p) { | ||
return Point.distance(p, this.c) <= this.radius && new Angle(this.start, this.c, p).rad <= this.angle; | ||
} | ||
toString() { | ||
return `sector(${this.c},${this.start},${this.angle})`; | ||
} | ||
}; | ||
// src/angle.ts | ||
@@ -519,3 +623,3 @@ var RAD_TO_DEG = 180 / Math.PI; | ||
get isRight() { | ||
return (0, import_fermat5.nearlyEquals)(this.rad, Math.PI / 2, Math.PI / 360); | ||
return (0, import_fermat6.nearlyEquals)(this.rad, Math.PI / 2, Math.PI / 360); | ||
} | ||
@@ -548,2 +652,5 @@ get bisector() { | ||
} | ||
offset() { | ||
return 0; | ||
} | ||
contains() { | ||
@@ -556,3 +663,3 @@ return false; | ||
rotate(a, c) { | ||
if ((0, import_fermat5.nearlyEquals)(a, 0)) | ||
if ((0, import_fermat6.nearlyEquals)(a, 0)) | ||
return this; | ||
@@ -576,2 +683,5 @@ return new Angle(this.a.rotate(a, c), this.b.rotate(a, c), this.c.rotate(a, c)); | ||
} | ||
toString() { | ||
return `angle(${this.a},${this.b},${this.c})`; | ||
} | ||
}; | ||
@@ -591,3 +701,3 @@ | ||
var import_core2 = require("@mathigon/core"); | ||
var import_fermat6 = require("@mathigon/fermat"); | ||
var import_fermat7 = require("@mathigon/fermat"); | ||
var PRECISION = 1e-6; | ||
@@ -611,4 +721,4 @@ function pointAboveOrOnLine(pt, left, right) { | ||
function pointsCompare(p1, p2) { | ||
if ((0, import_fermat6.nearlyEquals)(p1.x, p2.x)) { | ||
return (0, import_fermat6.nearlyEquals)(p1.y, p2.y) ? 0 : p1.y < p2.y ? -1 : 1; | ||
if ((0, import_fermat7.nearlyEquals)(p1.x, p2.x)) { | ||
return (0, import_fermat7.nearlyEquals)(p1.y, p2.y) ? 0 : p1.y < p2.y ? -1 : 1; | ||
} | ||
@@ -634,3 +744,3 @@ return p1.x < p2.x ? -1 : 1; | ||
const axb = adx * bdy - ady * bdx; | ||
if ((0, import_fermat6.nearlyEquals)(axb, 0)) | ||
if ((0, import_fermat7.nearlyEquals)(axb, 0)) | ||
return false; | ||
@@ -1034,71 +1144,2 @@ const dx = a0.x - b0.x; | ||
// src/circle.ts | ||
var import_fermat7 = require("@mathigon/fermat"); | ||
var Circle = class { | ||
constructor(c = ORIGIN, r = 1) { | ||
this.c = c; | ||
this.r = r; | ||
this.type = "circle"; | ||
} | ||
get circumference() { | ||
return TWO_PI * this.r; | ||
} | ||
get area() { | ||
return Math.PI * this.r ** 2; | ||
} | ||
get arc() { | ||
const start = this.c.shift(this.r, 0); | ||
return new Arc(this.c, start, TWO_PI); | ||
} | ||
tangentAt(t) { | ||
const p1 = this.at(t); | ||
const p2 = this.c.rotate(Math.PI / 2, p1); | ||
return new Line(p1, p2); | ||
} | ||
collision(r) { | ||
const tX = this.c.x < r.p.x ? r.p.x : this.c.x > r.p.x + r.w ? r.p.x + r.w : this.c.x; | ||
const tY = this.c.y < r.p.y ? r.p.y : this.c.y > r.p.y + r.h ? r.p.y + r.h : this.c.y; | ||
const d = Point.distance(this.c, new Point(tX, tY)); | ||
return d <= this.r; | ||
} | ||
project(p) { | ||
const proj = p.subtract(this.c).unitVector.scale(this.r); | ||
return Point.sum(this.c, proj); | ||
} | ||
at(t) { | ||
const a = TWO_PI * t; | ||
return this.c.shift(this.r * Math.cos(a), this.r * Math.sin(a)); | ||
} | ||
contains(p) { | ||
return Point.distance(p, this.c) <= this.r; | ||
} | ||
transform(m) { | ||
const scale = Math.abs(m[0][0]) + Math.abs(m[1][1]); | ||
return new Circle(this.c.transform(m), this.r * scale / 2); | ||
} | ||
rotate(a, c = ORIGIN) { | ||
if ((0, import_fermat7.nearlyEquals)(a, 0)) | ||
return this; | ||
return new Circle(this.c.rotate(a, c), this.r); | ||
} | ||
reflect(l) { | ||
return new Circle(this.c.reflect(l), this.r); | ||
} | ||
scale(sx, sy = sx) { | ||
return new Circle(this.c.scale(sx, sy), this.r * (sx + sy) / 2); | ||
} | ||
shift(x, y = x) { | ||
return new Circle(this.c.shift(x, y), this.r); | ||
} | ||
translate(p) { | ||
return this.shift(p.x, p.y); | ||
} | ||
equals(other, tolerance) { | ||
return (0, import_fermat7.nearlyEquals)(this.r, other.r, tolerance) && this.c.equals(other.c, tolerance); | ||
} | ||
toString() { | ||
return `Circle(c: ${this.c}, r: ${this.r})`; | ||
} | ||
}; | ||
// src/intersection.ts | ||
@@ -1159,7 +1200,9 @@ var import_core3 = require("@mathigon/core"); | ||
function liesOnRay(r, p) { | ||
if ((0, import_fermat8.nearlyEquals)(r.p1.x, r.p2.x)) { | ||
if ((0, import_fermat8.nearlyEquals)(r.p1.x, r.p2.x)) | ||
return (p.y - r.p1.y) / (r.p2.y - r.p1.y) > 0; | ||
} | ||
return (p.x - r.p1.x) / (r.p2.x - r.p1.x) > 0; | ||
} | ||
function liesOnArc(a, p) { | ||
return (0, import_fermat8.isBetween)(a.offset(p), 0, 1); | ||
} | ||
function lineLineIntersection(l1, l2) { | ||
@@ -1217,17 +1260,20 @@ const d1x = l1.p1.x - l1.p2.x; | ||
let results = []; | ||
const a1 = isArc(a) ? a.circle : a; | ||
const b1 = isArc(b) ? b.circle : b; | ||
if (isLineLike(a) && isLineLike(b)) { | ||
results = lineLineIntersection(a, b); | ||
} else if (isLineLike(a) && isCircle(b)) { | ||
results = lineCircleIntersection(a, b); | ||
} else if (isCircle(a) && isLineLike(b)) { | ||
results = lineCircleIntersection(b, a); | ||
} else if (isCircle(a) && isCircle(b)) { | ||
results = circleCircleIntersection(a, b); | ||
} else if (isLineLike(a1) && isCircle(b1)) { | ||
results = lineCircleIntersection(a1, b1); | ||
} else if (isCircle(a1) && isLineLike(b1)) { | ||
results = lineCircleIntersection(b1, a1); | ||
} else if (isCircle(a1) && isCircle(b1)) { | ||
results = circleCircleIntersection(a1, b1); | ||
} | ||
for (const x of [a, b]) { | ||
if (x.type === "segment") { | ||
if (isSegment(x)) | ||
results = results.filter((i) => liesOnSegment(x, i)); | ||
} | ||
if (x.type === "ray") | ||
if (isRay(x)) | ||
results = results.filter((i) => liesOnRay(x, i)); | ||
if (isArc(x)) | ||
results = results.filter((i) => liesOnArc(x, i)); | ||
} | ||
@@ -1293,7 +1339,7 @@ return results; | ||
get edges() { | ||
const p = this.points; | ||
const n = p.length; | ||
const n = this.points.length; | ||
const edges = []; | ||
for (let i = 0; i < n; ++i) | ||
edges.push(new Segment(p[i], p[(i + 1) % n])); | ||
for (let i = 0; i < n; ++i) { | ||
edges.push(new Segment(this.points[i], this.points[(i + 1) % n])); | ||
} | ||
return edges; | ||
@@ -1391,17 +1437,27 @@ } | ||
at(t) { | ||
return Point.interpolateList([...this.points, this.points[0]], t); | ||
} | ||
project(p) { | ||
let q = void 0; | ||
let d = Infinity; | ||
if (t < 0) | ||
t += Math.floor(t); | ||
const offset = t * this.circumference; | ||
let cum = 0; | ||
for (const e of this.edges) { | ||
const q1 = e.project(p); | ||
const d1 = Point.distance(p, q1); | ||
if (d1 < d) { | ||
q = q1; | ||
d = d1; | ||
} | ||
const l = e.length; | ||
if (cum + l > offset) | ||
return e.at((offset - cum) / l); | ||
cum += l; | ||
} | ||
return q || this.points[0]; | ||
return this.points[0]; | ||
} | ||
offset(p) { | ||
const edges = this.edges; | ||
const proj = findClosest(p, this.edges) || [this.points[0], 0]; | ||
let offset = 0; | ||
for (let i = 0; i < proj[1]; ++i) | ||
offset += edges[i].length; | ||
offset += edges[proj[1]].offset(p) * edges[proj[1]].length; | ||
return offset / this.circumference; | ||
} | ||
project(p) { | ||
const proj = findClosest(p, this.edges); | ||
return proj ? proj[0] : this.points[0]; | ||
} | ||
centerAt(on = ORIGIN) { | ||
@@ -1447,2 +1503,5 @@ return this.translate(on.subtract(this.centroid)); | ||
} | ||
toString() { | ||
return `polygon(${this.points.join(",")})`; | ||
} | ||
}; | ||
@@ -1454,2 +1513,5 @@ var Polyline = class extends Polygon { | ||
} | ||
get circumference() { | ||
return this.length; | ||
} | ||
get length() { | ||
@@ -1469,2 +1531,5 @@ let length = 0; | ||
} | ||
toString() { | ||
return `polyline(${this.points.join(",")})`; | ||
} | ||
}; | ||
@@ -1567,5 +1632,8 @@ var Triangle = class extends Polygon { | ||
} | ||
at(_t) { | ||
return this.p; | ||
at(t) { | ||
return this.polygon.at(t); | ||
} | ||
offset(p) { | ||
return this.polygon.offset(p); | ||
} | ||
transform(m) { | ||
@@ -1595,3 +1663,3 @@ return this.polygon.transform(m); | ||
toString() { | ||
return `Rectangle(p: ${this.p}, w: ${this.w}, h: ${this.h})`; | ||
return `rectangle(${this.p},${this.w},${this.h})`; | ||
} | ||
@@ -1863,2 +1931,5 @@ }; | ||
} | ||
offset(p) { | ||
return 0.5; | ||
} | ||
contains(_p) { | ||
@@ -1888,4 +1959,7 @@ return false; | ||
} | ||
toString() { | ||
return `ellipse(${this.c},${this.a},${this.b})`; | ||
} | ||
}; | ||
module.exports = __toCommonJS(src_exports); | ||
//# sourceMappingURL=index.cjs.js.map |
@@ -14,2 +14,2 @@ export * from './angle'; | ||
export * from './types'; | ||
export { GeoElement, GeoShape, rad, SimplePoint, TransformMatrix } from './utilities'; | ||
export { GeoElement, GeoShape, rad, SimplePoint, TransformMatrix, TWO_PI } from './utilities'; |
// src/angle.ts | ||
import { nearlyEquals as nearlyEquals4 } from "@mathigon/fermat"; | ||
import { nearlyEquals as nearlyEquals5 } from "@mathigon/fermat"; | ||
// src/arc.ts | ||
import { clamp as clamp2, nearlyEquals as nearlyEquals2 } from "@mathigon/fermat"; | ||
import { clamp as clamp3, nearlyEquals as nearlyEquals4 } from "@mathigon/fermat"; | ||
// src/circle.ts | ||
import { nearlyEquals as nearlyEquals3 } from "@mathigon/fermat"; | ||
// src/line.ts | ||
import { clamp as clamp2, isBetween, nearlyEquals as nearlyEquals2 } from "@mathigon/fermat"; | ||
// src/point.ts | ||
@@ -18,2 +24,17 @@ import { total } from "@mathigon/core"; | ||
} | ||
function findClosest(p, items) { | ||
let q = void 0; | ||
let d = Infinity; | ||
let index = -1; | ||
for (const [i, e] of items.entries()) { | ||
const q1 = e.project(p); | ||
const d1 = Point.distance(p, q1); | ||
if (d1 < d) { | ||
q = q1; | ||
d = d1; | ||
index = i; | ||
} | ||
} | ||
return q ? [q, index] : void 0; | ||
} | ||
@@ -170,3 +191,3 @@ // src/point.ts | ||
toString() { | ||
return `Point(x: ${this.x}, y: ${this.y})`; | ||
return `point(${this.x},${this.y})`; | ||
} | ||
@@ -176,86 +197,3 @@ }; | ||
// src/arc.ts | ||
var Arc = class { | ||
constructor(c, start, angle) { | ||
this.c = c; | ||
this.start = start; | ||
this.angle = angle; | ||
this.type = "arc"; | ||
} | ||
get radius() { | ||
return Point.distance(this.c, this.start); | ||
} | ||
get end() { | ||
return this.start.rotate(this.angle, this.c); | ||
} | ||
get startAngle() { | ||
return rad(this.start, this.c); | ||
} | ||
contract(p) { | ||
return new this.constructor(this.c, this.at(p / 2), this.angle * (1 - p)); | ||
} | ||
get minor() { | ||
if (this.angle <= Math.PI) | ||
return this; | ||
return new this.constructor(this.c, this.end, TWO_PI - this.angle); | ||
} | ||
get major() { | ||
if (this.angle >= Math.PI) | ||
return this; | ||
return new this.constructor(this.c, this.end, TWO_PI - this.angle); | ||
} | ||
get center() { | ||
return this.at(0.5); | ||
} | ||
project(p) { | ||
const start = this.startAngle; | ||
const end = start + this.angle; | ||
let angle = rad(p, this.c); | ||
if (end > TWO_PI && angle < end - TWO_PI) | ||
angle += TWO_PI; | ||
angle = clamp2(angle, start, end); | ||
return this.c.shift(this.radius, 0).rotate(angle, this.c); | ||
} | ||
at(t) { | ||
return this.start.rotate(this.angle * t, this.c); | ||
} | ||
contains(p) { | ||
return p.equals(this.project(p)); | ||
} | ||
transform(m) { | ||
return new this.constructor(this.c.transform(m), this.start.transform(m), this.angle); | ||
} | ||
rotate(a, c = ORIGIN) { | ||
if (nearlyEquals2(a, 0)) | ||
return this; | ||
return new this.constructor(this.c.rotate(a, c), this.start.rotate(a, c), this.angle); | ||
} | ||
reflect(l) { | ||
return new this.constructor(this.c.reflect(l), this.start.reflect(l), this.angle); | ||
} | ||
scale(sx, sy = sx) { | ||
return new this.constructor(this.c.scale(sx, sy), this.start.scale(sx, sy), this.angle); | ||
} | ||
shift(x, y = x) { | ||
return new this.constructor(this.c.shift(x, y), this.start.shift(x, y), this.angle); | ||
} | ||
translate(p) { | ||
return this.shift(p.x, p.y); | ||
} | ||
equals() { | ||
return false; | ||
} | ||
}; | ||
var Sector = class extends Arc { | ||
constructor() { | ||
super(...arguments); | ||
this.type = "sector"; | ||
} | ||
contains(p) { | ||
return Point.distance(p, this.c) <= this.radius && new Angle(this.start, this.c, p).rad <= this.angle; | ||
} | ||
}; | ||
// src/line.ts | ||
import { clamp as clamp3, isBetween, nearlyEquals as nearlyEquals3 } from "@mathigon/fermat"; | ||
var Line = class { | ||
@@ -320,3 +258,3 @@ constructor(p1, p2) { | ||
const d = b.x * a.y - b.y * a.x; | ||
return nearlyEquals3(d, 0, tolerance) ? 0 : Math.sign(d); | ||
return nearlyEquals2(d, 0, tolerance) ? 0 : Math.sign(d); | ||
} | ||
@@ -333,3 +271,3 @@ contains(p, tolerance) { | ||
rotate(a, c = ORIGIN) { | ||
if (nearlyEquals3(a, 0)) | ||
if (nearlyEquals2(a, 0)) | ||
return this; | ||
@@ -354,3 +292,3 @@ return new this.constructor(this.p1.rotate(a, c), this.p2.rotate(a, c)); | ||
toString() { | ||
return `Line(p1: ${this.p1}, p2: ${this.p2})`; | ||
return `line(${this.p1},${this.p2})`; | ||
} | ||
@@ -372,3 +310,3 @@ }; | ||
toString() { | ||
return `Ray(p1: ${this.p1}, p2: ${this.p2})`; | ||
return `ray(${this.p1},${this.p2})`; | ||
} | ||
@@ -386,3 +324,3 @@ }; | ||
return true; | ||
if (nearlyEquals3(this.p1.x, this.p2.x, tolerance)) { | ||
if (nearlyEquals2(this.p1.x, this.p2.x, tolerance)) { | ||
return isBetween(p.y, this.p1.y, this.p2.y); | ||
@@ -399,3 +337,3 @@ } else { | ||
const b = Point.difference(p, this.p1); | ||
const q = clamp3(Point.dot(a, b) / this.lengthSquared, 0, 1); | ||
const q = clamp2(Point.dot(a, b) / this.lengthSquared, 0, 1); | ||
return Point.sum(this.p1, a.scale(q)); | ||
@@ -412,6 +350,171 @@ } | ||
toString() { | ||
return `Segment(p1: ${this.p1}, p2: ${this.p2})`; | ||
return `segment(${this.p1},${this.p2})`; | ||
} | ||
}; | ||
// src/circle.ts | ||
var Circle = class { | ||
constructor(c = ORIGIN, r = 1) { | ||
this.c = c; | ||
this.r = r; | ||
this.type = "circle"; | ||
} | ||
get circumference() { | ||
return TWO_PI * this.r; | ||
} | ||
get area() { | ||
return Math.PI * this.r ** 2; | ||
} | ||
get arc() { | ||
const start = this.c.shift(this.r, 0); | ||
return new Arc(this.c, start, TWO_PI); | ||
} | ||
tangentAt(t) { | ||
const p1 = this.at(t); | ||
const p2 = this.c.rotate(Math.PI / 2, p1); | ||
return new Line(p1, p2); | ||
} | ||
collision(r) { | ||
const tX = this.c.x < r.p.x ? r.p.x : this.c.x > r.p.x + r.w ? r.p.x + r.w : this.c.x; | ||
const tY = this.c.y < r.p.y ? r.p.y : this.c.y > r.p.y + r.h ? r.p.y + r.h : this.c.y; | ||
const d = Point.distance(this.c, new Point(tX, tY)); | ||
return d <= this.r; | ||
} | ||
project(p) { | ||
const proj = p.subtract(this.c).unitVector.scale(this.r); | ||
return Point.sum(this.c, proj); | ||
} | ||
at(t) { | ||
const a = TWO_PI * t; | ||
return this.c.shift(this.r * Math.cos(a), this.r * Math.sin(a)); | ||
} | ||
offset(p) { | ||
return rad(p, this.c) / TWO_PI; | ||
} | ||
contains(p) { | ||
return Point.distance(p, this.c) <= this.r; | ||
} | ||
transform(m) { | ||
const scale = Math.abs(m[0][0]) + Math.abs(m[1][1]); | ||
return new Circle(this.c.transform(m), this.r * scale / 2); | ||
} | ||
rotate(a, c = ORIGIN) { | ||
if (nearlyEquals3(a, 0)) | ||
return this; | ||
return new Circle(this.c.rotate(a, c), this.r); | ||
} | ||
reflect(l) { | ||
return new Circle(this.c.reflect(l), this.r); | ||
} | ||
scale(sx, sy = sx) { | ||
return new Circle(this.c.scale(sx, sy), this.r * (sx + sy) / 2); | ||
} | ||
shift(x, y = x) { | ||
return new Circle(this.c.shift(x, y), this.r); | ||
} | ||
translate(p) { | ||
return this.shift(p.x, p.y); | ||
} | ||
equals(other, tolerance) { | ||
return nearlyEquals3(this.r, other.r, tolerance) && this.c.equals(other.c, tolerance); | ||
} | ||
toString() { | ||
return `circle(${this.c},${this.r})`; | ||
} | ||
}; | ||
// src/arc.ts | ||
var Arc = class { | ||
constructor(c, start, angle) { | ||
this.c = c; | ||
this.start = start; | ||
this.angle = angle; | ||
this.type = "arc"; | ||
} | ||
get circle() { | ||
return new Circle(this.c, this.radius); | ||
} | ||
get radius() { | ||
return Point.distance(this.c, this.start); | ||
} | ||
get end() { | ||
return this.start.rotate(this.angle, this.c); | ||
} | ||
get startAngle() { | ||
return rad(this.start, this.c); | ||
} | ||
contract(p) { | ||
return new this.constructor(this.c, this.at(p / 2), this.angle * (1 - p)); | ||
} | ||
get minor() { | ||
if (this.angle <= Math.PI) | ||
return this; | ||
return new this.constructor(this.c, this.end, TWO_PI - this.angle); | ||
} | ||
get major() { | ||
if (this.angle >= Math.PI) | ||
return this; | ||
return new this.constructor(this.c, this.end, TWO_PI - this.angle); | ||
} | ||
get center() { | ||
return this.at(0.5); | ||
} | ||
project(p) { | ||
const start = this.startAngle; | ||
const end = start + this.angle; | ||
let angle = rad(p, this.c); | ||
if (end > TWO_PI && angle < end - TWO_PI) | ||
angle += TWO_PI; | ||
angle = clamp3(angle, start, end); | ||
return this.c.shift(this.radius, 0).rotate(angle, this.c); | ||
} | ||
at(t) { | ||
return this.start.rotate(this.angle * t, this.c); | ||
} | ||
offset(p) { | ||
return new Angle(this.start, this.c, p).rad / this.angle; | ||
} | ||
contains(p) { | ||
return p.equals(this.project(p)); | ||
} | ||
transform(m) { | ||
return new this.constructor(this.c.transform(m), this.start.transform(m), this.angle); | ||
} | ||
rotate(a, c = ORIGIN) { | ||
if (nearlyEquals4(a, 0)) | ||
return this; | ||
return new this.constructor(this.c.rotate(a, c), this.start.rotate(a, c), this.angle); | ||
} | ||
reflect(l) { | ||
return new this.constructor(this.c.reflect(l), this.start.reflect(l), this.angle); | ||
} | ||
scale(sx, sy = sx) { | ||
return new this.constructor(this.c.scale(sx, sy), this.start.scale(sx, sy), this.angle); | ||
} | ||
shift(x, y = x) { | ||
return new this.constructor(this.c.shift(x, y), this.start.shift(x, y), this.angle); | ||
} | ||
translate(p) { | ||
return this.shift(p.x, p.y); | ||
} | ||
equals() { | ||
return false; | ||
} | ||
toString() { | ||
return `arc(${this.c},${this.start},${this.angle})`; | ||
} | ||
}; | ||
var Sector = class extends Arc { | ||
constructor() { | ||
super(...arguments); | ||
this.type = "sector"; | ||
} | ||
contains(p) { | ||
return Point.distance(p, this.c) <= this.radius && new Angle(this.start, this.c, p).rad <= this.angle; | ||
} | ||
toString() { | ||
return `sector(${this.c},${this.start},${this.angle})`; | ||
} | ||
}; | ||
// src/angle.ts | ||
@@ -453,3 +556,3 @@ var RAD_TO_DEG = 180 / Math.PI; | ||
get isRight() { | ||
return nearlyEquals4(this.rad, Math.PI / 2, Math.PI / 360); | ||
return nearlyEquals5(this.rad, Math.PI / 2, Math.PI / 360); | ||
} | ||
@@ -482,2 +585,5 @@ get bisector() { | ||
} | ||
offset() { | ||
return 0; | ||
} | ||
contains() { | ||
@@ -490,3 +596,3 @@ return false; | ||
rotate(a, c) { | ||
if (nearlyEquals4(a, 0)) | ||
if (nearlyEquals5(a, 0)) | ||
return this; | ||
@@ -510,2 +616,5 @@ return new Angle(this.a.rotate(a, c), this.b.rotate(a, c), this.c.rotate(a, c)); | ||
} | ||
toString() { | ||
return `angle(${this.a},${this.b},${this.c})`; | ||
} | ||
}; | ||
@@ -525,3 +634,3 @@ | ||
import { last } from "@mathigon/core"; | ||
import { nearlyEquals as nearlyEquals5 } from "@mathigon/fermat"; | ||
import { nearlyEquals as nearlyEquals6 } from "@mathigon/fermat"; | ||
var PRECISION = 1e-6; | ||
@@ -545,4 +654,4 @@ function pointAboveOrOnLine(pt, left, right) { | ||
function pointsCompare(p1, p2) { | ||
if (nearlyEquals5(p1.x, p2.x)) { | ||
return nearlyEquals5(p1.y, p2.y) ? 0 : p1.y < p2.y ? -1 : 1; | ||
if (nearlyEquals6(p1.x, p2.x)) { | ||
return nearlyEquals6(p1.y, p2.y) ? 0 : p1.y < p2.y ? -1 : 1; | ||
} | ||
@@ -568,3 +677,3 @@ return p1.x < p2.x ? -1 : 1; | ||
const axb = adx * bdy - ady * bdx; | ||
if (nearlyEquals5(axb, 0)) | ||
if (nearlyEquals6(axb, 0)) | ||
return false; | ||
@@ -968,71 +1077,2 @@ const dx = a0.x - b0.x; | ||
// src/circle.ts | ||
import { nearlyEquals as nearlyEquals6 } from "@mathigon/fermat"; | ||
var Circle = class { | ||
constructor(c = ORIGIN, r = 1) { | ||
this.c = c; | ||
this.r = r; | ||
this.type = "circle"; | ||
} | ||
get circumference() { | ||
return TWO_PI * this.r; | ||
} | ||
get area() { | ||
return Math.PI * this.r ** 2; | ||
} | ||
get arc() { | ||
const start = this.c.shift(this.r, 0); | ||
return new Arc(this.c, start, TWO_PI); | ||
} | ||
tangentAt(t) { | ||
const p1 = this.at(t); | ||
const p2 = this.c.rotate(Math.PI / 2, p1); | ||
return new Line(p1, p2); | ||
} | ||
collision(r) { | ||
const tX = this.c.x < r.p.x ? r.p.x : this.c.x > r.p.x + r.w ? r.p.x + r.w : this.c.x; | ||
const tY = this.c.y < r.p.y ? r.p.y : this.c.y > r.p.y + r.h ? r.p.y + r.h : this.c.y; | ||
const d = Point.distance(this.c, new Point(tX, tY)); | ||
return d <= this.r; | ||
} | ||
project(p) { | ||
const proj = p.subtract(this.c).unitVector.scale(this.r); | ||
return Point.sum(this.c, proj); | ||
} | ||
at(t) { | ||
const a = TWO_PI * t; | ||
return this.c.shift(this.r * Math.cos(a), this.r * Math.sin(a)); | ||
} | ||
contains(p) { | ||
return Point.distance(p, this.c) <= this.r; | ||
} | ||
transform(m) { | ||
const scale = Math.abs(m[0][0]) + Math.abs(m[1][1]); | ||
return new Circle(this.c.transform(m), this.r * scale / 2); | ||
} | ||
rotate(a, c = ORIGIN) { | ||
if (nearlyEquals6(a, 0)) | ||
return this; | ||
return new Circle(this.c.rotate(a, c), this.r); | ||
} | ||
reflect(l) { | ||
return new Circle(this.c.reflect(l), this.r); | ||
} | ||
scale(sx, sy = sx) { | ||
return new Circle(this.c.scale(sx, sy), this.r * (sx + sy) / 2); | ||
} | ||
shift(x, y = x) { | ||
return new Circle(this.c.shift(x, y), this.r); | ||
} | ||
translate(p) { | ||
return this.shift(p.x, p.y); | ||
} | ||
equals(other, tolerance) { | ||
return nearlyEquals6(this.r, other.r, tolerance) && this.c.equals(other.c, tolerance); | ||
} | ||
toString() { | ||
return `Circle(c: ${this.c}, r: ${this.r})`; | ||
} | ||
}; | ||
// src/intersection.ts | ||
@@ -1093,7 +1133,9 @@ import { flatten } from "@mathigon/core"; | ||
function liesOnRay(r, p) { | ||
if (nearlyEquals7(r.p1.x, r.p2.x)) { | ||
if (nearlyEquals7(r.p1.x, r.p2.x)) | ||
return (p.y - r.p1.y) / (r.p2.y - r.p1.y) > 0; | ||
} | ||
return (p.x - r.p1.x) / (r.p2.x - r.p1.x) > 0; | ||
} | ||
function liesOnArc(a, p) { | ||
return isBetween2(a.offset(p), 0, 1); | ||
} | ||
function lineLineIntersection(l1, l2) { | ||
@@ -1151,17 +1193,20 @@ const d1x = l1.p1.x - l1.p2.x; | ||
let results = []; | ||
const a1 = isArc(a) ? a.circle : a; | ||
const b1 = isArc(b) ? b.circle : b; | ||
if (isLineLike(a) && isLineLike(b)) { | ||
results = lineLineIntersection(a, b); | ||
} else if (isLineLike(a) && isCircle(b)) { | ||
results = lineCircleIntersection(a, b); | ||
} else if (isCircle(a) && isLineLike(b)) { | ||
results = lineCircleIntersection(b, a); | ||
} else if (isCircle(a) && isCircle(b)) { | ||
results = circleCircleIntersection(a, b); | ||
} else if (isLineLike(a1) && isCircle(b1)) { | ||
results = lineCircleIntersection(a1, b1); | ||
} else if (isCircle(a1) && isLineLike(b1)) { | ||
results = lineCircleIntersection(b1, a1); | ||
} else if (isCircle(a1) && isCircle(b1)) { | ||
results = circleCircleIntersection(a1, b1); | ||
} | ||
for (const x of [a, b]) { | ||
if (x.type === "segment") { | ||
if (isSegment(x)) | ||
results = results.filter((i) => liesOnSegment(x, i)); | ||
} | ||
if (x.type === "ray") | ||
if (isRay(x)) | ||
results = results.filter((i) => liesOnRay(x, i)); | ||
if (isArc(x)) | ||
results = results.filter((i) => liesOnArc(x, i)); | ||
} | ||
@@ -1227,7 +1272,7 @@ return results; | ||
get edges() { | ||
const p = this.points; | ||
const n = p.length; | ||
const n = this.points.length; | ||
const edges = []; | ||
for (let i = 0; i < n; ++i) | ||
edges.push(new Segment(p[i], p[(i + 1) % n])); | ||
for (let i = 0; i < n; ++i) { | ||
edges.push(new Segment(this.points[i], this.points[(i + 1) % n])); | ||
} | ||
return edges; | ||
@@ -1325,17 +1370,27 @@ } | ||
at(t) { | ||
return Point.interpolateList([...this.points, this.points[0]], t); | ||
} | ||
project(p) { | ||
let q = void 0; | ||
let d = Infinity; | ||
if (t < 0) | ||
t += Math.floor(t); | ||
const offset = t * this.circumference; | ||
let cum = 0; | ||
for (const e of this.edges) { | ||
const q1 = e.project(p); | ||
const d1 = Point.distance(p, q1); | ||
if (d1 < d) { | ||
q = q1; | ||
d = d1; | ||
} | ||
const l = e.length; | ||
if (cum + l > offset) | ||
return e.at((offset - cum) / l); | ||
cum += l; | ||
} | ||
return q || this.points[0]; | ||
return this.points[0]; | ||
} | ||
offset(p) { | ||
const edges = this.edges; | ||
const proj = findClosest(p, this.edges) || [this.points[0], 0]; | ||
let offset = 0; | ||
for (let i = 0; i < proj[1]; ++i) | ||
offset += edges[i].length; | ||
offset += edges[proj[1]].offset(p) * edges[proj[1]].length; | ||
return offset / this.circumference; | ||
} | ||
project(p) { | ||
const proj = findClosest(p, this.edges); | ||
return proj ? proj[0] : this.points[0]; | ||
} | ||
centerAt(on = ORIGIN) { | ||
@@ -1381,2 +1436,5 @@ return this.translate(on.subtract(this.centroid)); | ||
} | ||
toString() { | ||
return `polygon(${this.points.join(",")})`; | ||
} | ||
}; | ||
@@ -1388,2 +1446,5 @@ var Polyline = class extends Polygon { | ||
} | ||
get circumference() { | ||
return this.length; | ||
} | ||
get length() { | ||
@@ -1403,2 +1464,5 @@ let length = 0; | ||
} | ||
toString() { | ||
return `polyline(${this.points.join(",")})`; | ||
} | ||
}; | ||
@@ -1501,5 +1565,8 @@ var Triangle = class extends Polygon { | ||
} | ||
at(_t) { | ||
return this.p; | ||
at(t) { | ||
return this.polygon.at(t); | ||
} | ||
offset(p) { | ||
return this.polygon.offset(p); | ||
} | ||
transform(m) { | ||
@@ -1529,3 +1596,3 @@ return this.polygon.transform(m); | ||
toString() { | ||
return `Rectangle(p: ${this.p}, w: ${this.w}, h: ${this.h})`; | ||
return `rectangle(${this.p},${this.w},${this.h})`; | ||
} | ||
@@ -1797,2 +1864,5 @@ }; | ||
} | ||
offset(p) { | ||
return 0.5; | ||
} | ||
contains(_p) { | ||
@@ -1822,2 +1892,5 @@ return false; | ||
} | ||
toString() { | ||
return `ellipse(${this.c},${this.a},${this.b})`; | ||
} | ||
}; | ||
@@ -1839,2 +1912,3 @@ export { | ||
Segment, | ||
TWO_PI, | ||
Triangle, | ||
@@ -1841,0 +1915,0 @@ angleSize, |
@@ -38,2 +38,3 @@ import { Circle } from './circle'; | ||
at(t: number): Point; | ||
offset(p: Point): number; | ||
project(p: Point): Point; | ||
@@ -50,2 +51,3 @@ /** Center this polygon on a given point or the origin */ | ||
equals(other: Polygon, tolerance?: number, oriented?: boolean): boolean; | ||
toString(): string; | ||
} | ||
@@ -55,5 +57,7 @@ /** A polyline defined by its vertex points. */ | ||
readonly type = "polyline"; | ||
get circumference(): number; | ||
get length(): number; | ||
/** @returns {Segment[]} */ | ||
get edges(): Segment[]; | ||
toString(): string; | ||
} | ||
@@ -60,0 +64,0 @@ /** A triangle defined by its three vertices. */ |
@@ -28,6 +28,7 @@ import { Line } from './line'; | ||
project(p: SimplePoint): Point; | ||
at(_t: number): Point; | ||
at(t: number): Point; | ||
offset(p: Point): number; | ||
transform(m: TransformMatrix): Polygon; | ||
/** Rotates this rectangle by a given angle (in radians), optionally around point `c`. */ | ||
rotate(a: number, c?: Point): this | Polygon; | ||
rotate(a: number, c?: Point): Polygon | this; | ||
reflect(l: Line): Polygon; | ||
@@ -34,0 +35,0 @@ scale(sx: number, sy?: number): Rectangle; |
@@ -17,2 +17,3 @@ import { Line } from './line'; | ||
equals(other: GeoElement, tolerance?: number, oriented?: boolean): boolean; | ||
toString(): string; | ||
} | ||
@@ -23,2 +24,3 @@ export interface GeoShape extends GeoElement { | ||
at(t: number): Point; | ||
offset(p: Point): number; | ||
rotate(angle: number, center?: SimplePoint): GeoShape; | ||
@@ -32,1 +34,2 @@ reflect(l: Line): GeoShape; | ||
export declare function rad(p: SimplePoint, c?: SimplePoint): number; | ||
export declare function findClosest(p: Point, items: GeoShape[]): [Point, number] | undefined; |
{ | ||
"name": "@mathigon/euclid", | ||
"version": "1.0.6", | ||
"version": "1.0.7", | ||
"license": "MIT", | ||
@@ -31,3 +31,3 @@ "homepage": "https://mathigon.io/euclid", | ||
"dependencies": { | ||
"@mathigon/core": "1.0.6", | ||
"@mathigon/core": "1.0.7", | ||
"@mathigon/fermat": "1.0.7" | ||
@@ -34,0 +34,0 @@ }, |
@@ -103,2 +103,6 @@ // ============================================================================= | ||
offset() { | ||
return 0; | ||
} | ||
contains() { | ||
@@ -139,2 +143,6 @@ return false; | ||
} | ||
toString() { | ||
return `angle(${this.a},${this.b},${this.c})`; | ||
} | ||
} |
@@ -9,2 +9,3 @@ // ============================================================================= | ||
import {Angle} from './angle'; | ||
import {Circle} from './circle'; | ||
import {Line} from './line'; | ||
@@ -23,2 +24,6 @@ import {ORIGIN, Point} from './point'; | ||
get circle() { | ||
return new Circle(this.c, this.radius); | ||
} | ||
get radius() { | ||
@@ -71,2 +76,6 @@ return Point.distance(this.c, this.start); | ||
offset(p: Point) { | ||
return new Angle(this.start, this.c, p).rad / this.angle; | ||
} | ||
contains(p: Point) { | ||
@@ -114,2 +123,6 @@ // TODO Is there a better way to do this? | ||
} | ||
toString() { | ||
return `arc(${this.c},${this.start},${this.angle})`; | ||
} | ||
} | ||
@@ -123,2 +136,6 @@ | ||
} | ||
toString() { | ||
return `sector(${this.c},${this.start},${this.angle})`; | ||
} | ||
} |
@@ -12,3 +12,3 @@ // ============================================================================= | ||
import {Rectangle} from './rectangle'; | ||
import {GeoShape, TransformMatrix, TWO_PI} from './utilities'; | ||
import {GeoShape, rad, TransformMatrix, TWO_PI} from './utilities'; | ||
@@ -63,2 +63,6 @@ | ||
offset(p: Point) { | ||
return rad(p, this.c) / TWO_PI; | ||
} | ||
contains(p: Point) { | ||
@@ -101,4 +105,4 @@ return Point.distance(p, this.c) <= this.r; | ||
toString() { | ||
return `Circle(c: ${this.c}, r: ${this.r})`; | ||
return `circle(${this.c},${this.r})`; | ||
} | ||
} |
@@ -73,2 +73,7 @@ // ============================================================================= | ||
offset(p: Point) { | ||
// TODO Implement | ||
return 0.5; | ||
} | ||
contains(_p: Point) { | ||
@@ -115,2 +120,6 @@ // TODO Implement | ||
} | ||
toString() { | ||
return `ellipse(${this.c},${this.a},${this.b})`; | ||
} | ||
} |
@@ -20,2 +20,2 @@ // ============================================================================= | ||
export * from './types'; | ||
export {GeoElement, GeoShape, rad, SimplePoint, TransformMatrix} from './utilities'; | ||
export {GeoElement, GeoShape, rad, SimplePoint, TransformMatrix, TWO_PI} from './utilities'; |
@@ -9,9 +9,13 @@ // ============================================================================= | ||
import {isBetween, nearlyEquals, square, subsets} from '@mathigon/fermat'; | ||
import {Arc} from './arc'; | ||
import {Circle} from './circle'; | ||
import {Line, Ray, Segment} from './line'; | ||
import {Point} from './point'; | ||
import {isCircle, isLineLike, isPolygonLike} from './types'; | ||
import {isArc, isCircle, isLineLike, isPolygonLike, isRay, isSegment} from './types'; | ||
import {GeoShape} from './utilities'; | ||
// ----------------------------------------------------------------------------- | ||
// Helper functions | ||
function liesOnSegment(s: Segment, p: Point) { | ||
@@ -23,8 +27,14 @@ if (nearlyEquals(s.p1.x, s.p2.x)) return isBetween(p.y, s.p1.y, s.p2.y); | ||
function liesOnRay(r: Ray, p: Point) { | ||
if (nearlyEquals(r.p1.x, r.p2.x)) { | ||
return (p.y - r.p1.y) / (r.p2.y - r.p1.y) > 0; | ||
} | ||
if (nearlyEquals(r.p1.x, r.p2.x)) return (p.y - r.p1.y) / (r.p2.y - r.p1.y) > 0; | ||
return (p.x - r.p1.x) / (r.p2.x - r.p1.x) > 0; | ||
} | ||
function liesOnArc(a: Arc, p: Point) { | ||
return isBetween(a.offset(p), 0, 1); | ||
} | ||
// ----------------------------------------------------------------------------- | ||
// Foundations | ||
function lineLineIntersection(l1: Line, l2: Line) { | ||
@@ -96,22 +106,26 @@ const d1x = l1.p1.x - l1.p2.x; | ||
/** Finds the intersection of two lines or circles. */ | ||
function simpleIntersection(a: Line|Circle, b: Line|Circle): Point[] { | ||
// ----------------------------------------------------------------------------- | ||
// Exported functions | ||
function simpleIntersection(a: Line|Circle|Arc, b: Line|Circle|Arc): Point[] { | ||
let results: Point[] = []; | ||
// TODO Handle Arcs and Rays | ||
const a1 = isArc(a) ? a.circle : a; | ||
const b1 = isArc(b) ? b.circle : b; | ||
if (isLineLike(a) && isLineLike(b)) { | ||
results = lineLineIntersection(a, b); | ||
} else if (isLineLike(a) && isCircle(b)) { | ||
results = lineCircleIntersection(a, b); | ||
} else if (isCircle(a) && isLineLike(b)) { | ||
results = lineCircleIntersection(b, a); | ||
} else if (isCircle(a) && isCircle(b)) { | ||
results = circleCircleIntersection(a, b); | ||
} else if (isLineLike(a1) && isCircle(b1)) { | ||
results = lineCircleIntersection(a1, b1); | ||
} else if (isCircle(a1) && isLineLike(b1)) { | ||
results = lineCircleIntersection(b1, a1); | ||
} else if (isCircle(a1) && isCircle(b1)) { | ||
results = circleCircleIntersection(a1, b1); | ||
} | ||
for (const x of [a, b]) { | ||
if (x.type === 'segment') { | ||
results = results.filter(i => liesOnSegment(x as Segment, i)); | ||
} | ||
if (x.type === 'ray') results = results.filter(i => liesOnRay(x as Ray, i)); | ||
if (isSegment(x)) results = results.filter(i => liesOnSegment(x, i)); | ||
if (isRay(x)) results = results.filter(i => liesOnRay(x, i)); | ||
if (isArc(x)) results = results.filter(i => liesOnArc(x, i)); | ||
} | ||
@@ -143,3 +157,3 @@ | ||
// TODO Handle arcs, sectors and angles! | ||
return simpleIntersection(a as (Line|Circle), b as (Line|Circle)); | ||
return simpleIntersection(a as (Line|Circle|Arc), b as (Line|Circle|Arc)); | ||
} |
@@ -154,3 +154,3 @@ // ============================================================================= | ||
toString() { | ||
return `Line(p1: ${this.p1}, p2: ${this.p2})`; | ||
return `line(${this.p1},${this.p2})`; | ||
} | ||
@@ -174,3 +174,3 @@ } | ||
toString() { | ||
return `Ray(p1: ${this.p1}, p2: ${this.p2})`; | ||
return `ray(${this.p1},${this.p2})`; | ||
} | ||
@@ -219,4 +219,4 @@ } | ||
toString() { | ||
return `Segment(p1: ${this.p1}, p2: ${this.p2})`; | ||
return `segment(${this.p1},${this.p2})`; | ||
} | ||
} |
@@ -219,3 +219,3 @@ // ============================================================================= | ||
toString() { | ||
return `Point(x: ${this.x}, y: ${this.y})`; | ||
return `point(${this.x},${this.y})`; | ||
} | ||
@@ -222,0 +222,0 @@ } |
@@ -14,3 +14,3 @@ // ============================================================================= | ||
import {ORIGIN, Point} from './point'; | ||
import {GeoShape, TransformMatrix, TWO_PI} from './utilities'; | ||
import {findClosest, GeoShape, TransformMatrix, TWO_PI} from './utilities'; | ||
@@ -70,7 +70,7 @@ | ||
get edges() { | ||
const p = this.points; | ||
const n = p.length; | ||
const n = this.points.length; | ||
const edges = []; | ||
for (let i = 0; i < n; ++i) edges.push(new Segment(p[i], p[(i + 1) % n])); | ||
for (let i = 0; i < n; ++i) { | ||
edges.push(new Segment(this.points[i], this.points[(i + 1) % n])); | ||
} | ||
return edges; | ||
@@ -199,21 +199,29 @@ } | ||
at(t: number) { | ||
return Point.interpolateList([...this.points, this.points[0]], t); | ||
if (t < 0) t += Math.floor(t); | ||
const offset = t * this.circumference; | ||
let cum = 0; | ||
for (const e of this.edges) { | ||
const l = e.length; | ||
if (cum + l > offset) return e.at((offset - cum) / l); | ||
cum += l; | ||
} | ||
return this.points[0]; | ||
} | ||
project(p: Point) { | ||
let q: Point|undefined = undefined; | ||
let d = Infinity; | ||
offset(p: Point) { | ||
const edges = this.edges; | ||
const proj = findClosest(p, this.edges) || [this.points[0], 0] as const; | ||
for (const e of this.edges) { | ||
const q1 = e.project(p); | ||
const d1 = Point.distance(p, q1); | ||
if (d1 < d) { | ||
q = q1; | ||
d = d1; | ||
} | ||
} | ||
let offset = 0; | ||
for (let i = 0; i < proj[1]; ++i) offset += edges[i].length; | ||
offset += edges[proj[1]].offset(p) * edges[proj[1]].length; | ||
return q || this.points[0]; | ||
return offset / this.circumference; | ||
} | ||
project(p: Point) { | ||
const proj = findClosest(p, this.edges); | ||
return proj ? proj[0] : this.points[0]; | ||
} | ||
/** Center this polygon on a given point or the origin */ | ||
@@ -271,2 +279,6 @@ centerAt(on = ORIGIN) { | ||
} | ||
toString() { | ||
return `polygon(${this.points.join(',')})`; | ||
} | ||
} | ||
@@ -279,2 +291,6 @@ | ||
get circumference() { | ||
return this.length; | ||
} | ||
get length() { | ||
@@ -296,2 +312,6 @@ let length = 0; | ||
} | ||
toString() { | ||
return `polyline(${this.points.join(',')})`; | ||
} | ||
} | ||
@@ -298,0 +318,0 @@ |
@@ -98,7 +98,10 @@ // ============================================================================= | ||
at(_t: number) { | ||
// TODO Implement | ||
return this.p; | ||
at(t: number) { | ||
return this.polygon.at(t); | ||
} | ||
offset(p: Point) { | ||
return this.polygon.offset(p); | ||
} | ||
// --------------------------------------------------------------------------- | ||
@@ -138,4 +141,4 @@ | ||
toString() { | ||
return `Rectangle(p: ${this.p}, w: ${this.w}, h: ${this.h})`; | ||
return `rectangle(${this.p},${this.w},${this.h})`; | ||
} | ||
} |
@@ -25,2 +25,3 @@ // ============================================================================= | ||
equals(other: GeoElement, tolerance?: number, oriented?: boolean): boolean; | ||
toString(): string; | ||
} | ||
@@ -32,2 +33,3 @@ | ||
at(t: number): Point; | ||
offset(p: Point): number; | ||
@@ -47,1 +49,20 @@ rotate(angle: number, center?: SimplePoint): GeoShape; | ||
} | ||
// TODO Merge this with findMin() in @mathigon/core | ||
export function findClosest(p: Point, items: GeoShape[]): [Point, number]|undefined { | ||
let q: Point|undefined = undefined; | ||
let d = Infinity; | ||
let index = -1; | ||
for (const [i, e] of items.entries()) { | ||
const q1 = e.project(p); | ||
const d1 = Point.distance(p, q1); | ||
if (d1 < d) { | ||
q = q1; | ||
d = d1; | ||
index = i; | ||
} | ||
} | ||
return q ? [q, index] : undefined; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
482810
6729
+ Added@mathigon/core@1.0.7(transitive)
Updated@mathigon/core@1.0.7