Feedback

Please leave feedback and comments. I am always interested to hear how people get on using these LScripts!

Wednesday, 24 September 2014

LScript - Motion_Wheel&Axle


LScript (Layout) to make an object rotate around its axle dynamically on collision with a plane. Objects to be used must be designed to rotate around there pivot on its B Axis. This then allows key control over any other axis.*Master_MotionBaker compatible.

Changes

  • Scaling on X,Y are averaged if not equal
  • Objects to rotate must rotate around center on B axis
  • Roughness has been added to add noise to surface friction
  • Fully validated and interactive interface
  • Fully implemented envelopes for animating all controls
  • Gizmo implemented to improve setting up
  • Presets added for damping, friction and roughness
  • *Motion Baking implemented for quicker playback and network baking
  • Motion blur is correctly calculated
  • Realtime dynamicly calculated with momentum, friction, roughness, damping, braking and acceleration
  • Fixed "Automatic" to retrieve correct bounding box extents
  • Added Lock / Unlock
  • Bake feature can now be used via Master_MotionBaker
  • Fixed judder under extreme circumstances

Compatible with Newtek LightWave 9.6 and above.

// LScript Item Animation - www.StephenCulley.co.uk
//
// web   address: http://www.stephenculley.co.uk
// email address: email@stephenculley.co.uk

/*  
    LScript Item Animation - Wheel & Axle v1.0

    Motion_Wheel&Axle.ls
      
*/

@version 2.2
@warnings
@script motion
@name *Wheel & Axle

    // Title
    sTitle = "*Wheel & Axle";

    // Version
    sVersion = "v1.0";

    // Items
    Item = nil; // Item
    GroundItem = nil; // Item
    GroundItemName = "nil"; // Item

    // Constant
    Scene; // Scene()

    // Variable
    bAcceleratorInvert = false; // Accelerator Invert
    iIteration = 0; // Iteration
    nLength = 1.0; // Length

    // Samples [1..5]

    aSample; // Array of Wheel Edge Samples World Position

        // [1] [1] = b Enable/Disable
        // [1] [2] = n Offset from center
        // [1] [3] = n Radius
        // [1] [4] = v World Position
        // [1] [5] = v Ground World Position
        // [1] [6] = n Distance to Ground

    // Envelope
    envAccelerator; // Accelerator
    envBrake; // Brake
    envDamping; // Damping
    envFriction; // Friction
    envMotionBaker; // Motion Baker
    envRoughness; // Roughness

    // Cache
    aCache;

        // [1] = i Frame
        // [2] = n time
        // [3] = v World Position
        // [4] = n Rotation
        // [5] = n Momentum

    // Motion Baker
    bMotionBakerLocked = false; // Locked
    bMotionBakerParent = false; // Parent
    iMotionBakerState = 2; // State

            // 1 = Disabled
            // 2 = Reset
            // 3 = Write
            // 4 = Read

    // Requester
    bRequesterUpdate; // Update
    iRequesterFrame; // Frame

    // Gizmo
    aCirclePoints; // Array of Points of Circle

    ctrl_d0,ctrl_d0i,ctrl_d1,ctrl_d2,ctrl_d3,ctrl_d4; // Dynamics
    ctrl_pl,ctrl_pls,ctrl_p0,ctrl_p1,ctrl_p1e,ctrl_p1o,ctrl_p1r,ctrl_p2,ctrl_p2e,ctrl_p2o,ctrl_p2r,
    ctrl_p3,ctrl_p3e,ctrl_p3o,ctrl_p3r,ctrl_p4,ctrl_p4e,ctrl_p4o,ctrl_p4r,ctrl_p5,ctrl_p5e,ctrl_p5o,ctrl_p5r,
    ctrl_p6,ctrl_p6e,ctrl_p6o,ctrl_p6r,ctrl_p7,ctrl_p7e,ctrl_p7o,ctrl_p7r,ctrl_p8,ctrl_p8e,ctrl_p8o,ctrl_p8r,
    ctrl_p9,ctrl_p9e,ctrl_p9o,ctrl_p9r; // Profile
    ctrl_mb,ctrl_mbe,ctrl_mbl; // Motion Baker

/*   
---------------------------------------------------------------------------------------------------
                                                                                            CREATE
---------------------------------------------------------------------------------------------------
*/

create : id
{
    // Description
    setdesc(sTitle);
    
    // Items
    Item = id; // Item

    // Constant   
    Scene = Scene();

    // Envelope
    envAccelerator = Envelope("Accelerator (Wheel&Axle)",CHAN_NUMBER,id.name);
    envAccelerator.persist(false);
    envAccelerator.createKey(0,0.0);    
    envBrake = Envelope("Brake (Wheel&Axle)",CHAN_NUMBER,id.name);
    envBrake.persist(false);
    envBrake.createKey(0,0.0);    
    envDamping = Envelope("Damping (Wheel&Axle)",CHAN_NUMBER,id.name);
    envDamping.persist(false);
    envDamping.createKey(0,0.05);  
    envFriction = Envelope("Friction (Wheel&Axle)",CHAN_NUMBER,id.name);
    envFriction.persist(false);
    envFriction.createKey(0,1.0);   
    envMotionBaker = Envelope("Motion Baker (Wheel&Axle)",CHAN_NUMBER,id.name); // Motion Baker
    envMotionBaker.persist(false);
    envRoughness = Envelope("Roughness (Wheel&Axle)",CHAN_NUMBER,id.name);
    envRoughness.persist(false);
    envRoughness.createKey(0,0.0);   

    // Cache
    aCache[1] = -1; // Frame
    aCache[2] = 0.0; // Time
    aCache[3] = <0.0,0.0,0.0>; // World Position
    aCache[4] = 0.0; // Rotation
    aCache[5] = 0.0; // Momentum

    // Requester
    bRequesterUpdate = false; // Update
    iRequesterFrame = -1; // Frame

    // Comring
    comringattach("*MotionBaker","comring_motionbaker"); // Comring *MotionBaker

    // Variable
    nRadius = 1.0; // Radius

    if(id.genus == 1 && id.null == false) // Automatic
        {

        // Bounding Box
        aBoundingBox = Item.position(getlayernumber(Item));

        // Length
        nLength = aBoundingBox[2].z - aBoundingBox[1].z;

        // Radius
        nRadius = 0.0;
        if(abs(aBoundingBox[1].x) > nRadius){nRadius = abs(aBoundingBox[1].x);}   
        if(abs(aBoundingBox[1].y) > nRadius){nRadius = abs(aBoundingBox[1].y);}   
        if(abs(aBoundingBox[2].x) > nRadius){nRadius = abs(aBoundingBox[2].x);}   
        if(abs(aBoundingBox[2].y) > nRadius){nRadius = abs(aBoundingBox[2].y);}   

        }

    // Sample - 1
    aSample[1][1] = true; // b Enable/Disable
    aSample[1][2] = -0.5; // n Offset
    aSample[1][3] = nRadius; // n Radius
    aSample[1][4] = <0.0,0.0,0.0>; // v World Position
    aSample[1][5] = <0.0,0.0,0.0>; // v Ground World Position
    aSample[1][6] = 0.0; // n Distance to Ground

    // Sample - 2
    aSample[2][1] = false; // b Enable/Disable
    aSample[2][2] = -0.375; // n Offset
    aSample[2][3] = nRadius; // n Radius
    aSample[2][4] = <0.0,0.0,0.0>; // v World Position
    aSample[2][5] = <0.0,0.0,0.0>; // v Ground World Position
    aSample[2][6] = 0.0; // n Distance to Ground

    // Sample - 3
    aSample[3][1] = false; // b Enable/Disable
    aSample[3][2] = -0.25; // n Offset
    aSample[3][3] = nRadius; // n Radius
    aSample[3][4] = <0.0,0.0,0.0>; // v World Position
    aSample[3][5] = <0.0,0.0,0.0>; // v Ground World Position
    aSample[3][6] = 0.0; // n Distance to Ground

    // Sample - 4
    aSample[4][1] = false; // b Enable/Disable
    aSample[4][2] = -0.125; // n Offset
    aSample[4][3] = nRadius; // n Radius
    aSample[4][4] = <0.0,0.0,0.0>; // v World Position
    aSample[4][5] = <0.0,0.0,0.0>; // v Ground World Position
    aSample[4][6] = 0.0; // n Distance to Ground

    // Sample - 5
    aSample[5][1] = true; // b Enable/Disable
    aSample[5][2] = 0.0; // n Offset
    aSample[5][3] = nRadius; // n Radius
    aSample[5][4] = <0.0,0.0,0.0>; // v World Position
    aSample[5][5] = <0.0,0.0,0.0>; // v Ground World Position
    aSample[5][6] = 0.0; // n Distance to Ground

    // Sample - 6
    aSample[6][1] = false; // b Enable/Disable
    aSample[6][2] = 0.125; // n Offset
    aSample[6][3] = nRadius; // n Radius
    aSample[6][4] = <0.0,0.0,0.0>; // v World Position
    aSample[6][5] = <0.0,0.0,0.0>; // v Ground World Position
    aSample[6][6] = 0.0; // n Distance to Ground

    // Sample - 7
    aSample[7][1] = false; // b Enable/Disable
    aSample[7][2] = 0.25; // n Offset
    aSample[7][3] = nRadius; // n Radius
    aSample[7][4] = <0.0,0.0,0.0>; // v World Position
    aSample[7][5] = <0.0,0.0,0.0>; // v Ground World Position
    aSample[7][6] = 0.0; // n Distance to Ground

    // Sample - 8
    aSample[8][1] = false; // b Enable/Disable
    aSample[8][2] = 0.375; // n Offset
    aSample[8][3] = nRadius; // n Radius
    aSample[8][4] = <0.0,0.0,0.0>; // v World Position
    aSample[8][5] = <0.0,0.0,0.0>; // v Ground World Position
    aSample[8][6] = 0.0; // n Distance to Ground

    // Sample - 9
    aSample[9][1] = true; // b Enable/Disable
    aSample[9][2] = 0.5; // n Offset
    aSample[9][3] = nRadius; // n Radius
    aSample[9][4] = <0.0,0.0,0.0>; // v World Position
    aSample[9][5] = <0.0,0.0,0.0>; // v Ground World Position
    aSample[9][6] = 0.0; // n Distance to Ground

    // Gizmo
    nDegree = 0;
    for(d = 0; d <= 32; d++)
        {
        vPoint = ;
        aCirclePoints[d + 1] = vPoint; // Store vertex
        nDegree += 360 / 32; // Add Division
        }
    aCirclePoints[33] = aCirclePoints[1]; // Add copy of first point   
}

