Feedback

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

Wednesday, 24 September 2014

LScript - Custom_Shadow


LScript (Layout) adds a shadow visual aid to Layout. It is purely a visual cue for objects positioned or sitting on the ground plane.

There are three types available
  • Simple - Basic distance from ground plane
  • Simple (Projected) - Projected style shadow (experimental)
  • Object (Projected) - Projected shadow of object (experimental)
Changes
  • Error found by erikalst has now been fixed
  • Shadow will nolonger disappear if object drops below ground plane

Compatible with Newtek LightWave 9.6 and above.

// LScript Custom Object - www.StephenCulley.co.uk
//
// web   address: http://www.stephenculley.co.uk
// email address: email@stephenculley.co.uk

/*  
    LScript Custom Object - Shadow

    Custom_Shadow.ls

*/

@version 2.4
@warnings
@script custom
@name *Shadow

    // Title
    sTitle = "*Shadow";

    // Version
    sVersion = "v1.0";  

    // Items
    Item; // Item
    LightItem = nil; // Item
    LightItemName = "nil";

    bCreate; // Create
    iSteps = 32; // Circle detail
    sType = @"Simple","Simple (Projected)","Object (Projected)"@;
    iType = 1; // 1 = Simple, 2 = Complex
    nDegStep = 360 / iSteps; // Degree per step
    nOffset = 0.0001;
    nTime; // Time

    vColor = <75.0,75.0,75.0>; // Color;

create: ma
{
    bCreate = false; // Create

    setdesc(sTitle + " " + sVersion + " - " + sType[iType]);

    Item = ma; // Item
}

destroy
{
}

newtime: frame, time
{
    nTime = time;
}

init
{
}

process: ca
{
    // Create - invalid argument fix
    if(bCreate == false){bCreate = true; return;}

    // Item
    if(Item)
        {
        vnRight = normalize3D(Item.getRight(nTime));
        vnUp = normalize3D(Item.getUp(nTime));
        vnForward = normalize3D(Item.getForward(nTime));
        vWorldPosition = Item.getWorldPosition(nTime);
        vScaling = Item.getScaling(nTime);

        aBoundingBox = Item.position(getlayernumber(Item));

        nSize = ((aBoundingBox[2].x - aBoundingBox[1].x) +
                 (aBoundingBox[2].y - aBoundingBox[1].y) +
                 (aBoundingBox[2].z - aBoundingBox[1].z)) * vScaling * 0.333333; // Size of object averaged

        nHeight = (aBoundingBox[2].y - aBoundingBox[1].y) * vScaling.y;

        nBottom = min(vWorldPosition.y - (aBoundingBox[2].y * vScaling.y),vWorldPosition.y - (aBoundingBox[1].y * vScaling.y));
        }
    else
        {
        vnRight = <0.0,0.0,0.0>;
        vnUp = <0.0,0.0,0.0>;
        vnForward = <0.0,0.0,0.0>;
        vWorldPosition = <0.0,0.0,0.0>;
        vScaling = <0.0,0.0,0.0>;
        nHeight = 0.0;
        nSize = 0.0000001;
        }


    // Light Item
    if(LightItemName != "nil") {LightItem = Light(LightItemName); LightItemName = "nil";}
    if(LightItem)
        {
        vLight = LightItem.getWorldPosition(nTime);
        }
    else
        {
        TempItem = Light(1);
        vLight = TempItem.getWorldPosition(nTime);
        }
    if(vLight.y < 0.0) return; // Break

// Simple

    if(iType == 1) // Type 1 = Simple
      {
      nRadius = (nSize * 0.5) * (1 - ((1 / nSize * 0.5) * max(0.0,(vWorldPosition.y - (nHeight * 0.5)) )));
      if(nRadius < 0.0) return; // Check

      // Calculate circle
      nDegree = 0;
      iPoints = 1;

      for(d = 0; d <= iSteps; d++)
        {
        vPoint = <(cos(DEGtoRAD(nDegree)) * nRadius),nOffset,(sin(DEGtoRAD(nDegree)) * nRadius)>;
        aPoints[iPoints] = vPoint + ; // Store vertex
        iPoints++; // Count
        nDegree += nDegStep; // Add step
        }

      aPoints[iPoints] = aPoints[1]; // Add copy of first point

      // Draw
      ca.setColor(vColor * (1/255)); // Set color
      for(p = 1; p < iPoints; p++)
        {
        ca.drawTriangle(,aPoints[p],aPoints[p + 1],SYS_WORLD);
        }  

      }

// Simple (Projected)

    if(iType == 2)
      {
      // Circle center
      aIntersect = lineplaneintersect3D(vLight,normalize3D(vWorldPosition - vLight),<0.0,0.0,0.0>,<0.0,1.0,0.0>);
      if(aIntersect[1] == false) return; // Break No shadow
      vCenter = aIntersect[3] + <0.0,nOffset,0.0>;

      aOrthonormal[1] = normalize3D(vLight - vWorldPosition);
      vPoint = pointplane3D(vWorldPosition + <1.0,0.0,1.0>,vWorldPosition,normalize3D(vWorldPosition - vLight));
      aOrthonormal[2] = normalize3D(vPoint - vWorldPosition);       
      aOrthonormal[3] = normalize3D(crossproduct3D(aOrthonormal[2],aOrthonormal[1]));

      nRadius = nSize * 0.5; // Radius
      nDegree = 0;
      iPoints = 1;

      for(d = 0; d <= iSteps; d++)
        {
        vPoint = (aOrthonormal[2] * (cos(DEGtoRAD(nDegree)) * nRadius)) + 
                 (aOrthonormal[3] * (sin(DEGtoRAD(nDegree)) * nRadius)) + vWorldPosition;
        aIntersect = lineplaneintersect3D(vLight,normalize3D(vPoint - vLight),<0.0,0.0,0.0>,<0.0,1.0,0.0>);
        aPoints[iPoints] = aIntersect[3] + <0.0,nOffset,0.0>; // Store vertex
        iPoints++; // Count
        nDegree += nDegStep; // Add step
        }

      aPoints[iPoints] = aPoints[1]; // Add copy of first point

      // Draw
      ca.setColor(vColor * (1/255)); // Set color
      for(p = 1; p < iPoints; p++)
        {
        ca.drawTriangle(vCenter,aPoints[p],aPoints[p + 1],SYS_WORLD);
        }  
      }

// Object (Projected)

    if(iType == 3)
      {
      if(Item == nil){return;}

      for(p = 1; p <= Item.polygonCount(); p++)
        {
        aPoints = nil;
        for(v = 1; v <= Item.vertexCount(Item.polygons[p]); v++)
          {
          vPoint = Item.position(Item.vertex(Item.polygons[p],v));
          vPoint = (vnRight * vPoint.x * vScaling.x) +
                   (vnUp * vPoint.y * vScaling.y) +
                   (vnForward * vPoint.z * vScaling.z) + vWorldPosition;

          aIntersect = lineplaneintersect3D(vLight,normalize3D(vPoint - vLight),<0.0,0.0,0.0>,<0.0,1.0,0.0>);
          aPoints[v] = aIntersect[3]; 
          }

        // Draw
        ca.setColor(vColor * (1/255)); // Set color
        if(Item.vertexCount(Item.polygons[p]) == 3)
          {
          ca.drawTriangle(aPoints[1],aPoints[2],aPoints[3],SYS_WORLD);             
          }
        else
          {
          ca.drawTriangle(aPoints[1],aPoints[2],aPoints[3],SYS_WORLD);             
          ca.drawTriangle(aPoints[1],aPoints[3],aPoints[4],SYS_WORLD);             
          }
      
        }
      }

}

