text:
/**
 * @author yamahigashi https://github.com/yamahigashi
 */

( function() {

    function FBXNodes() {}

    FBXNodes.prototype.add = function ( key, val ) {
        this[ key ] =  val;
    };

    FBXNodes.prototype.searchConnectionParent = function ( id ) {

        if ( this.__cache_search_connection_parent === undefined ){
            this.__cache_search_connection_parent = [];
        }

        if ( this.__cache_search_connection_parent[ id ] !== undefined ){
            return this.__cache_search_connection_parent[ id ];
        } else {
            this.__cache_search_connection_parent[ id ] = [];
        }

        var conns   = this.Connections.properties.connections;

        var results = [];
        for (var i=0; i < conns.length; ++i) {

            if ( conns[i][0] == id ) {
                // 0 means scene root
                var res = conns[i][1] === 0 ? -1 : conns[i][1];
                results.push( res );

            }

        }

        if ( results.length > 0 ){
            this.__cache_search_connection_parent[ id ] = this.__cache_search_connection_parent[ id ].concat( results );
            return results;

        } else {

            this.__cache_search_connection_parent[ id ] = [-1];
            return [-1];
        }

    };

    FBXNodes.prototype.searchConnectionChildren = function ( id ) {
        if ( this.__cache_search_connection_children === undefined ){
            this.__cache_search_connection_children = [];
        }

        if ( this.__cache_search_connection_children[ id ] !== undefined ){
            return this.__cache_search_connection_children[ id ];
        } else {
            this.__cache_search_connection_children[ id ] = [];
        }

        var conns   = this.Connections.properties.connections;

        var res = [];
        for (var i=0; i < conns.length; ++i) {

            if ( conns[i][1] == id ) {
                // 0 means scene root
                res.push( conns[i][0] === 0 ? -1 : conns[i][0] );
                // there may more than one kid, then search to the end
            }

        }

        if ( res.length > 0 ){
            this.__cache_search_connection_children[ id ] = this.__cache_search_connection_children[ id ].concat( res );
            return res;

        } else {

            this.__cache_search_connection_children[ id ] = [-1];
            return [-1];
        }

    };

    FBXNodes.prototype.searchConnectionType = function ( id, to ) {
        var key = id + ',' + to;  // TODO: to hash
        if ( this.__cache_search_connection_type === undefined ){
            this.__cache_search_connection_type = '';
        }

        if ( this.__cache_search_connection_type[ key ] !== undefined ){
            return this.__cache_search_connection_type[ key ];
        } else {
            this.__cache_search_connection_type[ key ] = '';
        }

        var conns   = this.Connections.properties.connections;

        for (var i=0; i < conns.length; ++i) {

            if ( conns[i][0] == id && conns[i][1] == to ) {
                // 0 means scene root
                this.__cache_search_connection_type[ key ] = conns[i][2];
                return conns[i][2];
            }

        }

        this.__cache_search_connection_type[ id ] = null;
        return null;

    };

    function FBXParser() {}

    FBXParser.prototype = {

        // constructor: FBXParser,

        // ------------ node stack manipulations ----------------------------------

        getPrevNode: function () {
            return this.nodeStack[ this.currentIndent - 2 ];
        },

        getCurrentNode: function () {
            return this.nodeStack[ this.currentIndent - 1 ];
        },

        getCurrentProp: function () {
            return this.currentProp;
        },

        pushStack: function( node ) {

            this.nodeStack.push( node );
            this.currentIndent += 1;

        },

        popStack: function() {

            this.nodeStack.pop();
            this.currentIndent -= 1;

        },

        setCurrentProp: function ( val, name ){

            this.currentProp = val;
            this.currentPropName = name;

        },

        // ----------parse ---------------------------------------------------
        parse: function ( text ) {

            console.time( 'FBXLoader: total' );

            this.currentIndent = 0;
            this.allNodes = new FBXNodes();
            this.nodeStack = [];
            this.currentProp = [];
            this.currentPropName = '';

            console.time( 'FBXLoader: TextParser' );

            var split = text.split( "\n" );
            for ( var line in split ) {

                var l = split[line];

                // short cut
                if ( l.match( /^[\s\t]*;/ ) ){ continue; }  // skip comment line
                if ( l.match( /^[\s\t]*$/ ) ){ continue; }  // skip empty line

                // beginning of node
                var beginningOfNodeExp   = new RegExp( "^\\t{" + this.currentIndent + "}(\\w+):(.*){", '' );
                match = l.match( beginningOfNodeExp );
                if ( match ){

                    var nodeName  = match[1].trim().replace( /^"/, '' ).replace( /"$/, "" );
                    var nodeAttrs = match[2].split( ',' ).map( function ( element ) {
                        return element.trim().replace( /^"/, '' ).replace( /"$/, '' );
                    });

                    this.parseNodeBegin( l, nodeName, nodeAttrs || null );
                    continue;

                }

                // node's property
                var propExp = new RegExp( "^\\t{" + ( this.currentIndent )+ "}(\\w+):[\\s\\t\\r\\n](.*)" );
                match = l.match( propExp );
                if ( match ){

                    var propName  = match[1].replace( /^"/, '' ).replace( /"$/, "" ).trim();
                    var propValue = match[2].replace( /^"/, '' ).replace( /"$/, "" ).trim();

                    this.parseNodeProperty( l, propName, propValue );
                    continue;

                }

                // end of node
                var endOfNodeExp = new RegExp( "^\\t{" + ( this.currentIndent - 1 ) + "}}" );
                if ( l.match( endOfNodeExp ) ){

                    this.nodeEnd();
                    continue;

                }

                // for special case,
                //
                //      Vertices: *8670 {
                //          a: 0.0356229953467846,13.9599733352661,-0.399196773.....(snip)
                // -0.0612030513584614,13.960485458374,-0.409748703241348,-0.10.....
                // 0.12490539252758,13.7450733184814,-0.454119384288788,0.09272.....
                // 0.0836158767342567,13.5432004928589,-0.435397416353226,0.028.....
                //
                // these case the lines must contiue with previous line
                if ( l.match( /^[^\s\t}]/ ) ) {

                    this.parseNodePropertyContinued( l );

                }
            }

            //console.log( this.allNodes );
            return this.allNodes;

        },

        parseNodeBegin: function ( line, nodeName, nodeAttrs ) {

            // var nodeName = match[1];
            var node = { 'name': nodeName, properties: {}, 'subNodes': {} };
            var attrs = this.parseNodeAttr( nodeAttrs );
            var currentNode = this.getCurrentNode();

            // a top node
            if ( this.currentIndent === 0 ){

                this.allNodes.add( nodeName, node );

            } else {  // a subnode

                // already exists subnode, then append it
                if ( nodeName in currentNode.subNodes ){

                    var tmp = currentNode.subNodes[ nodeName ];

                    // console.log( "duped entry found\nkey: " + nodeName + "\nvalue: " + propValue );
                    if ( this.isFlattenNode( currentNode.subNodes[ nodeName ] ) ) {

                        if ( attrs.id === '' ){
                            currentNode.subNodes[ nodeName ] = [];
                            currentNode.subNodes[ nodeName ].push( tmp );
                        } else {
                            currentNode.subNodes[ nodeName ] = {};
                            currentNode.subNodes[ nodeName ][ tmp.id ] = tmp;
                        }

                    }

                    if ( attrs.id === '' ){
                        currentNode.subNodes[ nodeName ].push( node );
                    } else {
                        currentNode.subNodes[ nodeName ][ attrs.id ] = node;
                    }

                } else {

                    currentNode.subNodes[ nodeName ] =  node;

                }

            }

            // for this          ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
            // NodeAttribute: 1001463072, "NodeAttribute::", "LimbNode" {
            if ( nodeAttrs ) {

                node.id       = attrs.id;
                node.attrName = attrs.name;
                node.attrType = attrs.type;

            }

            this.pushStack( node );

        },

        parseNodeAttr: function ( attrs ) {

            var id = attrs[0];

            if ( attrs[0] !== "" ) {

                id = parseInt( attrs[0] );

                if ( isNaN( id) ){

                    // PolygonVertexIndex: *16380 {
                    id = attrs[0];
                }

            }

            var name;
            var type;
            if ( attrs.length > 1 ){

                name = attrs[1].replace( /^(\w+)::/, '' );
                type = attrs[2];

            }

            return { id:id, name:name || '', type:type || '' };

        },

        parseNodeProperty: function ( line, propName, propValue ){

            var currentNode   = this.getCurrentNode();
            var parentName = currentNode.name;

            // special case parent node's is like "Properties70"
            // these chilren nodes must treat with careful
            if ( parentName !== undefined ){

                var propMatch = parentName.match( /Properties(\d)+/ );
                if ( propMatch ) {
                    this.parseNodeSpecialProperty( line, propName, propValue );
                    return;
                }
            }

            // special case Connections
            if (  propName == 'C' ){

                var connProps = propValue.split( ',' ).slice( 1 );
                var from = parseInt( connProps[0] );
                var to   = parseInt( connProps[1] );

                var rest = propValue.split( ',' ).slice( 3 );

                propName  = 'connections';
                propValue = [ from, to ];
                propValue = propValue.concat( rest );

                if ( currentNode.properties[ propName ] === undefined ){
                    currentNode.properties[ propName ] = [];
                }

            }

            // special case Connections
            if (  propName == 'Node' ){

                var id = parseInt( propValue );
                currentNode.properties.id = id;
                currentNode.id = id;

            }

            // already exists in properties, then append this
            if ( propName in currentNode.properties ){

                // console.log( "duped entry found\nkey: " + propName + "\nvalue: " + propValue );
                if ( Array.isArray( currentNode.properties[ propName ] ) ){

                    currentNode.properties[ propName ].push( propValue );

                } else {

                    currentNode.properties[ propName ] += propValue;

                }

            } else {

                // console.log( propName + ":  " + propValue );
                if ( Array.isArray( currentNode.properties[ propName ] ) ){

                    currentNode.properties[ propName ].push( propValue );

                } else {

                    currentNode.properties[ propName ] = propValue;

                }

            }

            this.setCurrentProp( currentNode.properties, propName );

        },

        // TODO:
        parseNodePropertyContinued: function ( line ) {

            this.currentProp[ this.currentPropName ] += line;

        },

        parseNodeSpecialProperty: function ( line, propName, propValue ) {

            // split this
            // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1
            // into array like below
            // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ]
            var props = propValue.split( '",' ).map( function( element ) {

                return element.trim().replace( /^\"/, '' ).replace( /\s/, '_' );

            });

            var innerPropName  = props[0];
            var innerPropType1 = props[1];
            var innerPropType2 = props[2];
            var innerPropFlag  = props[3];
            var innerPropValue = props[4];

            /*
            if ( innerPropValue === undefined ){
                innerPropValue = props[3];
            }
            */

            // cast value in its type
            switch ( innerPropType1 ) {

                case "int":
                    innerPropValue = parseInt( innerPropValue );
                    break;

                case "double":
                    innerPropValue = parseFloat( innerPropValue );
                    break;

                case "ColorRGB":
                case "Vector3D":
                    var tmp = innerPropValue.split( ',' );
                    innerPropValue = new THREE.Vector3( tmp[0], tmp[1], tmp[2] );
                    break;

            }

            // CAUTION: these props must append to parent's parent
            this.getPrevNode().properties[ innerPropName ] = {

                'type':  innerPropType1,
                'type2': innerPropType2,
                'flag':  innerPropFlag,
                'value': innerPropValue

            };

            this.setCurrentProp(  this.getPrevNode().properties,  innerPropName );

        },

        nodeEnd: function ( line ) {

            this.popStack();

        },

        /* ---------------------------------------------------------------- */
        /*        util                                                      */
        isFlattenNode: function ( node ) {

            return ( 'subNodes' in node && 'properties' in node ) ? true : false;

        }

    };

    function FBXAnalyzer() {}

    FBXAnalyzer.prototype = {

    };

    // generate skinIndices, skinWeights
    //      @skinIndices: per vertex data, this represents the bone indexes affects that vertex
    //      @skinWeights: per vertex data, this represents the Weight Values affects that vertex
    //      @matrices:    per `bones` data
    function Weights () {

        this.skinIndices = [];
        this.skinWeights = [];

        this.matrices    = [];
    }

    Weights.prototype.parseCluster = function ( node, id, entry ){

        var _p         = node.searchConnectionParent( id );
        var _indices   = toInt( entry.subNodes.Indexes.properties.a.split( ',' ) );
        var _weights   = toFloat( entry.subNodes.Weights.properties.a.split( ',' ) );
        var _transform = toMat44( toFloat( entry.subNodes.Transform.properties.a.split( ',' ) ) );
        var _link      = toMat44( toFloat( entry.subNodes.TransformLink.properties.a.split( ',' ) ));

        return {

            'parent':        _p,
            'id':            parseInt( id ),
            'indices':       _indices,
            'weights':       _weights,
            'transform':     _transform,
            'transformlink': _link,
            'linkMode':      entry.properties.Mode

        };

    };

    Weights.prototype.parse = function ( node, bones ){

        this.skinIndices = [];
        this.skinWeights = [];

        this.matrices = [];

        var deformers = node.Objects.subNodes.Deformer;

        var clusters  = {};
        for ( var id in deformers ) {
            if ( deformers[id].attrType === 'Cluster' ){

                if ( !( 'Indexes' in deformers[id].subNodes )){ continue; }

                //clusters.push( this.parseCluster( node, id, deformers[id] ) );
                var cluster =  this.parseCluster( node, id, deformers[id] );
                var boneId = node.searchConnectionChildren( cluster.id )[ 0 ];
                clusters[ boneId ] = cluster;

            }
        }

        // this clusters is per Bone data, thus we make this into per vertex data
        var weights = [];
        var hi = bones.hierarchy;
        for (var b=0; b < hi.length; ++b) {

            var bid = hi[ b ].internalId;
            if ( clusters[ bid ] === undefined ){
                //console.log( bid );
                this.matrices.push( new THREE.Matrix4() );
                continue;
            }

            var clst = clusters[ bid ];
            // store transform matrix per bones
            this.matrices.push( clst.transform );
            //this.matrices.push( clst.transformlink );
            for ( var v=0; v < clst.indices.length; ++v ){

                if ( weights[ clst.indices[v] ] === undefined ){

                    weights[ clst.indices[v] ] = {};
                    weights[ clst.indices[v] ].joint  = [];
                    weights[ clst.indices[v] ].weight = [];

                }

                // indices
                var affect = node.searchConnectionChildren( clst.id );

                if ( affect.length > 1 ){
                    console.warn( "FBXLoader: node " + clst.id + " have many weight kids: " + affect );
                }
                weights[ clst.indices[v] ].joint.push( bones.getBoneIdfromInternalId( node, affect[0] ) );

                // weight value
                weights[ clst.indices[v] ].weight.push( clst.weights[v] );

            }

        }

        // normalize the skin weights
        // TODO -  this might be a good place to choose greatest 4 weights
        for ( var i = 0; i < weights.length; i ++ ) {

            var indicies = new THREE.Vector4(
                weights[ i ].joint[ 0 ] ? weights[ i ].joint[ 0 ] : 0,
                weights[ i ].joint[ 1 ] ? weights[ i ].joint[ 1 ] : 0,
                weights[ i ].joint[ 2 ] ? weights[ i ].joint[ 2 ] : 0,
                weights[ i ].joint[ 3 ] ? weights[ i ].joint[ 3 ] : 0 );

            var weight = new THREE.Vector4(
                weights[ i ].weight[ 0 ] ? weights[ i ].weight[ 0 ] : 0,
                weights[ i ].weight[ 1 ] ? weights[ i ].weight[ 1 ] : 0,
                weights[ i ].weight[ 2 ] ? weights[ i ].weight[ 2 ] : 0,
                weights[ i ].weight[ 3 ] ? weights[ i ].weight[ 3 ] : 0 );

            this.skinIndices.push( indicies );
            this.skinWeights.push( weight );

        }

        //console.log( this );
        return this;

    };

    function Bones() {

            // returns bones hierarchy tree.
            //      [
            //          {
            //              "parent": id,
            //              "name": name,
            //              "pos": pos,
            //              "rotq": quat
            //          },
            //          ...
            //          {},
            //          ...
            //      ]
            //
            /* sample response

               "bones" : [
                {"parent":-1, "name":"Fbx01",            "pos":[-0.002,     98.739,   1.6e-05],     "rotq":[0, 0, 0, 1]},
                {"parent":0,  "name":"Fbx01_Pelvis",     "pos":[0.00015963, 0,        7.33107e-08], "rotq":[0, 0, 0, 1]},
                {"parent":1,  "name":"Fbx01_Spine",      "pos":[6.577e-06,  10.216,   0.0106811],   "rotq":[0, 0, 0, 1]},
                {"parent":2,  "name":"Fbx01_R_Thigh",    "pos":[14.6537,    -10.216,  -0.00918758], "rotq":[0, 0, 0, 1]},
                {"parent":3,  "name":"Fbx01_R_Calf",     "pos":[-3.70047,     -42.9681,     -7.78158],     "rotq":[0, 0, 0, 1]},
                {"parent":4,  "name":"Fbx01_R_Foot",     "pos":[-2.0696,      -46.0488,     9.42052],      "rotq":[0, 0, 0, 1]},
                {"parent":5,  "name":"Fbx01_R_Toe0",     "pos":[-0.0234785,   -9.46233,     -15.3187],     "rotq":[0, 0, 0, 1]},
                {"parent":2,  "name":"Fbx01_L_Thigh",    "pos":[-14.6537,     -10.216,      -0.00918314],  "rotq":[0, 0, 0, 1]},
                {"parent":7,  "name":"Fbx01_L_Calf",     "pos":[3.70037,      -42.968,      -7.78155],     "rotq":[0, 0, 0, 1]},
                {"parent":8,  "name":"Fbx01_L_Foot",     "pos":[2.06954,      -46.0488,     9.42052],      "rotq":[0, 0, 0, 1]},
                {"parent":9,  "name":"Fbx01_L_Toe0",     "pos":[0.0234566,    -9.46235,     -15.3187],     "rotq":[0, 0, 0, 1]},
                {"parent":2,  "name":"Fbx01_Spine1",     "pos":[-2.97523e-05, 11.5892,      -9.81027e-05], "rotq":[0, 0, 0, 1]},
                {"parent":11, "name":"Fbx01_Spine2",     "pos":[-2.91292e-05, 11.4685,      8.27126e-05],  "rotq":[0, 0, 0, 1]},
                {"parent":12, "name":"Fbx01_Spine3",     "pos":[-4.48857e-05, 11.5783,      8.35108e-05],  "rotq":[0, 0, 0, 1]},
                {"parent":13, "name":"Fbx01_Neck",       "pos":[1.22987e-05,  11.5582,      -0.0044775],   "rotq":[0, 0, 0, 1]},
                {"parent":14, "name":"Fbx01_Head",       "pos":[-3.50709e-05, 6.62915,      -0.00523254],  "rotq":[0, 0, 0, 1]},
                {"parent":15, "name":"Fbx01_R_Eye",      "pos":[3.31681,      12.739,       -10.5267],     "rotq":[0, 0, 0, 1]},
                {"parent":15, "name":"Fbx01_L_Eye",      "pos":[-3.32038,     12.7391,      -10.5267],     "rotq":[0, 0, 0, 1]},
                {"parent":15, "name":"Jaw",              "pos":[-0.0017738,   7.43481,      -4.08114],     "rotq":[0, 0, 0, 1]},
                {"parent":14, "name":"Fbx01_R_Clavicle", "pos":[3.10919,      2.46577,      -0.0115284],   "rotq":[0, 0, 0, 1]},
                {"parent":19, "name":"Fbx01_R_UpperArm", "pos":[16.014,       4.57764e-05,  3.10405],      "rotq":[0, 0, 0, 1]},
                {"parent":20, "name":"Fbx01_R_Forearm",  "pos":[22.7068,      -1.66322,     -2.13803],     "rotq":[0, 0, 0, 1]},
                {"parent":21, "name":"Fbx01_R_Hand",     "pos":[25.5881,      -0.80249,     -6.37307],     "rotq":[0, 0, 0, 1]},
                ...
                {"parent":27, "name":"Fbx01_R_Finger32", "pos":[2.15572,      -0.548737,    -0.539604],    "rotq":[0, 0, 0, 1]},
                {"parent":22, "name":"Fbx01_R_Finger2",  "pos":[9.79318,      0.132553,     -2.97845],     "rotq":[0, 0, 0, 1]},
                {"parent":29, "name":"Fbx01_R_Finger21", "pos":[2.74037,      0.0483093,    -0.650531],    "rotq":[0, 0, 0, 1]},
                {"parent":55, "name":"Fbx01_L_Finger02", "pos":[-1.65308,     -1.43208,     -1.82885],     "rotq":[0, 0, 0, 1]}
                ]
            */
        this.hierarchy = [];

    }

    Bones.prototype.parseHierarchy = function ( node ){

        var objects = node.Objects;
        var models  = objects.subNodes.Model;

        var bones = [];
        for (var id in models ) {

            if ( models[ id ].attrType === undefined ){ continue; }
            bones.push( models[ id ] );

        }

        this.hierarchy = [];
        for (var i=0; i < bones.length; ++i) {

            var bone = bones[i];

            var p = node.searchConnectionParent( bone.id )[0];
            var t = [ 0.0, 0.0, 0.0 ];
            var r = [ 0.0, 0.0, 0.0, 1.0 ];
            var s = [ 1.0, 1.0, 1.0 ];

            if ( 'Lcl_Translation' in bone.properties ){
                t = toFloat( bone.properties.Lcl_Translation.value.split( ',' ) );
            }

            if ( 'Lcl_Rotation' in bone.properties ){
                r = toRad( toFloat( bone.properties.Lcl_Rotation.value.split( ',' ) ));
                var q = new THREE.Quaternion();
                q.setFromEuler( new THREE.Euler( r[0], r[1], r[2], 'ZYX' ) );
                r = [ q.x, q.y, q.z, q.w ];
            }

            if ( 'Lcl_Scaling' in bone.properties ){
                s = toFloat( bone.properties.Lcl_Scaling.value.split( ',' ) );
            }

            // replace unsafe character
            var name = bone.attrName;
            name = name.replace( /:/, '' );
            name = name.replace( /_/, '' );
            name = name.replace( /-/, '' );
            this.hierarchy.push( { "parent": p, "name": name, "pos": t, "rotq": r, "scl": s, "internalId": bone.id } );
        }

        this.reindexParentId();

        this.restoreBindPose( node );

        return this;

    };

    Bones.prototype.reindexParentId = function () {

        for ( var h=0; h < this.hierarchy.length; h++ ){
            for (var ii=0; ii < this.hierarchy.length; ++ii) {
                if( this.hierarchy[h].parent == this.hierarchy[ii].internalId ){
                    this.hierarchy[h].parent = ii;
                    break;
                }
            }
        }

    };

    Bones.prototype.restoreBindPose = function ( node ) {

        var bindPoseNode =  node.Objects.subNodes.Pose;
        if( bindPoseNode === undefined ){
            return;
        }

        var poseNode = bindPoseNode.subNodes.PoseNode;
        var localMatrices = {};  // store local matrices, modified later( initialy world space )
        var worldMatrices = {};  // store world matrices

        for (var  i=0;  i < poseNode.length; ++ i) {
            var rawMatLcl = toMat44( poseNode[ i ].subNodes.Matrix.properties.a.split( ',' )  );
            var rawMatWrd = toMat44( poseNode[ i ].subNodes.Matrix.properties.a.split( ',' )  );

            localMatrices[ poseNode[i].id ] = rawMatLcl;
            worldMatrices[ poseNode[i].id ] = rawMatWrd;

        }

        for (var  h=0;  h < this.hierarchy.length; ++ h) {

            var bone = this.hierarchy[ h ];
            var inId = bone.internalId;

            if ( worldMatrices[ inId ] === undefined ){
                // has no bind pose node, possibly be mesh
                // console.log( bone );
                continue;
            }

            var t = new THREE.Vector3(0, 0, 0);
            var r = new THREE.Quaternion();
            var s = new THREE.Vector3( 1, 1, 1 );

            var parentId;
            var parentNodes = node.searchConnectionParent( inId );
            for (var pn=0; pn < parentNodes.length; ++pn) {
                if ( this.isBoneNode( parentNodes[ pn ] )){
                    parentId = parentNodes[ pn ];
                    break;
                }
            }

            if( parentId !== undefined && localMatrices[ parentId] !== undefined ){

                // convert world space matrix into local space
                var inv = new THREE.Matrix4();
                inv.getInverse( worldMatrices[ parentId ] );
                inv.multiply( localMatrices[ inId ] );
                localMatrices[ inId ] = inv;

            } else {
                //console.log( bone );
            }

            localMatrices[ inId ].decompose( t, r, s );
            bone.pos  = [ t.x, t.y, t.z ];
            bone.rotq = [ r.x, r.y, r.z, r.w ];
            bone.scl  = [ s.x, s.y, s.z ];

        }

    };

    Bones.prototype.searchRealId = function ( internalId ) {

        for ( var h=0; h < this.hierarchy.length; h++ ){
            if( internalId == this.hierarchy[h].internalId ){
                return h;
            }
        }

        // console.warn( 'FBXLoader: notfound internalId in bones: ' + internalId);
        return -1;

    };

    Bones.prototype.getByInternalId = function ( internalId ) {

        for ( var h=0; h < this.hierarchy.length; h++ ){
            if( internalId == this.hierarchy[h].internalId ){
                return this.hierarchy[ h ];
            }
        }

        return null;

    };

    Bones.prototype.isBoneNode = function  ( id ) {

        for (var i=0; i < this.hierarchy.length; ++i) {
            if( id === this.hierarchy[i].internalId ){
                return true;
            }
        }
        return false;
    };

    Bones.prototype.getBoneIdfromInternalId = function ( node, id ) {

        if ( node.__cache_get_boneid_from_internalid === undefined ){
            node.__cache_get_boneid_from_internalid = [];
        }

        if ( node.__cache_get_boneid_from_internalid[ id ] !== undefined ){
            return node.__cache_get_boneid_from_internalid[ id ];
        }

        for (var i=0; i < this.hierarchy.length; ++i) {
            if( this.hierarchy[i].internalId == id ){
                var res = i;
                node.__cache_get_boneid_from_internalid[ id ] = i;
                return i;
            }
        }

        // console.warn( 'FBXLoader: bone internalId(' + id + ') not found in bone hierarchy' );
        return -1;

    };

    function Geometry() {

        this.node = null;
        this.name = null;

        this.vertices = [];
        this.indices = [];
        this.normals = [];
        this.uvs = [];

        this.bones = [];
        this.skins = null;

    }

    Geometry.prototype.parse = function ( geoNode ) {

        this.node = geoNode;
        this.name = geoNode.id;

        this.vertices  = this.getVertices();

        if ( this.vertices === undefined ){
            console.log( 'FBXLoader: Geometry.parse(): pass' + this.node.id );
            return;
        }

        this.indices   = this.getPolygonVertexIndices();
        this.uvs       = ( new UV() ).parse( this.node, this );
        this.normals   = ( new Normal() ).parse( this.node, this );

        if ( this.getPolygonTopologyMax() > 3 ){

            this.indices = this.convertPolyIndicesToTri(
                                this.indices, this.getPolygonTopologyArray() );

        }

        return this;

    };

    Geometry.prototype.getVertices = function () {

        if ( this.node.__cache_vertices ){
            return this.node.__cache_vertices;
        }

        if ( this.node.subNodes.Vertices === undefined ){
            console.warn( 'this.node: ' + this.node.attrName + "(" + this.node.id + ") does not have Vertices" );
            this.node.__cache_vertices = undefined;
            return null;
        }

        var rawTextVert    = this.node.subNodes.Vertices.properties.a;
        var vertices  = rawTextVert.split( ',' ).map( function ( element ) {
            return parseFloat( element );
        } );

        this.node.__cache_vertices = vertices;
        return this.node.__cache_vertices;

    };

    Geometry.prototype.getPolygonVertexIndices = function (){

        if ( this.node.__cache_indices && this.node.__cache_poly_topology_max ){
            return this.node.__cache_indices;
        }

        if ( this.node.subNodes === undefined ){
            console.error( 'this.node.subNodes undefined' );
            console.log( this.node );
            return;
        }

        if ( this.node.subNodes.PolygonVertexIndex === undefined ){
            console.warn( 'this.node: ' + this.node.attrName + "(" + this.node.id + ") does not have PolygonVertexIndex " );
            this.node.__cache_indices = undefined;
            return;
        }

        var rawTextIndices = this.node.subNodes.PolygonVertexIndex.properties.a;
        var indices = rawTextIndices.split( ',' );

        var currentTopo = 1;
        var topologyN = null;
        var topologyArr = [];

        // The indices that make up the polygon are in order and a negative index
        // means that it's the last index of the polygon. That index needs
        // to be made positive and then you have to subtract 1 from it!
        for (var i=0; i < indices.length; ++i) {

            var tmpI = parseInt( indices[i] );
            // found n
            if (  tmpI < 0 ) {
                if( currentTopo > topologyN ) { topologyN =  currentTopo; }

                indices[i] = tmpI ^ -1;
                topologyArr.push( currentTopo );
                currentTopo = 1;

            } else {

                indices[i] = tmpI;
                currentTopo++;
            }

        }

        if( topologyN === null ) {
            console.warn( "FBXLoader: topology N not found: " + this.node.attrName );
            console.warn( this.node );
            topologyN =  3;
        }

        this.node.__cache_poly_topology_max = topologyN;
        this.node.__cache_poly_topology_arr = topologyArr;
        this.node.__cache_indices = indices;

        return this.node.__cache_indices;

    };

    Geometry.prototype.getPolygonTopologyMax = function () {

        if ( this.node.__cache_indices && this.node.__cache_poly_topology_max ){
            return this.node.__cache_poly_topology_max;
        }

        this.getPolygonVertexIndices( this.node );
        return this.node.__cache_poly_topology_max;

    };

    Geometry.prototype.getPolygonTopologyArray = function  () {

        if ( this.node.__cache_indices && this.node.__cache_poly_topology_max ){
            return this.node.__cache_poly_topology_arr;
        }

        this.getPolygonVertexIndices( this.node );
        return this.node.__cache_poly_topology_arr;

    };

    // a - d
    // |   |
    // b - c
    //
    // [( a, b, c, d ) ...........
    // [( a, b, c ), (a, c, d )....
    Geometry.prototype.convertPolyIndicesToTri = function ( indices, strides ) {

        var res = [];

        var i = 0;
        var tmp = [];
        var currentPolyNum  = 0;
        var currentStride = 0;

        while ( i < indices.length ) {
            currentStride = strides[ currentPolyNum ];

            // CAUTIN: NG over 6gon
            for ( var j=0; j <= (currentStride - 3); j++ ){

                res.push( indices[ i                             ] );
                res.push( indices[ i + ( currentStride - 2 - j ) ] );
                res.push( indices[ i + ( currentStride - 1 - j ) ] );

            }

            currentPolyNum++;
            i += currentStride;

        }

        return res;

    };

    Geometry.prototype.addBones = function ( bones ) {
        this.bones = bones;
    };

    function UV () {

        this.uv = null;
        this.map = null;
        this.ref = null;
        this.node = null;
        this.index = null;

    }

    UV.prototype.getUV = function ( node ){

        if ( this.node && this.uv && this.map && this.ref ) {

            return this.uv;

        } else {

            return this._parseText( node );
        }

    };

    UV.prototype.getMap = function ( node ){

        if ( this.node && this.uv && this.map && this.ref ) {

            return this.map;

        } else {

            this._parseText( node );
            return this.map;
        }

    };

    UV.prototype.getRef = function ( node ){

        if ( this.node && this.uv && this.map && this.ref ) {

            return this.ref;

        } else {

            this._parseText( node );
            return this.ref;
        }

    };

    UV.prototype.getIndex = function ( node ){

        if ( this.node && this.uv && this.map && this.ref ) {

            return this.index;

        } else {

            this._parseText( node );
            return this.index;
        }

    };

    UV.prototype.getNode = function ( topnode ){
        if ( this.node !== null ){
            return this.node;
        }

        this.node = topnode.subNodes.LayerElementUV;
        return this.node;
    };

    UV.prototype._parseText = function ( node ){

        var uvNode  = this.getNode( node );
        if ( uvNode === undefined ){
            // console.log( node.attrName + "(" + node.id + ")" + " has no LayerElementUV." );
            return [];
        }

        var count = 0;
        var x = '';
        for ( var n in uvNode ){
            if ( n.match( /^\d+$/ ) ){
                count++;
                x = n;
            }
        }

        if ( count > 0 ){
            console.warn( 'multi uv not supported' );
            uvNode = uvNode[n];
        }

        var uvIndex = uvNode.subNodes.UVIndex.properties.a;
        var uvs     = uvNode.subNodes.UV.properties.a;
        var uvMap   = uvNode.properties.MappingInformationType;
        var uvRef   = uvNode.properties.ReferenceInformationType;

        this.uv    = toFloat( uvs.split( ',' ) );
        this.index = toInt( uvIndex.split( ',' ) );

        this.map   = uvMap;  // TODO: normalize notation shaking... FOR BLENDER
        this.ref   = uvRef;

        return this.uv;

    };

    UV.prototype.parse = function ( node, geo ){

        this.uvNode      = this.getNode( node );

        this.uv         = this.getUV( node );
        var mappingType = this.getMap( node );
        var refType     = this.getRef( node );
        var indices     = this.getIndex( node );

        var strides     = geo.getPolygonTopologyArray();

        // it means that there is a normal for every vertex of every polygon of the model.
        // For example, if the models has 8 vertices that make up four quads, then there
        // will be 16 normals (one normal * 4 polygons * 4 vertices of the polygon). Note
        // that generally a game engine needs the vertices to have only one normal defined.
        // So, if you find a vertex has more tha one normal, you can either ignore the normals
        // you find after the first, or calculate the mean from all of them (normal smoothing).
        //if ( mappingType == "ByPolygonVertex" ){
        switch ( mappingType ) {

            case "ByPolygonVertex":

                switch ( refType ) {

                    // Direct
                    // The this.uv are in order.
                    case "Direct":
                        this.uv = this.parseUV_ByPolygonVertex_Direct( this.uv, indices, strides, 2 );
                        break;

                    // IndexToDirect
                    // The order of the this.uv is given by the uvsIndex property.
                    case "IndexToDirect":
                        this.uv = this.parseUV_ByPolygonVertex_IndexToDirect( this.uv, indices );
                        break;

                }

                // convert from by polygon(vert) data into by verts data
                this.uv = mapByPolygonVertexToByVertex( this.uv,  geo.getPolygonVertexIndices( node ), 2 );
                break;

            case "ByPolygon":

                switch ( refType ) {

                    // Direct
                    // The this.uv are in order.
                    case "Direct":
                        this.uv = this.parseUV_ByPolygon_Direct();
                        break;

                    // IndexToDirect
                    // The order of the this.uv is given by the uvsIndex property.
                    case "IndexToDirect":
                        this.uv = this.parseUV_ByPolygon_IndexToDirect();
                        break;

                }
                break;
        }

        return this.uv;

    };

    UV.prototype.parseUV_ByPolygonVertex_Direct = function ( node, indices, strides, itemSize ) {

        return parse_Data_ByPolygonVertex_Direct( node, indices, strides, itemSize );

    };

    UV.prototype.parseUV_ByPolygonVertex_IndexToDirect = function ( node, indices ) {

        return parse_Data_ByPolygonVertex_IndexToDirect( node, indices, 2 );

    };

    UV.prototype.parseUV_ByPolygon_Direct = function ( node ) {

        console.warn( "not implemented" );
        return node;

    };

    UV.prototype.parseUV_ByPolygon_IndexToDirect = function ( node ) {

        console.warn( "not implemented" );
        return node;

    };

    UV.prototype.parseUV_ByVertex_Direct = function ( node ) {

        console.warn( "not implemented" );
        return node;

    };

    function Normal () {

        this.normal = null;
        this.map    = null;
        this.ref    = null;
        this.node   = null;
        this.index  = null;

    }

    Normal.prototype.getNormal = function ( node ){

        if ( this.node && this.normal && this.map && this.ref ) {

            return this.normal;

        } else {

            this._parseText( node );
            return this.normal;
        }

    };

    // mappingType: possible variant
    //      ByPolygon
    //      ByPolygonVertex
    //      ByVertex (or also ByVertice, as the Blender exporter writes)
    //      ByEdge
    //      AllSame
    //    var mappingType = node.properties.MappingInformationType;
    Normal.prototype.getMap = function ( node ){

        if ( this.node && this.normal && this.map && this.ref ) {

            return this.map;

        } else {

            this._parseText( node );
            return this.map;
        }

    };

    // refType: possible variants
    //      Direct
    //      IndexToDirect (or Index for older versions)
    // var refType     = node.properties.ReferenceInformationType;
    Normal.prototype.getRef = function ( node ){

        if ( this.node && this.normal && this.map && this.ref ) {

            return this.ref;

        } else {

            this._parseText( node );
            return this.ref;
        }

    };

    Normal.prototype.getNode = function ( node ){

        if ( this.node ){
            return this.node;
        }

        this.node = node.subNodes.LayerElementNormal;
        return this.node;

    };

    Normal.prototype._parseText = function ( node ) {

        var normalNode  = this.getNode( node );

        if ( normalNode === undefined ){
            console.warn( 'node: ' + node.attrName + "(" + node.id + ") does not have LayerElementNormal" );
            return;
        }

        var mappingType = normalNode.properties.MappingInformationType;
        var refType     = normalNode.properties.ReferenceInformationType;

        var rawTextNormals = normalNode.subNodes.Normals.properties.a;
        this.normal  = toFloat( rawTextNormals.split( ',' ) );

        // TODO: normalize notation shaking, vertex / vertice... blender...
        this.map    = mappingType;
        this.ref    = refType;

    };

    Normal.prototype.parse = function ( topnode, geo ) {

        var normals     = this.getNormal( topnode );
        var normalNode  = this.getNode( topnode );
        var mappingType = this.getMap( topnode );
        var refType     = this.getRef( topnode );

        var indices     = geo.getPolygonVertexIndices( topnode );
        var strides     = geo.getPolygonTopologyArray( topnode );

        // it means that there is a normal for every vertex of every polygon of the model.
        // For example, if the models has 8 vertices that make up four quads, then there
        // will be 16 normals (one normal * 4 polygons * 4 vertices of the polygon). Note
        // that generally a game engine needs the vertices to have only one normal defined.
        // So, if you find a vertex has more tha one normal, you can either ignore the normals
        // you find after the first, or calculate the mean from all of them (normal smoothing).
        //if ( mappingType == "ByPolygonVertex" ){
        switch ( mappingType ) {

            case "ByPolygonVertex":

                switch ( refType ) {

                    // Direct
                    // The normals are in order.
                    case "Direct":
                        normals = this.parseNormal_ByPolygonVertex_Direct( normals, indices, strides, 3 );
                        break;

                    // IndexToDirect
                    // The order of the normals is given by the NormalsIndex property.
                    case "IndexToDirect":
                        normals = this.parseNormal_ByPolygonVertex_IndexToDirect();
                        break;

                }
                break;

            case "ByPolygon":

                switch ( refType ) {

                    // Direct
                    // The normals are in order.
                    case "Direct":
                        normals = this.parseNormal_ByPolygon_Direct();
                        break;

                    // IndexToDirect
                    // The order of the normals is given by the NormalsIndex property.
                    case "IndexToDirect":
                        normals = this.parseNormal_ByPolygon_IndexToDirect();
                        break;

                }
                break;
        }

        return normals;

    };

    Normal.prototype.parseNormal_ByPolygonVertex_Direct = function ( node, indices, strides, itemSize ) {

        return parse_Data_ByPolygonVertex_Direct( node, indices, strides, itemSize );

    };

    Normal.prototype.parseNormal_ByPolygonVertex_IndexToDirect = function ( node ) {

        console.warn( "not implemented" );
        return node;

    };

    Normal.prototype.parseNormal_ByPolygon_Direct = function ( node ) {

        console.warn( "not implemented" );
        return node;

    };

    Normal.prototype.parseNormal_ByPolygon_IndexToDirect = function ( node ) {

        console.warn( "not implemented" );
        return node;

    };

    Normal.prototype.parseNormal_ByVertex_Direct = function ( node ) {

        console.warn( "not implemented" );
        return node;

    };

    function AnimationCurve () {

        this.version = null;

        this.id         = null;
        this.internalId = null;
        this.times      = null;
        this.values     = null;

        this.attrFlag   = null;  // tangeant
        this.attrData   = null;  // slope, weight

    }

    AnimationCurve.prototype.fromNode = function ( curveNode ){

        this.id         = curveNode.id;
        this.internalId = curveNode.id;
        this.times      = curveNode.subNodes.KeyTime.properties.a;
        this.values     = curveNode.subNodes.KeyValueFloat.properties.a;

        this.attrFlag   = curveNode.subNodes.KeyAttrFlags.properties.a;
        this.attrData   = curveNode.subNodes.KeyAttrDataFloat.properties.a;

        this.times      = toFloat( this.times.split(    ',' ) );
        this.values     = toFloat( this.values.split(   ',' ) );
        this.attrData   = toFloat( this.attrData.split( ',' ) );
        this.attrFlag   = toInt(   this.attrFlag.split( ',' ) );

        this.times = this.times.map( function ( element ) {
            return FBXTimeToSeconds( element );
        } );

        return this;

    };

    AnimationCurve.prototype.getLength = function () {

        return this.times[ this.times.length - 1 ];
    };

    function AnimationNode (){

        this.id                  = null;
        this.attr                = null;  // S, R, T
        this.attrX               = false;
        this.attrY               = false;
        this.attrZ               = false;
        this.internalId          = null;
        this.containerInternalId = null;  // bone, null etc Id
        this.containerBoneId     = null;  // bone, null etc Id
        this.curveIdx            = null;  // AnimationCurve's indices
        this.curves              = [];    // AnimationCurve refs

    }

    AnimationNode.prototype.fromNode = function ( allNodes, node, bones ){

        this.id          = node.id;
        this.attr        = node.attrName;
        this.internalId  = node.id;

        if ( this.attr.match( /S|R|T/ ) ){
            for ( var attrKey in node.properties ){
                if ( attrKey.match( /X/ )) { this.attrX = true; }
                if ( attrKey.match( /Y/ )) { this.attrY = true; }
                if ( attrKey.match( /Z/ )) { this.attrZ = true; }
            }

        } else {
            // may be deform percent nodes
            return null;
        }

        this.containerIndices = allNodes.searchConnectionParent( this.id );
        this.curveIdx    = allNodes.searchConnectionChildren( this.id );

        for ( var i = this.containerIndices.length - 1; i>=0;  --i) {

            var boneId = bones.searchRealId( this.containerIndices[ i ] );
            if ( boneId >= 0 ) {

                this.containerBoneId = boneId;
                this.containerId = this.containerIndices [ i ];
            }

            if ( boneId >= 0 ){ break; }
        }
        // this.containerBoneId = bones.searchRealId( this.containerIndices );

        return this;

    };

    AnimationNode.prototype.setCurve = function ( curve ){
        this.curves.push( curve );

    };

    function Animation () {

        this.curves = {};
        this.length = 0.0;
        this.fps    = 30.0;
        this.frames = 0.0;

    }

    Animation.prototype.parse = function ( node, bones ) {

        var rawNodes  = node.Objects.subNodes.AnimationCurveNode;
        var rawCurves = node.Objects.subNodes.AnimationCurve;

        // first: expand AnimationCurveNode into curve nodes
        var curveNodes = [];
        for ( var key in rawNodes ) {

            if ( key.match( /\d+/ ) ){
                var a = ( new AnimationNode() ).fromNode( node, rawNodes[ key ], bones );
                curveNodes.push( a );
            }

        }

        // second: gen dict, mapped by internalId
        var tmp = {};
        for (var i=0; i < curveNodes.length; ++i) {
            if( curveNodes[i] === null ){ continue; }

            tmp[ curveNodes[i].id ] = curveNodes[i];
        }

        // third: insert curves into the dict
        var ac = [];
        var max = 0.0;
        for ( key in rawCurves ) {

            if ( key.match( /\d+/ ) ){
                var c = ( new AnimationCurve() ).fromNode( rawCurves[key] );
                ac.push( c );
                max = c.getLength() ? c.getLength() : max;

                var parentId = node.searchConnectionParent( c.id )[ 0 ];
                var axis = node.searchConnectionType( c.id, parentId );

                if ( axis.match( /X/ ) ){ axis = 'x'; }
                if ( axis.match( /Y/ ) ){ axis = 'y'; }
                if ( axis.match( /Z/ ) ){ axis = 'z'; }

                tmp[ parentId ].curves[ axis ] =  c;
            }

        }

        // forth:
        for ( var t in tmp ){
            var id = tmp[ t ].containerBoneId;
            if( this.curves[ id ] === undefined ){
                this.curves[ id ] = {};
            }

            this.curves[ id ][ tmp[ t ].attr ] = tmp[ t ];
        }

        this.length = max;
        this.frames = this.length * this.fps;

        return this;

    };

    /* --------------------------------------------------------------------- */
    /* --------------------------------------------------------------------- */
    /* --------------------------------------------------------------------- */
    /* --------------------------------------------------------------------- */

    THREE.FBXLoader = function ( manager ) {

        this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
        this.textureLoader = null;

    };

    THREE.FBXLoader.prototype = {

        constructor: THREE.FBXLoader,

        load: function ( url, onLoad, onProgress, onError ) {

            var scope = this;

            var loader = new THREE.XHRLoader( scope.manager );
            loader.load( url, function ( text ) {

                if(  !scope.isFbxFormatASCII( text ) ){
                    console.warn( 'FBXLoader: !!! FBX Binary format not supported !!!' );
                } else if ( !scope.isFbxVersionSupported( text ) ) {
                    console.warn( 'FBXLoader: !!! FBX Version below 7 not supported !!!' );
                } else {
                    parser = new FBXParser();
                    var nodes = parser.parse( text );
                    onLoad( scope.parse( nodes ) );
                }

            }, onProgress, onError );

        },

        setCrossOrigin: function ( value ) {

            this.crossOrigin = value;

        },

        isFbxFormatASCII: function ( body ) {

            CORRECT = ['K', 'a', 'y', 'd', 'a', 'r', 'a', '\\',  'F', 'B', 'X', '\\',  'B', 'i', 'n', 'a', 'r', 'y', '\\',  '\\' ];

            var cursor = 0;
            var read = function( offset ){
                var result = body[ offset - 1 ];
                body = body.slice( cursor + offset );
                cursor++;
                return result;
            };

            for (var i=0; i < CORRECT.length; ++i) {
                num = read( 1 );
                if ( num == CORRECT[i] ){
                    return false;
                }
            }

            //false if file.read( 1 ) != "\x00"
            //false if file.read( 2 ) != "\x1A\x00" # Byte 21-22

            // ascii
            return true;
        },

        isFbxVersionSupported: function ( body ){
            var versionExp = /FBXVersion: (\d+)/;
            match = body.match( versionExp );
            if ( match ){
                var version = parseInt( match[1] );
                console.log( 'FBXLoader: FBX version ' + version );
                return version >= 7000;
            }
            return false;
        },

        parse: function ( allNodes ) {

            function parseGeometries ( node ) {

                // has not geo, return []
                if ( !( 'Geometry' in node.subNodes ) ) { return []; }

                // has many
                var geoCount = 0;
                for ( var geo in node.subNodes.Geometry ) {
                    if ( geo.match( /^\d+$/ )){ geoCount++; }
                }

                var res = [];
                if ( geoCount > 0 ){

                    for ( geo in node.subNodes.Geometry ) {
                        if ( node.subNodes.Geometry[geo].attrType === 'Mesh' ) {
                            res.push( parseGeometry( node.subNodes.Geometry[geo] ) );
                        }
                    }

                } else {

                    res.push( parseGeometry( node.subNodes.Geometry ) );

                }

                return res;

            }

            function parseGeometry ( node ) {

                geo = ( new Geometry() ).parse( node );
                geo.addBones( hierarchy.hierarchy );

                //*
                var geometry = new THREE.BufferGeometry();
                geometry.name = geo.name;
                geometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geo.vertices ), 3 ) );

                if ( geo.normals !== undefined && geo.normals.length > 0 ) {
                    geometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geo.normals ), 3 ) );
                }

                if ( geo.uvs !== undefined && geo.uvs.length > 0 ) {
                    geometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geo.uvs ), 2 ) );
                }

                if ( geo.indices !== undefined && geo.indices.length > 65535 ){
                    geometry.setIndex( new THREE.BufferAttribute( new Uint32Array( geo.indices ), 1 ) );
                } else if ( geo.indices !== undefined ) {
                    geometry.setIndex( new THREE.BufferAttribute( new Uint16Array( geo.indices ), 1 ) );
                }

                geometry.verticesNeedUpdate = true;
                // geometry.computeVertexNormals();
                // geometry.computeFaceNormals();
                geometry.computeBoundingSphere();
                geometry.computeBoundingBox();

                // TODO: for debug
                var url = 'obj/Char_UV_Texture.gif';
                if ( this.textureLoader === undefined ){
                    this.textureLoader = new THREE.TextureLoader();
                }

                var texture = this.textureLoader.load( url );
                geometry = new THREE.Geometry().fromBufferGeometry( geometry );

                geometry.bones = geo.bones;  // hierarchy.hi;
                geometry.skinIndices = weights.skinIndices;
                geometry.skinWeights = weights.skinWeights;

                addAnimation( geometry, weights.matrices, animations );

                var material = new THREE.MeshBasicMaterial( { map: texture } );
                //var material = new THREE.MeshBasicMaterial( { color: 0x3300ff } );
				//var material = new THREE.MeshLambertMaterial( { color: 0x3300ff } );

                var mesh = null;
                //if ( geo.bones === undefined || geo.skins === undefined || geo.uvs < 10 ){
                if ( geo.bones === undefined || geo.skins === undefined || geometry.animations === undefined || geometry.animations.length < 1 ){

                    mesh = new THREE.Mesh( geometry, material );

                } else {

                    material.skinning = true;
                    mesh = new THREE.SkinnedMesh( geometry, material );

                }

                return mesh;

            }

            /* ------------------------------------------------------------------*/

            function addAnimation ( geometry, matrices, animations ) {

                var animationdata = { "name": 'animationtest', "fps": 30, "length": animations.length, "hierarchy": [] };

                for (var i=0; i < geometry.bones.length; ++i) {
                    var name = geometry.bones[i].name;
                    name = name.replace( /.*:/, '' );
                    animationdata.hierarchy.push( { parent: geometry.bones[i].parent, name: name, keys:[] } );
                }

                var hasCurve = function ( animNode, attr ) {

                    if ( animNode === undefined ){ return false; }

                    var attrNode;
                    switch ( attr ) {
                        case 'S':
                            if( animNode.S === undefined ){ return false; }
                            attrNode = animNode.S;
                            break;

                        case 'R':
                            if( animNode.R === undefined ){ return false; }
                            attrNode = animNode.R;
                            break;

                        case 'T':
                            if( animNode.T === undefined ){ return false; }
                            attrNode = animNode.T;
                            break;
                    }

                    if( attrNode.curves.x === undefined ){ return false; }
                    if( attrNode.curves.y === undefined ){ return false; }
                    if( attrNode.curves.z === undefined ){ return false; }

                    return true;

                };

                var hasKeyOnFrame = function ( attrNode, frame ) {
                    var x = isKeyExistOnFrame( attrNode.curves.x, frame );
                    var y = isKeyExistOnFrame( attrNode.curves.y, frame );
                    var z = isKeyExistOnFrame( attrNode.curves.z, frame );

                    return x && y && z;
                };

                var isKeyExistOnFrame = function ( curve, frame ) {
                    var value = curve.values[ frame ];
                    return value !== undefined;
                };

                var genKey = function ( animNode, bone ) {

                    // key initialize with its bone's bind pose at first
                    var key = {};
                    key.time = frame / animations.fps;  // TODO:
                    key.pos = bone.pos;
                    key.rot = bone.rotq;
                    key.scl = bone.scl;

                    if ( animNode === undefined ){
                        return key;
                    }

                    // TODO: if values[ frame ] not exists
                    try {
                        if ( hasCurve( animNode, 'T' ) && hasKeyOnFrame( animNode.T, frame ) ) {
                            var pos = new THREE.Vector3(
                                animNode.T.curves.x.values[ frame ],
                                animNode.T.curves.y.values[ frame ],
                                animNode.T.curves.z.values[ frame ] );
                            key.pos = [ pos.x, pos.y, pos.z ];
                        } else {
                            delete key.pos;
                        }

                        if ( hasCurve( animNode, 'R' ) && hasKeyOnFrame( animNode.R, frame ) ){
                            var rx  = degToRad( animNode.R.curves.x.values[ frame ] );
                            var ry  = degToRad( animNode.R.curves.y.values[ frame ] );
                            var rz  = degToRad( animNode.R.curves.z.values[ frame ] );
                            var eul = new THREE.Vector3( rx, ry, rz );
                            var rot = quatFromVec( eul.x, eul.y, eul.z );
                            key.rot = [ rot.x, rot.y, rot.z, rot.w ];
                        } else {
                            delete key.rot;
                        }

                        if ( hasCurve( animNode, 'S' ) && hasKeyOnFrame( animNode.S, frame ) ) {
                            var scl = new THREE.Vector3(
                                animNode.S.curves.x.values[ frame ],
                                animNode.S.curves.y.values[ frame ],
                                animNode.S.curves.z.values[ frame ]  );
                            key.scl = [ scl.x, scl.y, scl.z ];
                        } else {
                            delete key.scl;
                        }

                    } catch ( e ){
                        // curve is not full plotted
                        console.log( bone );
                        console.log( e );
                    }

                    return key;

                };

                var bones = geometry.bones;
                for ( frame = 0; frame < animations.frames; frame ++ ) {

                    for ( i=0; i < bones.length; i ++ ) {
                        var bone = bones[ i ];
                        var animNode = animations.curves[ i ];

                        for ( var j = 0; j < animationdata.hierarchy.length; j ++ ) {

                            if ( animationdata.hierarchy[ j ].name === bone.name ) {
                                animationdata.hierarchy[ j ].keys.push( genKey( animNode, bone ) );
                            }
                        }
                    }
                }

                var outputAnimations = [];
                var clip = THREE.AnimationClip.parseAnimation( animationdata, geometry.bones );
                if ( clip ){ outputAnimations.push( clip ); }

                geometry.animations = outputAnimations;

            }

            // -----------------------------------------------------------

            // -----------------------------------------------------------
            /* ---------------------------------------------------------------- */
            /* util                                                             */
            /* ---------------------------------------------------------------- */

            /* ----------------------------------------------------------------- */
            function parseMaterials ( node ) {

                // has not mat, return []
                if ( !( 'Material' in node.subNodes ) ) { return []; }

                // has many
                var matCount = 0;
                for ( var mat in node.subNodes.Materials ) {
                    if ( mat.match( /^\d+$/ )){ matCount++; }
                }

                var res = [];
                if ( matCount > 0 ){

                    for ( mat in node.subNodes.Material ) {
                        res.push( parseMaterial( node.subNodes.Material[mat] ) );
                    }

                } else {

                    res.push( parseMaterial( node.subNodes.Material ) );

                }

                return res;

            }

            function parseMaterial ( node ) {

                matName  = node.attrName;

                eff = new Effect();
                console.log( eff.parse( node ) );

            }

            /* ************************************************************* */
            console.timeEnd( 'FBXLoader: TextParser' );

            console.time( 'FBXLoader: ObjectParser' );
            hierarchy  = ( new Bones() ).parseHierarchy( allNodes );
            weights    = ( new Weights() ).parse( allNodes, hierarchy );
            animations = ( new Animation() ).parse( allNodes, hierarchy );
            console.timeEnd( 'FBXLoader: ObjectParser' );

            console.time( 'FBXLoader: GeometryParser' );
            geometries = parseGeometries( allNodes.Objects );
            // materials  = parseMaterials( allNodes.Objects );
            console.timeEnd( 'FBXLoader: GeometryParser' );

            var container = new THREE.Object3D();
            for (var i=0; i < geometries.length; ++i) {

                if ( geometries[i] === undefined ){ continue; }

                container.add( geometries[i] );

                //wireframe = new THREE.WireframeHelper( geometries[i], 0x00ff00 );
                //container.add( wireframe );

                //vnh = new THREE.VertexNormalsHelper( geometries[i], 0.6 );
                //container.add( vnh );

                //skh = new THREE.SkeletonHelper( geometries[i] );
                //container.add( skh );

                // container.add( new THREE.BoxHelper( geometries[i] ) );
            }

            console.timeEnd( 'FBXLoader: total' );
            return container;

        }

    };

    function loadTextureImage ( texture, url ) {

        var loader = new THREE.ImageLoader();

        loader.load( url, function ( image ) {

        } );

        loader.load( url, function( image ) {

                texture.image = image;
                texture.needUpdate = true;
                console.log( 'tex load done' );

            },

            // Function called when download progresses
            function ( xhr ) {
                console.log( (xhr.loaded / xhr.total * 100) + '% loaded' );
            },

            // Function called when download errors
            function ( xhr ) {
                console.log( 'An error happened' );
            }
        );

    }

    // LayerElementUV: 0 {
    // 	Version: 101
    //	Name: "Texture_Projection"
    //	MappingInformationType: "ByPolygonVertex"
    //	ReferenceInformationType: "IndexToDirect"
    //	UV: *1746 {
    //	UVIndex: *7068 {
    //
    //	The order of the uvs is given by the UVIndex property.
    function parse_Data_ByPolygonVertex_IndexToDirect( node, indices, itemSize ) {

        var res = [];

        for (var i=0; i < indices.length; ++i ) {

            for (var j=0; j < itemSize; ++j) {

                res.push( node[ ( indices[i] * itemSize ) + j ] );

            }

        }

        return res;

    }

    // what want: normal per vertex, order vertice
    // i have: normal per polygon
    // i have: indice per polygon
    parse_Data_ByPolygonVertex_Direct = function ( node, indices, strides, itemSize ) {

        // *21204 > 3573
        // Geometry: 690680816, "Geometry::", "Mesh" {
        //  Vertices: *3573 {
        //  PolygonVertexIndex: *7068 {

        var tmp = [];
        var currentIndex = 0;

        // first: sort to per vertex
        for (var i=0; i < indices.length; ++i) {

            tmp[ indices[i] ] = [];

            // TODO: duped entry? blend or something?
            for ( var s=0; s < itemSize; ++s ){

                tmp[ indices[i] ][ s ] = node[ currentIndex + s ];

            }

            currentIndex += itemSize;

        }

        // second: expand x,y,z into serial array
        var res = [];
        for ( var jj=0; jj < tmp.length; ++jj) {
            if ( tmp[jj] === undefined ){ continue; }

            for ( var t=0; t < itemSize; ++t ){
                if ( tmp[jj][t] === undefined ){ continue; }
                res.push( tmp[jj][t] );
            }
        }

        return res;

    };

    // convert from by polygon(vert) data into by verts data
    function mapByPolygonVertexToByVertex( data, indices, stride ) {

        var tmp = {};
        var res = [];
        var max = 0;

        for ( var i=0; i < indices.length; ++i ) {
            if ( indices[i] in tmp ){ continue; }

            tmp[ indices[i] ] = {};

            for (var j=0; j < stride; ++j) {
                tmp[ indices[i] ][ j ] = data[ i*stride + j];
            }

            max =  max < indices[i] ? indices[i] : max;
        }

        try {
            for ( i=0; i <= max; i++ ){

                for ( var s=0; s < stride; s++ ){
                    res.push( tmp[ i ][ s ] );
                }

            }
        } catch (e) {
            //console.log( max );
            //console.log( tmp );
            //console.log( i );
            //console.log( e );
        }

        return res;

    }

    // AUTODESK uses broken clock. i guess
    var FBXTimeToSeconds =  function( adskTime ){
        return adskTime / 46186158000;
    };

    degToRad = function ( degrees ) {
        return degrees * Math.PI / 180;
    };

    radToDeg = function ( radians ) {
        return radians * 180 / Math.PI;
    };

    quatFromVec = function ( x, y, z ){
        var euler = new THREE.Euler( x, y, z, 'ZYX' );
        var quat  = new THREE.Quaternion();
        quat.setFromEuler( euler );

        return quat;
    };

    // extend Array.prototype ?  ....uuuh
    toInt = function ( arr ) {
        return arr.map( function ( element ) { return parseInt( element ); } );
    };

    toFloat = function ( arr ) {
        return arr.map( function ( element ) { return parseFloat( element ); } );
    };

    toRad = function ( arr ){
        return arr.map( function ( element ) { return degToRad( element ); } );
    };

    toMat44 = function ( arr ) {
        var mat = new THREE.Matrix4();
        mat.set(
            arr[ 0], arr[ 4], arr[ 8], arr[12],
            arr[ 1], arr[ 5], arr[ 9], arr[13],
            arr[ 2], arr[ 6], arr[10], arr[14],
            arr[ 3], arr[ 7], arr[11], arr[15]
        );

        /*
        mat.set(
            arr[ 0], arr[ 1], arr[ 2], arr[ 3],
            arr[ 4], arr[ 5], arr[ 6], arr[ 7],
            arr[ 8], arr[ 9], arr[10], arr[11],
            arr[12], arr[13], arr[14], arr[15]
        );
        // */
        return mat;
    };

} )();


