//
// CheetahBullet.js
//
// 20110512: first

var VERSION = "1.0";

var simFile_ext = "chdata";
var inputFileBase = "/tmp/CheetahBullet";
var exeName = "CheetahBullet";
var exePath = '';

var toRAD = Math.PI/180;
var toDEG = 180/Math.PI;

var DEBUG = false;

var collisionNames = [];

function buildUI(obj) {
    
    obj.addParameterString("sim. file", "", false);
    obj.addParameterButton("set sim. file", "set", "setSimFile");
    
    obj.addParameterSeparator("Helper");
    obj.addParameterButton("set file/target to all", "set", "setTargetToAll");
    obj.addParameterInt("frame", 0, 0, 1000000, false, false);
    obj.addParameterButton("set frame to all", "set", "setFrameAll");
    
    obj.addParameterSeparator("Simulation");
    obj.addParameterButton("update sim. file", "update", "updateSimFile");
    obj.addParameterButton("create sim. file", "create", "createSimFile");
    
    obj.addParameterInt("thread count", 1, 1, 128, false, false);
    obj.addParameterInt("frame count", 121, 1, 100000, false, false);
    obj.addParameterFloat("frame rate", 30, 1, 1000, false, false);
    obj.addParameterInt("frame offset", 0, 0, 100000, false, false);
    
    //obj.addParameterFloat("world scale", 1, 0.00001, 100000, false, false );
    
    obj.addParameterFloat("gravity vec x", 0, -1000, 1000, false, false);
    obj.addParameterFloat("gravity vec y", -10, -1000, 1000, false, false);
    obj.addParameterFloat("gravity vec z", 0, -1000, 1000, false, false);
    
    obj.addParameterFloat("air density",1.2, 0, 10000, false, false);
    obj.addParameterFloat("water density", 0, 0, 10000, false, false);
    obj.addParameterFloat("water offset", 0, 0, 10000, false, false);
    
    obj.addParameterFloat("water normal x", 0, -100, 100, false, false);
    obj.addParameterFloat("water normal y", 0, -100, 100, false, false);
    obj.addParameterFloat("water normal z", 0, -100, 100, false, false);
    
    obj.addParameterSeparator("broadphase");
    
    obj.addParameterBool("aabb auto calc", 1, 0, 1, false, false);
    
    obj.addParameterFloat("aabb max x", -1000, -10000, 10000, false, false);
    obj.addParameterFloat("aabb max y", -1000, -10000, 10000, false, false);
    obj.addParameterFloat("aabb max z", -1000, -10000, 10000, false, false);
    
    obj.addParameterFloat("aabb min x", 1000, -10000, 10000, false, false);
    obj.addParameterFloat("aabb min y", 1000, -10000, 10000, false, false);
    obj.addParameterFloat("aabb min z", 1000, -10000, 10000, false, false);
    
    
    //
    if (obj.scriptPath) {
        var scrPath = obj.scriptPath();
    } else {
        var scrPath = ''; // set this manually, if you're using old version of Cheetah3D.
    }
    var scrFile = new File( scrPath );
    
    exePath = scrFile.directory().replace(/ /g, "\\ ") + '/' + exeName;
    if (DEBUG) exePath = scrFile.directory().replace(/ /g, "\\ ") + '/CheetahBullet_src/build/Debug/' + exeName;
    
    if (! (new File( scrFile.directory() + '/' + exeName )).exist() ) {
        OS.beep();
        print('error: '+exeName+' is not found at "'+exePath+'"');
    }
}

function setTargetToAll( obj ) {
    var root = obj.document().root();
    
    var list = [];
    list = list.concat( objectsForScriptName( root, "CheetahBullet Polygon Rep.js", true ),
                        objectsForScriptName( root, "CheetahBullet Spline Rep.js", true ),
                        objectsForScriptName( root, "CheetahBullet Particle Rep.js", true ) );
    print('target count:'+list.length);
    print('target :'+list);
    for (var i = 0;i < list.length;i++) {
        list[i].setParameter("sim. target", obj.owner());
        list[i].setParameter("sim. file", obj.getParameter("sim. file"));
    }
}

function setFrameToAll( obj ) {
    var root = obj.document().root();
    
    var list = [];
    list = list.concat( objectsForScriptName( root, "CheetahBullet Polygon Rep.js", true ),
                        objectsForScriptName( root, "CheetahBullet Spline Rep.js", true ),
                        objectsForScriptName( root, "CheetahBullet Particle Rep.js", true ) );
    
    for (var i = 0;i < list.length;i++) {
        list[i].setParameter("frame", obj.getParameter("frame"));
    }
}

function setSimFile( obj ) {
    var simFile = OS.runOpenPanel(simFile_ext);
    
    if (simFile != null) {
        obj.setParameter("sim. file", simFile);
    }
}

function updateSimFile( obj ) {
    var simFile = obj.getParameter("sim. file");
    
    if (simFile != null) {
        var simFile_cmd = simFile.replace(/ /g, "\\ ");
        //var inputFile = inputFileBase + '_' + (new Date().getTime()) + '.' + simFile_ext;
        var inputFile = inputFileBase + '_input.' + simFile_ext;
        
        st1 = new Date().getTime();
    	print('----');
        if (createInputFile( obj, inputFile )) {
            var cmd = exePath + ' ' + inputFile + ' ' + simFile_cmd;
            
            //print( cmd );
            OS.system( cmd );
            
            if (!DEBUG) OS.system( 'rm '+inputFile );
            obj.setParameter("sim. file", simFile);
            
    		var st2 = new Date().getTime();
    		printElapsedTime("sim. calculation t"+obj.getParameter("thread count"), st2-st1); 
        }
    }
}

