API Docs for: 0.5.5
Show:

File: kick/scene/Transform.js

define(["kick/math/Mat4", "kick/math/Vec3", "kick/math/Quat", "kick/core/Constants", "kick/core/Util", "kick/core/EngineSingleton"], function (Mat4, Vec3, Quat, Constants, Util, EngineSingleton) {
    "use strict";

    var ASSERT = Constants._ASSERT,
        Transform;

    /**
     * Position, rotation and scale of a game object. This component should not be created manually.
     * It is created when a GameObject is created.
     *
     * KickJS uses a right handed coordinate system.
     * @namespace kick.scene
     * @class Transform
     * @extends kick.scene.Component
     */
    Transform = function () {
        var localMatrix = Mat4.create(),
            globalMatrix = Mat4.create(),
            localMatrixInverse = Mat4.create(),
            globalMatrixInverse = Mat4.create(),
            globalPosition = Vec3.create(),
            localPosition = Vec3.create(),
            globalRotationQuat = Quat.create(),
            localRotationQuat = Quat.create(),
            localScale = Vec3.clone([1, 1, 1]),
            // the dirty parameter let the
            LOCAL = 0,
            LOCAL_INV = 1,
            GLOBAL = 2,
            GLOBAL_INV = 3,
            GLOBAL_POSITION = 4,
            GLOBAL_ROTATION = 5,
            dirty = new Int8Array(6), // local,localInverse,global,globalInverse
            children = [],
            parentTransform = null,
            thisObj = this,
            isNaNArray = function(array){
                var i;
                for (i=0;i<array.length;i++){
                    if (isNaN(array[i])){
                        Util.warn("NaN");
                    }
                }
            },
            markGlobalDirty = function () {
                var i;
                dirty[GLOBAL] = 1;
                dirty[GLOBAL_INV] = 1;
                dirty[GLOBAL_POSITION] = 1;
                dirty[GLOBAL_ROTATION] = 1;
                for (i = children.length - 1; i >= 0; i--) {
                    children[i]._markGlobalDirty();
                }
            },
            markLocalDirty = function () {
                dirty[LOCAL] = 1;
                dirty[LOCAL_INV] = 1;
                markGlobalDirty();
            };

        Object.defineProperties(this, {
            /**
             * Global position.
             * @property position
             * @type kick.math.Vec3
             */
            position: {
                get: function () {
                    // if no parent - use local position
                    if (parentTransform === null) {
                        return Vec3.clone(localPosition);
                    }
                    if (dirty[GLOBAL_POSITION]) {
                        Mat4.multiplyVec3(globalPosition, thisObj.getGlobalMatrix(), [0, 0, 0]);
                        dirty[GLOBAL_POSITION] = 0;
                    }
                    return Vec3.clone(globalPosition);
                },
                set: function (newValue) {
                    if (ASSERT){
                        isNaNArray(newValue);
                    }
                    var currentPosition;
                    if (parentTransform === null) {
                        thisObj.localPosition = newValue;
                        return;
                    }
                    currentPosition = thisObj.position;
                    Vec3.copy(localPosition, newValue);
                    thisObj.localPosition = [
                        localPosition[0] + currentPosition[0] - newValue[0],
                        localPosition[1] + currentPosition[1] - newValue[1],
                        localPosition[2] + currentPosition[2] - newValue[2]
                    ];
                    markLocalDirty();
                }
            },
            /**
             * Local position.
             * @property localPosition
             * @type kick.math.Vec3
             */
            localPosition: {
                get: function () {
                    return Vec3.clone(localPosition);
                },
                set: function (newValue) {
                    if (ASSERT){
                        isNaNArray(newValue);
                    }
                    Vec3.copy(localPosition, newValue);
                    markLocalDirty();
                }
            },
            /**
             * Local rotation in euler angles.
             * @property localRotationEuler
             * @type kick.math.Vec3
             */
            localRotationEuler: {
                get: function () {
                    var vec = Vec3.create();
                    Quat.toEuler(vec, localRotationQuat);
                    return vec;
                },
                set: function (newValue) {
                    if (ASSERT){
                        isNaNArray(newValue);
                    }
                    Quat.setEuler(localRotationQuat, newValue);
                    markLocalDirty();
                }
            },
            /**
             * Global rotation in euler angles.
             * @property rotationEuler
             * @type kick.math.Vec3
             */
            rotationEuler: {
                get: function () {
                    var vec = Vec3.create();
                    Quat.toEuler(vec, thisObj.rotation);
                    return vec;
                },
                set: function (newValue) {
                    if (ASSERT){
                        isNaNArray(newValue);
                    }
                    var tmp = Quat.create();
                    Quat.setEuler(tmp, newValue);
                    this.rotation = tmp;
                }
            },

            /**
             * Global rotation in quaternion.
             * @property rotation
             * @type kick.math.Quat
             */
            rotation: {
                get: function () {
                    var parentIterator = null;
                    if (parentTransform === null) {
                        return Quat.clone(localRotationQuat);
                    }
                    if (dirty[GLOBAL_ROTATION]) {
                        Quat.copy(globalRotationQuat, localRotationQuat);
                        parentIterator = thisObj.parent;
                        while (parentIterator !== null) {
                            Quat.multiply(globalRotationQuat, parentIterator.localRotation, globalRotationQuat);
                            parentIterator = parentIterator.parent;
                        }
                        dirty[GLOBAL_ROTATION] = false;
                    }
                    return globalRotationQuat;
                },
                set: function (newValue) {
                    if (ASSERT){
                        isNaNArray(newValue);
                    }
                    if (parentTransform === null) {
                        this.localRotation = newValue;
                        return;
                    }
                    var rotationDifference = Quat.create();
                    Quat.difference(rotationDifference, newValue, thisObj.rotation);
                    this.localRotation = Quat.multiply(localRotationQuat, localRotationQuat, rotationDifference);
                }
            },
            /**
             * Local rotation in quaternion.
             * @property localRotation
             * @type kick.math.Quat
             */
            localRotation: {
                get: function () {
                    return localRotationQuat;
                },
                set: function (newValue) {
                    if (ASSERT){
                        isNaNArray(newValue);
                    }
                    Quat.copy(localRotationQuat, newValue);
                    markLocalDirty();
                }
            },
            /**
             * Local scale.
             * Any zero value will be replaced with an epsilon value.
             * @property localScale
             * @type kick.math.Vec3
             */
            localScale: {
                get: function () {
                    return Vec3.clone(localScale);
                },
                set: function (newValue) {
                    if (ASSERT){
                        isNaNArray(newValue);
                    }
                    var i;
                    Vec3.copy(localScale, newValue);
                    // replace 0 value with epsilon to prevent a singular matrix
                    for (i = 0; i < localScale.length; i++) {
                        if (localScale[i] === 0) {
                            localScale[i] = Constants._EPSILON;
                        }
                    }
                    markLocalDirty();
                }
            },
            /**
             * Array of children. The children should not be modified directly. Instead use the parent property
             * @property children
             * @type Array_kick.scene.Transform
             */
            children: {
                value: children
            },
            /**
             * Parent transform. Initial null.
             * @property parent
             * @type kick.scene.Transform
             */
            parent: {
                get: function () {
                    return parentTransform;
                },
                set: function (newParent) {
                    if (newParent === this) {
                        Util.fail('Cannot assign parent to self');
                    }
                    if (ASSERT) {
                        if (newParent === undefined) {
                            Util.fail("Cannot set newParent to undefined - should be null");
                        }
                    }
                    if (newParent !== parentTransform) {
                        if (newParent === null) {
                            parentTransform = null;
                            Util.removeElementFromArray(newParent.children, this);
                        } else {
                            parentTransform = newParent;
                            newParent.children.push(this);
                        }
                        markGlobalDirty();
                    }
                }
            },
            /**
             * Name of the component type = "transform".
             * @example
             *      var transform = gameObject.transform;
             * @property componentType
             * @type String
             * @final
             */
            componentType: {value:"transform"}
        });

        /**
         * Changes the rotation of the object to look at input (Transform) object.
         * @method lookAt
         * @param {kick.scene.Transform} transform target object to look at
         * @param {kick.math.Vec3} up the up-vector used in the lookAt
         */
        this.lookAt = function (transform, up) {
            if (ASSERT) {
                if (!(transform instanceof Transform)) {
                    Util.fail("transform must be a kick.scene.Transform");
                }
            }
            Quat.lookAt(localRotationQuat, thisObj.position, transform.position, up);
            markLocalDirty();
        };

        /**
         * Return the local transformation matrix
         * @method getLocalMatrix
         * @return {kick.math.Mat4} local transformation
         */
        this.getLocalMatrix = function () {
            if (dirty[LOCAL]) {
                Mat4.setTRS(localMatrix, localPosition, localRotationQuat, localScale);
                dirty[LOCAL] = 0;
            }
            return localMatrix;
        };

        /**
         * Return the local inverse of translate rotate scale
         * @method getLocalTRSInverse
         * @return {kick.math.Mat4} inverse of local transformation
         */
        this.getLocalTRSInverse = function () {
            if (dirty[LOCAL_INV]) {
                Mat4.setTRSInverse(localMatrixInverse, localPosition, localRotationQuat, localScale);
                dirty[LOCAL_INV] = 0;
            }
            return localMatrixInverse;
        };

        /**
         * @method getGlobalMatrix
         * @return {kick.math.Mat4} global transform
         */
        this.getGlobalMatrix = function () {
            if (dirty[GLOBAL]) {
                Mat4.copy(globalMatrix, thisObj.getLocalMatrix());

                var transformIterator = thisObj.parent;
                while (transformIterator !== null) {
                    Mat4.multiply(globalMatrix, transformIterator.getLocalMatrix(), globalMatrix);
                    transformIterator  = transformIterator.parent;
                }
                dirty[GLOBAL] = 0;
            }
            return globalMatrix;
        };

        /**
         * Return the inverse of global rotate translate transform
         * @method getGlobalTRSInverse
         * @return {kick.math.Mat4} inverse global transform
         */
        this.getGlobalTRSInverse = function () {
            if (dirty[GLOBAL_INV]) {
                Mat4.copy(globalMatrixInverse, thisObj.getLocalTRSInverse());
                var transformIterator = thisObj.parent;
                while (transformIterator !== null) {
                    Mat4.multiply(globalMatrixInverse, globalMatrixInverse, transformIterator.getLocalTRSInverse());
                    transformIterator  = transformIterator.parent;
                }
                dirty[GLOBAL_INV] = 0;
            }
            return globalMatrixInverse;
        };

        /**
         * Mark the global transform updated.
         * This will mark the transform updated (meaning the global transform must be recomputed based on
         * translation, rotation, scale)
         * @method markGlobalDirty
         * @private
         */
        this._markGlobalDirty = markGlobalDirty;

        /**
         * @method toJSON
         * @return {Object} JSON formatted object
         */
        this.toJSON = function () {
            var typedArrayToArray = Util.typedArrayToArray;
            if (ASSERT) {
                if (!thisObj.gameObject) {
                    Util.fail("Cannot serialize a Transform object that has no reference to gameObject");
                }
            }
            return {
                type: "kick.scene.Transform",
                uid: EngineSingleton.engine.getUID(thisObj),
                config: {
                    localPosition: typedArrayToArray(localPosition),
                    localRotation: typedArrayToArray(localRotationQuat),
                    localScale: typedArrayToArray(localScale),
                    parent: parentTransform ? Util.getJSONReference(parentTransform) : null
                }
            };
        };



        /**
         * @method str
         * @return {String} stringify JSON
         */
        this.str = function () {
            return JSON.stringify(thisObj.toJSON());
        };
    };

    return Transform;
});