text:
FOUNDATION_EXPORT double Pods_myFUMCVersionNumber;
FOUNDATION_EXPORT const unsigned char Pods_myFUMCVersionString[];

redpajama_set_name: RedPajamaGithub


text:
<TITLE>Review for First Kid (1996)</TITLE>
<LINK REL="STYLESHEET" TYPE="text/css" HREF="/ramr.css">

First Kid (1996)

reviewed by
Steve Rhodes

                                 FIRST KID
                       A film review by Steve Rhodes
                        Copyright 1996 Steve Rhodes

RATING (0 TO ****):  * 1/2

     From the writer (Tim Kelleher) who is bringing the controversial
CD-ROM game LEISURE SUIT LARRY to the big screen, we have a movie about
the FIRST KID, a. k. a., the son of the president.  I found the
trailers for the movie pretty lame so I was not looking forward to
seeing the film.  My son, Jeffrey (age 7), on the other hand, thought
the trailers were a hoot and kept asking when it was going to start so
we saw it on opening day.

     Although most of the jokes fall flat, FIRST KID is not without its
charms.  In fact, as the movie developed I begin to like parts of it,
most especially the acting by Sinbad as the First Kid's Secret Service
Agent known formally as Agent Sam Simms and informally as Agent Double
O Simms.  The quality of the script is quite low, but Sinbad played the
character with great panache as if he had just been given the best role
of the year.  It is so refreshing to see actors try so hard.  Too
often, see Val Kilmer in THE ISLAND OF DR. MOREAU for example, actors
just brood and seem angry that the director expects them to do more
than just show up on the set.

     As the story starts, First Lady Linda Davenport (Lisa Eichhorn)