function createSimFile( obj ) {
    var simFile = OS.runSavePanel(simFile_ext);
    
    if (simFile != null) {
        var simFile_cmd = simFile.replace(/ /g, "\\ ");
        //var inputFile = inputFileBase + '_' + (new Date().getTime()) + '.' + simFile_ext;
        var inputFile = inputFileBase + '_input.' + simFile_ext;
        
        st1 = new Date().getTime();
    	print('----');
        if (createInputFile( obj, inputFile )) {
            var cmd = exePath + ' ' + inputFile + ' ' + simFile_cmd;
            
            //print( cmd );
            OS.system( cmd );
            
            if (!DEBUG) OS.system( 'rm '+inputFile );
            obj.setParameter("sim. file", simFile);
    		var st2 = new Date().getTime();
    		printElapsedTime("sim. calculation t"+obj.getParameter("thread count"), st2-st1); 
        }
    }
}

/*
    [total_frame(unsigned int)][frame_rate(float)][gravity vec x(float)][gravity vec y(float)][gravity vec z(float)]
    [object_count(unsinged int)]
    ([object type(int)][object parameters][mass][origin vec x(float)][origin_mat ...(flaot)][inertia vec(float)])...
    
 */

function createInputFile( obj, inputFile ) {
    
    var doc = obj.document();
    var target = obj.owner();
    if (target == undefined || target.childCount() == 0) return false;
    
    var fh = new File( inputFile );
    fh.open( WRITE_MODE, LITTLE_ENDIAN );
    
    var i,j; // loop index
    var child; // 
    var target_childCount = target.childCount();
    
    collisionNames.length = 0;
    
    var sim_type = 0; // 0: rigid / 1: softbody
    var world_scale = 1; //obj.getParameter("world scale"); //
    
    obj.document().setAnimPosition(0);
    
    if (obj.getParameter("aabb auto calc")) {
        setAabb( obj );
    }
    
    var collisionList = [];
    var constraintList = [];
    var attrList = [];
    // checking collision objects
    for (i = target_childCount-1;i >= 0;i--) {
        child = target.childAtIndex(i);
        
        // softbody check
        if (tagForScriptName( child, "CheetahBullet Tags/SoftBody Tag.js" ))
            sim_type = 1;
        if (child.family() == SPLINEFAMILY)
            sim_type = 1;
        // register collision objects
        if (child.family() == NGONFAMILY || child.family() == PARTICLEFAMILY || child.family() == SPLINEFAMILY || child.type() == FOLDER) {
            if (child.getParameter("scriptName").indexOf("CheetahBullet Constraint.js") == -1) {
                collisionList.push( child );
                collisionNames.push( child.getParameter("name") );
            } else {
                constraintList.push( child );
            }
        }
        // register atribute objects
        if (child.type() == GRAVITYFORCE) {
            var gf = new Vec3D();
            var gfMat = child.objMatrix();
            gf.y = -1 * child.getParameter("gravityConstant");
            gf = gfMat.multiply( gf );
            
            obj.setParameter("gravity vec x", gf.x);
            obj.setParameter("gravity vec y", gf.y);
            obj.setParameter("gravity vec z", gf.z);
        } else if (child.type() == WINDFORCE) {
            attrList.push( child );
        }
    }
    
    // if sim. target has no simulated object...
    if (collisionList.length == 0) {
        OS.messageBox("Invalid sim. tareget Error", "sim. target must contain at least 1 polygon object");
        return false;
    }
    
    var frame_count = obj.getParameter("frame count");
    var frame_rate = obj.getParameter("frame rate");
    var thread_count = obj.getParameter("thread count");;
    
    fh.writeInt( sim_type );
    fh.writeFloat( world_scale );
    fh.writeInt( thread_count );
    fh.writeUInt( frame_count );
    fh.writeFloat( frame_rate );
    fh.writeUInt( obj.getParameter("frame offset") );
    
    fh.writeFloat( obj.getParameter("gravity vec x") );
    fh.writeFloat( obj.getParameter("gravity vec y") );
    fh.writeFloat( obj.getParameter("gravity vec z") );
    
    fh.writeFloat( obj.getParameter("air density") );
    fh.writeFloat( obj.getParameter("water density") );
    fh.writeFloat( obj.getParameter("water offset") );
    
    var water_normal = Vec3D_normalize( new Vec3D(obj.getParameter("water normal x"), obj.getParameter("water normal y"), obj.getParameter("water normal z")));
    fh.writeFloat( water_normal.x ); fh.writeFloat( water_normal.y ); fh.writeFloat( water_normal.z );
    
    fh.writeFloat( obj.getParameter("aabb max x") ); fh.writeFloat( obj.getParameter("aabb max y") ); fh.writeFloat( obj.getParameter("aabb max z") );
    fh.writeFloat( obj.getParameter("aabb min x") ); fh.writeFloat( obj.getParameter("aabb min y") ); fh.writeFloat( obj.getParameter("aabb min z") );
    
    fh.writeUInt( collisionList.length ); // object_count;
    fh.writeUInt( attrList.length ); // attribute_count;
    fh.writeUInt( constraintList.length ); // constraint_count;
    
    var collisionCount = collisionList.length;
    for (i = 0;i < collisionCount;i++) {
        print('vertices/faces cache data for '+i);
        child = collisionList[i];
        
        if (child.family() == NGONFAMILY) {
            var cCore = child.modCore();
            fh.writeInt(cCore.vertexCount());
            fh.writeInt(cCore.triangleCount());
        } else if (child.family() == PARTICLEFAMILY) {
            if (child.childCount() > 0 && (child.childAtIndex(0)).family() == NGONFAMILY) {
                var cCore = (child.childAtIndex(0)).modCore();
                fh.writeInt(cCore.vertexCount());
                fh.writeInt(cCore.triangleCount());
            } else {
                fh.writeInt(0);
                fh.writeInt(0);
            }
        } else if (child.family() == SPLINEFAMILY) {
            var cCore = child.modCore();
            var pathCount = cCore.pathCount();
            var pointCount = 0;
            for (j = 0;j < pathCount;j++) {
                var cache = cCore.cache( j );
                pointCount += cache.length;
            }
            fh.writeInt( pointCount );
            fh.writeInt( 0 );
        } else if (child.type() == FOLDER) {
            var childCount = child.childCount();
            var vertexTotal = 0;
            var triangleTotal = 0;
            for (j = 0;j < childCount;j++) {
                if (child.childAtIndex(j).family() == NGONFAMILY) {
                    var cCore = (child.childAtIndex(j)).modCore();
                    vertexTotal += cCore.vertexCount();
                    triangleTotal += cCore.triangleCount();
                }
            }
            fh.writeInt( vertexTotal );
            fh.writeInt( triangleTotal );
        }
    }
    print('---');
    // write object data
    for (i = 0;i < collisionCount;i++) {
        print('collision data for '+i);
        child = collisionList[i];
        var type = child.type();
        
        var rTag = tagForScriptName( child, "CheetahBullet Tags/RigidBody Tag.js" );
        var sTag = tagForScriptName( child, "CheetahBullet Tags/SoftBody Tag.js");
        var pTag = tagForScriptName( child, "CheetahBullet Tags/Particle Tag.js");
        var lTag = tagForScriptName( child, "CheetahBullet Tags/Spline Tag.js");
        
        if (rTag && rTag.getParameter("alter collision")) {
            var child_col = rTag.getParameter("alter collision");
            type = child_col.type();
        } else {
            var child_col = child;
        }
        
        var scale = child_col.getParameter("scale");
        
        // set initial value
        var mass = 1.0;
        var inertia = new Vec3D( 0, 0, 0 );
        var velocity = new Vec3D( 0, 0, 0 );
        var velocity_rot = new Vec3D( 0, 0, 0 );
        var mesh_type = 0;
        var friction = 0.5;
        var restitution = 0;
        var margin = 0.04;
        
        if (rTag) {
            mass = rTag.getParameter("mass");
            inertia = new Vec3D( rTag.getParameter("inertia vec x"), rTag.getParameter("inertia vec y"), rTag.getParameter("inertia vec z") );
            mesh_type = rTag.getParameter("mesh type");
            friction = rTag.getParameter("friction");
            restitution = rTag.getParameter("restitution");
            margin = rTag.getParameter("margin");
            if (rTag.getParameter("f-curve key enabled")) {
                mass = 0;
                mesh_type = 2;
            }
            if (rTag.getParameter("morph/bone enabled") && child.type() != FOLDER) { // no compound
                mass = 0;
                mesh_type = 2;
                child_col = child; // reset alter collision
                type = POLYGONOBJ;
            }
        } else if (sTag) {
            mass = sTag.getParameter("mass");
            inertia = new Vec3D( 0, 0, 0 );
            mesh_type = 3; // SoftBody
            margin = sTag.getParameter("margin");
        } else if (lTag) { // spline
            mass = lTag.getParameter("mass");
            inertia = new Vec3D( 0, 0, 0 );
            margin = lTag.getParameter("margin");
        }
        
        if (pTag) { // particle
            mass = pTag.getParameter("mass");
            inertia = new Vec3D( pTag.getParameter("inertia vec x"), pTag.getParameter("inertia vec y"), pTag.getParameter("inertia vec z") );
            friction = pTag.getParameter("friction");
            restitution = pTag.getParameter("restitution");
            margin = pTag.getParameter("margin");
        }
        
        // type check
        if (mesh_type == 3) // softbody 
            type = POLYGONOBJ;
        //
        if (type == BALL && (scale.x != scale.y || scale.y != scale.z || scale.x != scale.z)) // if scale is not uniformed, treat as polygon.
            type = POLYGONOBJ;
        
        writeMeshDataToFile( fh, child, type, mesh_type, child_col );
        
        if (mesh_type == 2) mass = 0; // static mesh must have 0 mass.
        
        //print('mas:'+fh.getpos());
        fh.writeFloat( mass );
        fh.writeFloat( margin );
        //
        if (sTag) { // softbody
            fh.writeFloat( sTag.getParameter("velocities correction") ); //0
            fh.writeFloat( sTag.getParameter("damping") ); //1
            fh.writeFloat( sTag.getParameter("drag") ); //2
            fh.writeFloat( sTag.getParameter("lift") ); //3
            fh.writeFloat( sTag.getParameter("pressure") ); //4
            
            fh.writeFloat( sTag.getParameter("volume conversation") ); //5
            fh.writeFloat( sTag.getParameter("dynamic friction") ); //6
            fh.writeFloat( sTag.getParameter("pose matching") ); //7
            fh.writeFloat( sTag.getParameter("rigid contacts hardness") ); //8
            fh.writeFloat( sTag.getParameter("kinetic contacts hardness") );
            
            fh.writeFloat( sTag.getParameter("soft contacts hardness") ); //10
            fh.writeFloat( sTag.getParameter("anchors contacts hardness") );
            fh.writeFloat( sTag.getParameter("rigid contacts cl hardness") ); //12
            fh.writeFloat( sTag.getParameter("kinetic contacts cl hardness") );
            fh.writeFloat( sTag.getParameter("soft contacts cl hardness") );
            
            fh.writeFloat( sTag.getParameter("rigid contacts cl sp hardness") ); //15
            fh.writeFloat( sTag.getParameter("kinetic contacts cl sp hardness") );
            fh.writeFloat( sTag.getParameter("soft contacts cl sp hardness") );
            fh.writeFloat( sTag.getParameter("max volume ratio") ); //18
            fh.writeFloat( sTag.getParameter("time scale") ); //
            
            fh.writeFloat( sTag.getParameter("linear stiffness") ); //20
            fh.writeFloat( sTag.getParameter("area stiffness") );
            fh.writeFloat( sTag.getParameter("volume stiffness") );
            
            fh.writeInt( sTag.getParameter("cluster count") ); //0
            fh.writeInt( sTag.getParameter("velocities iter.") );
            fh.writeInt( sTag.getParameter("position iter.") );
            fh.writeInt( sTag.getParameter("drift iter.") );
            fh.writeInt( sTag.getParameter("cluster iter.") );
            
            fh.writeInt( sTag.getParameter("aero model") ); // 5
            fh.writeInt( sTag.getParameter("bending constraints" ) );
            fh.writeInt( sTag.getParameter("self collision") );
            fh.writeInt( sTag.getParameter("use cluster") );
            fh.writeInt( sTag.getParameter("set pose") ); //
        } else if (lTag) { // spline
            fh.writeFloat( lTag.getParameter("velocities correction") ); // 0
            fh.writeFloat( lTag.getParameter("damping") ); // 1
            fh.writeFloat( lTag.getParameter("drag") ); // 2
            fh.writeFloat( lTag.getParameter("lift") ); // 3
            fh.writeFloat( lTag.getParameter("pressure") ); // 4
            
            fh.writeFloat( lTag.getParameter("volume conversation") ); // 5
            fh.writeFloat( lTag.getParameter("dynamic friction") ); // 6
            fh.writeFloat( lTag.getParameter("pose matching") ); // 7
            fh.writeFloat( lTag.getParameter("rigid contacts hardness") ); //8
            fh.writeFloat( lTag.getParameter("kinetic contacts hardness") );
            
            fh.writeFloat( lTag.getParameter("soft contacts hardness") ); //10
            fh.writeFloat( lTag.getParameter("anchors contacts hardness") ); //11
            fh.writeFloat( lTag.getParameter("rigid contacts cl hardness") ); //12
            fh.writeFloat( lTag.getParameter("kinetic contacts cl hardness") );
            fh.writeFloat( lTag.getParameter("soft contacts cl hardness") );
            
            fh.writeFloat( lTag.getParameter("rigid contacts cl sp hardness") );
            fh.writeFloat( lTag.getParameter("kinetic contacts cl sp hardness") );
            fh.writeFloat( lTag.getParameter("soft contacts cl sp hardness") );
            fh.writeFloat( lTag.getParameter("max volume ratio") ); //18
            fh.writeFloat( lTag.getParameter("time scale") ); //
            
            fh.writeFloat( lTag.getParameter("linear stiffness") ); //20
            fh.writeFloat( lTag.getParameter("area stiffness") );
            fh.writeFloat( lTag.getParameter("volume stiffness") );
            
            fh.writeInt( lTag.getParameter("cluster count") ); //0
            fh.writeInt( lTag.getParameter("velocities iter.") );
            fh.writeInt( lTag.getParameter("position iter.") );
            fh.writeInt( lTag.getParameter("drift iter.") );
            fh.writeInt( lTag.getParameter("cluster iter.") );
            
            fh.writeInt( lTag.getParameter("aero model") ); // 5
            fh.writeInt( lTag.getParameter("bending constraints" ) );
            fh.writeInt( lTag.getParameter("self collision") );
            fh.writeInt( lTag.getParameter("use cluster") );
            fh.writeInt( 0 ); // no pose setting for spline
        } else if (child.family() == SPLINEFAMILY) { // if no tag is attached, write default for spline
            fh.writeFloat( 1 ); // 0
            fh.writeFloat( 0 ); // 1
            fh.writeFloat( 0 ); // 2
            fh.writeFloat( 0 ); // 3
            fh.writeFloat( 0 ); // 4
            
            fh.writeFloat( 0 ); // 5
            fh.writeFloat( 0.2 ); // 6
            fh.writeFloat( 0 ); // 7
            fh.writeFloat( 1 ); //8
            fh.writeFloat( 0.1 );
            
            fh.writeFloat( 1 ); //10
            fh.writeFloat( 0.7 ); //11
            fh.writeFloat( 0.1 ); //12
            fh.writeFloat( 1 );
            fh.writeFloat( 0.5 );
            
            fh.writeFloat( 0.5 );
            fh.writeFloat( 0.5 );
            fh.writeFloat( 0.5 );
            fh.writeFloat( 1.0 ); //18
            fh.writeFloat( 1.0 ); //
            
            fh.writeFloat( 1.0 ); //20
            fh.writeFloat( 1.0 );
            fh.writeFloat( 1.0 );
            
            fh.writeInt( 1 ); //0
            fh.writeInt( 0 );
            fh.writeInt( 1 );
            fh.writeInt( 0 );
            fh.writeInt( 4 );
            
            fh.writeInt( 0 ); // 5
            fh.writeInt( 2 );
            fh.writeInt( 0 );
            fh.writeInt( 0 );
            fh.writeInt( 0 ); // no pose setting for spline
        } else { // rigid or particle or compound
            fh.writeFloat( inertia.x ); fh.writeFloat( inertia.y ); fh.writeFloat( inertia.z );
            fh.writeFloat( friction ); fh.writeFloat( restitution );
        }
        
        // write force info
        var fTags = tagsForScriptName(child, "CheetahBullet Tags/Force Tag.js");
        if (fTags.length > 0) {
            fh.writeInt( fTags.length );
            
            for( var ti = 0;ti < fTags.length;ti++) {
                var fTag = fTags[ti];
                
                var velocity_tar = fTag.getParameter("linear velocity target");
                var velocity;
                if (velocity_tar) {
                    velocity = Vec3D_normalize( ( velocity_tar.obj2WorldMatrix().multiply( new Vec3D( 0, 0, 0 )) ).sub( child.obj2WorldMatrix().multiply( new Vec3D( 0, 0, 0 ) )) );
                } else {
                    velocity = Vec3D_normalize( new Vec3D( fTag.getParameter("linear velocity x"), fTag.getParameter("linear velocity y"),  fTag.getParameter("linear velocity z") ) );
                }
                velocity = velocity.multiply( fTag.getParameter("linear velocity intensity") );
                
                var velocity_rot = new Vec3D( fTag.getParameter("angular velocity x"), fTag.getParameter("angular velocity y"), fTag.getParameter("angular velocity z") );
                
                fh.writeFloat( fTag.getParameter("frame delay") );
                fh.writeFloat( velocity.x ); fh.writeFloat( velocity.y ); fh.writeFloat( velocity.z );
                fh.writeFloat( velocity_rot.x ); fh.writeFloat( velocity_rot.y ); fh.writeFloat( velocity_rot.z );
            }
        } else {
            fh.writeInt(0);
        }
        
        var dt = 1/frame_rate;
        // write key animation
        if (rTag && rTag.getParameter("f-curve key enabled") && child.family() == NGONFAMILY) {
            fh.writeInt( 1 );
            
            for (j = 0;j < frame_count;j++) {
                doc.setAnimPosition(j*dt);
                
                var pos = child.getParameter("position");
                var rot = child.getParameter("rotation");
                
                fh.writeFloat( pos.x ); fh.writeFloat( pos.y ); fh.writeFloat( pos.z );
                fh.writeFloat( rot.x ); fh.writeFloat( rot.y ); fh.writeFloat( rot.z );
           }
        } else {
            fh.writeInt( 0 );
        }
        
        if (rTag && rTag.getParameter("morph/bone enabled") && child.family() == NGONFAMILY) {
            fh.writeInt( 1 );
            
            var cCore = child.modCore();
            var vertexCount = cCore.vertexCount();
            fh.writeUInt( vertexCount );
            
            for (j = 0;j < frame_count;j++) {
                doc.setAnimPosition(j*dt);
                
                for (k = 0;k < vertexCount;k++) {
                    var v = cCore.vertex(k);
                    fh.writeFloat(v.x); fh.writeFloat(v.y); fh.writeFloat(v.z);
                }
            }
        } else {
            fh.writeInt( 0 );
        }
    }
    
    // write attributes
    var attrCount = attrList.length;
    for (i = 0;i < attrCount;i++) {
        var attr = attrList[i];
        if (attr.type() == WINDFORCE) {
            fh.writeInt( 1 ); // 1: wind
        } else {
            fh.writeInt( 0 );
        }
        
        var dt = 1/frame_rate;
        for (j = 0;j < frame_count;j++) {
            doc.setAnimPosition(j*dt);
            
            var attrVec = new Vec3D();
            var attrRot = attr.getParameter("rotation");
            var attrMat = new Mat4D( ROTATE_HPB, attrRot.x, attrRot.y, attrRot.z );
            attrVec.x = attr.getParameter("speed");
            attrVec = attrMat.multiply( attrVec );
            // mat
            //print( Vec3D_toString( attrVec ) );
            fh.writeFloat( attrVec.x ); fh.writeFloat( attrVec.y ); fh.writeFloat( attrVec.z );
       }
    }
    
    // write constraint
    var constraintCount = constraintList.length;
    for (i = 0;i < constraintCount;i++) {
        var constraint = constraintList[i];
        var constType = constraint.getParameter("constraint type");
        
        var constPosA = new Vec3D();
        var constPosB = new Vec3D();
        var constRotA = new Vec3D();
        var constRotB = new Vec3D();
        var aMat = new Mat4D();
        var bMat = new Mat4D();
        switch( constType ) {
            case 0: // point
                fh.writeInt( 0 );
                
                if (constraint.getParameter("target a")) {
                    fh.writeInt( collisionNames.indexOf( (constraint.getParameter("target a")).getParameter("name") ) );
                    constPosA = constraint.getParameter("target a").getParameter("position");
                    aMat = constraint.getParameter("target a").objMatrix();
                } else {
                    fh.writeInt( -1 );
                }
                if (constraint.getParameter("target b")) {
                    fh.writeInt( collisionNames.indexOf( (constraint.getParameter("target b")).getParameter("name") ) );
                    constPosB = constraint.getParameter("target b").getParameter("position");
                    bMat = constraint.getParameter("target b").objMatrix();
                } else {
                    fh.writeInt( -1 );
                }
                constMat = constraint.objMatrix();
                constPosA = aMat.inverse().multiply( constMat.multiply( new Vec3D(0,0,0) ) );
                constPosB = bMat.inverse().multiply( constMat.multiply( new Vec3D(0,0,0) ) );
                
                fh.writeFloat( constPosA.x ); fh.writeFloat( constPosA.y ); fh.writeFloat( constPosA.z );
                fh.writeFloat( constPosB.x ); fh.writeFloat( constPosB.y ); fh.writeFloat( constPosB.z );
                break;
            case 1: // hinge
                fh.writeInt( 1 );
                
                if (constraint.getParameter("target a")) {
                    fh.writeInt( collisionNames.indexOf( (constraint.getParameter("target a")).getParameter("name") ) );
                    constPosA = constraint.getParameter("target a").getParameter("position");
                    constRotA = constraint.getParameter("target a").getParameter("rotation");
                    aMat = constraint.getParameter("target a").objMatrix();
                } else {
                    fh.writeInt( -1 );
                }
                if (constraint.getParameter("target b")) {
                    fh.writeInt( collisionNames.indexOf( (constraint.getParameter("target b")).getParameter("name") ) );
                    constPosB = constraint.getParameter("target b").getParameter("position");
                    constRotB = constraint.getParameter("target b").getParameter("rotation");
                    bMat = constraint.getParameter("target b").objMatrix();
                } else {
                    fh.writeInt( -1 );
                }
                constRot = constraint.getParameter("rotation");
                constMat = constraint.objMatrix();
                
                constPosA = aMat.inverse().multiply( constMat.multiply( new Vec3D(0,0,0) ) );
                constPosB = bMat.inverse().multiply( constMat.multiply( new Vec3D(0,0,0) ) );
                
                constRotA = constRot.sub( constRotA );
                constRotB = constRot.sub( constRotB );
                
                var constMatA = (new Mat4D( ROTATE_HPB, constRotA.x, constRotA.y, constRotA.z ));
                var constAxisA = constMatA.multiply( new Vec3D( 0, 0, 1 ) );
                var constMatB = (new Mat4D( ROTATE_HPB, constRotB.x, constRotB.y, constRotB.z ));
                var constAxisB = constMatB.multiply( new Vec3D( 0, 0, 1 ) );
                
                fh.writeFloat( constPosA.x ); fh.writeFloat( constPosA.y ); fh.writeFloat( constPosA.z );
                fh.writeFloat( constAxisA.x ); fh.writeFloat( constAxisA.y ); fh.writeFloat( constAxisA.z );
                fh.writeFloat( constPosB.x ); fh.writeFloat( constPosB.y ); fh.writeFloat( constPosB.z );
                fh.writeFloat( constAxisB.x ); fh.writeFloat( constAxisB.y ); fh.writeFloat( constAxisB.z );
                fh.writeFloat( constraint.getParameter("hinge limit min.") );
                fh.writeFloat( constraint.getParameter("hinge limit max.") );
                break;
            case 2: // cone
                fh.writeInt( 2 );
                
                if (constraint.getParameter("target a")) {
                    fh.writeInt( collisionNames.indexOf( (constraint.getParameter("target a")).getParameter("name") ) );
                    aMat = constraint.getParameter("target a").objMatrix();
                } else {
                    fh.writeInt( -1 );
                }
                if (constraint.getParameter("target b")) {
                    fh.writeInt( collisionNames.indexOf( (constraint.getParameter("target b")).getParameter("name") ) );
                    bMat = constraint.getParameter("target b").objMatrix();
                } else {
                    fh.writeInt( -1 );
                }
                constMat = constraint.objMatrix();
                
                aMat = aMat.inverse();
                aMat.concat( constMat );
                
                bMat = bMat.inverse();
                bMat.concat( constMat );
                
                writeMat4D( fh, aMat );
                writeMat4D( fh, bMat );
                
                fh.writeFloat( constraint.getParameter("cone angle x limit") );
                fh.writeFloat( constraint.getParameter("cone angle z limit") );
                fh.writeFloat( constraint.getParameter("cone twist limit") );
                fh.writeFloat( constraint.getParameter("cone twist softness") );
                fh.writeFloat( constraint.getParameter("cone twist bias") );
                fh.writeFloat( constraint.getParameter("cone twist relax") );
                break;
        }
    }
    
    fh.close();
    
    obj.document().setAnimPosition(0);
    
    return true;
}

