Source: Projector.js

/**
 * @class Phaser.Plugin.Isometric.Projector
 *
 * @classdesc
 * Creates a new Isometric Projector object, which has helpers for projecting x, y and z coordinates into axonometric x and y equivalents.
 *
 * @constructor
 * @param {Phaser.Game} game - The current game object.
 * @param {number} projectionAngle - The angle of the axonometric projection in radians. Defaults to approx. 0.4636476 (Math.atan(0.5) which is suitable for 2:1 pixel art dimetric)
 * @return {Phaser.Plugin.Isometric.Cube} This Cube object.
 */
Phaser.Plugin.Isometric.Projector = function (game, projectionAngle) {

    /**
     * @property {Phaser.Game} game - The current game object.
     */
    this.game = game;

    /**
     * @property {array} _transform - The pre-calculated axonometric transformation values.
     * @private
     */
    this._transform = null;

    /**
     * @property {number} _projectionAngle - The cached angle of projection in radians.
     * @private
     */
    this._projectionAngle = 0;

    /**
     * @property {number} projectionAngle - The angle of projection in radians.
     * @default
     */
    this.projectionAngle = projectionAngle || Phaser.Plugin.Isometric.CLASSIC;

    /**
     * @property {Phaser.Point} anchor - The x and y offset multipliers as a ratio of the game world size.
     * @default
     */
    this.anchor = new Phaser.Point(0.5, 0);


};

//  Projection angles
Phaser.Plugin.Isometric.CLASSIC = Math.atan(0.5);
Phaser.Plugin.Isometric.ISOMETRIC = Math.PI / 6;
Phaser.Plugin.Isometric.MILITARY = Math.PI / 4;