and 13-year-old First Kid Luke Davenport (Brock Pierce from THE MIGHTY
DUCKS series) are making their annual shopping trip to the mall to
demonstrate to America that they are average working stiffs like
everyone else.  The First Lady even remarks to the reporters how she is
essentially a working woman.  Like all of us working stiffs, the family
arrives in a long limousine with a phalanx of bodyguards.  The First
Kid hates his life and moons the herd of reporters that follow them in
the mall.

     The First Lady does not like the way Agent Woods (Timothy Busfield
from "thirtysomething") touches her son so she demands a new agent for
her son's protection.  Always in trouble Agent Simms is assigned to the
kid.  He becomes Luke's friend by helping him escape frequently from
The White House.

     In an absolutely predictable show, Simms warns Luke when he is
surfing the net that, "Just be careful out there in Cyberspace.  Don't
tell them anything."  This of course, serves to scare overly cautious
parents that The Net is the enemy.  Personally, I'd worry more in the
parking lot when I leave the theater.

     I have some problems with the appropriateness of parts of script.
One of the early jokes, albeit one aimed at the adults, has Simms
telling the bartender, "I'll have a Harvey Wall-Banger."  Agent Woods
one ups him with, "I'll have a Harvey Oswald."  Confused, Simms asks,
"What's the difference?"  Agent Woods gives him a devious smile
retorting, "Oswald has three shots."  Remember, these are agents sworn
to guard the lives of the President and his family.  Actually, the joke
is nothing more than a plot device so the writer can telegraph the
ending.

     Brock Pierce starts off giving nothing to the role of First Kid,