function writeMeshDataToFile( fh, child, type, mesh_type, collision ) {
        
        var pos = child.getParameter("position");
        var rot = child.getParameter("rotation");
        var scale = collision.getParameter("scale"); // use scale parameters of collision
        
        switch( type ) { // 0:ball,1:box,2:cylinder,3:cone,4:polygon,5:particle
            case BALL:
                fh.writeInt( 0 );
                writeTransformToFile( fh, pos, rot, scale );
                fh.writeFloat( collision.getParameter("radius") );
                break;
            case BOX:
                fh.writeInt( 1 );
                writeTransformToFile( fh, pos, rot, scale );
                fh.writeFloat( collision.getParameter("sizeX") );
                fh.writeFloat( collision.getParameter("sizeY") );
                fh.writeFloat( collision.getParameter("sizeZ") );
                break;
            case CYLINDER:
                fh.writeInt( 2 );
                writeTransformToFile( fh, pos, rot, scale );
                fh.writeFloat( collision.getParameter("radius") );
                fh.writeFloat( collision.getParameter("height") );
                break;
            case CONE: // if you using cone, you have to apply transform modifier to translate y: - height / 2. 
                fh.writeInt( 3 );
                writeTransformToFile( fh, pos, rot, scale );
                fh.writeFloat( collision.getParameter("radius") );
                fh.writeFloat( collision.getParameter("height") );
                break;
            case PARTICLE:
            case PARTICLEEMITTER:
            case PARTICLEARRAY:
            case PARTICLEMESH:
            case PARTICLESPLINE:
            case PARTICLESCRIPT:
                fh.writeInt( 5 );
                writeTransformToFile( fh, pos, rot, scale );
                var pCore = child.core();
                var particleCount = pCore.particleCount();
                fh.writeUInt( particleCount );
                for (j = particleCount-1;j >= 0;j--) {
                    var p = pCore.particleAtIndex( j );
                    var pPos = p.getPosition();
                    var pRot = p.getRotation();
                    var pScale = p.getScale();
                    fh.writeFloat(pPos.x); fh.writeFloat(pPos.y); fh.writeFloat(pPos.z);
                    fh.writeFloat(pRot.x); fh.writeFloat(pRot.y); fh.writeFloat(pRot.z);
                    fh.writeFloat(pScale.x); fh.writeFloat(pScale.y); fh.writeFloat(pScale.z);
                }
                if (child.childCount() > 0) { // just 1st child shepe is available for collision
                    pChild = child.childAtIndex( 0 );
                    if (pChild.type() == FOLDER) {
                        writeMeshDataToFile( fh, pChild, pChild.type(), mesh_type, pChild );
                    } else if (pChild.family() != NGONFAMILY) { // if child isn't polygon object, set box ( 0.1, 0.1, 0.1 );
                        fh.writeInt( 1 );
                        writeTransformToFile( fh, new Vec3D(), new Vec3D(), new Vec3D(1,1,1) );
                        fh.writeFloat( 0.1 );
                        fh.writeFloat( 0.1 );
                        fh.writeFloat( 0.1 );
                    } else {
                        writeMeshDataToFile( fh, pChild, pChild.type(), mesh_type, pChild )
                    }
                } else { // if no child, set box (0.1, 0.1, 0.1);
                    fh.writeInt( 1 );
                    writeTransformToFile( fh, new Vec3D(), new Vec3D(), new Vec3D(1,1,1) );
                    fh.writeFloat( 0.1 );
                    fh.writeFloat( 0.1 );
                    fh.writeFloat( 0.1 );
                }
                break;
            case SPLINE:
            case CIRCLE:
            case COG:
            case FLOWER:
            case HELIX:
            case NEDGE:
            case RECT:
            case SCRIPTSPLINE:
            case STAR:
            case TEXT:
                print('write spline');
                fh.writeInt( 6 );
                writeTransformToFile( fh, pos, rot, scale );
                var sCore = child.modCore();
                var lengthList = [];
                var pathCount = sCore.pathCount();
                fh.writeUInt( pathCount );
                
                var cache;
                var cacheLengthTotal = 0;
                for (j = 0;j < pathCount;j++) {
                    cache = sCore.cache(j);
                    cacheLengthTotal += cache.length;
                }
                fh.writeUInt( cacheLengthTotal );
                
                var lTag = tagForScriptName( child, "CheetahBullet Tags/Spline Tag.js");
                var pointPinned = 0;
                var anchorStart = -1;
                var anchorEnd = -1;
                if (lTag) {
                    pointPinned = lTag.getParameter("point pin");
                    if (lTag.getParameter("anchor start")) anchorStart = collisionNames.indexOf( (lTag.getParameter("anchor start")).getParameter("name") );
                    if (lTag.getParameter("anchor end")) anchorEnd = collisionNames.indexOf( (lTag.getParameter("anchor end")).getParameter("name") );
                }
                fh.writeInt( pointPinned );
                fh.writeInt( anchorStart );
                fh.writeInt( anchorEnd );
                
                for (j = 0;j < pathCount;j++) {
                    var cache = sCore.cache(j);
                    var cacheLength = cache.length;
                    lengthList.push( cacheLength );
                    for (k = 0;k < cacheLength;k++) {
                        var p = cache[k];
                        fh.writeFloat( p.x );
                        fh.writeFloat( p.y );
                        fh.writeFloat( p.z );
                    }
                }
                for (j = 0;j < pathCount;j++) {
                    fh.writeUInt( lengthList[j] );
                }
                break;
            case FOLDER: // compound
                fh.writeInt( 10 );
                writeTransformToFile( fh, pos, rot, scale );
                
                if (childCount < 1) return false;
                
                var childCount = collision.childCount();
                fh.writeInt( childCount );
                for (var ii = 0;ii < childCount;ii++) {
                    var fChild = collision.childAtIndex(ii);
                    writeMeshDataToFile( fh, fChild, fChild.type(), mesh_type, fChild )
                }
                break;
            case POLYGONOBJ:
            default:
                fh.writeInt( 4 );
                writeTransformToFile( fh, pos, rot, scale );
                fh.writeInt( mesh_type ); // write mesh type
                var cCore = collision.modCore();
                var vertexCount = cCore.vertexCount();
                var polygonCount = cCore.polygonCount();
                var triangleCount = cCore.triangleCount();
                fh.writeInt( vertexCount );
                for (j = 0;j < vertexCount;j++) {
                    var v = cCore.vertex(j);
                    fh.writeFloat(v.x); fh.writeFloat(v.y); fh.writeFloat(v.z);
                }
                fh.writeInt( triangleCount );
                for (j = 0;j < polygonCount;j++) {
                    var polySize = cCore.polygonSize( j );
                    for (var k = 0;k < polySize - 2;k++) {
                        var indices = cCore.triangle( j, k );
                        fh.writeInt( cCore.vertexIndex( j, indices[2] ) );
                        fh.writeInt( cCore.vertexIndex( j, indices[1] ) );
                        fh.writeInt( cCore.vertexIndex( j, indices[0] ) );
                    }
                }
                // set anchor data if vertex is selected.
                var aTags = tagsForScriptName(child, "CheetahBullet Tags/Anchor Tag.js");
                fh.writeInt( aTags.length );
                for(j = 0;j < aTags.length;j++) {
                    var aTag = aTags[j];
                    
                    if (aTag.getParameter("anchor type")) {
                        print( 'anchor :' + aTag.getParameter("anchor type") );
                        fh.writeInt( collisionNames.indexOf( (aTag.getParameter("anchor target object")).getParameter("name") ) );
                    } else {
                        print( 'anchor type: -1' );
                        fh.writeInt( -1 );
                    }
                    var aIndices = aTag.getParameter("vertex indices").split(",");
                    fh.writeInt( aIndices.length );
                    print( 'anchor count:'+aIndices.length );
                    for (k = 0;k < aIndices.length;k++) {
                        if (aIndices[k] != '') fh.writeUInt( parseInt(aIndices[k]) );
                    }
                }
                // 
        }
}

