Newer
Older
pixi.js / packages / graphics / test / index.js
// const MockPointer = require('../interaction/MockPointer');
const { Renderer, BatchRenderer } = require('@pixi/core');
const { Graphics } = require('../');
const { BLEND_MODES } = require('@pixi/constants');
const { Point } = require('@pixi/math');
const { skipHello } = require('@pixi/utils');

Renderer.registerPlugin('batch', BatchRenderer);

skipHello();

function withGL(fn)
{
    return !process.env.DISABLE_WEBGL ? (fn || true) : undefined;
}

describe('Graphics', function ()
{
    describe('constructor', function ()
    {
        it('should set defaults', function ()
        {
            const graphics = new Graphics();

            expect(graphics.fill.color).to.be.equals(0xFFFFFF);
            expect(graphics.fill.alpha).to.be.equals(1);
            expect(graphics.line.width).to.be.equals(0);
            expect(graphics.line.color).to.be.equals(0);
            expect(graphics.tint).to.be.equals(0xFFFFFF);
            expect(graphics.blendMode).to.be.equals(BLEND_MODES.NORMAL);
        });
    });

    describe('lineTo', function ()
    {
        it('should return correct bounds - north', function ()
        {
            const graphics = new Graphics();

            graphics.lineStyle(1);
            graphics.moveTo(0, 0);
            graphics.lineTo(0, 10);

            expect(graphics.width).to.be.below(1.00001);
            expect(graphics.width).to.be.above(0.99999);
            expect(graphics.height).to.be.equals(10);
        });

        it('should return correct bounds - south', function ()
        {
            const graphics = new Graphics();

            graphics.moveTo(0, 0);
            graphics.lineStyle(1);
            graphics.lineTo(0, -10);

            expect(graphics.width).to.be.below(1.00001);
            expect(graphics.width).to.be.above(0.99999);
            expect(graphics.height).to.be.equals(10);
        });

        it('should return correct bounds - east', function ()
        {
            const graphics = new Graphics();

            graphics.moveTo(0, 0);
            graphics.lineStyle(1);
            graphics.lineTo(10, 0);

            expect(graphics.height).to.be.equals(1);
            expect(graphics.width).to.be.equals(10);
        });

        it('should return correct bounds - west', function ()
        {
            const graphics = new Graphics();

            graphics.moveTo(0, 0);
            graphics.lineStyle(1);
            graphics.lineTo(-10, 0);

            expect(graphics.height).to.be.above(0.9999);
            expect(graphics.height).to.be.below(1.0001);
            expect(graphics.width).to.be.equals(10);
        });

        it('should return correct bounds when stacked with circle', function ()
        {
            const graphics = new Graphics();

            graphics.beginFill(0xFF0000);
            graphics.drawCircle(50, 50, 50);
            graphics.endFill();

            expect(graphics.width).to.be.equals(100);
            expect(graphics.height).to.be.equals(100);

            graphics.lineStyle(20, 0);
            graphics.moveTo(25, 50);
            graphics.lineTo(75, 50);

            expect(graphics.width).to.be.equals(100);
            expect(graphics.height).to.be.equals(100);
        });

        it('should return correct bounds when square', function ()
        {
            const graphics = new Graphics();

            graphics.lineStyle(20, 0, 0.5);
            graphics.moveTo(0, 0);
            graphics.lineTo(50, 0);
            graphics.lineTo(50, 50);
            graphics.lineTo(0, 50);
            graphics.lineTo(0, 0);

            expect(graphics.width).to.be.equals(70);
            expect(graphics.height).to.be.equals(70);
        });

        it('should ignore duplicate calls', function ()
        {
            const graphics = new Graphics();

            graphics.moveTo(0, 0);
            graphics.lineTo(0, 0);
            graphics.lineTo(10, 0);
            graphics.lineTo(10, 0);

            expect(graphics.currentPath.points).to.deep.equal([0, 0, 10, 0]);
        });

        describe('lineJoin', function ()
        {
            describe('miter', function ()
            {
                it('is miter by default (backwards compatible)', function ()
                {
                    // given
                    const graphics = new Graphics();

                    // then
                    expect(graphics.line.lineJoin).to.be.equal('miter');
                });

                it('clockwise miter', withGL(function ()
                {
                    // given
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'miter';

                    graphics.moveTo(0, 0);
                    graphics.lineTo(50, 0);
                    graphics.lineTo(50, 50);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // 6 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(6);

                    expect(points[0], 'x1').to.be.eql(0);
                    expect(points[1], 'y1').to.be.eql(1);

                    expect(points[2], 'x2').to.be.eql(0);
                    expect(points[3], 'y2').to.be.eql(-1);

                    expect(points[4], 'x3').to.be.eql(49);
                    expect(points[5], 'y3').to.be.eql(1);

                    expect(points[6], 'x4').to.be.eql(51);
                    expect(points[7], 'y4').to.be.eql(-1);

                    expect(points[8], 'x5').to.be.eql(49);
                    expect(points[9], 'y5').to.be.eql(50);

                    expect(points[10], 'x6').to.be.eql(51);
                    expect(points[11], 'y6').to.be.eql(50);
                }));

                it('counterclockwise miter', withGL(function ()
                {
                    // given
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'miter';

                    graphics.moveTo(0, 0);
                    graphics.lineTo(50, 0);
                    graphics.lineTo(50, -50);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // 6 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(6);

                    expect(points[0], 'x1').to.be.eql(0);
                    expect(points[1], 'y1').to.be.eql(1);

                    expect(points[2], 'x2').to.be.eql(0);
                    expect(points[3], 'y2').to.be.eql(-1);

                    expect(points[4], 'x3').to.be.eql(51);
                    expect(points[5], 'y3').to.be.eql(1);

                    expect(points[6], 'x4').to.be.eql(49);
                    expect(points[7], 'y4').to.be.eql(-1);

                    expect(points[8], 'x5').to.be.eql(51);
                    expect(points[9], 'y5').to.be.eql(-50);

                    expect(points[10], 'x6').to.be.eql(49);
                    expect(points[11], 'y6').to.be.eql(-50);
                }));

                it('flat line miter', withGL(function ()
                {
                    // given
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'miter';

                    graphics.moveTo(0, 0);
                    graphics.lineTo(50, 0);
                    graphics.lineTo(100, 0);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // 6 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(6);

                    expect(points[0], 'x1').to.be.eql(0);
                    expect(points[1], 'y1').to.be.eql(1);

                    expect(points[2], 'x2').to.be.eql(0);
                    expect(points[3], 'y2').to.be.eql(-1);

                    expect(points[4], 'x3').to.be.eql(50);
                    expect(points[5], 'y3').to.be.eql(1);

                    expect(points[6], 'x5').to.be.eql(50);
                    expect(points[7], 'y5').to.be.eql(-1);

                    expect(points[8], 'x7').to.be.eql(100);
                    expect(points[9], 'y7').to.be.eql(1);

                    expect(points[10], 'x8').to.be.eql(100);
                    expect(points[11], 'y8').to.be.eql(-1);
                }));

                it('very sharp clockwise miter falling back to bevel', withGL(function ()
                {
                    // given
                    const p1 = [0, 0];
                    const p2 = [50, 0];
                    const p3 = [0, 2];
                    // normalized perpendicular lines
                    const perp1 = [0, 0.5];
                    const perp2 = [0.019984019174435787, 0.4996004793608947];
                    const anchor = [24.990003996803196, 0.5];
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(1, 0, 1, 0.5);
                    graphics.line.lineJoin = 'miter';

                    graphics.moveTo(p1[0], p1[1]);
                    graphics.lineTo(p2[0], p2[1]);

                    // when
                    graphics.lineTo(p3[0], p3[1]);
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // 8 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(8);

                    expect(points[0], 'x1').to.be.eql(p1[0] + perp1[0]);
                    expect(points[1], 'y1').to.be.eql(p1[1] + perp1[1]);

                    expect(points[2], 'x2').to.be.eql(p1[0] - perp1[0]);
                    expect(points[3], 'y2').to.be.eql(p1[1] - perp1[1]);

                    expect(points[4], 'x3').to.be.eql(anchor[0]);
                    expect(points[5], 'y3').to.be.eql(anchor[1]);

                    expect(points[6], 'x4').to.be.eql(p2[0] - perp1[0]);
                    expect(points[7], 'y4').to.be.eql(p2[1] - perp1[1]);

                    expect(points[8], 'x5').to.be.eql(anchor[0]);
                    expect(points[9], 'y5').to.be.eql(anchor[1]);

                    expect(points[10], 'x6').to.be.eql(p2[0] + perp2[0]);
                    expect(points[11], 'y6').to.be.eql(p2[1] + perp2[1]);

                    expect(points[12], 'x7').to.be.eql(p3[0] - perp2[0]);
                    expect(points[13], 'y7').to.be.eql(p3[1] - perp2[1]);

                    expect(points[14], 'x8').to.be.eql(p3[0] + perp2[0]);
                    expect(points[15], 'y8').to.be.eql(p3[1] + perp2[1]);
                }));

                it('very sharp counterclockwise miter falling back to bevel', withGL(function ()
                {
                    // given
                    const p1 = [0, 0];
                    const p2 = [50, 0];
                    const p3 = [0, -2];
                    // normalized perpendicular vectors
                    const perp1 = [0, 0.5];
                    const perp2 = [0.019984019174435787, -0.4996004793608947];
                    const anchor = [24.990003996803196, -0.5];
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(1, 0, 1, 0.5);
                    graphics.line.lineJoin = 'miter';

                    graphics.moveTo(p1[0], p1[1]);
                    graphics.lineTo(p2[0], p2[1]);

                    // when
                    graphics.lineTo(p3[0], p3[1]);
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // 8 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(8);

                    expect(points[0], 'x1').to.be.eql(p1[0] + perp1[0]);
                    expect(points[1], 'y1').to.be.eql(p1[1] + perp1[1]);

                    expect(points[2], 'x2').to.be.eql(p1[0] - perp1[0]);
                    expect(points[3], 'y2').to.be.eql(p1[1] - perp1[1]);

                    expect(points[4], 'x3').to.be.eql(p2[0] + perp1[0]);
                    expect(points[5], 'y3').to.be.eql(p2[1] + perp1[1]);

                    expect(points[6], 'x4').to.be.eql(anchor[0]);
                    expect(points[7], 'y4').to.be.eql(anchor[1]);

                    expect(points[8], 'x5').to.be.eql(p2[0] + perp2[0]);
                    expect(points[9], 'y5').to.be.eql(p2[1] + perp2[1]);

                    expect(points[10], 'x6').to.be.eql(anchor[0]);
                    expect(points[11], 'y6').to.be.eql(anchor[1]);

                    expect(points[12], 'x7').to.be.eql(p3[0] + perp2[0]);
                    expect(points[13], 'y7').to.be.eql(p3[1] + perp2[1]);

                    expect(points[14], 'x8').to.be.eql(p3[0] - perp2[0]);
                    expect(points[15], 'y8').to.be.eql(p3[1] - perp2[1]);
                }));

                it('miter join paralel lines', withGL(function ()
                {
                    // given
                    const p1 = [0, 0];
                    const p2 = [50, 0];
                    const p3 = [100, 0];
                    // normalized perpendicular vectors
                    const perp1 = [0, 1];
                    const perp2 = [0, -1];
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphicsMiter = new Graphics();

                    graphicsMiter.lineStyle(2, 0, 1, 0.5);
                    graphicsMiter.lineJoin = 'miter';

                    graphicsMiter.moveTo(p1[0], p1[1]);
                    graphicsMiter.lineTo(p2[0], p2[1]);
                    graphicsMiter.lineTo(p3[0], p3[1]);

                    // when
                    renderer.render(graphicsMiter);

                    // then
                    const points = graphicsMiter.vertexData;

                    // 6 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(6);

                    expect(points[0], 'x1').to.be.eql(p1[0] + perp1[0]);
                    expect(points[1], 'y1').to.be.eql(p1[1] + perp1[1]);

                    expect(points[2], 'x2').to.be.eql(p1[0] - perp1[0]);
                    expect(points[3], 'y2').to.be.eql(p1[1] - perp1[1]);

                    expect(points[4], 'x3').to.be.eql(p2[0] + perp1[0]);
                    expect(points[5], 'y3').to.be.eql(p2[1] + perp1[1]);

                    expect(points[6], 'x4').to.be.eql(p2[0] + perp2[0]);
                    expect(points[7], 'y4').to.be.eql(p2[1] + perp2[1]);

                    expect(points[8], 'x5').to.be.eql(p3[0] - perp2[0]);
                    expect(points[9], 'y5').to.be.eql(p3[1] - perp2[1]);

                    expect(points[10], 'x6').to.be.eql(p3[0] + perp2[0]);
                    expect(points[11], 'y6').to.be.eql(p3[1] + perp2[1]);
                }));
            });

            describe('bevel', function ()
            {
                it('clockwise bevel', withGL(function ()
                {
                    // given
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'bevel';

                    graphics.moveTo(0, 0);
                    graphics.lineTo(50, 0);
                    graphics.lineTo(50, 50);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // 8 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(8);

                    expect(points[0], 'x1').to.be.eql(0);
                    expect(points[1], 'y1').to.be.eql(1);

                    expect(points[2], 'x2').to.be.eql(0);
                    expect(points[3], 'y2').to.be.eql(-1);

                    expect(points[4], 'x3').to.be.eql(49);
                    expect(points[5], 'y3').to.be.eql(1);

                    expect(points[6], 'x4').to.be.eql(50);
                    expect(points[7], 'y4').to.be.eql(-1);

                    expect(points[8], 'x5').to.be.eql(49);
                    expect(points[9], 'y5').to.be.eql(1);

                    expect(points[10], 'x6').to.be.eql(51);
                    expect(points[11], 'y6').to.be.eql(0);

                    expect(points[12], 'x7').to.be.eql(49);
                    expect(points[13], 'y7').to.be.eql(50);

                    expect(points[14], 'x8').to.be.eql(51);
                    expect(points[15], 'y8').to.be.eql(50);
                }));

                it('counterclockwise bevel', withGL(function ()
                {
                    // given
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'bevel';

                    graphics.moveTo(0, 0);
                    graphics.lineTo(50, 0);
                    graphics.lineTo(50, -50);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // 8 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(8);

                    expect(points[0], 'x1').to.be.eql(0);
                    expect(points[1], 'y1').to.be.eql(1);

                    expect(points[2], 'x2').to.be.eql(0);
                    expect(points[3], 'y2').to.be.eql(-1);

                    expect(points[4], 'x3').to.be.eql(50);
                    expect(points[5], 'y3').to.be.eql(1);

                    expect(points[6], 'x4').to.be.eql(49);
                    expect(points[7], 'y4').to.be.eql(-1);

                    expect(points[8], 'x5').to.be.eql(51);
                    expect(points[9], 'y5').to.be.eql(0);

                    expect(points[10], 'x6').to.be.eql(49);
                    expect(points[11], 'y6').to.be.eql(-1);

                    expect(points[12], 'x7').to.be.eql(51);
                    expect(points[13], 'y7').to.be.eql(-50);

                    expect(points[14], 'x8').to.be.eql(49);
                    expect(points[15], 'y8').to.be.eql(-50);
                }));

                it('bevel join paralel lines', withGL(function ()
                {
                    // given
                    const p1 = [0, 0];
                    const p2 = [50, 0];
                    const p3 = [100, 0];
                    // normalized perpendicular vectors
                    const perp1 = [0, 1];
                    const perp2 = [0, -1];
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphicsMiter = new Graphics();

                    graphicsMiter.lineStyle(2, 0, 1, 0.5);
                    graphicsMiter.line.lineJoin = 'bevel';

                    graphicsMiter.moveTo(p1[0], p1[1]);
                    graphicsMiter.lineTo(p2[0], p2[1]);
                    graphicsMiter.lineTo(p3[0], p3[1]);

                    // when
                    renderer.render(graphicsMiter);

                    // then
                    const points = graphicsMiter.vertexData;

                    // 6 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(6);

                    expect(points[0], 'x1').to.be.eql(p1[0] + perp1[0]);
                    expect(points[1], 'y1').to.be.eql(p1[1] + perp1[1]);

                    expect(points[2], 'x2').to.be.eql(p1[0] - perp1[0]);
                    expect(points[3], 'y2').to.be.eql(p1[1] - perp1[1]);

                    expect(points[4], 'x3').to.be.eql(p2[0] + perp1[0]);
                    expect(points[5], 'y3').to.be.eql(p2[1] + perp1[1]);

                    expect(points[6], 'x4').to.be.eql(p2[0] + perp2[0]);
                    expect(points[7], 'y4').to.be.eql(p2[1] + perp2[1]);

                    expect(points[8], 'x5').to.be.eql(p3[0] - perp2[0]);
                    expect(points[9], 'y5').to.be.eql(p3[1] - perp2[1]);

                    expect(points[10], 'x6').to.be.eql(p3[0] + perp2[0]);
                    expect(points[11], 'y6').to.be.eql(p3[1] + perp2[1]);
                }));

                it('flat line bevel', withGL(function ()
                {
                    // given
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'bevel';

                    graphics.moveTo(0, 0);
                    graphics.lineTo(50, 0);
                    graphics.lineTo(100, 0);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // 6 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(6);

                    expect(points[0], 'x1').to.be.eql(0);
                    expect(points[1], 'y1').to.be.eql(1);

                    expect(points[2], 'x2').to.be.eql(0);
                    expect(points[3], 'y2').to.be.eql(-1);

                    expect(points[4], 'x3').to.be.eql(50);
                    expect(points[5], 'y3').to.be.eql(1);

                    expect(points[6], 'x4').to.be.eql(50);
                    expect(points[7], 'y4').to.be.eql(-1);

                    expect(points[8], 'x5').to.be.eql(100);
                    expect(points[9], 'y5').to.be.eql(1);

                    expect(points[10], 'x6').to.be.eql(100);
                    expect(points[11], 'y6').to.be.eql(-1);
                }));
            });

            describe('round', function ()
            {
                it('round join clockwise', withGL(function ()
                {
                    // given
                    const p1 = [0, 0];
                    const p2 = [50, 0];
                    const p3 = [50, 50];
                    // normalized perpendicular vectors
                    const perp1 = [0, -1];
                    const perp2 = [1, 0];
                    const anchor = [p2[0] - perp1[0] - perp2[0], p2[1] - perp1[1] - perp2[1]];
                    // doubles cause every point is followed with center point
                    // 1 + 1 + 15 * absAngleDiff * Math.sqrt(radius) / Math.PI
                    const noOfCtlPts = 6 * 2;
                    const r = 2.23606797749979; // sqrt(1^2 + 2^2)
                    const angleIncrease = -0.12870022175865686; // anlge diff / 5
                    let angle = 2.677945044588987; // Math.atan2(1, -2)
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'round';

                    graphics.moveTo(p1[0], p1[1]);
                    graphics.lineTo(p2[0], p2[1]);
                    graphics.lineTo(p3[0], p3[1]);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal((4 + noOfCtlPts));

                    expect(points[0], 'x1').to.be.eql(p1[0] - perp1[0]);
                    expect(points[1], 'y1').to.be.eql(p1[1] - perp1[1]);

                    expect(points[2], 'x2').to.be.eql(p1[0] + perp1[0]);
                    expect(points[3], 'y2').to.be.eql(p1[1] + perp1[1]);

                    // center
                    expect(points[4], 'center1 x').to.be.eql(anchor[0]);
                    expect(points[5], 'center1 y').to.be.eql(anchor[1]);

                    expect(points[8], 'center2 x').to.be.eql(anchor[0]);
                    expect(points[9], 'center2 y').to.be.eql(anchor[1]);

                    expect(points[12], 'center3 x').to.be.eql(anchor[0]);
                    expect(points[13], 'center3 y').to.be.eql(anchor[1]);

                    expect(points[16], 'center4 x').to.be.eql(anchor[0]);
                    expect(points[17], 'center4 y').to.be.eql(anchor[1]);

                    expect(points[20], 'center5 x').to.be.eql(anchor[0]);
                    expect(points[21], 'center5 y').to.be.eql(anchor[1]);

                    expect(points[24], 'center6 x').to.be.eql(anchor[0]);
                    expect(points[25], 'center6 y').to.be.eql(anchor[1]);

                    // peripheral pts
                    expect(points[6], 'peripheral1 x').to.be.eql(p2[0] + perp1[0]);
                    expect(points[7], 'peripheral1 y').to.be.eql(p2[1] + perp1[1]);

                    angle += angleIncrease;
                    expect(points[10], 'peripheral2 x').to.be.eql(anchor[0] + (Math.sin(angle) * r));
                    expect(points[11], 'peripheral2 y').to.be.eql(anchor[1] + (Math.cos(angle) * r));

                    angle += angleIncrease;
                    expect(points[14], 'peripheral3 x').to.be.eql(anchor[0] + (Math.sin(angle) * r));
                    expect(points[15], 'peripheral3 y').to.be.eql(anchor[1] + (Math.cos(angle) * r));

                    angle += angleIncrease;
                    expect(points[18], 'peripheral4 x').to.be.eql(anchor[0] + (Math.sin(angle) * r));
                    expect(points[19], 'peripheral4 y').to.be.eql(anchor[1] + (Math.cos(angle) * r));

                    angle += angleIncrease;
                    expect(points[22], 'peripheral5 x').to.be.eql(anchor[0] + (Math.sin(angle) * r));
                    expect(points[23], 'peripheral5 y').to.be.eql(anchor[1] + (Math.cos(angle) * r));

                    expect(points[26], 'peripheral6 x').to.be.eql(p2[0] + perp2[0]);
                    expect(points[27], 'peripheral6 y').to.be.eql(p2[1] + perp2[1]);

                    expect(points[28], 'x[last-1]').to.be.eql(p3[0] - perp2[0]);
                    expect(points[29], 'y[last-1]').to.be.eql(p3[1] - perp2[1]);

                    expect(points[30], 'x[last]').to.be.eql(p3[0] + perp2[0]);
                    expect(points[31], 'y[last]').to.be.eql(p3[1] + perp2[1]);
                }));

                it('round join counterclockwise', withGL(function ()
                {
                    // given
                    const p1 = [0, 0];
                    const p2 = [50, 0];
                    const p3 = [50, -50];
                    // normalized perpendicular vectors
                    const perp1 = [0, 1];
                    const perp2 = [1, 0];
                    const anchor = [p2[0] - perp1[0] - perp2[0], p2[1] - perp1[1] - perp2[1]];
                    // doubles cause every point is followed with center point
                    // 1 + 1 + 15 * absAngleDiff * Math.sqrt(radius) / Math.PI
                    const noOfCtlPts = 6 * 2;
                    const r = 2.23606797749979; // sqrt(1^2 + 2^2)
                    const angleIncrease = 0.12870022175865686; // anlge diff / 5
                    let angle = 0.4636476090008061; // Math.atan2(1, -2)
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'round';

                    graphics.moveTo(p1[0], p1[1]);
                    graphics.lineTo(p2[0], p2[1]);
                    graphics.lineTo(p3[0], p3[1]);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal((4 + noOfCtlPts));

                    expect(points[0], 'x1').to.be.eql(p1[0] + perp1[0]);
                    expect(points[1], 'y1').to.be.eql(p1[1] + perp1[1]);

                    expect(points[2], 'x2').to.be.eql(p1[0] - perp1[0]);
                    expect(points[3], 'y2').to.be.eql(p1[1] - perp1[1]);

                    // center
                    expect(points[6], 'center1 x').to.be.eql(anchor[0]);
                    expect(points[7], 'center1 y').to.be.eql(anchor[1]);

                    expect(points[10], 'center2 x').to.be.eql(anchor[0]);
                    expect(points[11], 'center2 y').to.be.eql(anchor[1]);

                    expect(points[14], 'center3 x').to.be.eql(anchor[0]);
                    expect(points[15], 'center3 y').to.be.eql(anchor[1]);

                    expect(points[18], 'center4 x').to.be.eql(anchor[0]);
                    expect(points[19], 'center4 y').to.be.eql(anchor[1]);

                    expect(points[22], 'center5 x').to.be.eql(anchor[0]);
                    expect(points[23], 'center5 y').to.be.eql(anchor[1]);

                    expect(points[26], 'center6 x').to.be.eql(anchor[0]);
                    expect(points[27], 'center6 y').to.be.eql(anchor[1]);

                    // peripheral pts
                    expect(points[4], 'peripheral1 x').to.be.eql(p2[0] + perp1[0]);
                    expect(points[5], 'peripheral1 y').to.be.eql(p2[1] + perp1[1]);

                    angle += angleIncrease;
                    expect(points[8], 'peripheral2 x').to.be.eql(anchor[0] + (Math.sin(angle) * r));
                    expect(points[9], 'peripheral2 y').to.be.eql(anchor[1] + (Math.cos(angle) * r));

                    angle += angleIncrease;
                    expect(points[12], 'peripheral3 x').to.be.eql(anchor[0] + (Math.sin(angle) * r));
                    expect(points[13], 'peripheral3 y').to.be.eql(anchor[1] + (Math.cos(angle) * r));

                    angle += angleIncrease;
                    expect(points[16], 'peripheral4 x').to.be.eql(anchor[0] + (Math.sin(angle) * r));
                    expect(points[17], 'peripheral4 y').to.be.eql(anchor[1] + (Math.cos(angle) * r));

                    angle += angleIncrease;
                    expect(points[20], 'peripheral5 x').to.be.eql(anchor[0] + (Math.sin(angle) * r));
                    expect(points[21], 'peripheral5 y').to.be.eql(anchor[1] + (Math.cos(angle) * r));

                    expect(points[24], 'peripheral6 x').to.be.eql(p2[0] + perp2[0]);
                    expect(points[25], 'peripheral6 y').to.be.eql(p2[1] + perp2[1]);

                    expect(points[28], 'x[last-1]').to.be.eql(p3[0] + perp2[0]);
                    expect(points[29], 'y[last-1]').to.be.eql(p3[1] + perp2[1]);

                    expect(points[30], 'x[last]').to.be.eql(p3[0] - perp2[0]);
                    expect(points[31], 'y[last]').to.be.eql(p3[1] - perp2[1]);
                }));

                it('round join back and forth', withGL(function ()
                {
                    // given
                    const p1 = [0, 0];
                    const p2 = [50, 0];
                    const p3 = [10, 0];
                    // normalized perpendicular vectors
                    const perp1 = [0, -1];
                    const perp2 = [0, 1];
                    const anchor = [p2[0], p2[1]];
                    // doubles cause every point is followed with center point
                    // 1 + 1 + 15 * absAngleDiff * Math.sqrt(radius) / Math.PI
                    const noOfCtlPts = 16 * 2;
                    const r = 1;
                    const angleIncrease = -0.20943951023931953;
                    let angle = 3.141592653589793;
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'round';

                    graphics.moveTo(p1[0], p1[1]);
                    graphics.lineTo(p2[0], p2[1]);
                    graphics.lineTo(p3[0], p3[1]);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;
                    const len = points.length;

                    // control points, xy each
                    expect(len / 2, 'number of control points is not right').to.be.equal((4 + noOfCtlPts));

                    expect(points[0], 'x1').to.be.eql(p1[0] - perp1[0]);
                    expect(points[1], 'y1').to.be.eql(p1[1] - perp1[1]);

                    expect(points[2], 'x2').to.be.eql(p1[0] + perp1[0]);
                    expect(points[3], 'y2').to.be.eql(p1[1] + perp1[1]);

                    // center
                    for (let i = 4, j = 1; j <= 16; i += 4, j++)
                    {
                        expect(points[i], `center${j} x`).to.be.eql(p2[0]);
                        expect(points[i + 1], `center${j} y`).to.be.eql(p2[1]);
                    }

                    // peripheral pts

                    expect(points[6], 'peripheral1 x').to.be.eql(p2[0] + perp1[0]);
                    expect(points[7], 'peripheral1 y').to.be.eql(p2[1] + perp1[1]);

                    for (let i = 10, j = 2; j < 16; i += 4, j++)
                    {
                        angle += angleIncrease;
                        expect(points[i], `peripheral${j} x`).to.be.eql(anchor[0] + (Math.sin(angle) * r));
                        expect(points[i + 1], `peripheral${j} y`).to.be.eql(anchor[1] + (Math.cos(angle) * r));
                    }

                    expect(points[len - 6], 'peripheral16 x').to.be.eql(p2[0] + perp2[0]);
                    expect(points[len - 5], 'peripheral16 y').to.be.eql(p2[1] + perp2[1]);

                    expect(points[len - 4], 'x[last-1]').to.be.eql(p3[0] - perp2[0]);
                    expect(points[len - 3], 'y[last-1]').to.be.eql(p3[1] - perp2[1]);

                    expect(points[len - 2], 'x[last]').to.be.eql(p3[0] + perp2[0]);
                    expect(points[len - 1], 'y[last]').to.be.eql(p3[1] + perp2[1]);
                }));

                it('round join back and forth other way around', withGL(function ()
                {
                    // given
                    const p1 = [0, 0];
                    const p2 = [-50, 0];
                    const p3 = [10, 0];
                    // normalized perpendicular vectors
                    const perp1 = [0, 1];
                    const perp2 = [0, -1];
                    const anchor = [p2[0], p2[1]];
                    // doubles cause every point is followed with center point
                    // 1 + 1 + 15 * absAngleDiff * Math.sqrt(radius) / Math.PI
                    const noOfCtlPts = 16 * 2;
                    const r = 1;
                    const angleIncrease = -0.20943951023931953;
                    let angle = 0;
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'round';

                    graphics.moveTo(p1[0], p1[1]);
                    graphics.lineTo(p2[0], p2[1]);
                    graphics.lineTo(p3[0], p3[1]);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;
                    const len = points.length;

                    // control points, xy each
                    expect(len / 2, 'number of control points is not right').to.be.equal((4 + noOfCtlPts));

                    expect(points[0], 'x1').to.be.eql(p1[0] - perp1[0]);
                    expect(points[1], 'y1').to.be.eql(p1[1] - perp1[1]);

                    expect(points[2], 'x2').to.be.eql(p1[0] + perp1[0]);
                    expect(points[3], 'y2').to.be.eql(p1[1] + perp1[1]);

                    // center
                    for (let i = 4, j = 1; j <= 16; i += 4, j++)
                    {
                        expect(points[i], `center${j} x`).to.be.eql(p2[0]);
                        expect(points[i + 1], `center${j} y`).to.be.eql(p2[1]);
                    }

                    // peripheral pts

                    expect(points[6], 'peripheral1 x').to.be.eql(p2[0] + perp1[0]);
                    expect(points[7], 'peripheral1 y').to.be.eql(p2[1] + perp1[1]);

                    for (let i = 10, j = 2; j < 16; i += 4, j++)
                    {
                        angle += angleIncrease;
                        expect(points[i], `peripheral${j} x`).to.be.eql(anchor[0] + (Math.sin(angle) * r));
                        expect(points[i + 1], `peripheral${j} y`).to.be.eql(anchor[1] + (Math.cos(angle) * r));
                    }

                    expect(points[len - 6], 'peripheral16 x').to.be.eql(p2[0] + perp2[0]);
                    expect(points[len - 5], 'peripheral16 y').to.be.eql(p2[1] + perp2[1]);

                    expect(points[len - 4], 'x[last-1]').to.be.eql(p3[0] - perp2[0]);
                    expect(points[len - 3], 'y[last-1]').to.be.eql(p3[1] - perp2[1]);

                    expect(points[len - 2], 'x[last]').to.be.eql(p3[0] + perp2[0]);
                    expect(points[len - 1], 'y[last]').to.be.eql(p3[1] + perp2[1]);
                }));

                it('flat line round', withGL(function ()
                {
                    // given
                    const renderer = new Renderer({ width: 200, height: 200 });
                    const graphics = new Graphics();

                    graphics.lineStyle(2, 0, 1, 0.5);
                    graphics.line.lineJoin = 'round';

                    graphics.moveTo(0, 0);
                    graphics.lineTo(50, 0);
                    graphics.lineTo(100, 0);

                    // when
                    renderer.render(graphics);

                    // then
                    const points = graphics.geometry.points;

                    // 6 control points, xy each
                    expect(points.length / 2, 'number of control points is not right').to.be.equal(6);

                    expect(points[0], 'x1').to.be.eql(0);
                    expect(points[1], 'y1').to.be.eql(1);

                    expect(points[2], 'x2').to.be.eql(0);
                    expect(points[3], 'y2').to.be.eql(-1);

                    expect(points[4], 'x3').to.be.eql(50);
                    expect(points[5], 'y3').to.be.eql(1);

                    expect(points[6], 'x4').to.be.eql(50);
                    expect(points[7], 'y4').to.be.eql(-1);

                    expect(points[8], 'x5').to.be.eql(100);
                    expect(points[9], 'y5').to.be.eql(1);

                    expect(points[10], 'x6').to.be.eql(100);
                    expect(points[11], 'y6').to.be.eql(-1);
                }));
            });
        });
    });

    describe('containsPoint', function ()
    {
        it('should return true when point inside', function ()
        {
            const point = new Point(1, 1);
            const graphics = new Graphics();

            graphics.beginFill(0);
            graphics.drawRect(0, 0, 10, 10);

            expect(graphics.containsPoint(point)).to.be.true;
        });

        it('should return false when point outside', function ()
        {
            const point = new Point(20, 20);
            const graphics = new Graphics();

            graphics.beginFill(0);
            graphics.drawRect(0, 0, 10, 10);

            expect(graphics.containsPoint(point)).to.be.false;
        });

        it('should return false when no fill', function ()
        {
            const point = new Point(1, 1);
            const graphics = new Graphics();

            graphics.drawRect(0, 0, 10, 10);

            expect(graphics.containsPoint(point)).to.be.false;
        });

        it('should return false with hole', function ()
        {
            const point1 = new Point(1, 1);
            const point2 = new Point(5, 5);
            const graphics = new Graphics();

            graphics.beginFill(0)
                .moveTo(0, 0)
                .lineTo(10, 0)
                .lineTo(10, 10)
                .lineTo(0, 10)
                .beginHole()
                .moveTo(2, 2)
                .lineTo(8, 2)
                .lineTo(8, 8)
                .lineTo(2, 8)
                .endHole();

            expect(graphics.containsPoint(point1)).to.be.true;
            expect(graphics.containsPoint(point2)).to.be.false;
        });
    });

    describe('chaining', function ()
    {
        it('should chain draw commands', function ()
        {
            // complex drawing #1: draw triangle, rounder rect and an arc (issue #3433)
            const graphics = new Graphics().beginFill(0xFF3300)
                .lineStyle(4, 0xffd900, 1)
                .moveTo(50, 50)
                .lineTo(250, 50)
                .endFill()
                .drawRoundedRect(150, 450, 300, 100, 15)
                .beginHole()
                .endHole()
                .quadraticCurveTo(1, 1, 1, 1)
                .bezierCurveTo(1, 1, 1, 1)
                .arcTo(1, 1, 1, 1, 1)
                .arc(1, 1, 1, 1, 1, false)
                .drawRect(1, 1, 1, 1)
                .drawRoundedRect(1, 1, 1, 1, 0.1)
                .drawCircle(1, 1, 20)
                .drawEllipse(1, 1, 1, 1)
                .drawPolygon([1, 1, 1, 1, 1, 1])
                .drawStar(1, 1, 1, 1, 1, 1)
                .clear();

            expect(graphics).to.be.not.null;
        });
    });

    describe('arc', function ()
    {
        it('should draw an arc', function ()
        {
            const graphics = new Graphics();

            expect(graphics.currentPath).to.be.null;

            expect(() => graphics.arc(100, 30, 20, 0, Math.PI)).to.not.throw();

            expect(graphics.currentPath).to.be.not.null;
        });

        it('should not throw with other shapes', function ()
        {
            // complex drawing #1: draw triangle, rounder rect and an arc (issue #3433)
            const graphics = new Graphics();

            // set a fill and line style
            graphics.beginFill(0xFF3300);
            graphics.lineStyle(4, 0xffd900, 1);

            // draw a shape
            graphics.moveTo(50, 50);
            graphics.lineTo(250, 50);
            graphics.lineTo(100, 100);
            graphics.lineTo(50, 50);
            graphics.endFill();

            graphics.lineStyle(2, 0xFF00FF, 1);
            graphics.beginFill(0xFF00BB, 0.25);
            graphics.drawRoundedRect(150, 450, 300, 100, 15);
            graphics.endFill();

            graphics.beginFill();
            graphics.lineStyle(4, 0x00ff00, 1);

            expect(() => graphics.arc(300, 100, 20, 0, Math.PI)).to.not.throw();
        });

        it('should do nothing when startAngle and endAngle are equal', function ()
        {
            const graphics = new Graphics();

            expect(graphics.currentPath).to.be.null;

            graphics.arc(0, 0, 10, 0, 0);

            expect(graphics.currentPath).to.be.null;
        });

        it('should do nothing if sweep equals zero', function ()
        {
            const graphics = new Graphics();

            expect(graphics.currentPath).to.be.null;

            graphics.arc(0, 0, 10, 10, 10);

            expect(graphics.currentPath).to.be.null;
        });
    });

    describe('_calculateBounds', function ()
    {
        it('should only call updateLocalBounds once', function ()
        {
            const graphics = new Graphics();
            const spy = sinon.spy(graphics.geometry, 'calculateBounds');

            graphics._calculateBounds();

            expect(spy).to.have.been.calledOnce;

            graphics._calculateBounds();

            expect(spy).to.have.been.calledOnce;
        });
    });

    describe('drawCircle', function ()
    {
        it('should have no gaps in line border', withGL(function ()
        {
            const renderer = new Renderer({ width: 200, height: 200 });

            try
            {
                const graphics = new Graphics();

                graphics.lineStyle(15, 0x8FC7E6);
                graphics.drawCircle(100, 100, 30);
                renderer.render(graphics);
                const points = graphics.geometry.graphicsData[0].points;

                const firstX = points[0];
                const firstY = points[1];

                const lastX = points[points.length - 2];
                const lastY = points[points.length - 1];

                expect(firstX).to.equals(lastX);
                expect(firstY).to.equals(lastY);
            }
            finally
            {
                renderer.destroy();
            }
        }));
    });

    describe('startPoly', function ()
    {
        it('should fill two triangles', withGL(function ()
        {
            const graphics = new Graphics();

            graphics.beginFill(0xffffff, 1.0);
            graphics.moveTo(50, 50);
            graphics.lineTo(250, 50);
            graphics.lineTo(100, 100);
            graphics.lineTo(50, 50);

            graphics.moveTo(250, 50);
            graphics.lineTo(450, 50);
            graphics.lineTo(300, 100);
            graphics.lineTo(250, 50);
            graphics.endFill();

            const data = graphics.geometry.graphicsData;

            expect(data.length).to.equals(2);
            expect(data[0].shape.points).to.eql([50, 50, 250, 50, 100, 100, 50, 50]);
            expect(data[1].shape.points).to.eql([250, 50, 450, 50, 300, 100, 250, 50]);
        }));

        it('should honor lineStyle break', withGL(function ()
        {
            const graphics = new Graphics();

            graphics.lineStyle(1.0, 0xffffff);
            graphics.moveTo(50, 50);
            graphics.lineTo(250, 50);
            graphics.lineStyle(2.0, 0xffffff);
            graphics.lineTo(100, 100);
            graphics.lineTo(50, 50);
            graphics.lineStyle(0.0);

            const data = graphics.geometry.graphicsData;

            expect(data.length).to.equals(2);
            expect(data[0].shape.points).to.eql([50, 50, 250, 50]);
            expect(data[1].shape.points).to.eql([250, 50, 100, 100, 50, 50]);
        }));
    });
});