/*   
---------------------------------------------------------------------------------------------------
                                                                                           DESTROY
---------------------------------------------------------------------------------------------------
*/

destroy
{
    // Comring
    comringdetach("*MotionBaker"); // Comring *MotionBaker
}

/*   
---------------------------------------------------------------------------------------------------
                                                                                           COMRING
---------------------------------------------------------------------------------------------------
*/

comring_motionbaker: event,data
{

    // Parent
    ParentItem = Item.parent;
    iParent = 1;
    while(ParentItem != nil)
        {
        iParent++;
        ParentItem = ParentItem.parent;
        }

    if(event != 0 && event != iParent)
        {
        return;
        }

    sMessage = comringdecode(@"s:200"@,data);

    if(strlower(sMessage) == "lock") // Lock
        {
        bMotionBakerLocked = true;
        }
    if(strlower(sMessage) == "unlock") // Unlock
        {
        bMotionBakerLocked = false;
        }
    if(strlower(sMessage) == "disable" && !bMotionBakerLocked) // Disable
        {
        iMotionBakerState = 1;
        }
    if(strlower(sMessage) == "reset" && !bMotionBakerLocked) // Reset 
        {
        iMotionBakerState = 2;       
        }
    if(strlower(sMessage) == "write" && !bMotionBakerLocked) // Write
        {
        iMotionBakerState = 3;        
        }
    if(strlower(sMessage) == "read" && !bMotionBakerLocked) // Read
        {
        iMotionBakerState = 4;
        }
    if(strlower(sMessage) == "parent" && !bMotionBakerLocked) // Parent
        {
        bMotionBakerParent = true;
        }
    if(strlower(sMessage) == "bake" && !bMotionBakerLocked) // Bake
        {
        motionbaker_bake(); // Bake
        iMotionBakerState = 1; // Disable
        bMotionBakerLocked = true; // Lock
        }

    // Requester
    if(reqisopen())
        {
        bRequesterUpdate = true; // Update
        setvalue(ctrl_mb,iMotionBakerState); // Motion Baker State
        setvalue(ctrl_mbl,bMotionBakerLocked); // Motion Baker Locked
        bRequesterUpdate = false; // Update
        }
}

/*   
---------------------------------------------------------------------------------------------------
                                                                                              BAKE
---------------------------------------------------------------------------------------------------
*/

motionbaker_bake
{
    envChannel = Envelope("Rotation.B",CHAN_NUMBER,Item.name); // B 

    if(envChannel != nil)
      {
      for(frame = Scene().previewstart;frame <= Scene().previewend; frame++)
        {
        time = (1 / Scene().fps) * frame;
        iKey = envChannel.keyExists(time);
        if(iKey == nil)
            {envChannel.createKey(time, envMotionBaker.value(time) * (3.1415926535 / 180));}
        else
            {envChannel.setKeyValue(iKey,envMotionBaker.value(time) * (3.1415926535 / 180));}
        }  
      }

    info(Item.name + " baked"); // Info
    Refresh(); // Refresh
}

/*   
---------------------------------------------------------------------------------------------------
                                                                                           PROCESS
---------------------------------------------------------------------------------------------------
*/

process: ma, frame, time
{

/*   
-------------------------------------------------------------------------------
                                                                          Item
-------------------------------------------------------------------------------
*/

    // Ground Item
    if(GroundItemName != "nil") {GroundItem = Mesh(GroundItemName); GroundItemName = "nil";}

// ---------------------------------------------------------------------- end -

/*   
-------------------------------------------------------------------------------
                                                                     Requester
-------------------------------------------------------------------------------
*/

    if(reqisopen() && iRequesterFrame != frame)
        {
        bRequesterUpdate = true; // Update

        setvalue(ctrl_d0,envAccelerator.value(Scene.currenttime)); // Accelerator
        setvalue(ctrl_d1,envBrake.value(Scene.currenttime)); // Brake
        setvalue(ctrl_d2,envDamping.value(Scene.currenttime)); // Damping
        setvalue(ctrl_d3,envFriction.value(Scene.currenttime)); // Friction
        setvalue(ctrl_d4,envRoughness.value(Scene.currenttime)); // Roughness

        iRequesterFrame = frame; // Frame
        bRequesterUpdate = false; // Update
        }

// ---------------------------------------------------------------------- end - 

/*   
-------------------------------------------------------------------------------
                                                                     Iteration
-------------------------------------------------------------------------------
*/

    // Iteration
    iIteration++;
    if(aCache[1] <> frame)
        {
        iIteration = 1;
        }

// ---------------------------------------------------------------------- end -

/*   
-------------------------------------------------------------------------------
                                                         Motion Baker Disabled
-------------------------------------------------------------------------------
*/

    if(iMotionBakerState == 1)
        {
        return;
        }

// ---------------------------------------------------------------------- end -

/*    
-------------------------------------------------------------------------------
                                                           Motion Baker Parent
-------------------------------------------------------------------------------
*/
    if(bMotionBakerParent)
        {
        // Parent
        ParentItem = Item.parent;
        iParent = 1;
        while(ParentItem != nil)
            {
            iParent++;
            ParentItem = ParentItem.parent;
            }
        sMessage = "*parent";
        cMessage = comringencode(@"s:200"@,sMessage);
        comringmsg("*MotionBaker",iParent,cMessage);
        bMotionBakerParent = false;
        }

// ---------------------------------------------------------------------- end -

/*    
-------------------------------------------------------------------------------
                                                            Motion Baker Reset
-------------------------------------------------------------------------------
*/

    if(iMotionBakerState == 2)
        {
        nAxleMomentum = 0.0; // Momentum

        aCache[1] = frame; // Frame
        aCache[2] = time; // Time
        aCache[3] = ma.get(WPOSITION,time); // World Position
        aCache[4] = ma.get(ROTATION,time).z; // Rotation
        aCache[5] = 0.0; // Momentum

        // Motion Baker Write
        iKey = envMotionBaker.keyExists((1 / Scene.fps) * frame);
        if(iKey == nil)
            {envMotionBaker.createKey((1 / Scene.fps) * frame, aCache[4]);}
        else
            {envMotionBaker.setKeyValue(iKey,aCache[4]);}
    
        // Motion Baker State
        iMotionBakerState = 3; // Motion Baker Write
        if(reqisopen())
            {
            bRequesterUpdate = true; // Update
            setvalue(ctrl_mb,iMotionBakerState); // Motion Baker
            bRequesterUpdate = false; // Update
            }
        }

// ---------------------------------------------------------------------- end -

/*
-------------------------------------------------------------------------------
                                                             Motion Baker Read
-------------------------------------------------------------------------------
*/ 

    if(iMotionBakerState == 3 && iIteration > 1) // Write
        {
        vRotation = ma.get(ROTATION,time); // Rotation

        vRotation.z = aCache[4]; // B Channel

        ma.set(ROTATION,vRotation);

        return;
        }  

    if(iMotionBakerState == 4) // Read
        {
        vRotation = ma.get(ROTATION,time); // Rotation

        vRotation.z = envMotionBaker.value(time); // B Channel

        ma.set(ROTATION,vRotation);

        return;
        }

// ---------------------------------------------------------------------- end -

/*
-------------------------------------------------------------------------------
                                                                       Process
-------------------------------------------------------------------------------
*/

    // Variable
    nCircumference = 0.0; // Circumference
    nDiameter = 0.0; // Diameter
    nDistanceToGround = 999999999999999.0; // Distance to ground
    nDistanceDegree = 0.0; // Distance per degree
    nRadius = 0.0; // Radius
    nRotation = 0.0; // Wheel Rotation
    vWorldPosition = ma.get(WPOSITION,time); // World Position
    vRotation = ma.get(ROTATION,time); // Rotation
    vScaling = ma.get(SCALING,time); // Scaling
    vNearest = <0.0,0.0,0.0>;
    vGroundNearest = <0.0,0.0,0.0>;
    vnRight = normalize3D(Item.getRight(time));
    vnUp = normalize3D(Item.getUp(time));
    vnForward = normalize3D(Item.getForward(time));

    // Ground Item
    if(GroundItem)
        {
        vGroundWorldPosition = GroundItem.getWorldPosition(time);
        vnGround = normalize3D(GroundItem.getUp(time));
        }
    else
        {
        vGroundWorldPosition = <0.0,0.0,0.0>; // World Position
        vnGround = <0.0,1.0,0.0>; // Up Vector
        }

    // Nearest Ground / Axle
    vGroundNearest = pointplane3D(vWorldPosition,vGroundWorldPosition,vnGround);
    vnToGround = normalize3D(vGroundNearest - vWorldPosition); // Vector Normal to Ground
    vNearest = pointplane3D(vGroundNearest,vWorldPosition,vnForward);
    vnToward = normalize3D(vWorldPosition - vNearest); // Vector Normal towards center 
    vnAway = normalize3D(vNearest - vWorldPosition); // Vector Normal away from center  

    // Samples

        // [1] [1] = b Enable/Disable
        // [1] [2] = n Offset from center
        // [1] [3] = n Radius
        // [1] [4] = v World Position
        // [1] [5] = v Ground World Position
        // [1] [6] = n Distance to Ground

    for(s = 1; s <= 9; s++)
        {
        if(aSample[s][1])
            {
            aSample[s][4] =  vWorldPosition + (vnAway * (aSample[s][3] * ((vScaling.x + vScaling.y) * 0.5))) + (vnForward * (nLength * aSample[s][2] * vScaling.z)); // v World Position
            aIntersection = lineplaneintersect3D(aSample[s][4],vnToGround,vGroundWorldPosition,vnGround); // Intersection with ground
            aSample[s][5] = aIntersection[3]; // v Ground World Position
            aSample[s][6] = aIntersection[2]; // n Distance to Ground
            if(aSample[s][6] < nDistanceToGround)
                {
                nRadius = aSample[s][3]; // n Radius
                vNearest = aSample[s][4]; // v World Position
                vGroundNearest = aSample[s][5]; // v Ground World Position
                nDistanceToGround = aSample[s][6]; // n Distant to ground
                nDiameter = 2 * nRadius; // Diameter
                nCircumference = 3.1415926535 * nDiameter; // Circumference
                if(nCircumference <> 0.0){nDistanceDegree = 360 / nCircumference;} else {nDistanceDegree = 0.0;} // Distance per degree
                }
            }
        }

    // Motion
    nMotionDistance = distance3D(aCache[3],vWorldPosition); // Distance frame to frame
    vnMotionDirection = normalize3D(aCache[3] - vWorldPosition); // Direction Vector

// Rotation

    vnDirectionForwardCrossProduct = normalize3D(crossproduct3D(vnMotionDirection,vnForward));
    vnForwardAlignCrossProduct = normalize3D(crossproduct3D(vnMotionDirection,vnDirectionForwardCrossProduct));
   
    if(bAcceleratorInvert)
        {
        nAccelerator = -envAccelerator.value(time); // Accelerator
        }
    else
        {
        nAccelerator = envAccelerator.value(time); // Accelerator
        }

    nBrake = 1.0 - clip(0,1.0,envBrake.value(time)); // Brake
    nDamping = 1.0 - clip(0,1.0,envDamping.value(time)); // Damping
    nFriction = clip(0,1.0,envFriction.value(time)); // Friction
    nSlip = abs(dotproduct3D(vnForwardAlignCrossProduct,vnForward)); // Slip
    nVelocity = dotproduct3D(-vnDirectionForwardCrossProduct,vnGround); // Velocity
    nRoughness = 1.0 - (randu() * clip(0,1.0,envRoughness.value(time))); // Roughness
    
    // Moment
    if(nDistanceToGround <= 0.0)
        {
        // in contact
        nMomentum = maxmin(maxmin((nDistanceDegree * nMotionDistance * nVelocity * nSlip * (nFriction * (nRoughness * nFriction))),(aCache[5] * (nDamping * (1.0 - (nFriction * (nRoughness * nFriction)))))),nAccelerator) * nBrake;
        }
    else
        {
        // free wheel
        nMomentum = maxmin(aCache[5] * nDamping,nAccelerator) * nBrake;
        }

    // Rotation   
    nRotation = aCache[4] + nMomentum; // Compound rotation
    vRotation.z = nRotation; // Rotation

// ---------------------------------------------------------------------- end -

/*
-------------------------------------------------------------------------------
                                                                            MA
-------------------------------------------------------------------------------
*/

    ma.set(ROTATION,vRotation);

// ---------------------------------------------------------------------- end -

/*
-------------------------------------------------------------------------------
                                                            Motion Baker Write
-------------------------------------------------------------------------------
*/ 

    iKey = envMotionBaker.keyExists((1 / Scene.fps) * frame);
    if(iKey == nil)
        {envMotionBaker.createKey((1 / Scene.fps) * frame, nRotation);}
    else
        {envMotionBaker.setKeyValue(iKey,nRotation);}

// ---------------------------------------------------------------------- end -

/*
-------------------------------------------------------------------------------
                                                                         Cache
-------------------------------------------------------------------------------
*/

    aCache[1] = frame; // Frame
    aCache[2] = time; // Time
    aCache[3] = vWorldPosition; // World Position
    aCache[4] = nRotation; // Rotation
    aCache[5] = nMomentum; // Momentum

// ---------------------------------------------------------------------- end -

}