Phaser.Plugin.Isometric.Projector.prototype = {

    /**
     * Use axonometric projection to transform a 3D Point3 coordinate to a 2D Point coordinate. If given the coordinates will be set into the object, otherwise a brand new Point object will be created and returned.
     * @method Phaser.Plugin.Isometric.Projector#project
     * @param {Phaser.Plugin.Isometric.Point3} point3 - The Point3 to project from.
     * @param {Phaser.Point} out - The Point to project to.
     * @return {Phaser.Point} The transformed Point.
     */
    project: function (point3, out) {
        if (typeof out === "undefined") {
            out = new Phaser.Point();
        }

        out.x = (point3.x - point3.y) * this._transform[0];
        out.y = ((point3.x + point3.y) * this._transform[1]) - point3.z;

        out.x += this.game.world.width * this.anchor.x;
        out.y += this.game.world.height * this.anchor.y;

        return out;
    },

    /**
     * Use axonometric projection to transform a 3D Point3 coordinate to a 2D Point coordinate, ignoring the z-axis. If given the coordinates will be set into the object, otherwise a brand new Point object will be created and returned.
     * @method Phaser.Plugin.Isometric.Projector#projectXY
     * @param {Phaser.Plugin.Isometric.Point3} point3 - The Point3 to project from.
     * @param {Phaser.Point} out - The Point to project to.
     * @return {Phaser.Point} The transformed Point.
     */
    projectXY: function (point3, out) {
        if (typeof out === "undefined") {
            out = new Phaser.Point();
        }

        out.x = (point3.x - point3.y) * this._transform[0];
        out.y = (point3.x + point3.y) * this._transform[1];

        out.x += this.game.world.width * this.anchor.x;
        out.y += this.game.world.height * this.anchor.y;

        return out;
    },

    /**
     * Use reverse axonometric projection to transform a 2D Point coordinate to a 3D Point3 coordinate. If given the coordinates will be set into the object, otherwise a brand new Point3 object will be created and returned.
     * @method Phaser.Plugin.Isometric.Projector#unproject
     * @param {Phaser.Plugin.Isometric.Point} point - The Point to project from.
     * @param {Phaser.Plugin.Isometric.Point3} out - The Point3 to project to.
     * @param {number} [z] - Specified z-plane to project to.
     * @return {Phaser.Plugin.Isometric.Point3} The transformed Point3.
     */
    unproject: function (point, out, z) {
        if (typeof out === "undefined") {
            out = new Phaser.Plugin.Isometric.Point3();
        }

        z = z || 0;


        var x = point.x - this.game.world.x - (this.game.world.width * this.anchor.x);
        var y = point.y - this.game.world.y - (this.game.world.height * this.anchor.y) + z;

        out.x = x / (2 * this._transform[0]) + y / (2 * this._transform[1]);
        out.y = -(x / (2 * this._transform[0])) + y / (2 * this._transform[1]);
        out.z = z;

        return out;
    },

    /**
     * Perform a simple depth sort on all IsoSprites in the passed group. This function is fast and will accurately sort items on a single z-plane, but breaks down when items are above/below one another in certain configurations.
     *
     * @method Phaser.Plugin.Isometric.Projector#simpleSort
     * @param {Phaser.Group} group - A group of IsoSprites to sort.
     */
    simpleSort: function(group) {
        group.sort("depth");
    },

    /**
     * Perform a volume-based topological sort on all IsoSprites in the passed group or array. Will use the body if available, otherwise it will use an automatically generated bounding cube. If a group is passed, <code>Phaser.Group#sort</code> is automatically called on the specified property.
     * Routine adapted from this tutorial: http://mazebert.com/2013/04/18/isometric-depth-sorting/
     *
     * @method Phaser.Plugin.Isometric.Projector#topologicalSort
     * @param {Phaser.Group|array} group - A group or array of IsoSprites to sort.
     * @param {number} [padding] - The amount of extra tolerance in the depth sorting; larger values reduce flickering when objects collide, but also introduce inaccuracy when objects are close. Defaults to 1.5.
     * @param {string} [prop] - The property to store the depth information on. If not specified, it will default to 'isoDepth'.
     */
    topologicalSort: function (group, padding, prop) {
        var children, isGroup;

        if (group instanceof Phaser.Group) {
            children = group.children;
            isGroup = true;
        }
        else if (group.length) {
            children = group;
        }
        else {
            return;
        }

        prop = prop || "isoDepth";

        if (typeof padding === "undefined") {
            padding = 1.5;
        }
        else {
            padding = padding;
        }

        var a, b, i, j, bounds, behindIndex, len = children.length;

        for (i = 0; i < len; i++) {
            a = children[i];
            behindIndex = 0;
            if (!a.isoSpritesBehind) {
                a.isoSpritesBehind = [];
            }

            for (j = 0; j < len; j++) {
                if (i != j) {
                    b = children[j];
                    bounds = a.body || a.isoBounds;
                    if (b._isoPosition.x + padding < bounds.frontX - padding && b._isoPosition.y + padding < bounds.frontY - padding && b._isoPosition.z + padding < bounds.top - padding) {
                        a.isoSpritesBehind[behindIndex++] = b;
                    }
                }
            }
            a.isoVisitedFlag = false;
        }

        var _sortDepth = 0;

        function visitNode(node) {
            if (node.isoVisitedFlag === false) {
                node.isoVisitedFlag = true;
                var spritesBehindLength = node.isoSpritesBehind.length;
                for (var k = 0; k < spritesBehindLength; k++) {
                    if (node.isoSpritesBehind[k] === null) {
                        break;
                    }
                    else {
                        visitNode(node.isoSpritesBehind[k]);
                        node.isoSpritesBehind[k] = null;
                    }
                }

                node[prop] = _sortDepth++;
            }
        }

        for (i = 0; i < len; i++) {
            visitNode(children[i]);
        }

        if (isGroup) {
            group.sort(prop);
        }
    }

};

/**
 * @name Phaser.Plugin.Isometric.Projector#projectionAngle
 * @property {number} projectionAngle - The angle of axonometric projection.
 */
Object.defineProperty(Phaser.Plugin.Isometric.Projector.prototype, "projectionAngle", {

    get: function () {
        return this._projectionAngle;
    },

    set: function (value) {

        if (value === this._projectionAngle) {
            return;
        }

        this._projectionAngle = value;

        this._transform = [Math.cos(this._projectionAngle), Math.sin(this._projectionAngle)];
    }

});