// CONVERSIONS

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

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

// 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);
}

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);
}

orthonormal3D: vn // a[1] vn (vector normal)
                  // a[2] vn (vector normal)
                  // a[3] vn (vector normal)
{
    // Orthonormal Vectors from Vector Normal
    a[1] = vn;
    if(vn.x |= vn.y)
        {
        nMagnitude = 1 / sqrt(vn.x * vn.x + vn.z * vn.z);
        a[2] = <-vn.z * nMagnitude,0.0,vn.x * nMagnitude>;
       }
    else
        {
        nMagnitude = 1 / sqrt(vn.y * vn.y + vn.z * vn.z);
        a[2] = <0.0,vn.z * nMagnitude,-vn.y * nMagnitude>;
        }
    a[3] = crossproduct3D(a[1],a[2]);
    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);
}

sqrlinemagnitude3d: v1,v2 // n
{
    // Vector square magnitude of v1 to v2
    return((v2.x - v1.x) * (v2.x - v1.x) + (v2.y - v1.y) * (v2.y - v1.y));
}

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: what,io
{
    if(what == SCENEMODE)   // processing an ASCII scene file
    {
        iType = io.read().asInt();
        vColor = io.read().asVec();
        nOffset = io.read().asNum();
        LightItemName = io.read().asStr();

        setdesc(sTitle + " " + sVersion + " - " + sType[iType]);
    }
}