// CLIP

clip: min,max,n
{
    if(n < min) n = min;
    if(n > max) n = max;
    return(n);
}

// CONVERSIONS

DEGtoRAD: n // Degree to radian
{
    return(n * (3.1415926535 / 180));
}

RADtoDEG: n // Radian to degree
{
    return(n * (180 / 3.1415926535));
}

// MAP RANGE

maprange01: n1,n2,i

{    
    if(n2-n1 == 0.0){return(0.0);}
  else
    {return((1/(n2-n1)) * (i-n1));}
}

// MAX MIN

maxmin: n1,n2
{
    if(n1 < 0.0 && n2 < 0.0){return(min(n1,n2));} // Min
  else
    if(n1 >= 0.0 && n2 >= 0.0){return(max(n1,n2));} // Max
  else
    {return(n1 + n2);}
}

// VECTOR 3D

crossproduct3D: v1,v2 // v
{
    // Vector
    return();
}

distance3D: v1, v2 // n
{
    // Vector
    return(sqrt(((v2.x - v1.x) * (v2.x - v1.x)) +
                ((v2.y - v1.y) * (v2.y - v1.y)) + 
                ((v2.z - v1.z) * (v2.z - v1.z))));
}

dotproduct3D: v1,v2 // n
{
    // Vector
    return(v1.x * v2.x + v1.y * v2.y + v1.z * v2.z);
}

lineplanedistance3D: v1,vn1,v2,vn2 // n
{
    // Vector - v1 v2 is Vector / vn1 vn2 is Vector Normal
    nNumerator = dotproduct3D(vn2,v1) + dotproduct3D(-v2,vn2);
    nDenomimator = dotproduct3D(vn2,vn1);
    if(nDenomimator <> 0.0){return(-(nNumerator / nDenomimator));} else{return(0.0);}
}

lineplaneintersect3D: v1,vn1,v2,vn2 // a[1] b (true / false)
                                    // a[2] n (distance)
                                    // a[3] v (vector)
{
    // Vector - v1 v2 is Vector / vn1 vn2 is Vector Normal
    nNumerator = dotproduct3D(vn2,v1) + dotproduct3D(-v2,vn2);
    nDenomimator = dotproduct3D(vn2,vn1);
    if(nDenomimator <> 0.0){nDistance =  -(nNumerator / nDenomimator);} else{ nDistance = 0.0;}
    if(nDistance >= 0.0){a[1] = true;}else{a[1] = false;} // True / False
    a[2] = nDistance; // Distance
    a[3] = v1 + (vn1 * nDistance); // Vector
    return(a);
}

pointplane3D: v1,v2,vn2 // v
{
    // Vector - v1 is Vector / v2 is Vector on Plane / vn2 is Vector Normal on Plane
    return(v1 + (dotproduct3D(vn2,v2) - dotproduct3D(vn2,v1)) * vn2);
}

normal3D: v1,v2 // vn
{
    // Vector
    return();
}

magnitude3D: v // n
{
    // Vector
    return(sqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z)));
}

normalize3D: v // v
{
    // Vector normalize to 0 - 1
    nMagnitude = magnitude3D(v);
    if(nMagnitude <> 0) nMagnitude = 1 / nMagnitude;
    return(v * nMagnitude);
}

// ITEM

getlayernumber: item
{
    if(item.totallayers == 1){return(1);}
    sTokens = parse(":",item.name);
    iLayer = 0;
    if (strleft(sTokens[2],5)=="Layer")
      {
      iLayer = integer(sTokens[2]);
      }
    else
      {
      iTotal = item.totallayers;
      for(i = 1; i <= iTotal; i++)
        {
        if(item.layerVisible(i) == 1)
          {
          if(item.layerName(i) == sTokens[2])
            {
            iLayer = i;
            break;
            }
          }
        else
          {
          iTotal++;
          }
        }
      }
  return iLayer;
}

/*   
---------------------------------------------------------------------------------------------------
                                                                                              LOAD
---------------------------------------------------------------------------------------------------
*/