function writeTransformToFile( fh, pos, rot, scale ) {
        
        fh.writeFloat( pos.x ); fh.writeFloat( pos.y ); fh.writeFloat( pos.z );
        fh.writeFloat( rot.x ); fh.writeFloat( rot.y ); fh.writeFloat( rot.z );
        fh.writeFloat( scale.x ); fh.writeFloat( scale.y ); fh.writeFloat( scale.z );
}

function setAabb( obj ) {
    var aMin = new Vec3D();
    var aMax = new Vec3D();
    
    var target = obj.owner();
    if (target == undefined || target.childCount() == 1) return;
    
    var count = target.childCount();
    for (i = count-1;i >= 0;i--) {
        child = target.childAtIndex(i);
        
        if (child.family() == NGONFAMILY) {
            var core = child.core();
            var vertexCount = core.vertexCount();
            var mat = child.objMatrix();
            var vec;
            
            for (var j = 0;j < vertexCount;j++) {
                vec = mat.multiply( core.vertex(j) );
                
                aMin.x = (aMin.x < vec.x)? aMin.x : vec.x;
                aMin.y = (aMin.y < vec.y)? aMin.y : vec.y;
                aMin.z = (aMin.z < vec.z)? aMin.z : vec.z;
                aMax.x = (aMax.x > vec.x)? aMax.x : vec.x;
                aMax.y = (aMax.y > vec.y)? aMax.y : vec.y;
                aMax.z = (aMax.z > vec.z)? aMax.z : vec.z;
            }
        }
    }
    obj.setParameter("aabb max x", Math.ceil(aMax.x));
    obj.setParameter("aabb max y", Math.ceil(aMax.y));
    obj.setParameter("aabb max z", Math.ceil(aMax.z));
    obj.setParameter("aabb min x", Math.floor(aMin.x));
    obj.setParameter("aabb min y", Math.floor(aMin.y));
    obj.setParameter("aabb min z", Math.floor(aMin.z));
}

