176 lines
7.8 KiB
JavaScript
176 lines
7.8 KiB
JavaScript
/**
|
|
* Determine the center of the circle.
|
|
* Trivial, but used to match center method for other shapes.
|
|
* @type {PIXI.Point}
|
|
*/
|
|
Object.defineProperty(PIXI.Circle.prototype, "center", { get: function() {
|
|
return new PIXI.Point(this.x, this.y);
|
|
}});
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Determine if a point is on or nearly on this circle.
|
|
* @param {Point} point Point to test
|
|
* @param {number} epsilon Tolerated margin of error
|
|
* @returns {boolean} Is the point on the circle within the allowed tolerance?
|
|
*/
|
|
PIXI.Circle.prototype.pointIsOn = function(point, epsilon = 1e-08) {
|
|
const dist2 = Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2);
|
|
const r2 = Math.pow(this.radius, 2);
|
|
return dist2.almostEqual(r2, epsilon);
|
|
};
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Get all intersection points on this circle for a segment A|B
|
|
* Intersections are sorted from A to B.
|
|
* @param {Point} a The first endpoint on segment A|B
|
|
* @param {Point} b The second endpoint on segment A|B
|
|
* @returns {Point[]} Points where the segment A|B intersects the circle
|
|
*/
|
|
PIXI.Circle.prototype.segmentIntersections = function(a, b) {
|
|
const ixs = foundry.utils.lineCircleIntersection(a, b, this, this.radius);
|
|
return ixs.intersections;
|
|
};
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Calculate an x,y point on this circle's circumference given an angle
|
|
* 0: due east
|
|
* π / 2: due south
|
|
* π or -π: due west
|
|
* -π/2: due north
|
|
* @param {number} angle Angle of the point, in radians
|
|
* @returns {Point} The point on the circle at the given angle
|
|
*/
|
|
PIXI.Circle.prototype.pointAtAngle = function(angle) {
|
|
return {
|
|
x: this.x + (this.radius * Math.cos(angle)),
|
|
y: this.y + (this.radius * Math.sin(angle))
|
|
};
|
|
};
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Get all the points for a polygon approximation of this circle between two points.
|
|
* The two points can be anywhere in 2d space. The intersection of this circle with the line from this circle center
|
|
* to the point will be used as the start or end point, respectively.
|
|
* This is used to draw the portion of the circle (the arc) between two intersection points on this circle.
|
|
* @param {Point} a Point in 2d space representing the start point
|
|
* @param {Point} b Point in 2d space representing the end point
|
|
* @param {object} [options] Options passed on to the pointsForArc method
|
|
* @returns { Point[]} An array of points arranged clockwise from start to end
|
|
*/
|
|
PIXI.Circle.prototype.pointsBetween = function(a, b, options) {
|
|
const fromAngle = Math.atan2(a.y - this.y, a.x - this.x);
|
|
const toAngle = Math.atan2(b.y - this.y, b.x - this.x);
|
|
return this.pointsForArc(fromAngle, toAngle, { includeEndpoints: false, ...options });
|
|
};
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Get the points that would approximate a circular arc along this circle, given a starting and ending angle.
|
|
* Points returned are clockwise. If from and to are the same, a full circle will be returned.
|
|
* @param {number} fromAngle Starting angle, in radians. π is due north, π/2 is due east
|
|
* @param {number} toAngle Ending angle, in radians
|
|
* @param {object} [options] Options which affect how the circle is converted
|
|
* @param {number} [options.density] The number of points which defines the density of approximation
|
|
* @param {boolean} [options.includeEndpoints] Whether to include points at the circle where the arc starts and ends
|
|
* @returns {Point[]} An array of points along the requested arc
|
|
*/
|
|
PIXI.Circle.prototype.pointsForArc = function(fromAngle, toAngle, {density, includeEndpoints=true} = {}) {
|
|
const pi2 = 2 * Math.PI;
|
|
density ??= this.constructor.approximateVertexDensity(this.radius);
|
|
const points = [];
|
|
const delta = pi2 / density;
|
|
if ( includeEndpoints ) points.push(this.pointAtAngle(fromAngle));
|
|
|
|
// Determine number of points to add
|
|
let dAngle = toAngle - fromAngle;
|
|
while ( dAngle <= 0 ) dAngle += pi2; // Angles may not be normalized, so normalize total.
|
|
const nPoints = Math.round(dAngle / delta);
|
|
|
|
// Construct padding rays (clockwise)
|
|
for ( let i = 1; i < nPoints; i++ ) points.push(this.pointAtAngle(fromAngle + (i * delta)));
|
|
if ( includeEndpoints ) points.push(this.pointAtAngle(toAngle));
|
|
return points;
|
|
};
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Approximate this PIXI.Circle as a PIXI.Polygon
|
|
* @param {object} [options] Options forwarded on to the pointsForArc method
|
|
* @returns {PIXI.Polygon} The Circle expressed as a PIXI.Polygon
|
|
*/
|
|
PIXI.Circle.prototype.toPolygon = function(options) {
|
|
const points = this.pointsForArc(0, 0, options);
|
|
points.pop(); // Drop the repeated endpoint
|
|
return new PIXI.Polygon(points);
|
|
};
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The recommended vertex density for the regular polygon approximation of a circle of a given radius.
|
|
* Small radius circles have fewer vertices. The returned value will be rounded up to the nearest integer.
|
|
* See the formula described at:
|
|
* https://math.stackexchange.com/questions/4132060/compute-number-of-regular-polgy-sides-to-approximate-circle-to-defined-precision
|
|
* @param {number} radius Circle radius
|
|
* @param {number} [epsilon] The maximum tolerable distance between an approximated line segment and the true radius.
|
|
* A larger epsilon results in fewer points for a given radius.
|
|
* @returns {number} The number of points for the approximated polygon
|
|
*/
|
|
PIXI.Circle.approximateVertexDensity = function(radius, epsilon=1) {
|
|
return Math.ceil(Math.PI / Math.sqrt(2 * (epsilon / radius)));
|
|
};
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Intersect this PIXI.Circle with a PIXI.Polygon.
|
|
* @param {PIXI.Polygon} polygon A PIXI.Polygon
|
|
* @param {object} [options] Options which configure how the intersection is computed
|
|
* @param {number} [options.density] The number of points which defines the density of approximation
|
|
* @param {number} [options.clipType] The clipper clip type
|
|
* @param {string} [options.weilerAtherton=true] Use the Weiler-Atherton algorithm. Otherwise, use Clipper.
|
|
* @returns {PIXI.Polygon} The intersected polygon
|
|
*/
|
|
PIXI.Circle.prototype.intersectPolygon = function(polygon, {density, clipType, weilerAtherton=true, ...options}={}) {
|
|
if ( !this.radius ) return new PIXI.Polygon([]);
|
|
clipType ??= ClipperLib.ClipType.ctIntersection;
|
|
|
|
// Use Weiler-Atherton for efficient intersection or union
|
|
if ( weilerAtherton && polygon.isPositive ) {
|
|
const res = WeilerAthertonClipper.combine(polygon, this, {clipType, density, ...options});
|
|
if ( !res.length ) return new PIXI.Polygon([]);
|
|
return res[0];
|
|
}
|
|
|
|
// Otherwise, use Clipper polygon intersection
|
|
const approx = this.toPolygon({density});
|
|
return polygon.intersectPolygon(approx, options);
|
|
};
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Intersect this PIXI.Circle with an array of ClipperPoints.
|
|
* Convert the circle to a Polygon approximation and use intersectPolygon.
|
|
* In the future we may replace this with more specialized logic which uses the line-circle intersection formula.
|
|
* @param {ClipperPoint[]} clipperPoints Array of ClipperPoints generated by PIXI.Polygon.toClipperPoints()
|
|
* @param {object} [options] Options which configure how the intersection is computed
|
|
* @param {number} [options.density] The number of points which defines the density of approximation
|
|
* @returns {PIXI.Polygon} The intersected polygon
|
|
*/
|
|
PIXI.Circle.prototype.intersectClipper = function(clipperPoints, {density, ...options}={}) {
|
|
if ( !this.radius ) return [];
|
|
const approx = this.toPolygon({density});
|
|
return approx.intersectClipper(clipperPoints, options);
|
|
};
|