load: what,io
{
    if(what == SCENEMODE)   // processing an ASCII scene file
    {
        if(io.read().asStr() == sTitle + " " + sVersion)
            {
            bAcceleratorInvert = io.read().asInt(); // Accelerator Invert
            nLength = io.read().asNum(); // Length

            // Samples
            for(s = 1; s <= 9; s++)
                {
                aSample[s][1] := io.read().asInt(); // b Enable/Disable
                aSample[s][2] := io.read().asNum(); // n Offset
                aSample[s][3] := io.read().asNum(); // n Radius
                }

            // Item
            GroundItemName = io.read().asStr();

            // Motion Baker
            bMotionBakerLocked = io.read().asInt(); // Motion Baker Locked
            iMotionBakerState = io.read().asInt(); // Motion Baker State

            // Envelope
            envAccelerator.load(); // Accelerator
            envBrake.load(); // Brake
            envDamping.load(); // Damping
            envFriction.load(); // Friction
            envMotionBaker.load(); // Motion Baker
            envRoughness.load(); // Roughness

            }
        else
            {
            info(sTitle + " - Error");
            }
    }
}

/*   
---------------------------------------------------------------------------------------------------
                                                                                              SAVE
---------------------------------------------------------------------------------------------------
*/

save: what,io
{
    if(what == SCENEMODE)
    {
        // Header
        io.writeln(sTitle + " " + sVersion);

        io.writeln(bAcceleratorInvert); // Accelerator Invert
        io.writeln(nLength); // Length
        
        // Samples
        for(s = 1; s <= 9; s++)
            { 
            io.writeln(aSample[s][1]); // b Enable/Disable
            io.writeln(aSample[s][2]); // n Offset
            io.writeln(aSample[s][3]); // n Radius
            }

        // Item
        if(GroundItem != nil)
            {
            io.writeln(string(GroundItem.name));
            }
        else
            {
            io.writeln("nil");
            } 

        // Motion Baker
        io.writeln(bMotionBakerLocked); // Motion Baker Locked
        io.writeln(iMotionBakerState); // Motion Baker State

        // Envelope
        envAccelerator.save(); // Accelerator
        envBrake.save(); // Brake
        envDamping.save(); // Damping
        envFriction.save(); // Friction
        envMotionBaker.save(); // Motion Baker
        envRoughness.save(); // Roughness
    }
}

/*   
---------------------------------------------------------------------------------------------------
                                                                                           OPTIONS
---------------------------------------------------------------------------------------------------
*/