save: what,io
{
    if(what == SCENEMODE)
    {
        io.writeln(iType);
        io.writeln(vColor);
        io.writeln(nOffset);

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

options
{
    reqbegin(sTitle + " " + sVersion);

    ctrl_c0 = ctlchoice("Type",iType,@"Simple","Simple (Projected)","Object (Projected)"@);
    ctrl_c1 = ctlcolor("Color",vColor);
    ctrl_c2 = ctlnumber("Offset",nOffset);

    ctlsep();
    ctrl_l0 = ctllightitems("Light Item",LightItem);

    // Developer
    ctlsep();
    ctrl_dev0 = ctltext("","developer: Stephen Culley","http://www.stephenculley.co.uk");

    return if !reqpost();

    iType = getvalue(ctrl_c0);
    vColor = getvalue(ctrl_c1);
    nOffset = getvalue(ctrl_c2);
    LightItem = getvalue(ctrl_l0);

        setdesc("*Shadow - " + sType[iType]);

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

LScript - Channel_VelocityNoise

LScript (Layout) to add noise to a channel based on the velocity of an item.

Compatible with Newtek LightWave 9.6 and above.

// LScript Channel Filter - www.StephenCulley.co.uk
//
// web   address: http://www.stephenculley.co.uk
// email address: email@stephenculley.co.uk

/* 
    LScript Channel Filter - Velocity Noise

    Channel_VelocityNoise.ls

*/

@version 2.2
@warnings
@script channel
@name *Velocity Noise

    // Title
    sTitle = "*Velocity Noise";

    // Version
    sVersion = "v1.0";

    // Item
    Item = nil;
    ItemName = "nil";

    iSeed = 1; // Seed
    iType = 1; // Type noise values.. -1.0..1.0 0.0..1.0 -1.0..0.0
    nScale0 = 0.0; // Scale
    nScale1 = 1.0; // Scale
    nSpeed0 = 100.0; // Speed
    nSpeed1 = 10.0; // Speed
    vOffset; // Offset

    // Controls
    ctrl_c0,ctrl_c1,ctrl_c2,ctrl_c3,ctrl_c4,ctrl_c5,ctrl_c6;

create: channel
{
    setdesc(sTitle);

    vOffset = ;
}

destroy
{
    // take care of final clean-up activities here
}

process: ca, frame, time
{
    nValue = ca.get(time);

    if(ItemName != "nil") {Item = Mesh(ItemName); ItemName = "nil";}
    if(Item)
      {
      // Velocity
      nVelocity = distance3D(Item.getWorldPosition(time - (1 / Scene().fps)),Item.getWorldPosition(time + (1 / Scene().fps)));

      // Scale
      nScale = linear1D(nScale0,nScale1,nVelocity);

      // Speed
      nSpeed = linear1D(nSpeed0,nSpeed1,nVelocity);

      // Add Noise
      nValue += (cosinenoise3D(<0.0,0.0,time * nSpeed> + vOffset) * nScale);

      // ca    
      ca.set(nValue);
      }
}

// CLIP

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

// INTERPOLATION

linear1D: n1,n2,i // i = interpolation point (0-1)
{
    return(n1 * (1 - i) + n2 * i);     
}

cosine1D: n1,n2,i // i = interpolation point (0-1)
{
    i2 = (1 - cos(i * 3.1415926535)) * 0.5;
    return(n1 * (1 - i2) + n2 * i2);     
}

// NOISE

_noiseseed = 1;
_noiseresolution = 256;
_noise = nil;
_noiseoffset = nil;
_noiseperm = nil;

noiseinit
{  
    randomseed(_noiseseed); // Random Seed

    if(iType == 3)  // -1.0..0.0
      {      
      for(iN = 1; iN <= _noiseresolution; iN++)
        {
        _noise[iN] = random() - 1.0;
        _noiseperm[iN] = wrap(1,_noiseresolution,floor(random() * _noiseresolution));
        }
      }
    else if(iType == 2)  // 0.0..1.0
      {      
      for(iN = 1; iN <= _noiseresolution; iN++)
        {
        _noise[iN] = random();
        _noiseperm[iN] = wrap(1,_noiseresolution,floor(random() * _noiseresolution));
        }
      }
    else  // -1.0..1.0
      {      
      for(iN = 1; iN <= _noiseresolution; iN++)
        {
        _noise[iN] = (random() * 2) - 1.0;
        _noiseperm[iN] = wrap(1,_noiseresolution,floor(random() * _noiseresolution));
        }
      }

    for(iO = 1; iO <= 64; iO++)
      {
      _noiseoffset[iO] = ;
      }
}

noiseseed: seed
{
    _noiseseed = seed;
    noiseinit(); // Init
}

noiseresolution: resolution
{
    _noiseresolution = resolution;
    noiseinit(); // Init 
}

noiseperm1D: n // i
{
    if(_noiseperm == nil){noiseinit();} // Init
    return(_noiseperm[wrap(1,_noiseresolution,floor(n))]);
}

noiseperm2D: v // n
{
    return(noiseperm1D(v.y + noiseperm1D(v.x)));
}

noiseperm3D: v // n
{
    return(noiseperm1D(v.z + noiseperm2D(v)));
}

noise1D: n // n
{
    if(_noise == nil){noiseinit();} // Init
    return(_noise[noiseperm1D(n)]);
}

noise2D: v // n
{
    if(_noise == nil){noiseinit();} // Init
    return(_noise[noiseperm2D(v)]);
}

noise3D: v // n
{
    if(_noise == nil){noiseinit();} // Init
    return(_noise[noiseperm3D(v)]);
}

cosinenoise3D: v // n
{
    v = wrap(1,_noiseresolution,v); 
    vFrac = frac(v);
    vInt = v - vFrac;
    nA1 = noise3D();
    nA2 = noise3D();
    nA3 = noise3D();
    nA4 = noise3D();
    nA = cosine1D(cosine1D(nA1,nA2,vFrac.x),cosine1D(nA3,nA4,vFrac.x),vFrac.z);
    nB1 = noise3D();
    nB2 = noise3D();
    nB3 = noise3D();
    nB4 = noise3D();
    nB = cosine1D(cosine1D(nB1,nB2,vFrac.x),cosine1D(nB3,nB4,vFrac.x),vFrac.z);
    return(cosine1D(nA,nB,vFrac.y));
}

// RANDOM

_randomseed = 0; // n Seed

randomseed: seed
{
    _randomseed = seed;
}

random
{
    n = (_randomseed * 214013 + 2531011) % 2^^24;
    _randomseed = n;
    n /= 2^^24; // 0..1
    return(n);
}

// WRAP

wrap: min,max,n
{
    n = n - floor((n - min) / (max - min)) * (max - min);
    if(n < 0) n = n + max - min; // error check
    return(n);
}

// VECTOR 3D

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))));
}

load: what,io
{
    if(what == SCENEMODE)   // processing an ASCII scene file
    {
        ItemName = io.read().asStr();
        iSeed = io.read().asInt();
        iType = io.read().asInt();
        nScale0 = io.read().asNum();
        nScale1 = io.read().asNum();
        nSpeed0 = io.read().asNum();
        nSpeed1 = io.read().asNum();

        noiseseed(iSeed); // Noise Seed 
    }
}

save: what,io
{
    if(what == SCENEMODE)
    {
        if(Item != nil)
            {
            io.writeln(string(Item.name));
            }
        else
            {
            io.writeln("nil");
            } 

        io.writeln(iSeed);
        io.writeln(iType);
        io.writeln(nScale0);
        io.writeln(nScale1);
        io.writeln(nSpeed0);
        io.writeln(nSpeed1);
    }
}

