components/RelativePosition.js

import AABB from '../AABB.js';
import createComponentClass from '../factory.js';

export default (function () {
    return createComponentClass(/** @lends platypus.components.RelativePosition.prototype */{
        
        id: 'RelativePosition',

        properties: {
            /**
             * This optional array sets varying properties depending on the aspect ratio of the camera. This is useful if the camera aspect ratio varies greatly and entities must be repositioned accordingly.
             *
             * In this example, the entity maintains a particular horizontal location until the camera viewport becomes narrower than 2:1, at which point it maintains a particular `left` value:
             *
             *      "cameraSizes": [{
             *          "minRatio": 2, // This block applies for wide aspect ratios until 2:1 is reached.
             *          "x": -1226
             *      }, {
             *          "minRatio": 0, // This block applies for everything else.
             *          "left": 130
             *      }]
             *
             * @property cameraSizes
             * @type Array
             * @default null
             */
            cameraSizes: null
        },
        
        publicProperties: {
            /**
             * This sets the distance in world units from the bottom of the camera's world viewport. If set, it will override the entity's y coordinate. This property is accessible on the entity as `entity.bottom`.
             *
             * @property bottom
             * @type Number
             * @default null
             */
            bottom: null,

            /**
             * This sets the distance in world units from the left of the camera's world viewport. If set, it will override the entity's x coordinate. This property is accessible on the entity as `entity.left`.
             *
             * @property left
             * @type Number
             * @default null
             */
            left: null,

            /**
             * This sets the distance in world units from the right of the camera's world viewport. If set, it will override the entity's x coordinate. This property is accessible on the entity as `entity.right`.
             *
             * @property right
             * @type Number
             * @default null
             */
            right: null,

            /**
             * This sets the distance in world units from the top of the camera's world viewport. If set, it will override the entity's y coordinate. This property is accessible on the entity as `entity.top`.
             *
             * @property top
             * @type Number
             * @default null
             */
            top: null,

            /**
             * This sets the scale in X of the entity.
             *
             * @property scaleX
             * @type Number
             * @default 1
             */
            scaleX: 1,

            /**
             * This sets the scale in Y of the entity.
             *
             * @property scaleY
             * @type Number
             * @default 1
             */
            scaleY: 1,

            /**
             * This sets the `x` coordinate for the entity. It is overridden by `left` and `right` properties if supplied.
             *
             * @property x
             * @type Number
             * @default 0
             */
            x: 0,

            /**
             * This sets the `y` coordinate for the entity. It is overridden by `top` and `bottom` properties if supplied.
             *
             * @property y
             * @type Number
             * @default 0
             */
            y: 0
        },
        
        /**
         * This component positions an entity relative to the camera viewport, according to `left`, `top`, `right`, and `left` properties.
         *
         * @memberof platypus.components
         * @uses platypus.Component
         * @constructs
         * @listens platypus.Entity#camera-update
         * @listens platypus.Entity#handle-logic
         */
        initialize: function (/*definition, callback*/) {
            this.aabb = AABB.setUp();
            this.lastBottom = null;
            this.lastLeft = null;
            this.lastRight = null;
            this.lastTop = null;
            this.cameraSizesIndex = -1;
        },

        events: {
            "handle-logic": function () {
                var bottom = this.bottom,
                    left = this.left,
                    right = this.right,
                    top = this.top;

                if ((this.lastBottom !== bottom) || (this.lastLeft !== left) || (this.lastRight !== right) || (this.lastTop !== top)) {
                    this.updatePosition(this.aabb);
                    this.lastBottom = bottom;
                    this.lastLeft = left;
                    this.lastRight = right;
                    this.lastTop = top;
                }
            },

            "camera-update": function (camera) {
                this.aabb.set(camera.viewport);
                if (this.cameraSizes) {
                    this.checkCamera(this.aabb);
                }
                this.updatePosition(this.aabb);
            }
        },
        
        methods: {// These are internal methods that are invoked by this component.
            checkCamera: function (aabb) {
                var arr = this.cameraSizes,
                    i = 0,
                    ratio = aabb.width / aabb.height;

                for (i = 0; i < arr.length; i++) {
                    if (ratio > arr[i].minRatio) {
                        if (i !== this.cameraSizesIndex) {
                            this.updateProperties(arr[i]);
                            this.cameraSizesIndex = i;
                        }
                        break;
                    }
                }
            },

            updateProperties: function (props) {
                this.right = (typeof props.right === 'number') ? props.right : null;
                this.left = (typeof props.left === 'number') ? props.left : null;
                this.top = (typeof props.top === 'number') ? props.top : null;
                this.bottom = (typeof props.bottom === 'number') ? props.bottom : null;
                if (typeof props.x === 'number') {
                    this.x = props.x;
                }
                if (typeof props.y === 'number') {
                    this.y = props.y;
                }
                if (typeof props.scaleX === 'number') {
                    this.scaleX = props.scaleX;
                }
                if (typeof props.scaleY === 'number') {
                    this.scaleY = props.scaleY;
                }
            },

            updatePosition: function (vp) {
                var bottom = this.bottom,
                    left = this.left,
                    owner = this.owner,
                    right = this.right,
                    top = this.top;

                if (typeof left === 'number') {
                    owner.x = vp.left + left;
                } else if (typeof right === 'number') {
                    owner.x = vp.right - right;
                }

                if (typeof top === 'number') {
                    owner.y = vp.top + top;
                } else if (typeof bottom === 'number') {
                    owner.y = vp.bottom - bottom;
                }
            },

            destroy: function () {
                this.aabb.recycle();
                this.aabb = null;
            }
        }
    });
}());