options
{
    if(reqisopen())
        {
        reqend();
        return;
        }

    // Variable
    iVar = 0;

    reqbegin(sTitle + " " + sVersion + " - " + Item.name);
    reqsize(300,390);

    ctrl_tab = ctltab("Profile","Dynamics","*Motion Baker","Developer");

    // Profile
    ctrl_p0 = ctlinfo(298,100,"draw_profile"); // Profile Preview
    ctrl_pb0 = ctlbutton("Reset",50,"button_reset"); // Button Reset
    ctrl_pb1 = ctlbutton("Automatic",50,"button_automatic"); // Button Automatic
    ctrl_pb2 = ctlbutton("Mirror 1..4 > 6..9",50,"button_mirror"); // Button Mirror
    ctrl_pb3 = ctlbutton("S",20,"button_profile_save"); // Button Profile Save
    ctrl_pb4 = ctlbutton("L",20,"button_profile_load"); // Button Profile Load
    ctrl_pls = ctlminislider(" ",iVar,-100,100);
    ctrl_pl = ctldistance("Length (Section Width)",nLength); // Length
    ctrl_p1o = ctlminislider(" ",iVar,-100,100); // Sample 1
    ctrl_p1r = ctlminislider(" ",iVar,-100,100);
    ctrl_p1 = ctldistance("1) Radius",aSample[1][3]);
    ctrl_p1e = ctlcheckbox("Enable",aSample[1][1]);
    ctrl_p2o = ctlminislider(" ",iVar,-100,100); // Sample 2
    ctrl_p2r = ctlminislider(" ",iVar,-100,100);
    ctrl_p2 = ctldistance("2) Radius",aSample[2][3]);
    ctrl_p2e = ctlcheckbox("Enable",aSample[2][1]);
    ctrl_p3o = ctlminislider(" ",iVar,-100,100); // Sample 3
    ctrl_p3r = ctlminislider(" ",iVar,-100,100);
    ctrl_p3 = ctldistance("3) Radius",aSample[3][3]);
    ctrl_p3e = ctlcheckbox("Enable",aSample[3][1]);
    ctrl_p4o = ctlminislider(" ",iVar,-100,100); // Sample 4
    ctrl_p4r = ctlminislider(" ",iVar,-100,100);
    ctrl_p4 = ctldistance("4) Radius",aSample[4][3]);
    ctrl_p4e = ctlcheckbox("Enable",aSample[4][1]);
    ctrl_p5o = ctlminislider(" ",iVar,-100,100); // Sample 5
    ctrl_p5r = ctlminislider(" ",iVar,-100,100);
    ctrl_p5 = ctldistance("5) Radius",aSample[5][3]);
    ctrl_p5e = ctlcheckbox("Enable",aSample[5][1]);
    ctrl_p6o = ctlminislider(" ",iVar,-100,100); // Sample 6
    ctrl_p6r = ctlminislider(" ",iVar,-100,100);
    ctrl_p6 = ctldistance("6) Radius",aSample[6][3]);
    ctrl_p6e = ctlcheckbox("Enable",aSample[6][1]);
    ctrl_p7o = ctlminislider(" ",iVar,-100,100); // Sample 7
    ctrl_p7r = ctlminislider(" ",iVar,-100,100);
    ctrl_p7 = ctldistance("7) Radius",aSample[7][3]);
    ctrl_p7e = ctlcheckbox("Enable",aSample[7][1]);
    ctrl_p8o = ctlminislider(" ",iVar,-100,100); // Sample 8
    ctrl_p8r = ctlminislider(" ",iVar,-100,100);
    ctrl_p8 = ctldistance("8) Radius",aSample[8][3]);
    ctrl_p8e = ctlcheckbox("Enable",aSample[8][1]);
    ctrl_p9o = ctlminislider(" ",iVar,-100,100); // Sample 9
    ctrl_p9r = ctlminislider(" ",iVar,-100,100);
    ctrl_p9 = ctldistance("9) Radius",aSample[9][3]); 
    ctrl_p9e = ctlcheckbox("Enable",aSample[9][1]);

    // Profile
    ctlposition(ctrl_p0,1,28,299,100); // Profile Preview
    ctlposition(ctrl_pb0,2,130,41,20); // Button Reset
    ctlposition(ctrl_pb1,45,130,58,20); // Button Automatic
    ctlposition(ctrl_pb2,105,130,90,20); // Button Mirror
    ctlposition(ctrl_pb3,250,130,20,20); // Button Profile Save
    ctlposition(ctrl_pb4,272,130,20,20); // Button Profile Load
    ctlposition(ctrl_pls,260,162,10,20); // Length Slider
    ctlposition(ctrl_pl,60,162,212,20,130); // Length
    ctlposition(ctrl_p1o,188,184,10,20); // Sample 1
    ctlposition(ctrl_p1r,168,184,10,20);
    ctlposition(ctrl_p1,8,184,172,20);
    ctlposition(ctrl_p1e,224,184,68,20);
    ctlposition(ctrl_p2o,188,206,10,20); // Sample 2
    ctlposition(ctrl_p2r,168,206,10,20);
    ctlposition(ctrl_p2,8,206,172,20);
    ctlposition(ctrl_p2e,224,206,68,20);
    ctlposition(ctrl_p3o,188,228,10,20); // Sample 3
    ctlposition(ctrl_p3r,168,228,10,20);
    ctlposition(ctrl_p3,8,228,172,20);
    ctlposition(ctrl_p3e,224,228,68,20);
    ctlposition(ctrl_p4o,188,250,10,20); // Sample 4
    ctlposition(ctrl_p4r,168,250,10,20);
    ctlposition(ctrl_p4,8,250,172,20);
    ctlposition(ctrl_p4e,224,250,68,20);
    ctlposition(ctrl_p5o,188,272,10,20); // Sample 5
    ctlposition(ctrl_p5r,168,272,10,20);
    ctlposition(ctrl_p5,8,272,172,20);
    ctlposition(ctrl_p5e,224,272,68,20);
    ctlposition(ctrl_p6o,188,294,10,20); // Sample 6
    ctlposition(ctrl_p6r,168,294,10,20);
    ctlposition(ctrl_p6,8,294,172,20);
    ctlposition(ctrl_p6e,224,294,68,20);
    ctlposition(ctrl_p7o,188,316,10,20); // Sample 7
    ctlposition(ctrl_p7r,168,316,10,20);
    ctlposition(ctrl_p7,8,316,172,20);
    ctlposition(ctrl_p7e,224,316,68,20);
    ctlposition(ctrl_p8o,188,338,10,20); // Sample 8
    ctlposition(ctrl_p8r,168,338,10,20);
    ctlposition(ctrl_p8,8,338,172,20);
    ctlposition(ctrl_p8e,224,338,68,20);
    ctlposition(ctrl_p9o,188,360,10,20); // Sample 9
    ctlposition(ctrl_p9r,168,360,10,20);
    ctlposition(ctrl_p9,8,360,172,20);
    ctlposition(ctrl_p9e,224,360,68,20);

    ctlpage(1,ctrl_p0,ctrl_pb0,ctrl_pb1,ctrl_pb2,ctrl_pb3,ctrl_pb4,ctrl_pls,ctrl_pl,ctrl_p1r,ctrl_p1o,ctrl_p1e,ctrl_p1,ctrl_p1e,
              ctrl_p2o,ctrl_p2r,ctrl_p2,ctrl_p2e,ctrl_p3o,ctrl_p3r,ctrl_p3,ctrl_p3e,ctrl_p4o,ctrl_p4r,ctrl_p4,ctrl_p4e,
              ctrl_p5o,ctrl_p5r,ctrl_p5,ctrl_p5e,ctrl_p6o,ctrl_p6r,ctrl_p6,ctrl_p6e,ctrl_p7o,ctrl_p7r,ctrl_p7,ctrl_p7e,
              ctrl_p8o,ctrl_p8r,ctrl_p8,ctrl_p8e,ctrl_p9o,ctrl_p9r,ctrl_p9,ctrl_p9e); // Profile

    // Dynamics
    ctrl_d0 = ctlangle("Accelerator",envAccelerator.value(Scene.currenttime)); // Accelerator
    ctrl_d0i = ctlcheckbox("Invert",bAcceleratorInvert); // Accelerator Invert
    ctrl_d0e = ctlbutton("E",20,"button_d0e"); // Button
    ctrl_d1 = ctlpercent("Brake",envBrake.value(Scene.currenttime)); // Brake
    ctrl_d1e = ctlbutton("E",20,"button_d1e"); // Button
    ctrl_d2 = ctlpercent("Damping",envDamping.value(Scene.currenttime)); // Damping
    ctrl_d2e = ctlbutton("E",20,"button_d2e"); // Button
    ctrl_d3 = ctlpercent("Friction",envFriction.value(Scene.currenttime)); // Friction
    ctrl_d3e = ctlbutton("E",20,"button_d3e"); // Button
    ctrl_d4 = ctlpercent("Roughness",envRoughness.value(Scene.currenttime)); // Roughness
    ctrl_d4e = ctlbutton("E",20,"button_d4e"); // Button
    ctrl_d5 = ctlpopup("Preset", 1, @"Rubber - Tarmac",
                                "Rubber - Gravel",
                                "Rubber - Snow",
                                "Rubber - Ice",
                                "Rollerskate - Tarmac",
                                "Plastic - Plastic"@);

    // Ground
    ctrl_g0 = ctlsep();
    ctrl_g1 = ctlmeshitems("Ground Item",GroundItem);

    ctlposition(ctrl_d0,36,32,152,20,100); // Accelerator
    ctlposition(ctrl_d0i,212,32,58,20); // Accelerator Invert
    ctlposition(ctrl_d0e,272,32,20,20); // Accelerator Button
    ctlposition(ctrl_d1,36,54,212,20,100); // Brake
    ctlposition(ctrl_d1e,272,54,20,20); // Brake Button
    ctlposition(ctrl_d2,36,76,212,20,100); // Damping
    ctlposition(ctrl_d2e,272,76,20,20); // Damping Button
    ctlposition(ctrl_d3,36,98,212,20,100); // Friction
    ctlposition(ctrl_d3e,272,98,20,20); // Friction Button
    ctlposition(ctrl_d4,36,120,212,20,100); // Roughness
    ctlposition(ctrl_d4e,272,120,20,20); // Roughness Button
    ctlposition(ctrl_d5,36,142,256,20,100); // Preset
    ctlposition(ctrl_g0,0,174,300,4); // Seperator
    ctlposition(ctrl_g1,36,188,256,20,100); // Ground Item

    ctlpage(2,ctrl_d0,ctrl_d0i,ctrl_d0e,ctrl_d1,ctrl_d1e,ctrl_d2,ctrl_d2e,ctrl_d3,ctrl_d3e,ctrl_d4,ctrl_d4e,ctrl_d5,ctrl_g0,ctrl_g1); // Dynamics

    // Motion Baker
    ctrl_mb = ctlchoice("State",iMotionBakerState,@"Disable","Reset","Write","Read"@); // Choice
    ctrl_mbe = ctlbutton("E",20,"button_mbe"); // Button
    ctrl_mbl = ctlcheckbox("Lock",bMotionBakerLocked); // Checkbox
    ctlposition(ctrl_mb,10,32,260,20,60); // Choice
    ctlposition(ctrl_mbe,272,32,20,20); // Button
    ctlposition(ctrl_mbl,220,58,72,20); // Checkbox
    ctlpage(3,ctrl_mb,ctrl_mbe,ctrl_mbl);

    // Developer
    ctrl_dev = ctltext("","developer: Stephen Culley","http://www.stephenculley.co.uk");
    ctlposition(ctrl_dev,10,350,280,20,100);
    ctlpage(4,ctrl_dev);

    // Refresh
    ctlrefresh(ctrl_pl,"refresh_pl"); // Length
    ctlrefresh(ctrl_pls,"refresh_pls"); // Length Slider
    ctlrefresh(ctrl_p1,"refresh_p1"); // Radius
    ctlrefresh(ctrl_p1r,"refresh_p1r"); // Radius Slider
    ctlrefresh(ctrl_p1o,"refresh_p1o"); // Offset Slider
    ctlrefresh(ctrl_p1e,"refresh_p1e"); // Enable
    ctlrefresh(ctrl_p2,"refresh_p2"); // Radius
    ctlrefresh(ctrl_p2r,"refresh_p2r"); // Radius Slider
    ctlrefresh(ctrl_p2o,"refresh_p2o"); // Offset Slider
    ctlrefresh(ctrl_p2e,"refresh_p2e"); // Enable
    ctlrefresh(ctrl_p3,"refresh_p3"); // Radius
    ctlrefresh(ctrl_p3r,"refresh_p3r"); // Radius Slider
    ctlrefresh(ctrl_p3o,"refresh_p3o"); // Offset Slider
    ctlrefresh(ctrl_p3e,"refresh_p3e"); // Enable
    ctlrefresh(ctrl_p4,"refresh_p4"); // Radius
    ctlrefresh(ctrl_p4r,"refresh_p4r"); // Radius Slider
    ctlrefresh(ctrl_p4o,"refresh_p4o"); // Offset Slider
    ctlrefresh(ctrl_p4e,"refresh_p4e"); // Enable
    ctlrefresh(ctrl_p5,"refresh_p5"); // Radius
    ctlrefresh(ctrl_p5r,"refresh_p5r"); // Radius Slider
    ctlrefresh(ctrl_p5o,"refresh_p5o"); // Offset Slider
    ctlrefresh(ctrl_p5e,"refresh_p5e"); // Enable
    ctlrefresh(ctrl_p6,"refresh_p6"); // Radius
    ctlrefresh(ctrl_p6r,"refresh_p6r"); // Radius Slider
    ctlrefresh(ctrl_p6o,"refresh_p6o"); // Offset Slider
    ctlrefresh(ctrl_p6e,"refresh_p6e"); // Enable
    ctlrefresh(ctrl_p7,"refresh_p7"); // Radius
    ctlrefresh(ctrl_p7r,"refresh_p7r"); // Radius Slider
    ctlrefresh(ctrl_p7o,"refresh_p7o"); // Offset Slider
    ctlrefresh(ctrl_p7e,"refresh_p7e"); // Enable
    ctlrefresh(ctrl_p8,"refresh_p8"); // Radius
    ctlrefresh(ctrl_p8r,"refresh_p8r"); // Radius Slider
    ctlrefresh(ctrl_p8o,"refresh_p8o"); // Offset Slider
    ctlrefresh(ctrl_p8e,"refresh_p8e"); // Enable
    ctlrefresh(ctrl_p9,"refresh_p9"); // Radius
    ctlrefresh(ctrl_p9r,"refresh_p9r"); // Radius Slider
    ctlrefresh(ctrl_p9o,"refresh_p9o"); // Offset Slider
    ctlrefresh(ctrl_p9e,"refresh_p9e"); // Enable
    ctlrefresh(ctrl_d0,"refresh_d0"); // Accelerator
    ctlrefresh(ctrl_d0i,"refresh_d0i"); // Accelerator Invert
    ctlrefresh(ctrl_d1,"refresh_d1"); // Brake
    ctlrefresh(ctrl_d2,"refresh_d2"); // Damping
    ctlrefresh(ctrl_d3,"refresh_d3"); // Friction
    ctlrefresh(ctrl_d4,"refresh_d4"); // Roughness
    ctlrefresh(ctrl_d5,"refresh_d5"); // Preset
    ctlrefresh(ctrl_g1,"refresh_g1"); // Ground Item
    ctlrefresh(ctrl_mb,"refresh_mb"); // Motion Baker State
    ctlrefresh(ctrl_mbl,"refresh_mbl"); // Motion Baker Locked
    reqopen();
}

