plotboilerplate
Version:
A simple javascript plotting boilerplate for 2d stuff.
158 lines (140 loc) • 6.2 kB
text/typescript
/**
* @author mbostock, extended and ported to TypeScript by Ikaros Kappler
* @date 2020-05-19
* @modified 2021-02-08 Fixed a lot of es2015 compatibility issues.
* @version 1.0.2
* @file contextPolygonIncircle
* @public
*/
import { Circle } from "../../Circle";
import { geomutils } from "../../geomutils";
import { Line } from "../../Line";
import { Polygon } from "../../Polygon";
import { Triangle } from "../../Triangle";
import { Vertex } from "../../Vertex";
/**
* For circle-polygon-intersection-count detection we need an epsilon to
* eliminate smaller precision errors.
*/
const EPS : number = 0.000001;
export interface PolygonIncircle {
circle : Circle;
triangle : Triangle;
};
/**
* Compute the max sized inlying circle in the given convex (!) polygon - also called the
* convex-polygon incircle.
*
* The function will return an object with either: the circle, and the triangle that defines
* the three tangent points where the circle touches the polygon.
*
* Inspired by
* https://observablehq.com/@mbostock/convex-polygon-incircle
* https://observablehq.com/@mbostock/circle-tangent-to-three-lines
*
*
* @requires Circle
* @requires Line
* @requires Vertex
* @requires Triangle
* @requires nsectAngle
* @requires geomutils
*
* @global
* @name convexPolygonIncircle
* @param {Polygon} convexHull - The actual convex polygon.
* @return {PolygonIncircle} A pair of a circle (the incircle) and a triangle (the three points where the circle is touching the polygon).
*/
export const convexPolygonIncircle = ( convexHull:Polygon ) : PolygonIncircle => {
var n : number = convexHull.vertices.length;
var bestCircle : Circle|undefined = undefined;
var bestTriangle : Triangle|undefined = undefined;
for( var a = 0; a < n; a++ ) {
for( var b = a+1; b < n; b++ ) {
for( var c = b+1; c < n; c++ ) {
// As these lines are part of the convex hull, we know that
// * line a preceeds line b and
// * line b preceeds line c :)
let lineA : Line = new Line( convexHull.vertices[a], convexHull.vertices[(a+1)%n] );
let lineB : Line = new Line( convexHull.vertices[b], convexHull.vertices[(b+1)%n] );
let lineC : Line = new Line( convexHull.vertices[c], convexHull.vertices[(c+1)%n] );
// Find intersections by expanding the lines
let vertB : Vertex = lineA.intersection(lineB);
let vertC : Vertex = lineB.intersection(lineC);
// An object: { center: Vertex, radius: number }
let triangle : Triangle = getTangentTriangle4( lineA.a, vertB, vertC, lineC.b );
// Workaround. There will be a future version where the 'getCircumCircle()' functions
// returns a real Circle instance.
let _circle : Circle = triangle.getCircumcircle();
let circle : Circle = new Circle( _circle.center, _circle.radius );
// Count the number of intersections with the convex hull:
// If there are exactly three, we have found an in-lying circle.
// * Check if this one is better (bigger) than the old one.
// * Also check if the circle is located inside the polygon;
// The construction can, in some cases, produce an out-lying circle.
if( !convexHull.containsVert(circle.center) ) {
continue;
}
var circleIntersections : Array<number> = findCircleIntersections( convexHull, circle );
if( circleIntersections.length == 3 && (bestCircle == undefined || bestCircle.radius < circle.radius) ) {
bestCircle = circle;
bestTriangle = triangle;
}
}
}
}
return { circle : bestCircle,
triangle : bestTriangle };
};
/**
* This function computes the three points for the inner maximum circle
* lying tangential to the three subsequential lines (given by four points).
*
* Compute the circle from that triangle by using Triangle.getCircumcircle().
*
* Not all three lines should be parallel, otherwise the circle might have infinite radius.
*
* LineA := [vertA, vertB]
* LineB := [vertB, vertC]
* LineC := [vertC, vertD]
*
* @param {Vertex} vertA - The first point of the three connected lines.
* @param {Vertex} vertB - The second point of the three connected lines.
* @param {Vertex} vertC - The third point of the three connected lines.
* @param {Vertex} vertD - The fourth point of the three connected lines.
* @return {Triangle} The triangle of the circular tangential points with the given lines (3 or 4 of them).
*/
const getTangentTriangle4 = ( vertA : Vertex,
vertB : Vertex,
vertC : Vertex,
vertD : Vertex ) : Triangle => {
const lineA : Line = new Line(vertA,vertB);
const lineB : Line = new Line(vertB,vertC);
const lineC : Line = new Line(vertC,vertD);
const bisector1 : Line = geomutils.nsectAngle( vertB, vertA, vertC, 2 )[0]; // bisector of first triangle
const bisector2 : Line = geomutils.nsectAngle( vertC, vertB, vertD, 2 )[0]; // bisector of second triangle
const intersection : Vertex = bisector1.intersection( bisector2 );
// Find the closest points on one of the polygon lines (all have same distance by construction)
const circleIntersA : Vertex = lineA.getClosestPoint( intersection );
const circleIntersB : Vertex = lineB.getClosestPoint( intersection );
const circleIntersC : Vertex = lineC.getClosestPoint( intersection );
return new Triangle(circleIntersA, circleIntersB, circleIntersC);
};
/**
* Find all intersecting lines (indices) on the polyon that intersect the circle.
*
* @param {Polygon} convexHull - The polygon to detect the intersections on (here this is the convex hull given).
* @param {Circle} circle - The circle to detect the intersections with.
* @return {Array<number>} The indices of those lines that intersect (or touch) the circle.
**/
const findCircleIntersections = ( convexHull:Polygon, circle:Circle ) : Array<number> => {
var result = [];
for( var i = 0; i < convexHull.vertices.length; i++ ) {
var line : Line = new Line( convexHull.vertices[i], convexHull.vertices[(i+1)%convexHull.vertices.length] );
// Use an epsilon here because circle coordinates can be kind of unprecise in the detail.
if( circle.lineDistance(line) < EPS ) {
result.push( i );
}
}
return result;
};