VectorPath — a new plugin for Tween Engine

A won­der­ful new plug-in for Tween Engine, which gives you the pos­si­bil­i­ty to bold­ly ani­mate sprites along a com­plex path WITHOUT any wor­ries con­cern­ing Besier curves. You just draw a path as #vec­tor­Shape, put it on the Stage and that is all. Below are some exam­ples:

Example 1

We want to ani­mate sprite 2 along a path. The path is on sprite 1. The min­i­mum of code for this might be:

on beginSprite( me )
  tweener = script( "TweenLite" ).getInstance()
  tweener.activatePlugin( "VectorPathPlugin" )
  tweener.to( sprite(2), 3, [ #vectorPath:sprite(1)] )
end beginSprite

In this par­tic­u­lar case, for the sake of the code per­spicu­ity, I have used

on beginSprite( me )
  tweener = script( "TweenMax" ).getInstance()
  tweener.activatePlugin( "VectorPathPlugin" )
  tweener.to( sprite(2), 3, [ #vectorPath:sprite(1), #repeat:-1, #delay:1] )
end beginSprite
Exam­ple 1 demo

The ani­ma­tion sequence begins from the cur­rent posi­tion of sprite 2 and devel­ops to the first point of the path.


Example 2

The #vec­tor­Path prop­er­ty can take two types of val­ues — sprite or #vec­tor­Shape type cast­mem­ber. See how the same exam­ple will look, if the #vec­tor­Shape val­ue is changed and instead of sprite 1 we indi­cate direct­ly its cast­mem­ber.

on beginSprite( me )
  tweener = script( "TweenMax" ).getInstance()
  tweener.activatePlugin( "VectorPathPlugin" )
  tweener.to( sprite(2), 3, [ #vectorPath:member("path1"), #delay:1] )
end beginSprite
Exam­ple 2 demo

We do not need sprite 1 any more. This time the ani­ma­tion behaves as if the sprite 2 posi­tion was the ini­tial point of the path.


Example 3

This plug-in can be inte­grat­ed with Time­lineLite and Time­line­Max. Below is a exam­ple of this:

property myTimeline

on beginSprite( me )
  tweener = script( "TweenMax" ).getInstance()
  tweener.activatePlugin( "VectorPathPlugin" )
  
  timelineSettings = [:]
  timelineSettings.addProp( #delay, 1 )
  timelineSettings.addProp( #onComplete, [ #handler:#reverseAnimation, #object:me ] )
  timelineSettings.addProp( #onReverseComplete, [ #handler:#playAnimation, #object:me ] )
  myTimeline = script( "TimelineLite" ).new( timelineSettings )
  
  d = 4.25
  e = "Sine.EaseOut"
  p = sprite(1)
  tweens = []
  repeat with i = 2 to 8
    tweens.append( tweener.to( sprite(i),d,[ #vectorPath:p, #orientToPath:true, #ease:e ] ) )
  end repeat  
  myTimeline.appendMultiple( tweens, 0, "start", 0.1 )
end beginSprite

on playAnimation( me, args )
    myTimeline.play()
end playAnimation

on reverseAnimation( me, args )
   myTimeline.reverse()
end reverseAnimation

This is how the final result should look like:

Exam­ple 3 demo

Example 4

In order to ani­mate 3D objects we direct­ly use a #vec­tor­Shape cast­mem­ber. For exam­ple:

on sceneReady( me, models )
  tweener = script( "TweenMax" ).getInstance()
  tweener.activatePlugin( "VectorPathPlugin" )
  d = 1
  repeat with i = 1 to count( models )
    tweener.to( models[i], 3, [ #vectorPath:member("path1"), #repeat:-1, #orientToPath:true, #ease:"Sine.EaseInOut", #yoyo:true, #delay:d] )
    d = d + 0.1
  end repeat
end sceneReady
Exam­ple 4 demo

Here is the code of the plug-in itself. Add it as a #par­ent script under the name of “Vec­tor­Path­Plug­in” to the same cast where you have put the rest of the Twee­nEngine scripts.

Show code
-- "VectorPathPlugin" parent script
on getPluginName( this )
  return #VectorPath
end getPluginName

-- @parent
property ancestor
property _RAD2DEG    -- #Float
property _orientData -- #list
property _orient     -- #boolean
property _segments   -- #List
property _count      -- #Integer
property _future     -- #Proplist
property _target     -- #Instance
property _targetType -- #symbol; change factor method - depends of the _target type - #sprite, #model, etc.
property _startRot   -- #float; Original 2D object rotation


-- USAGE:
-- tweener = script( "TweenMax" ).getInstance()
-- tweener.activatePlugin( "VectorPathPlugin" )
-- tweener.to( sprite(2), 5.25, [ #vectorPath:sprite(1), #orientToPath:true ] )
-- OR
-- tweener.to( sprite(2), 5.25, [ #vectorPath:member("path1"), #orientToPath:true ] )


on new( me, s )
  ancestor = script( "TweenPlugin" ).rawNew()
  callAncestor( #new, me )
  me.propName = #vectorpath
  me.overwriteProps = [ #x, #y ]
  _future = [:]
  _RAD2DEG = 180 / PI
  _startRot = 0
  return me
end new

on dispose( me )
  callAncestor( #dispose, me )
  _future.deleteAll()
  _future = VOID
  _target = VOID
  _segments.deleteAll()
  _segments = VOID
end dispose

on onInitTween( me, atarget, avalue, atween )
  if( atarget.is( #SpriteWrapper ) ) then
    _targetType = #two
    _startRot = atween.target.getRotationZ()
  else if( atarget.is( #NodeWrapper ) ) then
    _targetType = #three
  else
    return FALSE
  end if
  
  if( ilk( avalue ) = #sprite ) then
    if( avalue.member.type <> #vectorShape ) then return FALSE
    vlist = avalue.member.vertexList.duplicate()
    vlist.addAt( 1, me._screenToSprite( point( atarget.getX(), atarget.getY() ), avalue ) )
    
    _offset = map( point( avalue.member.width *0.5, avalue.member.height *0.5 ), avalue.member.rect, avalue.rect )
    repeat with i = 1 to count( vlist )
      element = vlist[i]
      if( voidP( element[#handle1] ) ) then
        element.addProp( #handle1, point( 0, 0 ) )
      end if
      if( voidP( element[#handle2] ) ) then
        element.addProp( #handle2, point( 0, 0 ) )
      end if
      p  = map( element.vertex, avalue.member.rect, rect( 0, 0, avalue.rect.width, avalue.rect.height ) ) + _offset
      h1 = p + map( element.handle1, avalue.member.rect, rect( 0, 0, avalue.rect.width, avalue.rect.height ) )
      h2 = p + map( element.handle2, avalue.member.rect, rect( 0, 0, avalue.rect.width, avalue.rect.height ) )
      element.vertex = p
      element.handle1 = h1
      element.handle2 = h2
    end repeat
    
  else if( ilk( avalue ) = #member ) then
    if( avalue.type <> #vectorShape ) then return FALSE
    vlist = avalue.vertexList.duplicate()
    -- get maximum x and y point coordinates
    maxX = -(the maxinteger)
    maxY = -(the maxinteger)
    repeat with v in vlist
      maxX = max( maxX, v.vertex.locH )
      maxY = max( maxY, v.vertex.locV )
    end repeat
    
    -- Modify the original vertexlist, addind maxX and maxY 
    -- and the difference between target and the first point of the path
    -- to "translate" the coordinates to the new origin.
    inv = 1
    if( _targetType = #three ) then
      inv = -1
    end if
    
    diff = point( atarget.getX(), atarget.getY() * inv ) - ( point( vlist[1].vertex.locH + maxX, vlist[1].vertex.locV + maxY ))
    offsetx = diff.locH
    offsety = diff.locV
    repeat with i = 1 to count( vlist )
      element = vlist[ i ]
      if( voidP( element[#handle1] ) ) then
        element.addProp( #handle1, point( 0, 0 ) )
      end if
      if( voidP( element[#handle2] ) ) then
        element.addProp( #handle2, point( 0, 0 ) )
      end if
      element.vertex = point( element.vertex.locH + maxX, element.vertex.locV + maxY ) + point( offsetx, offsety )
      element.handle1 = element.vertex + element.handle1
      element.handle2 = element.vertex + element.handle2
    end repeat 
  end if
  
  if( ilk( vlist ) <> #list ) then return FALSE
  me.init( atween, vlist, FALSE )
  return TRUE
end onInitTween

on _screenToSprite( me, apoint, asprite )
  p = apoint - point( asprite.left, asprite.top ) - point( asprite.member.width*0.5, asprite.member.height*0.5 )
  return [#vertex: p, #handle1:point(0,0), #handle2:point(0,0) ]
end _screenToSprite

on init( me, tween, beziers )
  _target = tween.target
  _segments = me.parseVectorPath( beziers )
  _count = count( _segments )
  enumerables = tween.vars
  if ( enumerables[ #orientToPath ] = TRUE ) then
    if( _target.is( #SpriteWrapper ) ) then
      _orientData =  [ [ #x, #y, #rotationZ, 0, 0.01  ] ]
    else if( _target.is( #NodeWrapper ) ) then
      _orientData =  [ [ #x, #y, #z, 0, 0.1 ] ]
    end if
    _orient = TRUE
  else if ( ilk( enumerables[ #orientToPath ] ) = #list ) then
    _orientData = enumerables[ #orientToPath ]
    _orient = TRUE
  end if 
end init

on changeFactor( me, n )
  if ( n < 0 ) then
    segment = 1
  else if ( n >= 1 ) then
    segment = _count
  else 
    segment = max( 1, ceil( _count * n ) )
  end if
  
  innerRatio = ( n - ( ( segment - 1 ) * ( 1.0 / _count ) ) ) * _count
  section = _segments[ segment ]
  r2 = innerRatio * innerRatio
  r3 = r2 * innerRatio
  p0 = section[1]
  p1 = section[2]
  p2 = section[3]
  repeat with i = 1 to 2
    prop = me.overwriteProps[i]
    c = 3 * ( p1[prop] - p0[prop] )
    b = 3 * ( p2[prop] - p1[prop]) - c
    a = section[4][prop] - p0[prop] - c - b
    xvalue = a * r3 + b * r2 + c * innerRatio + p0[prop]
    if ilk( me._target ) = #instance then
      call( prop, me._target, xvalue )
    else
      me._target[ prop ] = xvalue
    end if 
  end repeat
 
  if (_orient) then
    i = count( _orientData )
    curVals = [:]
    repeat while ( i > 0 )
      cotb = _orientData[ i ] --current orientToBezier Array
      j = 3 -- Iterate through cotb's props  - #x, #y, #z / #rotationZ
      if( ilk( me._target ) = #propList ) then
        repeat while( j > 0 )
          curVals[cotb[j]] = me._target[cotb[j]]
          j = j - 1
        end repeat
      else
        repeat while( j > 0 )
          curVals[cotb[j]] = call( symbol( "get" & cotb[ j ] ), me._target )
          j = j - 1
        end repeat
      end if
      i = i - 1
    end repeat
    oldTarget = me._target
    me._target = _future
    _orient = false
    i = count( _orientData )
    
    repeat while ( i > 0 )
      cotb = _orientData[ i ] --current orientToBezier Array
      me.changeFactor( n + max(cotb[4], cotb[5]) )
      if( _targetType = #two ) then
        toAdd = max( cotb[4], 0 )
        dx = _future[cotb[1]] - curVals[cotb[1]]
        dy = _future[cotb[2]] - curVals[cotb[2]]
        call( cotb[ 3 ], oldTarget, atan( dy, dx ) * _RAD2DEG + toAdd + _startRot )
      else if( _targetType = #three ) then
        dx = _future[ cotb[1] ]
        dy = _future[ cotb[2] ]
        dz = _future[ cotb[3] ]
        oldTarget._target.pointAt( vector( dx, dy, dz ), vector( 0, 0, 1 ) )
      end if
      i = i -1
    end repeat
    me._target = oldTarget
    _orient = true
  end if
end changeFactor 

on parseVectorPath( me, v )
  cnt = count( v ) - 1
  t = []
  inv = 1
  if( _targetType = #three ) then
    inv = -1
  end if
  repeat with i = 1 to cnt
    section = []
    a = v[ i ]
    b = v[ i + 1 ]
    p1 = a.vertex
    p2 = b.vertex
    h1 = a.handle1
    h2 = b.handle2
    section.append( [ #x:p1.locH, #y:p1.locV * inv ] ) -- first point
    section.append( [ #x:h1.locH, #y:h1.locV * inv ] ) -- first handle
    section.append( [ #x:h2.locH, #y:h2.locV * inv ] ) -- second handle
    section.append( [ #x:p2.locH, #y:p2.locV * inv ] ) -- second point
    t.append( section )
  end repeat
  return t 
end parseVectorPath

 
Comments

No comments yet.