and even by the end, he warms up maybe to mediocrity.  Even worse is
James Naughton as the president.  He is terribly miscast.  He looks and
acts like, well, a mayor of a small town - one who spends his days
selling insurance.  Never, is he believable as a president of anything
more than the local Elks Club.

     Soon the First Son goes to school and the local bully, Rob
(Zachery Ty Bryan), taunts him with "I knew it.  A wus just like his
dad, the draft dodger."  They get into a fist fight where Luke gets a
busted and bloody lip while Agent Simms just watches.  Sure.

     The script tries to get us feeling sorry for Luke since he has no
friends.  Simms approach is to teach the kid boxing at a gym so he can
beat up the bully.  Again, back to my concern about some of the
messages of the show.  Nevertheless, there are some funny scenes at the
gym and elsewhere.

     The three best scenes in the show are the one where Luke asks a
girl to a dance, where Simms shoots an extra large spitball in class at
the bully, and where President Clinton calls on the videophone to ask
President Davenport to look for his lost saxophone.  My favorite minor
character is Luke's girlfriend Katie (Erin Williby).  She is sweet and
innocent, and she charms every scene she is in.  An impressive screen
presence for such a minor part.

     Finally, I feel a warning is necessary about the ending.  Given
how innocuous most of the film is, I was kind of shocked by the ending.
We have a child kidnapping scene complete with real guns, a very scared
child, slightly bloody violence, a realistic shoot-out, and a man
apparently and realistically shot dead.  If your kids worry about being
abducted, this could push them over the edge.  Now, Jeffrey was not
scared, but I bet many kids, especially younger ones, are and some
could be very frightened.

     FIRST KID runs too long at 1:41.  Editing out of the slow parts by