options
{

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

    reqbegin(sTitle + " " + sVersion);

    ctrl_c0 = ctlallitems("Item",Item);
    ctrl_c1 = ctlinteger("Seed",iSeed);
    ctrl_c2 = ctlchoice("Type",iType,@"+/-","+","-"@);
    ctrl_c3 = ctlpercent("Scale @ 0m/s",nScale0);
    ctrl_c4 = ctlpercent("Scale @ 1m/s",nScale1);
    ctrl_c5 = ctlpercent("Speed @ 0m/s",nSpeed0 * 0.01);
    ctrl_c6 = ctlpercent("Speed @ 1m/s",nSpeed1 * 0.01);

    // Developer
    ctlsep();
    ctrl_dev0 = ctltext("","developer: Stephen Culley","http://www.stephenculley.co.uk");

    // Refresh
    ctlrefresh(ctrl_c0,"refresh_c0"); // Item
    ctlrefresh(ctrl_c1,"refresh_c1"); // Seed
    ctlrefresh(ctrl_c2,"refresh_c2"); // Type
    ctlrefresh(ctrl_c3,"refresh_c3"); // Scale 0
    ctlrefresh(ctrl_c4,"refresh_c4"); // Scale 1
    ctlrefresh(ctrl_c5,"refresh_c5"); // Speed 0
    ctlrefresh(ctrl_c6,"refresh_c6"); // Speed 1

    reqopen();
}

refresh_c0:value // Item
{
    Item = value;
}

refresh_c1:value // Seed
{
    iSeed = clip(1,9999999999,value);
    setvalue(ctrl_c1,iSeed);
    noiseseed(iSeed); // Noise Seed
}

refresh_c2:value // Type
{
    iType = value;
    noiseseed(iSeed); // Noise Seed
}

refresh_c3:value // Scale 0
{
    nScale0 = value;
}

refresh_c4:value // Scale 1
{
    nScale1 = value;
}

refresh_c5:value // Speed 0
{
    nSpeed0 = value * 100.0;
}

refresh_c6:value // Speed 1
{
    nSpeed1 = value * 100.0;
}
All scripts available at my Google Drive at
https://drive.google.com/open?id=1cR_q2GVUAJHumic1-A3eXV16acQnVTWs

LScript - Motion_Slider


LScript (Layout) to make desk draws and filling cabinets move dynamically in relation to being in an earthquake, it ended up being one of my most used scripts, making objects slide from one place to another fixed along an axis such as a sliding mechanism inside of an office desk draw or the slider a keyboard rests upon. It reacts dynamically as its parents object moves and is baked to curves. Use Master_MotionBaker script to bake motions. *Master_MotionBaker compatible.

Changes

  • *Motion Baking implemented for quicker playback and network baking
  • Gizmo implemented to improve setting up
  • Added Lock / Unlock
  • Bake feature can now be used via Master_MotionBaker
  • Fixed Threshold bug
  • Fixed judder resulting from an equation flipping in unexpected circumstance
  • Replaced stabilizer with Damping

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 - Slider

    Motion_Slider.ls
      
*/

@version 2.2
@warnings
@script motion
@name *Slider

    // Title
    sTitle = "*Slider";

    // Version
    sVersion = "v1.0";

    // Item
    Item = nil;

    // Constant
    Scene; // Scene()

    // Variable
    aChannel = @"X","Y","Z"@;
    iChannel = 1; // Channel
    iIteration = 0; // Iteration
    vnGravity = <0.0,-1.0,0.0>; // Gravity

    // Envelope
    envDamping; // Damping
    envFriction; // Friction
    envMass; // Mass
    envMax; // Max
    envMin; // Min
    envMotionBaker; // Motion Baker
    envParentMotion; // Parent Motion
    envRandom; // Random
    envThreshold; // Threshold

    // Cache
    aCache;

        // [1] = i Frame
        // [2] = n time
        // [3] = n Position
        // [4] = n Momentum -
        // [5] = n Momentum +
        // [6] = v Parent World Position
        // [7] = n Parent Momentum +/-

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

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

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

    ctrl_d0,ctrl_d1,ctrl_d1e,ctrl_d2v,ctrl_d2,ctrl_d2e,ctrl_d3v,ctrl_d3,ctrl_d3e,ctrl_d4,ctrl_d4e,
    ctrl_d5,ctrl_d5e,ctrl_d6,ctrl_d6e,ctrl_d7,ctrl_d7e,ctrl_d8,ctrl_d8e,ctrl_d9,ctrl_d10,ctrl_d11; // Dynamics
    ctrl_mb,ctrl_mbe,ctrl_mbl; // Motion Baker

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

