Newer
Older
pixi.js / packages / graphics / src / utils / ArcUtils.js
import { GRAPHICS_CURVES } from '../const';
import { PI_2 } from '@pixi/math';

/**
 * Utilities for arc curves
 * @class
 * @private
 */
export default class ArcUtils
{
    /**
     * The arcTo() method creates an arc/curve between two tangents on the canvas.
     *
     * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google!
     *
     * @param {number} x1 - The x-coordinate of the beginning of the arc
     * @param {number} y1 - The y-coordinate of the beginning of the arc
     * @param {number} x2 - The x-coordinate of the end of the arc
     * @param {number} y2 - The y-coordinate of the end of the arc
     * @param {number} radius - The radius of the arc
     * @return {object} If the arc length is valid, return center of circle, radius and other info otherwise `null`.
     */
    static curveTo(x1, y1, x2, y2, radius, points)
    {
        const fromX = points[points.length - 2];
        const fromY = points[points.length - 1];

        const a1 = fromY - y1;
        const b1 = fromX - x1;
        const a2 = y2 - y1;
        const b2 = x2 - x1;
        const mm = Math.abs((a1 * b2) - (b1 * a2));

        if (mm < 1.0e-8 || radius === 0)
        {
            if (points[points.length - 2] !== x1 || points[points.length - 1] !== y1)
            {
                points.push(x1, y1);
            }

            return null;
        }

        const dd = (a1 * a1) + (b1 * b1);
        const cc = (a2 * a2) + (b2 * b2);
        const tt = (a1 * a2) + (b1 * b2);
        const k1 = radius * Math.sqrt(dd) / mm;
        const k2 = radius * Math.sqrt(cc) / mm;
        const j1 = k1 * tt / dd;
        const j2 = k2 * tt / cc;
        const cx = (k1 * b2) + (k2 * b1);
        const cy = (k1 * a2) + (k2 * a1);
        const px = b1 * (k2 + j1);
        const py = a1 * (k2 + j1);
        const qx = b2 * (k1 + j2);
        const qy = a2 * (k1 + j2);
        const startAngle = Math.atan2(py - cy, px - cx);
        const endAngle = Math.atan2(qy - cy, qx - cx);

        return {
            cx: (cx + x1),
            cy: (cy + y1),
            radius,
            startAngle,
            endAngle,
            anticlockwise: (b1 * a2 > b2 * a1),
        };
    }

    /**
     * The arc method creates an arc/curve (used to create circles, or parts of circles).
     *
     * @param {number} startX - Start x location of arc
     * @param {number} startY - Start y location of arc
     * @param {number} cx - The x-coordinate of the center of the circle
     * @param {number} cy - The y-coordinate of the center of the circle
     * @param {number} radius - The radius of the circle
     * @param {number} startAngle - The starting angle, in radians (0 is at the 3 o'clock position
     *  of the arc's circle)
     * @param {number} endAngle - The ending angle, in radians
     * @param {boolean} anticlockwise - Specifies whether the drawing should be
     *  counter-clockwise or clockwise. False is default, and indicates clockwise, while true
     *  indicates counter-clockwise.
     * @param {number} n - Number of segments
     * @param {number[]} points - Collection of points to add to
     */
    static arc(startX, startY, cx, cy, radius, startAngle, endAngle, anticlockwise, points)
    {
        const sweep = endAngle - startAngle;
        const n = GRAPHICS_CURVES._segmentsCount(
            Math.abs(sweep) * radius,
            Math.ceil(Math.abs(sweep) / PI_2) * 40
        );

        const theta = (sweep) / (n * 2);
        const theta2 = theta * 2;
        const cTheta = Math.cos(theta);
        const sTheta = Math.sin(theta);
        const segMinus = n - 1;
        const remainder = (segMinus % 1) / segMinus;

        for (let i = 0; i <= segMinus; ++i)
        {
            const real = i + (remainder * i);
            const angle = ((theta) + startAngle + (theta2 * real));
            const c = Math.cos(angle);
            const s = -Math.sin(angle);

            points.push(
                (((cTheta * c) + (sTheta * s)) * radius) + cx,
                (((cTheta * -s) + (sTheta * c)) * radius) + cy
            );
        }
    }
}