editor Harry Keramidas would have helped a lot.  The film is rated PG.
The language is pretty mild, and there is no sex or nudity, but note
the previous warnings about the violence.  Jeffrey gives it three
thumbs up.  Although I laughed some, there is not enough there for me
to be able to recommend it.  No matter how much energy Sinbad threw
into his role, director David M. Evans could not get much out of the
other actors so I can only give it * 1/2.

**** = One of the top few films of this or any year.  A must see film.
***  = Excellent show.  Look for it.
**   = Average movie.  Kind of enjoyable.
*    = Poor show.  Don't waste your money.
0    = One of the worst films of this or any year.  Totally unbearable.

REVIEW WRITTEN ON: August 30, 1996

Opinions expressed are mine and not meant to reflect my employer's.

The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).

The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.

Please direct comments/criticisms of the review to relevant newsgroups.

Broken URLs inthe reviews are the responsibility of the author.

The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.

Related links: index of all rec.arts.movies.reviews reviews


text:
from pprint import pprint
from os import getenv
import json
import requests

from .base import Base
from ..utils.spec import build_spec_for_block, build_release_for_block

class PublishBlock(Base):

    def run(self):
        spec = self._fetch_spec()
        release = self._fetch_release()
        if self.options.get('--dry-run'):
            print("Specification that will be published:")
            pprint(spec)
            print("Release data that will be published:")
            pprint(release)
            return
        self._publish_spec(spec)
        self._publish_release(release)

    def _fetch_spec(self):
        """ Fetches the spec.json and fills in details

        Returns:
            spec (dict): The dictionary of spec info

        Raises:
            FileNotFoundError: No spec.json
        """
        output_spec = {}
        with open('spec.json', 'r') as f:
            spec = json.load(f)
        for block_name, block_spec in spec.items():
            from_python = block_spec.pop('from_python', None)
            from_readme = block_spec.pop('from_readme', None)
            if from_python:
                # We will load the block spec data from the block file itself
                # Anything provided in spec.json should take precedence though
                spec_data_from_block = build_spec_for_block(from_python)
                spec_data_from_block.update(block_spec)
                block_spec = spec_data_from_block
            if from_readme:
                # Load the long description from the readme file specified
                with open(from_readme, 'r') as f:
                    block_spec['long_description'] = f.read()
            output_spec[block_name] = block_spec
        return output_spec

    def _fetch_release(self):
        output_release = {}
        with open('release.json', 'r') as f:
            release = json.load(f)
        for block_name, release_info in release.items():
            from_python = release_info.pop('from_python', None)
            if from_python:
                # We will load the block release data from the block file
                # itself but anything provided in release.json should
                # take precedence though
                release_data_from_block = build_release_for_block(from_python)
                release_data_from_block.update(release_info)
                release_info = release_data_from_block
            output_release[block_name] = release_info
        return output_release

    def _publish_spec(self, spec):
        # Loop through the specs, we can only publish one at a time
        for block_name, block_spec in spec.items():
            print("Publishing version {} of {}".format(
                block_spec.get('version'), block_name))
            resp_json = self._make_request('specification', {
                block_name: block_spec
            })
            print(resp_json.get('message', 'No message received'))

    def _publish_release(self, release):
        for block_name, release_info in release.items():
            print("Publishing release {} of {}".format(
                release_info.get('version'), block_name))
            resp_json = self._make_request('release', {
                block_name: release_info
            })
            print(resp_json.get('message', 'No message received'))

    def _get_token(self):
        access_token = self.options.get('--api-token') or \
            getenv('API_ACCESS_TOKEN')
        if not access_token:
            raise RuntimeError("No access token specified, use the --api-token"
                               " argument or the API_ACCESS_TOKEN env var")
        return access_token

    def _make_request(self, path, body):
        resp = requests.post("{}/{}".format(
            self.options.get('--api-url') or 'https://api.n.io/v1/block',
            path),
            headers={
                'Authorization': 'bearer {}'.format(self._get_token()),
            },
            json=body,
        )
        try:
            resp.raise_for_status()
            return resp.json()
        except Exception:
            raise RuntimeError("ERROR: {}".format(
                resp.json().get('message', 'Unknown error')))


text:
<annotations>
  <annotation>
    <name>c44cc4ed-9647-44f2-b1d9-0523c01f0c94</name>
    <field>HiddenField</field>
    <type>CREATE_ATTRIBUTE</type>
    <properties>
      <property>
        <name>hidden</name>
        <value><![CDATA[true]]></value>
      </property>
    </properties>
  </annotation>
  <sharedDimension>N</sharedDimension>
  <description />
</annotations>


text:
package vspk

import "github.com/nuagenetworks/go-bambou/bambou"

// NSRedundantGatewayGroupIdentity represents the Identity of the object
var NSRedundantGatewayGroupIdentity = bambou.Identity{
	Name:     "nsgredundancygroup",
	Category: "nsgredundancygroups",
}

// NSRedundantGatewayGroupsList represents a list of NSRedundantGatewayGroups
type NSRedundantGatewayGroupsList []*NSRedundantGatewayGroup

// NSRedundantGatewayGroupsAncestor is the interface that an ancestor of a NSRedundantGatewayGroup must implement.
// An Ancestor is defined as an entity that has NSRedundantGatewayGroup as a descendant.
// An Ancestor can get a list of its child NSRedundantGatewayGroups, but not necessarily create one.
type NSRedundantGatewayGroupsAncestor interface {
	NSRedundantGatewayGroups(*bambou.FetchingInfo) (NSRedundantGatewayGroupsList, *bambou.Error)
}

// NSRedundantGatewayGroupsParent is the interface that a parent of a NSRedundantGatewayGroup must implement.
// A Parent is defined as an entity that has NSRedundantGatewayGroup as a child.
// A Parent is an Ancestor which can create a NSRedundantGatewayGroup.
type NSRedundantGatewayGroupsParent interface {
	NSRedundantGatewayGroupsAncestor
	CreateNSRedundantGatewayGroup(*NSRedundantGatewayGroup) *bambou.Error
}

// NSRedundantGatewayGroup represents the model of a nsgredundancygroup
type NSRedundantGatewayGroup struct {
	ID                                  string        `json:"ID,omitempty"`
	ParentID                            string        `json:"parentID,omitempty"`
	ParentType                          string        `json:"parentType,omitempty"`
	Owner                               string        `json:"owner,omitempty"`
	Name                                string        `json:"name,omitempty"`
	LastUpdatedBy                       string        `json:"lastUpdatedBy,omitempty"`
	GatewayPeer1AutodiscoveredGatewayID string        `json:"gatewayPeer1AutodiscoveredGatewayID,omitempty"`
	GatewayPeer1ID                      string        `json:"gatewayPeer1ID,omitempty"`
	GatewayPeer1Name                    string        `json:"gatewayPeer1Name,omitempty"`
	GatewayPeer2AutodiscoveredGatewayID string        `json:"gatewayPeer2AutodiscoveredGatewayID,omitempty"`
	GatewayPeer2ID                      string        `json:"gatewayPeer2ID,omitempty"`
	GatewayPeer2Name                    string        `json:"gatewayPeer2Name,omitempty"`
	HeartbeatInterval                   int           `json:"heartbeatInterval,omitempty"`
	HeartbeatVLANID                     int           `json:"heartbeatVLANID,omitempty"`
	RedundancyPortIDs                   []interface{} `json:"redundancyPortIDs,omitempty"`
	RedundantGatewayStatus              string        `json:"redundantGatewayStatus,omitempty"`
	PermittedAction                     string        `json:"permittedAction,omitempty"`
	Personality                         string        `json:"personality,omitempty"`
	Description                         string        `json:"description,omitempty"`
	EnterpriseID                        string        `json:"enterpriseID,omitempty"`
	EntityScope                         string        `json:"entityScope,omitempty"`
	ConsecutiveFailuresCount            int           `json:"consecutiveFailuresCount,omitempty"`
	ExternalID                          string        `json:"externalID,omitempty"`
}

// NewNSRedundantGatewayGroup returns a new *NSRedundantGatewayGroup
func NewNSRedundantGatewayGroup() *NSRedundantGatewayGroup {

	return &NSRedundantGatewayGroup{
		HeartbeatInterval:        500,
		HeartbeatVLANID:          4094,
		ConsecutiveFailuresCount: 3,
	}
}

// Identity returns the Identity of the object.
func (o *NSRedundantGatewayGroup) Identity() bambou.Identity {

	return NSRedundantGatewayGroupIdentity
}

// Identifier returns the value of the object's unique identifier.
func (o *NSRedundantGatewayGroup) Identifier() string {

	return o.ID
}

// SetIdentifier sets the value of the object's unique identifier.
func (o *NSRedundantGatewayGroup) SetIdentifier(ID string) {

	o.ID = ID
}

// Fetch retrieves the NSRedundantGatewayGroup from the server
func (o *NSRedundantGatewayGroup) Fetch() *bambou.Error {

	return bambou.CurrentSession().FetchEntity(o)
}

// Save saves the NSRedundantGatewayGroup into the server
func (o *NSRedundantGatewayGroup) Save() *bambou.Error {

	return bambou.CurrentSession().SaveEntity(o)
}

// Delete deletes the NSRedundantGatewayGroup from the server
func (o *NSRedundantGatewayGroup) Delete() *bambou.Error {

	return bambou.CurrentSession().DeleteEntity(o)
}

// Metadatas retrieves the list of child Metadatas of the NSRedundantGatewayGroup
func (o *NSRedundantGatewayGroup) Metadatas(info *bambou.FetchingInfo) (MetadatasList, *bambou.Error) {

	var list MetadatasList
	err := bambou.CurrentSession().FetchChildren(o, MetadataIdentity, &list, info)
	return list, err
}

// CreateMetadata creates a new child Metadata under the NSRedundantGatewayGroup
func (o *NSRedundantGatewayGroup) CreateMetadata(child *Metadata) *bambou.Error {

	return bambou.CurrentSession().CreateChild(o, child)
}

// Alarms retrieves the list of child Alarms of the NSRedundantGatewayGroup
func (o *NSRedundantGatewayGroup) Alarms(info *bambou.FetchingInfo) (AlarmsList, *bambou.Error) {

	var list AlarmsList
	err := bambou.CurrentSession().FetchChildren(o, AlarmIdentity, &list, info)
	return list, err
}

// GlobalMetadatas retrieves the list of child GlobalMetadatas of the NSRedundantGatewayGroup
func (o *NSRedundantGatewayGroup) GlobalMetadatas(info *bambou.FetchingInfo) (GlobalMetadatasList, *bambou.Error) {

	var list GlobalMetadatasList
	err := bambou.CurrentSession().FetchChildren(o, GlobalMetadataIdentity, &list, info)
	return list, err
}

// CreateGlobalMetadata creates a new child GlobalMetadata under the NSRedundantGatewayGroup
func (o *NSRedundantGatewayGroup) CreateGlobalMetadata(child *GlobalMetadata) *bambou.Error {

	return bambou.CurrentSession().CreateChild(o, child)
}

// NSGateways retrieves the list of child NSGateways of the NSRedundantGatewayGroup
func (o *NSRedundantGatewayGroup) NSGateways(info *bambou.FetchingInfo) (NSGatewaysList, *bambou.Error) {

	var list NSGatewaysList
	err := bambou.CurrentSession().FetchChildren(o, NSGatewayIdentity, &list, info)
	return list, err
}

// RedundantPorts retrieves the list of child RedundantPorts of the NSRedundantGatewayGroup
func (o *NSRedundantGatewayGroup) RedundantPorts(info *bambou.FetchingInfo) (RedundantPortsList, *bambou.Error) {

	var list RedundantPortsList
	err := bambou.CurrentSession().FetchChildren(o, RedundantPortIdentity, &list, info)
	return list, err
}

// CreateRedundantPort creates a new child RedundantPort under the NSRedundantGatewayGroup
func (o *NSRedundantGatewayGroup) CreateRedundantPort(child *RedundantPort) *bambou.Error {

	return bambou.CurrentSession().CreateChild(o, child)
}

// EventLogs retrieves the list of child EventLogs of the NSRedundantGatewayGroup
func (o *NSRedundantGatewayGroup) EventLogs(info *bambou.FetchingInfo) (EventLogsList, *bambou.Error) {

	var list EventLogsList
	err := bambou.CurrentSession().FetchChildren(o, EventLogIdentity, &list, info)
	return list, err
}