function tagForScriptName(obj, scriptName) {
	var tagCount = obj.tagCount();
	var resultTag = false;
	for (var i = 0;i < tagCount;i++) {
		var tag = obj.tagAtIndex(i);
        var sn = tag.getParameter("scriptName");
		if (sn.indexOf(scriptName) != -1 && tag.getParameter("tagOn")) {
			resultTag = tag;
		}
	}
	return resultTag;
}

function tagsForScriptName(obj, scriptName) {
	var tagCount = obj.tagCount();
	var resultTags = [];
	for (var i = 0;i < tagCount;i++) {
		var tag = obj.tagAtIndex(i);
        var sn = tag.getParameter("scriptName");
		if (sn.indexOf(scriptName) != -1 && tag.getParameter("tagOn")) {
			resultTags.push(tag);
		}
	}
	return resultTags;
}

function objectsForScriptName(obj, scriptName, recursive) {
    var result = [];
    var i;
    if (recursive) {
        for (i = 0;i < obj.childCount();i++) {
            var child = obj.childAtIndex( i );
            result = result.concat( objectsForScriptName( child, scriptName, recursive ) );
        }
    }
    var sn = obj.getParameter("scriptName");
    if (sn.indexOf(scriptName) != -1) {
        result.push( obj );
    }
    return result;
}

