UNPKG

pixi.js

Version:

<p align="center"> <a href="https://pixijs.com" target="_blank" rel="noopener noreferrer"> <img height="150" src="https://files.pixijs.download/branding/pixijs-logo-transparent-dark.svg?v=1" alt="PixiJS logo"> </a> </p> <br/> <p align="center">

339 lines (336 loc) 11.6 kB
import { squaredDistanceToLineSegment } from '../misc/squaredDistanceToLineSegment.mjs'; import { Rectangle } from './Rectangle.mjs'; "use strict"; let tempRect; let tempRect2; class Polygon { /** * @param points - This can be an array of Points * that form the polygon, a flat array of numbers that will be interpreted as [x,y, x,y, ...], or * the arguments passed can be all the points of the polygon e.g. * `new Polygon(new Point(), new Point(), ...)`, or the arguments passed can be flat * x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are Numbers. */ constructor(...points) { /** * The type of the object, mainly used to avoid `instanceof` checks * @example * ```ts * // Check shape type * const shape = new Polygon([0, 0, 100, 0, 50, 100]); * console.log(shape.type); // 'polygon' * * // Use in type guards * if (shape.type === 'polygon') { * // TypeScript knows this is a Polygon * console.log(shape.points.length); * } * ``` * @readonly * @default 'polygon' * @see {@link SHAPE_PRIMITIVE} For all shape types */ this.type = "polygon"; let flat = Array.isArray(points[0]) ? points[0] : points; if (typeof flat[0] !== "number") { const p = []; for (let i = 0, il = flat.length; i < il; i++) { p.push(flat[i].x, flat[i].y); } flat = p; } this.points = flat; this.closePath = true; } /** * Determines whether the polygon's points are arranged in a clockwise direction. * Uses the shoelace formula (surveyor's formula) to calculate the signed area. * * A positive area indicates clockwise winding, while negative indicates counter-clockwise. * * The formula sums up the cross products of adjacent vertices: * For each pair of adjacent points (x1,y1) and (x2,y2), we calculate (x1*y2 - x2*y1) * The final sum divided by 2 gives the signed area - positive for clockwise. * @example * ```ts * // Check polygon winding * const polygon = new Polygon([0, 0, 100, 0, 50, 100]); * console.log(polygon.isClockwise()); // Check direction * * // Use in path construction * const hole = new Polygon([25, 25, 75, 25, 75, 75, 25, 75]); * if (hole.isClockwise() === shape.isClockwise()) { * hole.points.reverse(); // Reverse for proper hole winding * } * ``` * @returns `true` if the polygon's points are arranged clockwise, `false` if counter-clockwise */ isClockwise() { let area = 0; const points = this.points; const length = points.length; for (let i = 0; i < length; i += 2) { const x1 = points[i]; const y1 = points[i + 1]; const x2 = points[(i + 2) % length]; const y2 = points[(i + 3) % length]; area += (x2 - x1) * (y2 + y1); } return area < 0; } /** * Checks if this polygon completely contains another polygon. * Used for detecting holes in shapes, like when parsing SVG paths. * @example * ```ts * // Basic containment check * const outerSquare = new Polygon([0,0, 100,0, 100,100, 0,100]); // A square * const innerSquare = new Polygon([25,25, 75,25, 75,75, 25,75]); // A smaller square inside * * outerSquare.containsPolygon(innerSquare); // Returns true * innerSquare.containsPolygon(outerSquare); // Returns false * ``` * @remarks * - Uses bounds check for quick rejection * - Tests all points for containment * @param polygon - The polygon to test for containment * @returns True if this polygon completely contains the other polygon * @see {@link Polygon.contains} For single point testing * @see {@link Polygon.getBounds} For bounds calculation */ containsPolygon(polygon) { const thisBounds = this.getBounds(tempRect); const otherBounds = polygon.getBounds(tempRect2); if (!thisBounds.containsRect(otherBounds)) { return false; } const points = polygon.points; for (let i = 0; i < points.length; i += 2) { const x = points[i]; const y = points[i + 1]; if (!this.contains(x, y)) { return false; } } return true; } /** * Creates a clone of this polygon. * @example * ```ts * // Basic cloning * const original = new Polygon([0, 0, 100, 0, 50, 100]); * const copy = original.clone(); * * // Clone and modify * const modified = original.clone(); * modified.points[0] = 10; // Modify first x coordinate * ``` * @returns A copy of the polygon * @see {@link Polygon.copyFrom} For copying into existing polygon * @see {@link Polygon.copyTo} For copying to another polygon */ clone() { const points = this.points.slice(); const polygon = new Polygon(points); polygon.closePath = this.closePath; return polygon; } /** * Checks whether the x and y coordinates passed to this function are contained within this polygon. * Uses raycasting algorithm for point-in-polygon testing. * @example * ```ts * // Basic containment check * const polygon = new Polygon([0, 0, 100, 0, 50, 100]); * const isInside = polygon.contains(25, 25); // true * ``` * @param x - The X coordinate of the point to test * @param y - The Y coordinate of the point to test * @returns Whether the x/y coordinates are within this polygon * @see {@link Polygon.strokeContains} For checking stroke intersection * @see {@link Polygon.containsPolygon} For polygon-in-polygon testing */ contains(x, y) { let inside = false; const length = this.points.length / 2; for (let i = 0, j = length - 1; i < length; j = i++) { const xi = this.points[i * 2]; const yi = this.points[i * 2 + 1]; const xj = this.points[j * 2]; const yj = this.points[j * 2 + 1]; const intersect = yi > y !== yj > y && x < (xj - xi) * ((y - yi) / (yj - yi)) + xi; if (intersect) { inside = !inside; } } return inside; } /** * Checks whether the x and y coordinates given are contained within this polygon including the stroke. * @example * ```ts * // Basic stroke check * const polygon = new Polygon([0, 0, 100, 0, 50, 100]); * const isOnStroke = polygon.strokeContains(25, 25, 4); // 4px line width * * // Check with different alignments * const innerStroke = polygon.strokeContains(25, 25, 4, 0); // Inside * const centerStroke = polygon.strokeContains(25, 25, 4, 0.5); // Centered * const outerStroke = polygon.strokeContains(25, 25, 4, 1); // Outside * ``` * @param x - The X coordinate of the point to test * @param y - The Y coordinate of the point to test * @param strokeWidth - The width of the line to check * @param alignment - The alignment of the stroke (0 = inner, 0.5 = centered, 1 = outer) * @returns Whether the x/y coordinates are within this polygon's stroke * @see {@link Polygon.contains} For checking fill containment * @see {@link Polygon.getBounds} For getting stroke bounds */ strokeContains(x, y, strokeWidth, alignment = 0.5) { const strokeWidthSquared = strokeWidth * strokeWidth; const rightWidthSquared = strokeWidthSquared * (1 - alignment); const leftWidthSquared = strokeWidthSquared - rightWidthSquared; const { points } = this; const iterationLength = points.length - (this.closePath ? 0 : 2); for (let i = 0; i < iterationLength; i += 2) { const x1 = points[i]; const y1 = points[i + 1]; const x2 = points[(i + 2) % points.length]; const y2 = points[(i + 3) % points.length]; const distanceSquared = squaredDistanceToLineSegment(x, y, x1, y1, x2, y2); const sign = Math.sign((x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)); if (distanceSquared <= (sign < 0 ? leftWidthSquared : rightWidthSquared)) { return true; } } return false; } /** * Returns the framing rectangle of the polygon as a Rectangle object. * @example * ```ts * // Basic bounds calculation * const polygon = new Polygon([0, 0, 100, 0, 50, 100]); * const bounds = polygon.getBounds(); * // bounds: x=0, y=0, width=100, height=100 * * // Reuse existing rectangle * const rect = new Rectangle(); * polygon.getBounds(rect); * ``` * @param out - Optional rectangle to store the result * @returns The framing rectangle * @see {@link Rectangle} For rectangle properties * @see {@link Polygon.contains} For checking if a point is inside */ getBounds(out) { out || (out = new Rectangle()); const points = this.points; let minX = Infinity; let maxX = -Infinity; let minY = Infinity; let maxY = -Infinity; for (let i = 0, n = points.length; i < n; i += 2) { const x = points[i]; const y = points[i + 1]; minX = x < minX ? x : minX; maxX = x > maxX ? x : maxX; minY = y < minY ? y : minY; maxY = y > maxY ? y : maxY; } out.x = minX; out.width = maxX - minX; out.y = minY; out.height = maxY - minY; return out; } /** * Copies another polygon to this one. * @example * ```ts * // Basic copying * const source = new Polygon([0, 0, 100, 0, 50, 100]); * const target = new Polygon(); * target.copyFrom(source); * ``` * @param polygon - The polygon to copy from * @returns Returns itself * @see {@link Polygon.copyTo} For copying to another polygon * @see {@link Polygon.clone} For creating new polygon copy */ copyFrom(polygon) { this.points = polygon.points.slice(); this.closePath = polygon.closePath; return this; } /** * Copies this polygon to another one. * @example * ```ts * // Basic copying * const source = new Polygon([0, 0, 100, 0, 50, 100]); * const target = new Polygon(); * source.copyTo(target); * ``` * @param polygon - The polygon to copy to * @returns Returns given parameter * @see {@link Polygon.copyFrom} For copying from another polygon * @see {@link Polygon.clone} For creating new polygon copy */ copyTo(polygon) { polygon.copyFrom(this); return polygon; } toString() { return `[pixi.js/math:PolygoncloseStroke=${this.closePath}points=${this.points.reduce((pointsDesc, currentPoint) => `${pointsDesc}, ${currentPoint}`, "")}]`; } /** * Get the last X coordinate of the polygon. * @example * ```ts * // Basic coordinate access * const polygon = new Polygon([0, 0, 100, 200, 300, 400]); * console.log(polygon.lastX); // 300 * ``` * @readonly * @returns The x-coordinate of the last vertex * @see {@link Polygon.lastY} For last Y coordinate * @see {@link Polygon.points} For raw points array */ get lastX() { return this.points[this.points.length - 2]; } /** * Get the last Y coordinate of the polygon. * @example * ```ts * // Basic coordinate access * const polygon = new Polygon([0, 0, 100, 200, 300, 400]); * console.log(polygon.lastY); // 400 * ``` * @readonly * @returns The y-coordinate of the last vertex * @see {@link Polygon.lastX} For last X coordinate * @see {@link Polygon.points} For raw points array */ get lastY() { return this.points[this.points.length - 1]; } /** * Get the first X coordinate of the polygon * @readonly */ get x() { return this.points[this.points.length - 2]; } /** * Get the first Y coordinate of the polygon * @readonly */ get y() { return this.points[this.points.length - 1]; } } export { Polygon }; //# sourceMappingURL=Polygon.mjs.map