button_reset
{
    bAcceleratorInvert = false; // Accelerator Invert

    // Sample - 1
    aSample[1][1] = true; // b Enable/Disable
    aSample[1][2] = -0.5; // n Offset
    aSample[1][3] = 1.0; // n Radius

    // Sample - 2
    aSample[2][1] = false; // b Enable/Disable
    aSample[2][2] = -0.375; // n Offset
    aSample[2][3] = 1.0; // n Radius

    // Sample - 3
    aSample[3][1] = false; // b Enable/Disable
    aSample[3][2] = -0.25; // n Offset
    aSample[3][3] = 1.0; // n Radius

    // Sample - 4
    aSample[4][1] = false; // b Enable/Disable
    aSample[4][2] = -0.125; // n Offset
    aSample[4][3] = 1.0; // n Radius

    // Sample - 5
    aSample[5][1] = true; // b Enable/Disable
    aSample[5][2] = 0.0; // n Offset
    aSample[5][3] = 1.0; // n Radius

    // Sample - 6
    aSample[6][1] = false; // b Enable/Disable
    aSample[6][2] = 0.125; // n Offset
    aSample[6][3] = 1.0; // n Radius

    // Sample - 7
    aSample[7][1] = false; // b Enable/Disable
    aSample[7][2] = 0.25; // n Offset
    aSample[7][3] = 1.0; // n Radius

    // Sample - 8
    aSample[8][1] = false; // b Enable/Disable
    aSample[8][2] = 0.375; // n Offset
    aSample[8][3] = 1.0; // n Radius

    // Sample - 9
    aSample[9][1] = true; // b Enable/Disable
    aSample[9][2] = 0.5; // n Offset
    aSample[9][3] = 1.0; // n Radius

    setvalue(ctrl_pl,1.0); // Length
    setvalue(ctrl_p1,aSample[1][3]); // Sample 1
    setvalue(ctrl_p1e,aSample[1][1]);
    setvalue(ctrl_p2,aSample[2][3]); // Sample 2
    setvalue(ctrl_p2e,aSample[2][1]);
    setvalue(ctrl_p3,aSample[3][3]); // Sample 3
    setvalue(ctrl_p3e,aSample[3][1]);
    setvalue(ctrl_p4,aSample[4][3]); // Sample 4
    setvalue(ctrl_p4e,aSample[4][1]);
    setvalue(ctrl_p5,aSample[5][3]); // Sample 5
    setvalue(ctrl_p5e,aSample[5][1]);
    setvalue(ctrl_p6,aSample[6][3]); // Sample 6
    setvalue(ctrl_p6e,aSample[6][1]);
    setvalue(ctrl_p7,aSample[7][3]); // Sample 7
    setvalue(ctrl_p7e,aSample[7][1]);
    setvalue(ctrl_p8,aSample[8][3]); // Sample 8
    setvalue(ctrl_p8e,aSample[8][1]);
    setvalue(ctrl_p9,aSample[9][3]); // Sample 9
    setvalue(ctrl_p9e,aSample[9][1]);

    // Motion Baker
    iMotionBakerState = 3; // Write

    // Envelope
    while(envAccelerator.keyCount >= 1)
        {
        envAccelerator.deleteKey(envAccelerator.keys[1]);       
        }
    envAccelerator.createKey(0,0.0); 
    setvalue(ctrl_d0,0.0); // Accelerator
    setvalue(ctrl_d0i,0); // Accelerator Invert

    while(envBrake.keyCount >= 1)
        {
        envBrake.deleteKey(envBrake.keys[1]);       
        }
    envBrake.createKey(0,0.0);    
    setvalue(ctrl_d1,0.0); // Brake

    while(envDamping.keyCount >= 1)
        {
        envDamping.deleteKey(envDamping.keys[1]);       
        }
    envDamping.createKey(0,0.01);  
    setvalue(ctrl_d2,0.05); // Damping

    while(envFriction.keyCount >= 1)
        {
        envFriction.deleteKey(envFriction.keys[1]);       
        }
    envFriction.createKey(0,1.0);   
    setvalue(ctrl_d3,1.0); // Friction

    while(envRoughness.keyCount >= 1)
        {
        envRoughness.deleteKey(envRoughness.keys[1]);       
        }
    envRoughness.createKey(0,0.0);
    setvalue(ctrl_d4,0.0); // Roughness

}

button_automatic
{
    if(Item.genus != 1) // Check for Mesh
        {
        error("*Wheel & Axle - Must be attached to an object to use automatic.");
        return;
        }

    if(Item.null == true) // Check for Null
        {
        error("*Wheel & Axle - Must be attached to an object to use automatic.");
        return; // Check for Mesh
        }

    // Bounding Box
    aBoundingBox = Item.position(getlayernumber(Item));

    // Length
    nLength = aBoundingBox[2].z - aBoundingBox[1].z;

    // Radius
    nRadius = 0.0;
    if(abs(aBoundingBox[1].x) > nRadius){nRadius = abs(aBoundingBox[1].x);}   
    if(abs(aBoundingBox[1].y) > nRadius){nRadius = abs(aBoundingBox[1].y);}   
    if(abs(aBoundingBox[2].x) > nRadius){nRadius = abs(aBoundingBox[2].x);}   
    if(abs(aBoundingBox[2].y) > nRadius){nRadius = abs(aBoundingBox[2].y);}   

    // Sample - 1
    aSample[1][1] = true; // b Enable/Disable
    aSample[1][2] = -0.5; // n Offset
    aSample[1][3] = nRadius; // n Radius

    // Sample - 2
    aSample[2][1] = false; // b Enable/Disable
    aSample[2][2] = -0.375; // n Offset
    aSample[2][3] = nRadius; // n Radius

    // Sample - 3
    aSample[3][1] = false; // b Enable/Disable
    aSample[3][2] = -0.25; // n Offset
    aSample[3][3] = nRadius; // n Radius

    // Sample - 4
    aSample[4][1] = false; // b Enable/Disable
    aSample[4][2] = -0.125; // n Offset
    aSample[4][3] = nRadius; // n Radius

    // Sample - 5
    aSample[5][1] = true; // b Enable/Disable
    aSample[5][2] = 0.0; // n Offset
    aSample[5][3] = nRadius; // n Radius

    // Sample - 6
    aSample[6][1] = false; // b Enable/Disable
    aSample[6][2] = 0.125; // n Offset
    aSample[6][3] = nRadius; // n Radius

    // Sample - 7
    aSample[7][1] = false; // b Enable/Disable
    aSample[7][2] = 0.25; // n Offset
    aSample[7][3] = nRadius; // n Radius

    // Sample - 8
    aSample[8][1] = false; // b Enable/Disable
    aSample[8][2] = 0.375; // n Offset
    aSample[8][3] = nRadius; // n Radius

    // Sample - 9
    aSample[9][1] = true; // b Enable/Disable
    aSample[9][2] = 0.5; // n Offset
    aSample[9][3] = nRadius; // n Radius

    setvalue(ctrl_pl,nLength); // Length
    setvalue(ctrl_p1,nRadius); // Sample 1
    setvalue(ctrl_p1e,1);
    setvalue(ctrl_p2,nRadius); // Sample 2
    setvalue(ctrl_p2e,0);
    setvalue(ctrl_p3,nRadius); // Sample 3
    setvalue(ctrl_p3e,0);
    setvalue(ctrl_p4,nRadius); // Sample 4
    setvalue(ctrl_p4e,0);
    setvalue(ctrl_p5,nRadius); // Sample 5
    setvalue(ctrl_p5e,1);
    setvalue(ctrl_p6,nRadius); // Sample 6
    setvalue(ctrl_p6e,0);
    setvalue(ctrl_p7,nRadius); // Sample 7
    setvalue(ctrl_p7e,0);
    setvalue(ctrl_p8,nRadius); // Sample 8
    setvalue(ctrl_p8e,0);
    setvalue(ctrl_p9,nRadius); // Sample 9
    setvalue(ctrl_p9e,1);
}

button_mirror
{
    // Sample - 1 to 9
    aSample[9][1] = aSample[1][1]; // b Enable/Disable
    aSample[9][2] = -aSample[1][2]; // n Offset
    aSample[9][3] = aSample[1][3]; // n Radius

    // Sample - 2 to 8
    aSample[8][1] = aSample[2][1]; // b Enable/Disable
    aSample[8][2] = -aSample[2][2]; // n Offset
    aSample[8][3] = aSample[2][3]; // n Radius

    // Sample - 3 to 7
    aSample[7][1] = aSample[3][1]; // b Enable/Disable
    aSample[7][2] = -aSample[3][2]; // n Offset
    aSample[7][3] = aSample[3][3]; // n Radius

    // Sample - 4 to 6
    aSample[6][1] = aSample[4][1]; // b Enable/Disable
    aSample[6][2] = -aSample[4][2]; // n Offset
    aSample[6][3] = aSample[4][3]; // n Radius

    // Sample - 5
    aSample[5][2] = 0.0; // n Offset

    setvalue(ctrl_p6,aSample[6][3]); // Sample 6
    setvalue(ctrl_p6e,aSample[6][1]);
    setvalue(ctrl_p7,aSample[7][3]); // Sample 7
    setvalue(ctrl_p7e,aSample[7][1]);
    setvalue(ctrl_p8,aSample[8][3]); // Sample 8
    setvalue(ctrl_p8e,aSample[8][1]);
    setvalue(ctrl_p9,aSample[9][3]); // Sample 9
    setvalue(ctrl_p9e,aSample[9][1]);
}

button_profile_save
{
    filename = getfile("Save Profile","*.prf",,0);
    if(filename != nil)
        {       
        if(filename == "") return;
        if((file = File(filename,"w")) == nil) return; 
        file.writeln("*Wheel & Axle" + sVersion + " - Profile");
        file.writeln(nLength); // Length
        // Samples
        for(s = 1; s <= 9; s++)
            { 
            file.writeln(aSample[s][1]); // b Enable/Disable
            file.writeln(aSample[s][2]); // n Offset
            file.writeln(aSample[s][3]); // n Radius
            }
        file.close();        
        }
}