create: obj
{
    // Item
    Item = obj;

    // Constant
    Scene = Scene();

    // Description
    setdesc(sTitle + " - Channel " + aChannel[iChannel]);

    // Envelope
    envDamping = Envelope("Damping (" + sTitle + ")",CHAN_NUMBER,Item.name); // Damping
    envDamping.persist(false);
    envDamping.createKey(0,0.8);
    envFriction = Envelope("Friction (" + sTitle + ")",CHAN_NUMBER,Item.name); // Friction
    envFriction.persist(false);
    envFriction.createKey(0,0.25);
    envMass = Envelope("Mass (" + sTitle + ")",CHAN_NUMBER,Item.name); // Mass
    envMass.persist(false);
    envMass.createKey(0,0.05);
    envMax = Envelope("Max (" + sTitle + ")",CHAN_NUMBER,Item.name); // Max
    envMax.persist(false);
    envMax.createKey(0,1.0);    
    envMin = Envelope("Min (" + sTitle + ")",CHAN_NUMBER,Item.name); // Min
    envMin.persist(false);
    envMin.createKey(0,-1.0);
    envMotionBaker = Envelope("Motion Baker (" + sTitle + ")",CHAN_NUMBER,Item.name); // Motion Baker
    envMotionBaker.persist(false);
    envParentMotion = Envelope("Parent Motion (" + sTitle + ")",CHAN_NUMBER,Item.name); // Parent Motion
    envParentMotion.persist(false);
    envParentMotion.createKey(0,1.0);
    envRandom = Envelope("Random (" + sTitle + ")",CHAN_NUMBER,Item.name); // Random
    envRandom.persist(false);
    envRandom.createKey(0,0.0);
    envThreshold = Envelope("Threshold (" + sTitle + ")",CHAN_NUMBER,Item.name); // Threshold
    envThreshold.persist(false);
    envThreshold.createKey(0,9.8);

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

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

/*   
---------------------------------------------------------------------------------------------------
                                                                                           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
{
    if(iChannel == 1){envChannel = Envelope("Position.X",CHAN_NUMBER,Item.name);} // X 
    if(iChannel == 2){envChannel = Envelope("Position.Y",CHAN_NUMBER,Item.name);} // Y 
    if(iChannel == 3){envChannel = Envelope("Position.Z",CHAN_NUMBER,Item.name);} // Z 

    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));}
        else
            {envChannel.setKeyValue(iKey,envMotionBaker.value(time));}
        }  
      }

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

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

process: ma, frame, time
{

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

    // Parent Item
    ParentItem = Item.parent;

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

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

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

        setvalue(ctrl_d1,envFriction.value(Scene.currenttime)); // Friction
        setvalue(ctrl_d2,envMin.value(Scene.currenttime)); // Min
        setvalue(ctrl_d3,envMax.value(Scene.currenttime)); // Max
        setvalue(ctrl_d4,envMass.value(Scene.currenttime)); // Mass
        setvalue(ctrl_d5,envRandom.value(Scene.currenttime)); // Random
        setvalue(ctrl_d6,envDamping.value(Scene.currenttime)); // Damping
        setvalue(ctrl_d7,envParentMotion.value(Scene.currenttime)); // Parent Motion
        setvalue(ctrl_d8,envThreshold.value(Scene.currenttime)); // Threshold (m/s)

        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)
        {
        aCache[1] = frame; // Frame
        aCache[2] = time; // Time
        if(iChannel == 1) aCache[3] = clip(envMin.value(time),envMax.value(time),ma.get(POSITION,time).x); // X Channel
        if(iChannel == 2) aCache[3] = clip(envMin.value(time),envMax.value(time),ma.get(POSITION,time).y); // Y Channel
        if(iChannel == 3) aCache[3] = clip(envMin.value(time),envMax.value(time),ma.get(POSITION,time).z); // Z Channel
        aCache[4] = 0.0; // Momentum -
        aCache[5] = 0.0; // Momentum +
        if(ParentItem != nil){aCache[6] = ParentItem.getWorldPosition(time);} else {aCache[6] = <0.0,0.0,0.0>;} // Parent World Position
        aCache[7] = 0.0; // Parent Momentum

        // Motion Baker Write
        iKey = envMotionBaker.keyExists((1 / Scene.fps) * frame);
        if(iKey == nil)
            {envMotionBaker.createKey((1 / Scene.fps) * frame, aCache[3]);}
        else
            {envMotionBaker.setKeyValue(iKey,aCache[3]);}
    
        // 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
        {
        vPosition = ma.get(POSITION,time); // Position

        if(iChannel == 1) vPosition.x = aCache[3]; // X Channel
        if(iChannel == 2) vPosition.y = aCache[3]; // Y Channel
        if(iChannel == 3) vPosition.z = aCache[3]; // Z Channel

        ma.set(POSITION,vPosition);

        return;
        }  

    if(iMotionBakerState == 4) // Read
        {
        vPosition = ma.get(POSITION,time); // Position

        if(iChannel == 1) vPosition.x = envMotionBaker.value(time); // X Channel
        if(iChannel == 2) vPosition.y = envMotionBaker.value(time); // Y Channel
        if(iChannel == 3) vPosition.z = envMotionBaker.value(time); // Z Channel

        ma.set(POSITION,vPosition);

        return;
        }

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

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

    // Variable
    nDamping = 1.0 - clip(0.0,1.0,envDamping.value(time)); // Damping
    nMass = envMass.value(time); // Mass
    nMax = envMax.value(time);
    nMin = envMin.value(time);
    nPosition = 0.0; // Position
    nParentMotion = clip(0,1,envParentMotion.value(time)); // Parent Motion
    nRandom = 1.0 - (randu() * clip(0.0,1.0,envRandom.value(time))); // Random
    nThreshold = (1 / Scene().fps) * abs(envThreshold.value(time)); // Threshold
    vWorldPosition = ma.get(WPOSITION,time); // World Position
    vPosition = ma.get(POSITION,time); // Position
    vnRight = normalize3D(Item.getRight(time));
    vnUp = normalize3D(Item.getUp(time));
    vnForward = normalize3D(Item.getForward(time));

    // Angle Motion
    if(iChannel == 1) nAngleDot = dotproduct3D(normalize3D(vnGravity),vnRight); // X Channel
    if(iChannel == 2) nAngleDot = dotproduct3D(normalize3D(vnGravity),vnUp); // Y Channel
    if(iChannel == 3) nAngleDot = dotproduct3D(normalize3D(vnGravity),vnForward); // Z Channel

    nFriction = 1.0 - clip(0.0,1.0,envFriction.value(time)); // Friction
    if(nFriction < 1.0) nFriction = linear1D(nFriction,1.0,abs(pow(nAngleDot,3))); // Friction

    if(nAngleDot < 0.0)
        {
        // -
        nMomentumN = (nMass * nAngleDot * nRandom * nFriction) + aCache[4];
        nMomentumP = aCache[5] * nFriction * nDamping;
        }
    else
        {
        // +
        nMomentumP = (nMass * nAngleDot * nRandom * nFriction) + aCache[5];
        nMomentumN = aCache[4] * nFriction * nDamping;
        }

    // Parent Motion
    if(ParentItem != nil && nParentMotion > 0.0)
        {
        vParentWorldPosition = ParentItem.getWorldPosition(time);
        nParentMotionDistance = distance3D(aCache[6],vParentWorldPosition);
        vnParentMotionDirection = normalize3D(aCache[6] - vParentWorldPosition);
        if(iChannel == 1) nParentDot = dotproduct3D(vnParentMotionDirection,vnRight); // X Channel
        if(iChannel == 2) nParentDot = dotproduct3D(vnParentMotionDirection,vnUp); // Y Channel
        if(iChannel == 3) nParentDot = dotproduct3D(vnParentMotionDirection,vnForward); // Z Channel
        nParentMoment = nParentMotionDistance * nParentDot * nParentMotion;
        nParentMomentum = aCache[7] * nFriction;
        if(nParentDot > 0.0 && nParentMomentum < nParentMoment)
            {
            nParentMomentum = (nParentMotionDistance * (1 - nFriction) * nParentDot * nRandom) + (aCache[7] * nFriction);
            if(nParentMomentum > nParentMoment) nParentMomentum = nParentMoment;
            }
        else
        if(nParentDot < 0.0 && nParentMomentum > nParentMoment)
            {
            nParentMomentum = (nParentMotionDistance * (1 - nFriction) * nParentDot * nRandom) + (aCache[7] * nFriction);
            if(nParentMomentum < nParentMoment) nParentMomentum = nParentMoment;
            }
        nParentMomentum *= nParentMotion;
        }
    else
        {
        vParentWorldPosition = <0.0,0.0,0.0>; // Parent World Position
        nParentMoment = 0.0;            
        nParentMomentum = aCache[7] * nFriction;
        }

    // Threshold (m/s)
    if(nMomentumN < -nThreshold) nMomentumN = -nThreshold;
    if(nMomentumP > nThreshold) nMomentumP = nThreshold;
    if(nParentMomentum < -nThreshold) nParentMomentum = -nThreshold;
    if(nParentMomentum > nThreshold) nParentMomentum = nThreshold;

    // Position
    nPosition = aCache[3] + nMomentumP + nMomentumN - nParentMomentum + nParentMoment; // Compound Position

    if(nPosition <= nMin)
        {
        nPosition = nMin;
        nMomentumP = -nMomentumN * nDamping;
        nMomentumN = 0.0;
        }

    if(nPosition >= nMax)
        {
        nPosition = nMax;
        nMomentumN = -nMomentumP * nDamping;
        nMomentumP = 0.0;
        }

    if(iChannel == 1) vPosition.x = nPosition; // X Channel
    if(iChannel == 2) vPosition.y = nPosition; // Y Channel
    if(iChannel == 3) vPosition.z = nPosition; // Z Channel

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

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

    ma.set(POSITION,vPosition);

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

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

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

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

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

    aCache[1] = frame; // Frame
    aCache[2] = time; // Time
    aCache[3] = nPosition; // Position
    aCache[4] = nMomentumN; // Momentum -
    aCache[5] = nMomentumP; // Momentum +
    aCache[6] = vParentWorldPosition; // Parent World Position
    aCache[7] = nParentMomentum; // Parent Momentum

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

}

// CLIP

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

// MAP RANGE

maprange01: n1,n2,i

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

// INTERPOLATION

linear1D: n1,n2,i // i = interpolation point (0-1)
{
    return(n1 * (1 - i) + n2 * i);     
}

cosine1D: n1,n2,i // i = interpolation point (0-1)
{
    i2 = (1 - cos(i * 3.1415926535)) * 0.5;
    return(n1 * (1 - i2) + n2 * i2);     
}

bicubic1D: n1,n2,n3,n4,i // i = interpolation point (0-1)
{
    i2 = i * i;
    n_01 = n4 - n3 - n1 + n2;
    n_02 = n1 - n2 - n_01;
    n_03 = n3 - n1;
    n_04 = n2;
    return(n_01 * i * i2 + n_02 * i2 + n_03 * i + n_04);
}

// 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);
}

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

load: what,io
{
    if(what == SCENEMODE)   // processing an ASCII scene file
    {
        if(io.read().asStr() == sTitle + " " + sVersion)
            {
            iChannel = io.read().asInt(); // Channel
            vnGravity = ; // Gravity

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

            // Envelope
            envDamping.load(); // Damping
            envFriction.load(); // Friction
            envMass.load(); // Mass
            envMax.load(); // Max
            envMin.load(); // Min
            envMotionBaker.load(); // Motion Baker
            envParentMotion.load(); // Parent Motion
            envRandom.load(); // Random
            envThreshold.load(); // Threshold (m/s)

            // Description
            setdesc(sTitle + " - Channel " + aChannel[iChannel]);
            }
        else
            {
            info(sTitle + " - Error");
            }
    }
}

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

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

        io.writeln(iChannel); // Channel
        io.writeln(vnGravity.x); // Gravity X
        io.writeln(vnGravity.y); // Gravity Y
        io.writeln(vnGravity.z); // Gravity Z
       
        // Motion Baker
        io.writeln(bMotionBakerLocked); // Motion Baker Locked
        io.writeln(iMotionBakerState); // Motion Baker State

        // Envelope
        envDamping.save(); // Damping
        envFriction.save(); // Friction
        envMass.save(); // Mass
        envMax.save(); // Max
        envMin.save(); // Min
        envMotionBaker.save(); // Motion Baker
        envParentMotion.save(); // Parent Motion
        envRandom.save(); // Random
        envThreshold.save(); // Threshold (m/s)
    }
}

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

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

    // Variable
    iVar = 0;

    reqbegin(sTitle + " " + sVersion);
    reqsize(300,307);

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

    // Dynamics
    ctrl_d0 = ctlchoice("Channel",iChannel,@"X","Y","Z"@);
    ctrl_d1 = ctlpercent("Friction",envFriction.value(Scene.currenttime)); // Friction
    ctrl_d1e = ctlbutton("E",20,"button_d1e"); // Button
    ctrl_d2v = ctlminislider(" ",iVar,-100,100);
    ctrl_d2 = ctldistance("Min",envMin.value(Scene.currenttime)); // Min
    ctrl_d2e = ctlbutton("E",20,"button_d2e"); // Button
    ctrl_d3v = ctlminislider(" ",iVar,-100,100);
    ctrl_d3 = ctldistance("Max",envMax.value(Scene.currenttime)); // Max
    ctrl_d3e = ctlbutton("E",20,"button_d3e"); // Button
    ctrl_d4 = ctlnumber("Mass",envMass.value(Scene.currenttime)); // Mass
    ctrl_d4e = ctlbutton("E",20,"button_d4e"); // Button
    ctrl_d5 = ctlpercent("Random",envRandom.value(Scene.currenttime)); // Random
    ctrl_d5e = ctlbutton("E",20,"button_d5e"); // Button
    ctrl_d6 = ctlpercent("Damping",envDamping.value(Scene.currenttime)); // Damping
    ctrl_d6e = ctlbutton("E",20,"button_d6e"); // Button
    ctrl_d7 = ctlpercent("Parent Motion",envParentMotion.value(Scene.currenttime)); // Parent Motion
    ctrl_d7e = ctlbutton("E",20,"button_d7e"); // Button
    ctrl_d8 = ctldistance("Threshold (m/s)",envThreshold.value(Scene.currenttime)); // Threshold (m/s)
    ctrl_d8e = ctlbutton("E",20,"button_d8e"); // Button
    ctrl_d9 = ctlnumber("Gravity X",vnGravity.x); // Gravity X
    ctrl_d10 = ctlnumber("Y",vnGravity.y); // Gravity Y
    ctrl_d11 = ctlnumber("Z",vnGravity.z); // Gravity Z

    ctlposition(ctrl_d0,36,32,212,20,100); // Channel
    ctlposition(ctrl_d1,36,54,212,20,100); // Friction
    ctlposition(ctrl_d1e,272,54,20,20); // Friction Button
    ctlposition(ctrl_d2v,238,76,10,20); // Min
    ctlposition(ctrl_d2,36,76,212,20,100);
    ctlposition(ctrl_d2e,272,76,20,20); // Min Button
    ctlposition(ctrl_d3v,238,98,10,20); // Max
    ctlposition(ctrl_d3,36,98,212,20,100);
    ctlposition(ctrl_d3e,272,98,20,20); // Max Button
    ctlposition(ctrl_d4,36,120,212,20,100); // Mass
    ctlposition(ctrl_d4e,272,120,20,20); // Mass Button
    ctlposition(ctrl_d5,36,142,212,20,100); // Random
    ctlposition(ctrl_d5e,272,142,20,20); // Random Button
    ctlposition(ctrl_d6,36,164,212,20,100); // Damping
    ctlposition(ctrl_d6e,272,164,20,20); // Damping Button
    ctlposition(ctrl_d7,36,186,212,20,100); // Parent Motion
    ctlposition(ctrl_d7e,272,186,20,20); // Parent Motion Button
    ctlposition(ctrl_d8,36,208,212,20,100); // Threshold (m/s)
    ctlposition(ctrl_d8e,272,208,20,20); // Threshold Button
    ctlposition(ctrl_d9,36,230,212,20,100); // Gravity X
    ctlposition(ctrl_d10,36,252,212,20,100); // Gravity Y
    ctlposition(ctrl_d11,36,274,212,20,100); // GRavity Z

    ctlpage(1,ctrl_d0,ctrl_d1,ctrl_d1e,ctrl_d2v,ctrl_d2,ctrl_d2e,ctrl_d3v,ctrl_d3,ctrl_d3e,
              ctrl_d4,ctrl_d4e,ctrl_d5,ctrl_d5e,ctrl_d6,ctrl_d6e,ctrl_d7,ctrl_d7e,ctrl_d8,ctrl_d8e,ctrl_d9,ctrl_d10,ctrl_d11); // 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(2,ctrl_mb,ctrl_mbe,ctrl_mbl);

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

    // Refresh
    ctlrefresh(ctrl_d0,"refresh_d0"); // Channel
    ctlrefresh(ctrl_d1,"refresh_d1"); // Friction
    ctlrefresh(ctrl_d2v,"refresh_d2v"); // Min
    ctlrefresh(ctrl_d2,"refresh_d2");
    ctlrefresh(ctrl_d3v,"refresh_d3v"); // Max
    ctlrefresh(ctrl_d3,"refresh_d3");
    ctlrefresh(ctrl_d4,"refresh_d4"); // Mass
    ctlrefresh(ctrl_d5,"refresh_d5"); // Random
    ctlrefresh(ctrl_d6,"refresh_d6"); // Damping
    ctlrefresh(ctrl_d7,"refresh_d7"); // Parent Motion
    ctlrefresh(ctrl_d8,"refresh_d8"); // Threshold (m/s)
    ctlrefresh(ctrl_d9,"refresh_d9"); // Gravity X
    ctlrefresh(ctrl_d10,"refresh_d10"); // Gravity Y
    ctlrefresh(ctrl_d11,"refresh_d11"); // Gravity Z
    ctlrefresh(ctrl_mb,"refresh_mb"); // Motion Baker State
    ctlrefresh(ctrl_mbl,"refresh_mbl"); // Motion Baker Locked
    reqopen();
}

refresh_d0:value // Channel
{
    iChannel = value;
    setdesc(sTitle + " - Channel " + aChannel[iChannel]);
}

refresh_d1:value // Friction
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d1,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_d1e // Friction
{
    envFriction.edit();
}

refresh_d2v:value // Min
{
    setvalue(ctrl_d2,clip(-999.0,envMax.value(Scene.currenttime),envMin.value(Scene.currenttime) + value *.001));
    setvalue(ctrl_d2v,0);
}

refresh_d2:value // Min
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d2,clip(-9999999999.0,envMax.value(Scene.currenttime),value));
    iKey = envMin.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envMin.createKey(Scene.currenttime,clip(-9999999999.0,envMax.value(Scene.currenttime),value));}
    else
        {envMin.setKeyValue(iKey,clip(-9999999999.0,envMax.value(Scene.currenttime),value));}
}

button_d2e // Min
{
    envMin.edit();
}

refresh_d3v:value // Max
{
    setvalue(ctrl_d3,clip(envMin.value(Scene.currenttime),9999999999.0,envMax.value(Scene.currenttime) + value *.001));
    setvalue(ctrl_d3v,0);
}

refresh_d3:value // Max
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d3,clip(envMin.value(Scene.currenttime),9999999999.0,value));
    iKey = envMax.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envMax.createKey(Scene.currenttime,clip(envMin.value(Scene.currenttime),9999999999.0,value));}
    else
        {envMax.setKeyValue(iKey,clip(envMin.value(Scene.currenttime),9999999999.0,value));}
}

button_d3e // Max
{
    envMax.edit();
}

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

button_d4e // Mass
{
    envMass.edit();
}

refresh_d5:value // Random
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d5,clip(0.0,1.0,value));
    iKey = envRandom.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envRandom.createKey(Scene.currenttime,max(0.0,value));}
    else
        {envRandom.setKeyValue(iKey,clip(0.0,1.0,value));}
}

button_d5e // Random
{
    envRandom.edit();
}

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

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

refresh_d7:value // Parent Motion
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d7,clip(0.0,1.0,value));
    iKey = envParentMotion.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envParentMotion.createKey(Scene.currenttime,clip(0.0,1.0,value));}
    else
        {envParentMotion.setKeyValue(iKey,clip(0.0,1.0,value));}
}

button_d7e // Parent Motion
{
    envParentMotion.edit();
}

refresh_d8:value // Threshold (m/s)
{
    if(bRequesterUpdate) return; // Requester Update
    setvalue(ctrl_d8,max(0.0,value));
    iKey = envThreshold.keyExists(Scene.currenttime);
    if(iKey == nil)
        {envThreshold.createKey(Scene.currenttime,max(0.0,value));}
    else
        {envThreshold.setKeyValue(iKey,max(0.0,value));}
}

button_d8e // Threshold (m/s)
{
    envThreshold.edit();
}

refresh_d9:value // Gravity X
{
    vnGravity.x = value;
}

refresh_d10:value // Gravity Y
{
    vnGravity.y = value;
}

refresh_d11:value // Gravity Z
{
    vnGravity.z = 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;
}

/*   
---------------------------------------------------------------------------------------------------
                                                                                             GIZMO
---------------------------------------------------------------------------------------------------
*/

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

    nTimeOffset = -0.0001;

    vPosition = Item.getPosition(Scene.currenttime + nTimeOffset); // Position
    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));
    nMin = envMin.value(Scene.currenttime + nTimeOffset);
    nMax = envMax.value(Scene.currenttime + nTimeOffset);

    if(iChannel == 1) // X
        {
        nMin *= vScaling.x;
        nMax *= vScaling.x;
        nStabilizer *= vScaling.x;    
        vMin = vWorldPosition + (vnRight * (nMin - vPosition.x));
        vMax = vWorldPosition + (vnRight * (nMax - vPosition.x));
        coa.setColor(0.78,0.0,0.0,1.0);
        coa.setPattern("dash");
        coa.drawLine(vMin,vMax,"world");     
        }

    if(iChannel == 2) // Y
        {
        nMin *= vScaling.y;
        nMax *= vScaling.y;
        vMin = vWorldPosition + (vnUp * (nMin - vPosition.y));
        vMax = vWorldPosition + (vnUp * (nMax - vPosition.y));
        coa.setColor(0.0,0.78,0.0,1.0);
        coa.setPattern("dash");
        coa.drawLine(vMin,vMax,"world");     
        }

    if(iChannel == 3) // Z
        {
        nMin *= vScaling.z;
        nMax *= vScaling.z;
        vMin = vWorldPosition + (vnForward * (nMin - vPosition.z));
        vMax = vWorldPosition + (vnForward * (nMax - vPosition.z));
        coa.setColor(0.0,0.0,0.78,1.0);
        coa.setPattern("dash");
        coa.drawLine(vMin,vMax,"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

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