Bouncing Ball Script (Transform Channels)

From The Foundry MODO SDK wiki
Jump to: navigation, search

modo takes a somewhat unique approach to item transforms. Rather than include the transforms as part of the item itself, the item is linked to secondary transform-specific items. This article explains how transform channels work and demonstrates them with script that creates a bouncing ball.

Transform Channels

Every item in modo has channels, and these are the primary manner in which item state is exposed for editing. Some channels are hidden from the user, or represent [Common Datatypes#Complex Datatypes|complex datatypes]] such as mesh stacks and item links. In general, scripts deal only with simple datatypes, notably numeric and string channels.

As described previously, most item channels can be walked through the Sceneservice ScriptQuery interface, and can be read or set using the Item.channel command. Transform channels, however, are a little different.

Transform Items

Locator item types (which includes Mesh, Camera, Light and so on) do not directly have their own position, rotation and scale channels. Instead, they have links to transform items that handle these properties. This allows multiple items to share the same transform items, and to save memory by not creating the transform items and associated channels when they aren’t required.

This unique system complicates how the position, rotation and scale of an item are read. You can’t directly ask the item for its channels, since they aren't on that item. Instead, you must ask the item for its transform item, and then query that item's channels. The sceneservice ScriptQuery interface provides a series of item.xfrm??? attributes to simplify getting the IDs of the transform items.

Attribute Transform
item.xfrmItems (all)
item.xfrmPos Position
item.xfrmRot Rotation
item.xfrmScl Scale
item.xfrmPiv Pivot
item.xfrmPivC Pivot Compensation

These each require an item ID as a selector, which can be obtained via the item.id attribute of sceneservice. The item.xfrmItems attribute returns a list of all transform items currently used by the selector item. For example, a mesh item might return the following:

scale021
rotation022
translation023

You can also directly request a transform item by using one of the other item.xfrm??? attributes. These simply return the item ID string, or an empty string if there is no linked transform item.

As you can see from the table above, there are currently five types of transform items. The position, rotation and scale transforms should be obvious. Pivot defines a "pivot point", which defines the position at which the rotation transform is centered. Pivot compensation is the inverse of the pivot transform; when you move the pivot of an item that has been scaled or rotated, a compensation transform is calculated to ensure that the item remains in the same wold position.

Creating Transform Items

If the transform you want to manipulate does not yet have a matching item, you need to create it first. This example shows how to check for the pivot transform item, and if it is not found, create it through the transform.add command.

 my $itemID = myItemID;
 my $pivotID = lxq( "query sceneservice item.xfrmPiv ? $itemID" );
 lxout( "pivot = $pivotID" );
 if( $pivotID eq "" ) {
     # Pivot item does not exist; create it
     lx( "transform.add type:piv" );
     $pivotID = lxq ("query sceneservice item.xfrmPiv ? $itemID" );
 }

 itemID = myItemID
 pivotID = lxq( "query sceneservice item.xfrmPiv ? " .. itemID )[1]
 lxout( "pivot = " .. pivotID );
 if pivotID == "" then
     -- Pivot item does not exist; create it
     lx( "transform.add type:piv" )
     pivotID = lxq( "query sceneservice item.xfrmPiv ? " .. iitemID )[1]
 end

 itemID = myItemID
 pivotID = lx.eval1( "query sceneservice item.xfrmPiv ? " + itemID )
 lx.out( "pivot = ", itemID )
 if pivotID == "" :
     # Pivot item does not exist; create it
     lx.command( "transform.add" type="piv" )
     pivotID = lx.eval1 ("query sceneservice item.xfrmPiv ? " + itemID )

Once you have the item ID, you can query it as normal through the item.channel command or through the sceneservice interface. Since item.channel works on the current item selection, you need to select the transform item first with select.item, or you can pass it as the item argument. This example sets the X position of the pivot item to 1.3 meters.

 lx( "item.channel pos.X 1.3 {$pivotID}" );

 lx( "item.channel pos.X 1.3 " ..pivotID )

 lx.command( "item.channel", channel="pos.X", value="1.3", item=pivotID )

Bouncing Ball Example

This example perl script by Arnie Cachelin creates a simple bouncing ball style animation by creating keyframes on the position transform item of the currently selected item.

 # perl -- Bouncer.pl
 # Arnie Cachelin, Luxology LLC, Copyright 2007-2008
 # A Simple Dynamics Script -- Physics 101 equations of
 # motion in one dimension
 
 # This version specializes in the trajectory of a dropped or
 # launched object which bounces.
 # To use this, select the object which is already at the time
 # and place at its motion should start.
 
 
 # User Input animation parameters
 my $v0=0;           # initial speed (m/s)
 my $time = 11.0;    # length of simulation (in seconds)
 my $ymin = 0.0;     # ground/wall position, where bounces would occur (m)
 my $elastic = 0.8;  # elasticity: percent energy kept per bounce
 my $fstep = 0.2;    # number of frames between keys
 
 # physical constants
 my $g=9.8;          # gravity on earth = 9.8 m/s^2 DOWN
 my $a=-$g;          # accleration (m/s^2) negative, since UP is positive
 
 #lxtrace(1);         # Use this for debugging
 
 # get selected locator-type item
 my $item = lxq( "query sceneservice selection ? locator" );
 
 # get its position transform item
 my $xfrm = lxq ("query sceneservice item.xfrmPos ? $item");
 
 if ($xfrm eq "") {
     # Position transform item does not exist; create it
     lx ("transform.add type:pos");
     $xfrm = lxq ("query sceneservice item.xfrmPiv ? $item");
 }
 
 # select the item in sceneservice for subsequent channel queries
 my $xnm = lxq ("query sceneservice item.name ? $xfrm");
 
 my $chan = lxq ("query sceneservice channel.index ?  pos.Y");.  
 my $tf = lxq ("time.range scene out:?");
 $ti = lxq ("select.time ?");     # starting time: Now!
 my $time = $tf - $ti;            # final time
 my $val = lxq ("query sceneservice channel.value ? $chan");
 my $fps = lxq ("time.fpsCustom ?");
 my $dt = $fstep / $fps;          # add keys every other frame
 my $nf = $time * $fps / $fstep;  # number of frames
 
 my $y0 = $val - $ymin;           # initial position
 
 #lxout ("Time = $time ( $nf frames at $fps fps ) from $ti s to $tf s by $dt s");    # Use this for debugging
 
 
 # calculation section, where plenty of other important things are derived from the inputs
 
 # maximum height reached, (where vtop==0)
 my $ytop = ((0.5 * $v0 * $v0)/$g) + $y0;
 
 # time to reach ytop from y0
 my $ttop = ($v0)/$g;
 
 # time to hit ground from ytop
 my $tbounce = sqrt (($ttop * $ttop) + 2 * $y0 / $g);
 
 # speed at bounce, deduced from energy conservation
 my $vbounce = sqrt (($v0*$v0) + 2*$g*$y0);
 
 #lxout ("Top at: $ttop s ($ytop m) Bounces $tbounce s later");
 
 
 # add initial key at current time
 lx ("channel.key mode:add channel:{$xfrm:$chan}");
 lx ("select.channel mode:set channel:{$xfrm:$chan}");
 lx ("channel.interpolation linear");
 lx ("channel.value mode:set value:{$val}"); 
 
 my $tscene = $ti;
 my $tt;
 my $y;
 
 # time of next bounce relative to t0
 my $tb = $ttop + $tbounce;
 #lxout ("Bounce at = $tb s Top: $ttop s ($ytop m) ");
 
 $t = 0;
 for(my $i = 0; $i < $nf; $i++) {
     $t += $dt;
 
     # initial position
     $val = $y0;
 
     # plus initial constant velocity contribution
     $val += $v0 * $t;
 
     # plus acceleration contribution $val+=0.5*$a*$t*$t;
     if ($t>$tb) {
          # add key directly at bounce point!
          $tt = $tscene + $tb;
          lx ("select.time $tt");
          lx ("channel.key mode:add");
          lx ("channel.value mode:set value:{$ymin}");
          #lx ("key.break slope");
          #lx ("key.slopeType linearIn in");
          #lx ("key.slopeType linearOut out");
          # reset motion params
          $y0 = $ymin;         # start at bounce point
          $v0 = $vbounce;      # new initial velocity
          $tscene += $tb;      # reset time so t=0 at bounce
          $t=$t-$tb;           # next key 't' a bit after bounce
 
          # attenuate t,v
          $tbounce = sqrt ($elastic) * $tbounce;
          $vbounce = sqrt ($elastic) * $vbounce;
 
          # time up and time down
          $tb =  2 * $tbounce;
 
          # recompute key after bounce
          $val = $y0;
 
          # plus initial constant velocity contribution
          $val += $v0 * $t;
 
          # plus acceleration contribution
          $val+=0.5*$a*$t*$t;
    }
 
    # use absolute time, not per-bounce time
    $tt = $tscene + $t;
 
    # convert relative y back to absolute
    $y = $val + $ymin;
 
    # set time, add and set key
    lx ("select.time $tt");
    lx ("channel.key mode:add");
    lx ("channel.value mode:set value:{$val}");
 }

 -- lua -- Bouncer.lua
 -- Arnie Cachelin, Luxology LLC, Copyright 2007-2008
 -- A Simple Dynamics Script -- Physics 101 equations of
 -- motion in one dimension
 
 -- This version specializes in the trajectory of a dropped or
 -- launched object which bounces.
 -- To use this, select the object which is already at the time
 -- and place at its motion should start.
 
 
 -- User Input animation parameters
 v0=0           -- initial speed (m/s)
 time = 11.0    -- length of simulation (in seconds)
 ymin = 0.0     -- ground/wall position, where bounces would occur (m)
 elastic = 0.8  -- elasticity: percent energy kept per bounce
 fstep = 0.2    -- number of frames between keys
 
 -- physical constants
 g=9.8          -- gravity on earth = 9.8 m/s^2 DOWN
 a=-g           -- accleration (m/s^2) negative, since UP is positive
 
 --lxtrace(1)   -- Use this for debugging
 
 -- get selected locator-type item
 item = lxq( "query sceneservice selection ? locator" )[1]
 
 -- get its position transform item
 xfrm = lxq ("query sceneservice item.xfrmPos ? "..item)[1]
 
 if xfrm == "" :
     -- Position transform item does not exist; create it
     lx ("transform.add type:pos")
     xfrm = lxq ("query sceneservice item.xfrmPiv ? " ..item)[1]
 end
 
 -- select the item in sceneservice for subsequent channel queries
 xnm = lxq ("query sceneservice item.name ? "..xfrm)[1]
 
 chan = lxq("query sceneservice channel.index ?  pos.Y")[1]
 tf = lxq ("time.range scene out:?")[1]
 ti = lxq ("select.time ?")[1]  -- starting time: Now!
 time = tf - ti                 -- final time
 val = lxq ("query sceneservice channel.value ? "..chan)[1]
 fps = lxq ("time.fpsCustom ?")[1]
 dt = fstep / fps               -- add keys every other frame
 nf = time * fps / fstep        -- number of frames
 
 y0 = val - ymin                -- initial position
 
 --lxout ("Time = "..time.." ( "..nf.." frames at "..fps.." fps ) from "..ti.." s to "..tf.." s by "..dt.." s")    -- Use this for debugging
 
 
 -- calculation section, where plenty of other important things are derived from the inputs
 
 -- maximum height reached, (where vtop==0)
 ytop = ((0.5 * v0 * v0)/g) + y0
 
 -- time to reach ytop from y0
 ttop = (v0)/g
 
 -- time to hit ground from ytop
 tbounce = sqrt ((ttop * ttop) + 2 * y0 / g)
 
 -- speed at bounce, deduced from energy conservation
 vbounce = sqrt ((v0*v0) + 2*g*y0)
 
 --lxout ("Top at: "..ttop.." s ("..ytop.." m) Bounces "..tbounce.." s later")
 
 
 -- add initial key at current time
 lx ("channel.key mode:add channel:{"..xfrm..":"..chan.."}")
 lx ("select.channel mode:set channel:{"..xfrm..":"..chan.."}")
 lx ("channel.interpolation linear")
 lx ("channel.value mode:set value:{"..val.."}") 
 
 tscene = ti
 tt = 0
 y = 0
 
 -- time of next bounce relative to t0
 tb = ttop + tbounce
 --lxout ("Bounce at = "..tb.." s Top: "..ttop.." s ("..ytop.." m) ")
 
 t = 0
 for i=0,nf do
     t += dt
 
     -- initial position
     val = y0
 
     -- plus initial constant velocity contribution
     val += v0 * t
 
     -- plus acceleration contribution val+=0.5*a*t*t
     if t > tb  then
          -- add key directly at bounce point!
          tt = tscene + tb
          lx ("select.time " ..tt)
          lx ("channel.key mode:add")
          lx ("channel.value mode:set value:{"..ymin.."}")
          --lx ("key.break slope")
          --lx ("key.slopeType linearIn in")
          --lx ("key.slopeType linearOut out")
          -- reset motion params
          y0 = ymin       -- start at bounce point
          v0 = vbounce    -- new initial velocity
          tscene += tb    -- reset time so t=0 at bounce
          t=t-tb          -- next key 't' a bit after bounce
 
          -- attenuate t,v
          tbounce = sqrt (elastic) * tbounce
          vbounce = sqrt (elastic) * vbounce
 
          -- time up and time down
          tb =  2 * tbounce
 
          -- recompute key after bounce
          val = y0
 
          -- plus initial constant velocity contribution
          val += v0 * t
 
          -- plus acceleration contribution
          val+=0.5*a*t*t
    end
 
    -- use absolute time, not per-bounce time
    tt = tscene + t
 
    -- convert relative y back to absolute
    y = val + ymin
 
    -- set time, add and set key
    lx ("select.time "..tt)
    lx ("channel.key mode:add")
    lx ("channel.value mode:set value:{"..val.."}")
 end

# python -- Bouncer.py
# Arnie Cachelin, Luxology LLC, Copyright 2007-2008
# A Simple Dynamics Script -- Physics 101 equations of
# motion in one dimension
 
# This version specializes in the trajectory of a dropped or
# launched object which bounces.
# To use this, select the object which is already at the time
# and place at its motion should start.
 
from math import sqrt
 
# User Input animation parameters
v0 = 0            # initial speed (m/s)
ymin = 0.0     # ground/wall position, where bounces would occur (m)
elastic = 0.8   # elasticity: percent energy kept per bounce
fstep = 2.0    # number of frames between keys
 
# physical constants
g = 9.8          # gravity on earth = 9.8 m/s^2 DOWN
a = -g           # accleration (m/s^2) negative, since UP is positive
 
#lx.trace(1)   # Use this for debugging
 
# get selected locator-type item
item = lx.eval1( "query sceneservice selection ? locator" )
if not item:
    sys.exit("LXe_FAILED: Need to select an item")
 
# get its position transform item
xfrm = lx.eval1( "query sceneservice item.xfrmPos ? " + item )
 
if not xfrm:
    # Position transform item does not exist; create it
     lx.command ("transform.add", type="pos")
     xfrm = lx.eval1 ("query sceneservice item.xfrmPos ? " + item)
 
# select the item in sceneservice for subsequent channel queries
xnm = lx.eval1 ("query sceneservice item.name ? " + xfrm)
 
chan = lx.eval1 ("query sceneservice channel.index ?  pos.Y")
tf = lx.eval1 ("time.range scene out:?")
ti = lx.eval1 ("select.time ?")     # starting time: Now!
time = tf - ti              # final time
 
val = float(lx.eval1 ("query sceneservice channel.value ? " + chan)) #bug: returns string value
fps = lx.eval1 ("time.fpsCustom ?")
dt = fstep / fps            # add keys every other frame
nf = time * fps / fstep     # number of frames
 
y0 = val - ymin              # initial position
 
#lx.out ("Time = ", time, " ( ", nf, " frames at ", fps, " fps ) from ", ti, " s to ", tf, " s by ", dt, " s",    # Use this for debugging
 
 
# calculation section, where plenty of other important things are derived from the inputs
 
# maximum height reached, (where vtop==0)
ytop = ((0.5 * v0 * v0) / g) + y0
 
# time to reach ytop from y0
ttop = v0 / g
 
# time to hit ground from ytop
tbounce = sqrt ((ttop * ttop) + 2 * y0 / g)
 
# speed at bounce, deduced from energy conservation
vbounce = sqrt ((v0 * v0) + 2 * g * y0)
 
#lx.out ("Top at: ", ttop, " s (", ytop, " m) Bounces ", tbounce, " s later")
 
 
# add initial key at current time
lx.command ("select.channel", mode="set", channel=xfrm + ":" + chan)
lx.command ("channel.interpolation", type="linear")
lx.command ("channel.key", value=val, mode="add") 
 
tscene = ti
tt = 0
y = 0
 
# time of next bounce relative to t0
tb = ttop + tbounce
 
#lx.out ("Bounce at = ", tb, " s Top: ", ttop, " s (", ytop, " m) ")
 
t = 0
for i in range(nf):
    t += dt
 
    if t > tb:
        # add key directly at bounce point!
        tt = tscene + tb
        lx.command ("channel.key", time=tt, value=ymin, mode="add")
 
        # attenuate t,v
        tbounce = sqrt (elastic) * tbounce
        vbounce = sqrt (elastic) * vbounce
 
        # reset motion params
        y0 = ymin         # start at bounce point
        v0 = vbounce      # new initial velocity
        tscene += tb      # reset time so t=0 at bounce
        t=t-tb          # next key 't' a bit after bounce
 
        # time up and time down
        tb =  2.0 * tbounce
 
    # recompute key after bounce
    val = y0
 
    # plus initial constant velocity contribution
    val += v0 * t
 
    # plus acceleration contribution
    val += 0.5 * a * t * t
 
    # use absolute time, not per-bounce time
    tt = tscene + t
 
    # convert relative y back to absolute
    y = val + ymin
 
    # set time, add and set key
    lx.command ("channel.key", time=tt, value=y, mode="add")

More Information