button_profile_load
{
    filename = getfile("Load Profile","*.prf",,1);
    if(filename != nil)
        {       
        if(filename == "") return;
        if((file = File(filename,"r")) == nil) return;
        if(file.linecount())
            {
            line = file.read();   
            if(line == "*Wheel & Axle" + sVersion + " - Profile")
                {
                nLength = file.read().asNum(); // Length
                // Samples
                for(s = 1; s <= 9; s++)
                    { 
                    aSample[s][1] = file.read().asInt(); // b Enable/Disable
                    aSample[s][2] = file.read().asNum(); // n Offset
                    aSample[s][3] = file.read().asNum(); // n Radius
                    }
                setvalue(ctrl_pl,nLength); // Length
                setvalue(ctrl_p1,aSample[1][3]); // Sample 1
                setvalue(ctrl_p1e,aSample[1][1]);
                setvalue(ctrl_p2,aSample[2][3]); // Sample 2
                setvalue(ctrl_p2e,aSample[2][1]);
                setvalue(ctrl_p3,aSample[3][3]); // Sample 3
                setvalue(ctrl_p3e,aSample[3][1]);
                setvalue(ctrl_p4,aSample[4][3]); // Sample 4
                setvalue(ctrl_p4e,aSample[4][1]);
                setvalue(ctrl_p5,aSample[5][3]); // Sample 5
                setvalue(ctrl_p5e,aSample[5][1]);
                setvalue(ctrl_p6,aSample[6][3]); // Sample 6
                setvalue(ctrl_p6e,aSample[6][1]);
                setvalue(ctrl_p7,aSample[7][3]); // Sample 7
                setvalue(ctrl_p7e,aSample[7][1]);
                setvalue(ctrl_p8,aSample[8][3]); // Sample 8
                setvalue(ctrl_p8e,aSample[8][1]);
                setvalue(ctrl_p9,aSample[9][3]); // Sample 9
                setvalue(ctrl_p9e,aSample[9][1]);
                }
            }
        file.close();   
        }
}

refresh_pls:value // Length Slider
{
    setvalue(ctrl_pl,clip(0.0,9999999999,nLength + value *.001));
    setvalue(ctrl_pls,0);
}

refresh_pl:value // Length
{
    nLength = clip(0.0,9999999999,value);
    setvalue(ctrl_pl,nLength);
}

refresh_p1:value // Sample 1
{
    aSample[1][3] = clip(0.0,9999999999,value);
    setvalue(ctrl_p1,aSample[1][3]);
    requpdate(ctrl_p0); // Update
}

refresh_p1r:value // Sample 1
{
    setvalue(ctrl_p1,clip(0.0,9999999999,aSample[1][3] + value *.001));
    setvalue(ctrl_p1r,0);
}

refresh_p1o:value // Sample 1
{
    aSample[1][2] = clip(-999.0,aSample[2][2] - 0.001,aSample[1][2] + value *.001);    
    setvalue(ctrl_p1o,0);   
    requpdate(ctrl_p0); // Update
}

refresh_p1e:value // Sample 1 
{
    aSample[1][1] = value;
    requpdate(ctrl_p0); // Update
}

refresh_p2:value // Sample 2
{
    aSample[2][3] = clip(0.0,9999999999,value);
    setvalue(ctrl_p2,aSample[2][3]);
    requpdate(ctrl_p0); // Update
}

refresh_p2r:value // Sample 2
{
    setvalue(ctrl_p2,clip(0.0,9999999999,aSample[2][3] + value *.001));
    setvalue(ctrl_p2r,0);
}

refresh_p2o:value // Sample 2
{
    aSample[2][2] = clip(aSample[1][2] + 0.001,aSample[3][2] - 0.001,aSample[2][2] + value *.001);    
    setvalue(ctrl_p2o,0);   
    requpdate(ctrl_p0); // Update
}

refresh_p2e:value // Sample 2
{
    aSample[2][1] = value;
    requpdate(ctrl_p0); // Update
}

refresh_p3:value // Sample 3
{
    aSample[3][3] = clip(0.0,9999999999,value);
    setvalue(ctrl_p3,aSample[3][3]);
    requpdate(ctrl_p0); // Update
}

refresh_p3r:value // Sample 3
{
    setvalue(ctrl_p3,clip(0.0,9999999999,aSample[3][3] + value *.001));
    setvalue(ctrl_p3r,0);
}

refresh_p3o:value // Sample 3
{
    aSample[3][2] = clip(aSample[2][2] + 0.001,aSample[4][2] - 0.001,aSample[3][2] + value *.001);    
    setvalue(ctrl_p3o,0);   
    requpdate(ctrl_p0); // Update
}

refresh_p3e:value // Sample 3
{
    aSample[3][1] = value;
    requpdate(ctrl_p0); // Update
}

refresh_p4:value // Sample 4
{
    aSample[4][3] = clip(0.0,9999999999,value);
    setvalue(ctrl_p4,aSample[4][3]);
    requpdate(ctrl_p0); // Update
}

refresh_p4r:value // Sample 4
{
    setvalue(ctrl_p4,clip(0.0,9999999999,aSample[4][3] + value *.001));
    setvalue(ctrl_p4r,0);
}

refresh_p4o:value // Sample 4
{
    aSample[4][2] = clip(aSample[3][2] + 0.001,aSample[5][2] - 0.001,aSample[4][2] + value *.001);    
    setvalue(ctrl_p4o,0);   
    requpdate(ctrl_p0); // Update
}

refresh_p4e:value // Sample 4
{
    aSample[4][1] = value;
    requpdate(ctrl_p0); // Update
}

refresh_p5:value // Sample 5
{
    aSample[5][3] = clip(0.0,9999999999,value);
    setvalue(ctrl_p5,aSample[5][3]);
    requpdate(ctrl_p0); // Update
}

refresh_p5r:value // Sample 5
{
    setvalue(ctrl_p5,clip(0.0,9999999999,aSample[5][3] + value *.001));
    setvalue(ctrl_p5r,0);
}

refresh_p5o:value // Sample 5
{
    aSample[5][2] = clip(aSample[4][2] + 0.001,aSample[6][2] - 0.001,aSample[5][2] + value *.001);    
    setvalue(ctrl_p5o,0);   
    requpdate(ctrl_p0); // Update
}

refresh_p5e:value // Sample 5
{
    aSample[5][1] = value;
    requpdate(ctrl_p0); // Update
}

refresh_p6:value // Sample 6
{
    aSample[6][3] = clip(0.0,9999999999,value);
    setvalue(ctrl_p6,aSample[6][3]);
    requpdate(ctrl_p0); // Update
}

refresh_p6r:value // Sample 6
{
    setvalue(ctrl_p6,clip(0.0,9999999999,aSample[6][3] + value *.001));
    setvalue(ctrl_p6r,0);
}

refresh_p6o:value // Sample 6
{
    aSample[6][2] = clip(aSample[5][2] + 0.001,aSample[7][2] - 0.001,aSample[6][2] + value *.001);    
    setvalue(ctrl_p6o,0);   
    requpdate(ctrl_p0); // Update
}

refresh_p6e:value // Sample 6
{
    aSample[6][1] = value;
    requpdate(ctrl_p0); // Update
}

refresh_p7:value // Sample 7
{
    aSample[7][3] = clip(0.0,9999999999,value);
    setvalue(ctrl_p7,aSample[7][3]);
    requpdate(ctrl_p0); // Update
}

refresh_p7r:value // Sample 7
{
    setvalue(ctrl_p7,clip(0.0,9999999999,aSample[7][3] + value *.001));
    setvalue(ctrl_p7r,0);
}

refresh_p7o:value // Sample 7
{
    aSample[7][2] = clip(aSample[6][2] + 0.001,aSample[8][2] - 0.001,aSample[7][2] + value *.001);    
    setvalue(ctrl_p7o,0);   
    requpdate(ctrl_p0); // Update
}

refresh_p7e:value // Sample 7
{
    aSample[7][1] = value;
    requpdate(ctrl_p0); // Update
}

refresh_p8:value // Sample 8
{
    aSample[8][3] = clip(0.0,9999999999,value);
    setvalue(ctrl_p8,aSample[8][3]);
    requpdate(ctrl_p0); // Update
}

refresh_p8r:value // Sample 8
{
    setvalue(ctrl_p8,clip(0.0,9999999999,aSample[8][3] + value *.001));
    setvalue(ctrl_p8r,0);
}

refresh_p8o:value // Sample 8
{
    aSample[8][2] = clip(aSample[7][2] + 0.001,aSample[9][2] - 0.001,aSample[8][2] + value *.001);    
    setvalue(ctrl_p8o,0);   
    requpdate(ctrl_p0); // Update
}

refresh_p8e:value // Sample 8
{
    aSample[8][1] = value;
    requpdate(ctrl_p0); // Update
}

refresh_p9:value // Sample 9
{
    aSample[9][3] = clip(0.0,9999999999,value);
    setvalue(ctrl_p9,aSample[9][3]);
    requpdate(ctrl_p0); // Update
}

refresh_p9r:value // Sample 9
{
    setvalue(ctrl_p9,clip(0.0,9999999999,aSample[9][3] + value *.001));
    setvalue(ctrl_p9r,0);
}

refresh_p9o:value // Sample 9
{
    aSample[9][2] = clip(aSample[8][2] + 0.001,999.0,aSample[9][2] + value *.001);    
    setvalue(ctrl_p9o,0);   
    requpdate(ctrl_p0); // Update
}

refresh_p9e:value // Sample 9
{
    aSample[9][1] = value;
    requpdate(ctrl_p0); // Update
}

refresh_d0:value // Accelerator
{
    if(bRequesterUpdate) return; // Requester Update
    iKey = envAccelerator.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envAccelerator.createKey(Scene.currenttime,value);}
    else
        {envAccelerator.setKeyValue(iKey,value);}
}