function writeMat4D( fh, mat ) {
    
    fh.writeFloat(mat.m00); fh.writeFloat(mat.m10); fh.writeFloat(mat.m20); fh.writeFloat(mat.m30);
    fh.writeFloat(mat.m01); fh.writeFloat(mat.m11); fh.writeFloat(mat.m21); fh.writeFloat(mat.m31);
    fh.writeFloat(mat.m02); fh.writeFloat(mat.m12); fh.writeFloat(mat.m22); fh.writeFloat(mat.m32);
    fh.writeFloat(mat.m03); fh.writeFloat(mat.m13); fh.writeFloat(mat.m23); fh.writeFloat(mat.m33);
}

function Vec3D_normalize( vec ) {
    if (vec.norm() == 0) return (new Vec3D());
    return vec.multiply( 1/vec.norm() );
}

function Vec3D_toString( vec ) {
	return vec.x.toFixed(3) + ', ' + vec.y.toFixed(3) + ', ' + vec.z.toFixed(3);
}

function Mat4D_toString( mat ) {
    var str = '(' + mat.m00.toFixed(3) + ', ' + mat.m01.toFixed(3) + ', ' + mat.m02.toFixed(3) + ', ' + mat.m03.toFixed(3) + "\n";
    str += ' ' + mat.m10.toFixed(3) + ', ' + mat.m11.toFixed(3) + ', ' + mat.m12.toFixed(3) + ', ' + mat.m13.toFixed(3) + "\n";
    str += ' ' + mat.m20.toFixed(3) + ', ' + mat.m21.toFixed(3) + ', ' + mat.m22.toFixed(3) + ', ' + mat.m23.toFixed(3) + "\n";
    str += ' ' + mat.m30.toFixed(3) + ', ' + mat.m31.toFixed(3) + ', ' + mat.m32.toFixed(3) + ', ' + mat.m33.toFixed(3) + ")\n";
    return str; 
}

function printElapsedTime(label, time) {
	if (time/1000 > 60) {
		print( label + ':' + (time/1000).toFixed(2) + 'sec(s) ( ' + Math.floor(time/1000/60) + '.' + Math.floor((time/1000)%60) + ' )');
	} else {
		print( label + ':' + (time/1000).toFixed(2) + 'sec(s)' );
	}
}