refresh_d0i:value // Accelerator Invert
{
    bAcceleratorInvert = value; // Accelerator Invert
}

button_d0e // Accelerator
{
    envAccelerator.edit();
}

refresh_d1:value // Brake
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d1,clip(0.0,1.0,value));
    iKey = envBrake.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envBrake.createKey(Scene.currenttime,clip(0.0,1.0,value));}
    else
        {envBrake.setKeyValue(iKey,clip(0.0,1.0,value));}
}

button_d1e // Brake
{
    envBrake.edit();
}

refresh_d2:value // Damping
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d2,clip(0.0,1.0,value));
    iKey = envDamping.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envDamping.createKey(Scene.currenttime,clip(0.0,1.0,value));}
    else
        {envDamping.setKeyValue(iKey,clip(0.0,1.0,value));}
}

button_d2e // Damping
{
    envDamping.edit();
}

refresh_d3:value // Friction
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d3,clip(0.0,1.0,value));
    iKey = envFriction.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envFriction.createKey(Scene.currenttime,clip(0.0,1.0,value));}
    else
        {envFriction.setKeyValue(iKey,clip(0.0,1.0,value));}
}

button_d3e // Friction
{
    envFriction.edit();
}

refresh_d4:value // Roughness
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d4,clip(0.0,1.0,value));
    iKey = envRoughness.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envRoughness.createKey(Scene.currenttime,clip(0.0,1.0,value));}
    else
        {envRoughness.setKeyValue(iKey,clip(0.0,1.0,value));}
}

button_d4e // Roughness
{
    envRoughness.edit();
}

refresh_d5:value
{
  if(value == 1) // Rubber - Tarmac
    {
    setvalue(ctrl_d2,0.025); // Damping
    setvalue(ctrl_d3,0.975); // Friction    
    setvalue(ctrl_d4,0.2); // Roughness    
    }

  if(value == 2) // Rubber - Gravel
    {
    setvalue(ctrl_d2,0.025); // Damping
    setvalue(ctrl_d3,0.9); // Friction    
    setvalue(ctrl_d4,0.5); // Roughness    
    }

  if(value == 3) // Rubber - Snow
    {
    setvalue(ctrl_d2,0.025); // Damping
    setvalue(ctrl_d3,0.75); // Friction    
    setvalue(ctrl_d4,0.1); // Roughness    
    }

  if(value == 4) // Rubber - Ice
    {
    setvalue(ctrl_d2,0.025); // Damping
    setvalue(ctrl_d3,0.5); // Friction    
    setvalue(ctrl_d4,0.1); // Roughness    
    }
 
  if(value == 5) // Rollerskate - Tarmac
    {
    setvalue(ctrl_d2,0.005); // Damping
    setvalue(ctrl_d3,0.975); // Friction    
    setvalue(ctrl_d4,0.2); // Roughness    
    }

  if(value == 6) // Plastic - Plastic
    {
    setvalue(ctrl_d2,0.05); // Damping
    setvalue(ctrl_d3,0.9); // Friction    
    setvalue(ctrl_d4,0.0); // Roughness    
    }
}

refresh_g1:value
{
    GroundItem = value;
}

refresh_mb: value // Motion Baker State
{
    if(bRequesterUpdate) return; // Requester Update
    iMotionBakerState = value;
}

button_mbe // Motion Baker Envelope
{
    envMotionBaker.edit();
}

refresh_mbl: value // Motion Baker Locked
{
    if(bRequesterUpdate) return; // Requester Update
    bMotionBakerLocked = value;
}

draw_profile
{
    // Background
    drawbox(<127,127,127>,0,0,298,100);

    // Border
    drawline(<0,0,0>,0,0,297,0);
    drawline(<0,0,0>,297,0,297,99);
    drawline(<0,0,0>,297,99,0,99);
    drawline(<0,0,0>,0,99,0,0);

    // Variables
    aXY = nil; // array of points enabled.
    nMinX = 0.0; // Min X
    nMaxX = 0.0; // Max X
    nMaxY = 0.0001; // Max Y

    // Min / Max
    for(s = 1; s <= 9; s++)
        {
        if(aSample[s][2] < nMinX) nMinX = aSample[s][2]; // Min X
        if(aSample[s][2] > nMaxX) nMaxX = aSample[s][2]; // Max X
        if(aSample[s][3] > nMaxY && aSample[s][1]) nMaxY = aSample[s][3]; // Max Y
        }

    nYMultiplier = 1 / nMaxY;

    // Draw 0
    iX = (277 * maprange01(nMinX,nMaxX,0.0)) + 10; // X    
    drawline(<117,117,117>,iX,30,iX,68);
    drawtext("- 0  0 +",<117,117,117>,iX - 15,43);   

    // Draw Samples (Disabled)
    for(s = 1; s <= 9; s++)
        {
        if(aSample[s][1] == false)
            {
            iX = (277 * maprange01(nMinX,nMaxX,aSample[s][2])) + 10; // X    
            iY = clip(10,88,(90 * (nYMultiplier * aSample[s][3])) + 10); // Y
            if(iY >= 20) drawline(<107,107,107>,iX,20,iX,iY);
            drawtext(string(s),<107,107,107>,iX - 3,3);
            }
        }

    // Draw Samples (Enabled)
    iC = 1; // Count
    for(s = 1; s <= 9; s++)
        {
        if(aSample[s][1])
            {
            iX = (277 * maprange01(nMinX,nMaxX,aSample[s][2])) + 10; // X    
            iY = clip(10,88,(90 * (nYMultiplier * aSample[s][3])) + 10); // Y
            if(iY >= 20) drawline(<255,255,255>,iX,20,iX,iY);
            drawtext(string(s),<255,255,255>,iX - 3,3);           
            // below used to draw profile preview
            aXY[iC] = ;            
            iC++;
            }
        }

    // Draw preview of enabled points
    if(iC > 2)
        {
        for(s = 2; s <= sizeof(aXY); s++)
            {
            drawline(<200,200,200>,aXY[s - 1].x,aXY[s - 1].y,aXY[s].x,aXY[s].y);
            }
        }
}

// GIZMO

gizmodraw: coa
{
    if(!reqisopen()) return;

    nTimeOffset = -0.0001;

    vWorldPosition = Item.getWorldPosition(Scene.currenttime + nTimeOffset); // World Position
    vScaling = Item.getScaling(Scene.currenttime + nTimeOffset); // Scaling
    vnRight = normalize3D(Item.getRight(Scene.currenttime + nTimeOffset));
    vnUp = normalize3D(Item.getUp(Scene.currenttime + nTimeOffset));
    vnForward = normalize3D(Item.getForward(Scene.currenttime + nTimeOffset));

    // Scaling
    if(vScaling.x != vScaling.y)
        {
        vScaling = <(vScaling.x + vScaling.y) * 0.5,(vScaling.x + vScaling.y) * 0.5,vScaling.z>;
        }

    // Draw Center
    coa.setColor(0.0,0.0,0.78,1.0);
    coa.setPattern("dash");
    coa.drawLine(vWorldPosition + (vnForward * (nLength * 0.5) * vScaling.z),vWorldPosition + (-vnForward * (nLength * 0.5) * vScaling.z),"world");

    // Draw Profile
    coa.setPattern("solid");
    for(s = 1; s <= 9; s++)
        {
        vCenter = vWorldPosition + (vnForward * aSample[s][2] * nLength * vScaling.z);
        if(aSample[s][1])
            {
            coa.setColor(1.0,1.0,1.0,1.0);
            coa.drawText(vCenter,s.asStr(),"world","center"); // Sample
            for(p = 1; p < 33; p++)
                {
                vA = (vnRight * aCirclePoints[p].x * aSample[s][3] * vScaling.x) +
                     (vnUp * aCirclePoints[p].y * aSample[s][3] * vScaling.y) +
                     (vnForward * aCirclePoints[p].z * aSample[s][3] * vScaling.z) + vCenter;
                vB = (vnRight * aCirclePoints[p + 1].x * aSample[s][3] * vScaling.x) +
                     (vnUp * aCirclePoints[p + 1].y * aSample[s][3] * vScaling.y) +
                     (vnForward * aCirclePoints[p + 1].z * aSample[s][3] * vScaling.z) + vCenter;
                coa.drawLine(vA,vB,"world");
                }  
            }
         else
            {
            coa.setColor(0.4,0.4,0.4,1.0);
            for(p = 1; p < 33; p++)
                {
                vA = (vnRight * aCirclePoints[p].x * aSample[s][3] * vScaling.x) +
                     (vnUp * aCirclePoints[p].y * aSample[s][3] * vScaling.y) +
                     (vnForward * aCirclePoints[p].z * aSample[s][3] * vScaling.z) + vCenter;
                vB = (vnRight * aCirclePoints[p + 1].x * aSample[s][3] * vScaling.x) +
                     (vnUp * aCirclePoints[p + 1].y * aSample[s][3] * vScaling.y) +
                     (vnForward * aCirclePoints[p + 1].z * aSample[s][3] * vScaling.z) + vCenter;
                coa.drawLine(vA,vB,"world");
                }  
            }
        }

}

gizmodown: te
{
}

gizmomove: te
{
}

gizmoup: te
{
}

gizmodirty
{
}


All scripts available at my Google Drive at
https://drive.google.com/open?id=1cR_q2GVUAJHumic1-A3eXV16acQnVTWs

1 comment: