#VRML V2.0 utf8


WorldInfo
{
    title "Damper nodes"

    info
    [
        "The ExternProto nodes found in this file implement principels described in"
        "the paper 'Linear Filters - Animating Objects in a Flexible and Pleasing Way'."
        "They have been proposed and added to the X3D standard in 2006."
        "Webpage: http://www.hersto.com/Followers"
        ""
        "Please use the code you find here in any content or application you like"
        "or modify it in any way."
        ""
        "The code here seams to be bug-free as it has been used in many cases."
    ]
}




#
# These commented out EXTERNPROTO statements may help
# you include the nodes.
#



#
#EXTERNPROTO PositionDamper
#[
#    eventIn      SFVec3f      set_destination
#    field        SFVec3f  initial_destination     # 0 0 0
#
#    eventOut     SFVec3f          value_changed
#    field        SFVec3f  initial_value          # 0 0 0
#    eventIn      SFVec3f      set_value
#
#    exposedField SFFloat  tau                     # 1
#
#    field        SFInt32  order                   # 1        # allowed: [1..5]
#
#    field        SFFloat  eps                     # .001     # not yet testet
#
#    eventOut     SFBool   isActive
#
#    eventOut     SFBool   isLoaded
#
#    field        SFBool   takeFirstInput          # TRUE
#
#]
#[
#    "Dampers.wrl#PositionDamper"
#    "http://www.hersto.de/VRML/Dampers.wrl#PositionDamper"
#]
#




#
#EXTERNPROTO OrientationDamper
#[
#    eventIn      SFRotation      set_destination
#    field        SFRotation  initial_destination     # 0 0 1 0
#
#    eventOut     SFRotation          value_changed
#    field        SFRotation  initial_value          # 0 0 1 0
#    eventIn      SFRotation      set_value
#
#    exposedField SFFloat  tau                        # 1
#
#    field        SFInt32  order                      # 1         # allowed: [1..5]
#
#    field        SFFloat  eps                        # .001      # not yet testet
#
#    eventOut     SFBool   isActive
#
#    eventOut     SFBool   isLoaded
#
#    field        SFBool   takeFirstInput          # TRUE
#
#]
#[
#    "Dampers.wrl#OrientationDamper"
#    "http://www.hersto.de/VRML/Dampers.wrl#OrientationDamper"
#]
#




#
#EXTERNPROTO ColorDamper
#[
#    eventIn      SFColor      set_destination
#    field        SFColor  initial_destination     # 0 0 0
#
#    eventOut     SFColor          value_changed
#    field        SFColor  initial_value          # 0 0 0
#    eventIn      SFColor      set_value
#
#    exposedField SFFloat  tau                     #  1
#    exposedField SFFloat  negTau                  # -1       # not yet testet
#
#    field        SFInt32  order                   #  1       # allowed: [1..5]
#
#    eventOut     SFBool   isActive
#
#    eventOut     SFBool   isLoaded
#]
#[
#    "Dampers.wrl#ColorDamper"
#    "http://www.hersto.de/VRML/Dampers.wrl#ColorDamper"
#]
#




#
#EXTERNPROTO Position2fDamper
#[
#    eventIn      SFVec2f      set_destination
#    field        SFVec2f  initial_destination     # 0 0
#
#    eventOut     SFVec2f          value_changed
#    field        SFVec2f  initial_value          # 0 0
#    eventIn      SFVec2f      set_value
#
#    exposedField SFFloat  tau                     # 1
#
#    field        SFInt32  order                   # 1        # allowed: [1..5]
#
#    field        SFFloat  eps                     # .001     # not yet testet
#
#    eventOut     SFBool   isActive
#
#    eventOut     SFBool   isLoaded
#]
#[
#    "Dampers.wrl#Position2fDamper"
#    "http://www.hersto.de/VRML/Dampers.wrl#Position2fDamper"
#]
#





#
#EXTERNPROTO CoordinateDamper
#[
#    eventIn      MFVec3f      set_destination
#    field        MFVec3f  initial_destination     # []
#
#    eventOut     MFVec3f          value_changed
#    field        MFVec3f  initial_value          # []
#    eventIn      MFVec3f      set_value
#
#    exposedField SFFloat  tau                     # 1
#
#    field        SFInt32  order                   # 1        # allowed: [1..5]
#
#    field        SFFloat  eps                     # .001     # not yet testet
#
#    eventOut     SFBool   isActive
#
#    eventOut     SFBool   isLoaded
#]
#[
#    "Dampers.wrl#CoordinateDamper"
#    "http://www.hersto.de/VRML/Dampers.wrl#CoordinateDamper"
#]
#




#
#EXTERNPROTO TexCoordDamper
#[
#    eventIn      MFVec2f      set_destination
#    field        MFVec2f  initial_destination     # []
#
#    eventOut     MFVec2f          value_changed
#    field        MFVec2f  initial_value          # []
#    eventIn      MFVec2f      set_value
#
#    exposedField SFFloat  tau                     # 1
#
#    field        SFInt32  order                   # 1        # allowed: [1..5]
#
#    field        SFFloat  eps                     # .001     # not yet testet
#
#    eventOut     SFBool   isActive
#
#    eventOut     SFBool   isLoaded
#]
#[   "Dampers.wrl#TexCoordDamper"
#    "http://www.hersto.de/VRML/Dampers.wrl#TexCoordDamper"
#]
#


#
#EXTERNPROTO ScalarDamper
#[
#    eventIn      SFFloat      set_destination
#    field        SFFloat  initial_destination     # 0
#
#    eventOut     SFFloat          value_changed
#    field        SFFloat  initial_value          # 0
#    eventIn      SFFloat      set_value
#
#    eventOut     SFFloat  transparency_changed
#
#    exposedField SFFloat  tau                     # 1
#
#    field        SFInt32  order                   # 1        # allowed: [1..5]
#
#    field        SFFloat  eps                     # .001     # not yet testet
#
#    eventOut     SFBool   isActive
#
#    eventOut     SFBool   isLoaded
#
#]
#[
#    "Dampers.wrl#ScalarDamper"
#    "http://www.hersto.de/VRML/Dampers.wrl#ScalarDamper"
#]
#







PROTO PositionDamper
[
    eventIn      SFVec3f      set_destination
    field        SFVec3f  initial_destination     0 0 0

    eventOut     SFVec3f          value_changed
    field        SFVec3f  initial_value          0 0 0
    eventIn      SFVec3f      set_value

    exposedField SFFloat  tau     1

    field        SFInt32  order   1

    field        SFFloat  eps     .001

    eventOut     SFBool   isActive

    eventOut     SFBool   isLoaded

    field        SFBool   takeFirstInput          TRUE
]
{
    PROTO EFFS   # for connecting exposedFields with Script nodes in a spec compliant (VRML97) way.
    [
        exposedField SFFloat tau  1
    ]
       { Group {} }



    DEF EFFS EFFS   # for connecting exposedFields with Script nodes in a spec compliant (VRML97) way.
    {
        tau IS tau
    }



    DEF Worker Script
    {
        eventIn      SFVec3f      set_destination    IS     set_destination
        field        SFVec3f          input          IS initial_destination

        eventOut     SFVec3f          value_changed IS         value_changed
        field        SFVec3f  initial_value         IS initial_value
        eventIn      SFVec3f      set_value         IS     set_value

        field        SFNode           effs           USE        EFFS
        eventIn      SFFloat      set_tau
        field        SFFloat          tau            1

        field        SFInt32          order          IS         order

        field        SFFloat          eps            IS         eps

        field        SFBool           takeFirstInput IS         takeFirstInput


        # internal fields
        eventOut     SFBool needTimer                IS         isActive
        eventIn      SFTime tick
        field        SFTime lastTick    0


        field        SFVec3f value5    0 0 0
        field        SFVec3f value4    0 0 0
        field        SFVec3f value3    0 0 0
        field        SFVec3f value2    0 0 0
        field        SFVec3f value1    0 0 0

        field        SFBool  bInitialized FALSE # This is a workaround because Contact does not allways call initialize() before the first event has been received.

        field        SFBool IsCortona FALSE # does it appear only to me, or does it have problems with receiving values on TimeSensor.enable?

        field        SFBool bNeedToTakeFirstInput TRUE

        url "vrmlscript:

        function StartTimer()
        {
            if(IsCortona)
        return;

            if(!needTimer)
            {
                lastTick= 0;
                needTimer= true;
            }
        }

        function StopTimer()
        {
            if(IsCortona)
        return;

            if(needTimer)
            {
                needTimer= false;
            }
        }



        function initialize()
        {
            CheckInit();
        }

        function CheckInit()
        {
            if(!bInitialized)
            {
                bInitialized= true;
                Init();
            }

        }


        function Init()
        {
            IsCortona= false && Browser.getName().indexOf('Cortona') != -1;

            bNeedToTakeFirstInput= takeFirstInput;

            tau= effs.tau;
            set_value(initial_value);
            if(IsCortona)
                needTimer= true;
            else
                needTimer=    input.x != initial_value.x
                           || input.y != initial_value.y
                           || input.z != initial_value.z
                           ;
        }

        function set_tau(t)
        {
            CheckInit();

            tau= t;
        }



        function set_destination(i)
        {
            CheckInit();

            if(bNeedToTakeFirstInput)
            {
                bNeedToTakeFirstInput= false;
                set_value(i);
            }


            if(i != input)
            {
                input= i;
                StartTimer();
            }
        }


        function set_value(o)
        {
            CheckInit();

            bNeedToTakeFirstInput= false;

            haveOutput= true;
            value1= value2= value3= value4= value5= o;
            value_changed= o;
            StartTimer();
        }



        function tick(now)
        {
            CheckInit();

            if(!lastTick)
            {
                lastTick= now;
        return;
            }

            var delta= now - lastTick;
            lastTick= now;

            var alpha= Math.exp(-delta / tau);


            if(bNeedToTakeFirstInput)  // then don't do any processing.
        return;



            value1= order > 0 && tau
                       ? input  .add(value1.subtract(input  ).multiply(alpha))
                       : input;

            value2= order > 1 && tau
                       ? value1.add(value2.subtract(value1).multiply(alpha))
                       : value1;

            value3= order > 2 && tau
                       ? value2.add(value3.subtract(value2).multiply(alpha))
                       : value2;

            value4= order > 3 && tau
                       ? value3.add(value4.subtract(value3).multiply(alpha))
                       : value3;

            value5= order > 4 && tau
                       ? value4.add(value5.subtract(value4).multiply(alpha))
                       : value4;


            var dist= value1.subtract(input).length();
            if(order > 1)
            {
                var dist2= value2.subtract(value1).length();
                if( dist2 > dist)  dist= dist2;
            }
            if(order > 2)
            {
                var dist3= value3.subtract(value2).length();
                if( dist3 > dist)  dist= dist3;
            }
            if(order > 3)
            {
                var dist4= value4.subtract(value3).length();
                if( dist4 > dist)  dist= dist4;
            }
            if(order > 4)
            {
                var dist5= value5.subtract(value4).length();
                if( dist5 > dist)  dist= dist5;
            }

            if(dist < eps)
            {
                value1= value2= value3= value4= value5= input;
                value_changed= input;
                StopTimer();
        return;
            }

            value_changed= value5;

        }





        "
    }

    DEF Timer TimeSensor
    {
        loop TRUE
    }

    ROUTE Worker.needTimer TO Timer.enabled
    ROUTE Timer.time       TO Worker.tick


    ROUTE EFFS.tau TO Worker.set_tau

    DEF LastNode Script
    {
        eventOut SFBool isLoaded IS isLoaded

        url "vrmlscript:

        function initialize()
        {
            isLoaded= true;
        }

        "
    }
}




PROTO OrientationDamper
[
    eventIn      SFRotation      set_destination
    field        SFRotation  initial_destination           0 0 1 0

    eventOut     SFRotation          value_changed
    field        SFRotation  initial_value          0 0 1 0
    eventIn      SFRotation      set_value

    exposedField SFFloat  tau     1

    field        SFInt32  order   1

    field        SFFloat  eps     .001

    eventOut     SFBool   isActive

    eventOut     SFBool   isLoaded

    field        SFBool   takeFirstInput          FALSE

]
{
    PROTO EFFS   # for connecting exposedFields with Script nodes in a spec compliant (VRML97) way.
    [
        exposedField SFFloat tau  1
    ]
       { Group {} }



    DEF EFFS EFFS
    {
        tau IS tau
    }



    DEF Worker Script
    {
        eventIn      SFRotation      set_destination    IS     set_destination
        field        SFRotation          input          IS initial_destination

        eventOut     SFRotation          value_changed IS         value_changed
        field        SFRotation  initial_value         IS initial_value
        eventIn      SFRotation      set_value         IS     set_value

        field        SFNode           effs              USE        EFFS
        eventIn      SFFloat      set_tau
        field        SFFloat          tau               1

        field        SFInt32          order             IS         order

        field        SFFloat          eps               IS         eps

        field        SFBool           takeFirstInput    IS         takeFirstInput


        # internal fields
        eventOut     SFBool needTimer                   IS      isActive
        eventIn      SFTime tick
        field        SFTime lastTick    0


        field        SFRotation value5    0 0 1 0
        field        SFRotation value4    0 0 1 0
        field        SFRotation value3    0 0 1 0
        field        SFRotation value2    0 0 1 0
        field        SFRotation value1    0 0 1 0


        field        SFBool  bInitialized FALSE # This is a workaround because Contact does not allways call initialize() before the first event has been received.

        field        SFBool bNeedToTakeFirstInput FALSE


        url "vrmlscript:

        function StartTimer()
        {
            if(!needTimer)
            {
                lastTick= 0;
                needTimer= true;
            }
        }

        function StopTimer()
        {
            if(needTimer)
            {
                needTimer= false;
            }
        }



        function initialize()
        {
            CheckInit();
        }

        function CheckInit()
        {
            if(!bInitialized)
            {
                bInitialized= true;
                Init();
            }
        }

        function Init()
        {
            bNeedToTakeFirstInput= takeFirstInput;

            tau= effs.tau;
            set_value(initial_value);
            needTimer=    input.x != initial_value.x
                       || input.y != initial_value.y
                       || input.z != initial_value.z
                       || input.angle != initial_value.angle
                       ;
        }

        function set_tau(t)
        {
            CheckInit();

            tau= t;
        }



        function set_destination(i)
        {
            CheckInit();

            if(bNeedToTakeFirstInput)
            {
                bNeedToTakeFirstInput= false;
                set_value(i);
            }

            input= i;
            StartTimer();
        }


        function set_value(o)
        {
            CheckInit();

            bNeedToTakeFirstInput= false;

            value1= value2= value3= value4= value5= o;
            value_changed= o;
            StartTimer();
        }



        function tick(now)
        {
            CheckInit();

            if(!lastTick)
            {
                lastTick= now;
        return;
            }


            var delta= now - lastTick;
            lastTick= now;

            var alpha= Math.exp(-delta / tau);

            if(bNeedToTakeFirstInput)  // then don't do any processing.
        return;
			

            value1= order > 0 && tau
                       ? input  .slerp(value1, alpha)
                       : input;


            value2= order > 1 && tau
                       ? value1.slerp(value2, alpha)
                       : value1;

            value3= order > 2 && tau
                       ? value2.slerp(value3, alpha)
                       : value2;

            value4= order > 3 && tau
                       ? value3.slerp(value4, alpha)
                       : value3;

            value5= order > 4 && tau
                       ? value5.slerp(value4, alpha)
                       : value4;



            var dist= Math.abs(value1.inverse().multiply(input).angle);
            if(order > 1)
            {
                var dist2= Math.abs(value2.inverse().multiply(value1).angle);
                if( dist2 > dist)  dist= dist2;
            }
            if(order > 2)
            {
                var dist3= Math.abs(value3.inverse().multiply(value2).angle);
                if( dist3 > dist)  dist= dist3;
            }
            if(order > 3)
            {
                var dist4= Math.abs(value4.inverse().multiply(value3).angle);
                if( dist4 > dist)  dist= dist4;
            }
            if(order > 4)
            {
                var dist5= Math.abs(value5.inverse().multiply(value4).angle);
                if( dist5 > dist)  dist= dist5;
            }

            if(dist < eps)
            {
                value1= value2= value3= value4= value5= input;
                value_changed= input;
                StopTimer();
        return;
            }

            value_changed= value5;

        }





        "
    }

    DEF Timer TimeSensor
    {
        loop TRUE
    }

    ROUTE Worker.needTimer TO Timer.enabled
    ROUTE Timer.time       TO Worker.tick


    ROUTE EFFS.tau TO Worker.set_tau

    DEF LastNode Script
    {
        eventOut SFBool isLoaded IS isLoaded

        url "vrmlscript:

        function initialize()
        {
            isLoaded= true;
        }

        "
    }
}


#
# NOTE:
# The ColorDamper animates SFColor values in the HSV space.
# Initially i though it'd be good to work in perception space
# rather than a technology oriented space (RGB).
# However, this leads to the case that when a color should
# go - say from red to blue, it does so by going through
# other colors, i.e. yellow and green, whereas an animation
# computed in RGB space appears to create a much directer
# transition.
#
# Animating in HSV space basically means to consider hue
# values of 0 and 1 as equal and finding the shorter
# of the two paths possible on a circle. Maybe there's an
# analogy with the slerp interpolation on a 4d sphere.
#
# The below implementation contains code for animating in
# HSV space, but it's commented out and replaced by code
# for calculating in RGB space.
#


PROTO ColorDamper
[
    eventIn      SFColor      set_destination
    field        SFColor  initial_destination     0 0 0

    eventOut     SFColor          value_changed
    field        SFColor  initial_value          0 0 0
    eventIn      SFColor      set_value

    exposedField SFFloat  tau      1
    exposedField SFFloat  negTau  -1                # not yet testet
                                                    # The idea behind negTau is to use a different
    field        SFInt32  order   1                 # speed for when the brighness is to be lowered
                                                    # than when it is to be raised. This allows to
    eventOut     SFBool   isActive                  # implement a lamp which comes to full brightness quickly
                                                    # when turned on, but has a much longer phase of after-
    eventOut     SFBool   isLoaded                  # burn when turned off.
]
{
    PROTO EFFS   # for connecting exposedFields with Script nodes in a spec compliant (VRML97) way.
    [
        exposedField SFFloat tau     1
        exposedField SFFloat negTau -1
    ]
       { Group {} }



    DEF EFFS EFFS
    {
        tau IS tau
    }



    DEF Worker Script
    {
        eventIn      SFColor      set_destination    IS     set_destination
        field        SFColor  initial_destination    IS initial_destination

        eventOut     SFColor          value_changed IS         value_changed
        field        SFColor  initial_value         IS initial_value
        eventIn      SFColor      set_value         IS     set_value

        field        SFNode           effs           USE        EFFS

        eventIn      SFFloat      set_tau
        field        SFFloat          tau            1

        eventIn      SFFloat      set_negTau
        field        SFFloat          negTau        -1

        field        SFInt32          order          IS         order

        field        SFFloat          eps            .001



        # internal fields
        eventOut     SFBool needTimer                IS         isActive
        eventIn      SFTime tick
        field        SFTime lastTick    0


        field        SFVec3f input_HSV      0 0 0
        field        SFVec3f value5_HSV    0 0 0
        field        SFVec3f value4_HSV    0 0 0
        field        SFVec3f value3_HSV    0 0 0
        field        SFVec3f value2_HSV    0 0 0
        field        SFVec3f value1_HSV    0 0 0



        url "vrmlscript:



        function StartTimer()
        {
            if(!needTimer)
            {
                lastTick= 0;
                needTimer= true;
            }
        }

        function StopTimer()
        {
            if(needTimer)
            {
                needTimer= false;
            }
        }



        function initialize()
        {
            tau= effs.tau;
            negTau= effs.negTau;

//            var HSV= initial_destination.getHSV();
//            input_HSV.x= HSV[0];
//            input_HSV.y= HSV[1];
//            input_HSV.z= HSV[2];
            input_HSV.x= initial_destination.r;
            input_HSV.y= initial_destination.g;
            input_HSV.z= initial_destination.b;

            set_value(initial_value);

            needTimer=    initial_destination.r != initial_value.r
                       || initial_destination.g != initial_value.g
                       || initial_destination.b != initial_value.b
                       ;
        }

        function set_tau(t)
        {
            tau= t;
        }

        function set_negTau(t)
        {
            negTau= t;
        }


        function set_destination(i)
        {
//            var HSV= i.getHSV();
//            input_HSV.x= HSV[0];
//            input_HSV.y= HSV[1];
//            input_HSV.z= HSV[2];
            input_HSV.x= i.r;
            input_HSV.y= i.g;
            input_HSV.z= i.b;

            StartTimer();
        }


        function set_value(o)
        {
//            var HSV= o.getHSV();
//            value1_HSV= value2_HSV= value3_HSV= value4_HSV= value5_HSV= new SFVec3f(HSV[0], HSV[1], HSV[2]);
            value1_HSV= value2_HSV= value3_HSV= value4_HSV= value5_HSV= new SFVec3f(o.r, o.g, o.b);
            value_changed= o;
            StartTimer();
        }


        function dynamics(dest, val, alpha)
        {
            var diff= val.subtract(dest);
//            if(diff[0] >  .5) diff[0]-= 1;
//            if(diff[0] < -.5) diff[0]+= 1;

            var ret= dest.add(diff.multiply(alpha));
//            if(ret[0] < 0) ret[0]+= 1;
//            if(ret[0] > 1) ret[0]-= 1;

        return ret;
        }




        function distanceHSV(a, b)
        {
            diffHue= a[0] - b[0];
//            if(diffHue >  .5) diffHue-= 1;
//            if(diffHue < -.5) diffHue+= 1;
            diffSat= a[1] - b[1];
            diffVal= a[2] - b[2];

        return Math.sqrt(  diffHue * diffHue
                         + diffSat * diffSat
                         + diffVal * diffVal
                        );
        }


        function tick(now)
        {
            if(!lastTick)
            {
                lastTick= now;
        return;
            }

            var delta= now - lastTick;

            var TauUsed= negTau < 0
                           ? tau
                           : ( value5_HSV[2] < input_HSV[2]
                                 ? tau
                                 : negTau
                             )
                           ;


            var alpha= Math.exp(-delta / TauUsed);




            value1_HSV= order > 0 && TauUsed
                           ? dynamics(input_HSV,   value1_HSV, alpha)
                           : input_HSV;

            value2_HSV= order > 1 && TauUsed
                           ? dynamics(value1_HSV, value2_HSV, alpha)
                           : value1_HSV;

            value3_HSV= order > 2 && TauUsed
                           ? dynamics(value2_HSV, value3_HSV, alpha)
                           : value2_HSV;

            value4_HSV= order > 3 && TauUsed
                           ? dynamics(value3_HSV, value4_HSV, alpha)
                           : value3_HSV;

            value5_HSV= order > 4 && TauUsed
                           ? dynamics(value4_HSV, value5_HSV, alpha)
                           : value4_HSV;


            // Hmm, don't know if it is perception theoretically correct to calculate the distance in HSV space or in RGB space.
            var dist= distanceHSV(value1_HSV, input_HSV);
            if(order > 1)
            {
                var dist2=  distanceHSV(value2_HSV, value1_HSV);
                if( dist2 > dist)  dist= dist2;
            }
            if(order > 2)
            {
                var dist3=  distanceHSV(value3_HSV, value2_HSV);
                if( dist3 > dist)  dist= dist3;
            }
            if(order > 3)
            {
                var dist4=  distanceHSV(value4_HSV, value3_HSV);
                if( dist4 > dist)  dist= dist4;
            }
            if(order > 4)
            {
                var dist5= distanceHSV(value5_HSV, value4_HSV);
                if( dist5 > dist)  dist= dist5;
            }

            if(dist < eps)
            {
                value1_HSV= value2_HSV= value3_HSV= value4_HSV= value5_HSV= input_HSV;
//                value_changed.setHSV(input_HSV[0], input_HSV[1], input_HSV[2]);
                value_changed= new SFColor(input_HSV[0], input_HSV[1], input_HSV[2]);
                StopTimer();
        return;
            }

//            value_changed.setHSV(value5_HSV[0], value5_HSV[1], value5_HSV[2]);
            value_changed= new SFColor(value5_HSV[0], value5_HSV[1], value5_HSV[2]);

            lastTick= now;

        }





        "
    }

    DEF Timer TimeSensor
    {
        loop TRUE
    }

    ROUTE Worker.needTimer TO Timer.enabled
    ROUTE Timer.time       TO Worker.tick


    ROUTE EFFS.tau TO Worker.set_tau

    DEF LastNode Script
    {
        eventOut SFBool isLoaded IS isLoaded

        url "vrmlscript:

        function initialize()
        {
            isLoaded= true;
        }

        "
    }
}




PROTO Position2fDamper
[
    eventIn      SFVec2f      set_destination
    field        SFVec2f  initial_destination     0 0

    eventOut     SFVec2f          value_changed
    field        SFVec2f  initial_value          0 0
    eventIn      SFVec2f      set_value

    exposedField SFFloat  tau     1

    field        SFInt32  order   1

    field        SFFloat  eps     .001

    eventOut     SFBool   isActive

    eventOut     SFBool   isLoaded
]
{
    PROTO EFFS   # for connecting exposedFields with Script nodes in a spec compliant (VRML97) way.
    [
        exposedField SFFloat tau  1
    ]
       { Group {} }



    DEF EFFS EFFS
    {
        tau IS tau
    }



    DEF Worker Script
    {
        eventIn      SFVec2f      set_destination    IS     set_destination
        field        SFVec2f          input          IS initial_destination

        eventOut     SFVec2f          value_changed IS         value_changed
        field        SFVec2f  initial_value         IS initial_value
        eventIn      SFVec2f      set_value         IS     set_value

        field        SFNode           effs           USE        EFFS
        eventIn      SFFloat      set_tau
        field        SFFloat          tau            1

        field        SFInt32          order          IS         order

        field        SFFloat          eps            IS         eps



        # internal fields
        eventOut     SFBool needTimer                IS         isActive
        eventIn      SFTime tick
        field        SFTime lastTick    0


        field        SFVec2f value5    0 0
        field        SFVec2f value4    0 0
        field        SFVec2f value3    0 0
        field        SFVec2f value2    0 0
        field        SFVec2f value1    0 0


        url "vrmlscript:

        function StartTimer()
        {
            if(!needTimer)
            {
                lastTick= 0;
                needTimer= true;
            }
        }

        function StopTimer()
        {
            if(needTimer)
            {
                needTimer= false;
            }
        }



        function initialize()
        {
            tau= effs.tau;
            set_value(initial_value);
            needTimer=    input.x != initial_value.x
                       || input.y != initial_value.y
                       ;
        }

        function set_tau(t)
        {
            tau= t;
        }



        function set_destination(i)
        {
            input= i;
            StartTimer();
        }


        function set_value(o)
        {
            value1= value2= value3= value4= value5= o;
            value_changed= o;
            StartTimer();
        }



        function tick(now)
        {
            if(!lastTick)
            {
                lastTick= now;
        return;
            }

            var delta= now - lastTick;

            var alpha= Math.exp(-delta / tau);



            value1= order > 0 && tau
                       ? input  .add(value1.subtract(input  ).multiply(alpha))
                       : input;

            value2= order > 1 && tau
                       ? value1.add(value2.subtract(value1).multiply(alpha))
                       : value1;

            value3= order > 2 && tau
                       ? value2.add(value3.subtract(value2).multiply(alpha))
                       : value2;

            value4= order > 3 && tau
                       ? value3.add(value4.subtract(value3).multiply(alpha))
                       : value3;

            value5= order > 4 && tau
                       ? value4.add(value5.subtract(value4).multiply(alpha))
                       : value4;


            var dist= value1.subtract(input).length();
            if(order > 1)
            {
                var dist2= value2.subtract(value1).length();
                if( dist2 > dist)  dist= dist2;
            }
            if(order > 2)
            {
                var dist3= value3.subtract(value2).length();
                if( dist3 > dist)  dist= dist3;
            }
            if(order > 3)
            {
                var dist4= value4.subtract(value3).length();
                if( dist4 > dist)  dist= dist4;
            }
            if(order > 4)
            {
                var dist5= value5.subtract(value4).length();
                if( dist5 > dist)  dist= dist5;
            }

            if(dist < eps)
            {
                value1= value2= value3= value4= value5= input;
                value_changed= input;
                StopTimer();
        return;
            }

            value_changed= value5;

            lastTick= now;

        }





        "
    }

    DEF Timer TimeSensor
    {
        loop TRUE
    }

    ROUTE Worker.needTimer TO Timer.enabled
    ROUTE Timer.time       TO Worker.tick


    ROUTE EFFS.tau TO Worker.set_tau

    DEF LastNode Script
    {
        eventOut SFBool isLoaded IS isLoaded

        url "vrmlscript:

        function initialize()
        {
            isLoaded= true;
        }

        "
    }
}






# The value has always the same number of elements
# than the input. If the nuber of elements differ
# between initial_destination and initial_value, initial_value
# is modified to contain the correct number.
# If the number of elements is changed on set_destination,
# this change is propagated immediately to the value.
# (Cannot smoothly animate the number of elements.)
#
# All the Damper nodes send their start value on the
# value. The CoordinateDamper does not follow this
# rule in one case:
# If neither initial_destination nor initial_value contain
# some elements. Then it has to assume that simply no
# initial values are given on the PROTO instantiation.
# The value_changed sends no value on initialization
# if both, initial_destination and initial_value contain
# no elements. This is to avoid emitting an empty array
# on value_changed just because the size of the array
# is not known before the first event on set_destination
# arrives. If you don't want this, set initial_value
# to a proper initialization value.
#
# When the array length of the value has to be adjusted,
# the last elements of the array are deleted, or elements
# with the default value (0, 0, 0) of SFVec3f is appended.
#
# The array length is adjusted to the value received not
# only when a set_destination event is received, but also when a
# set_value event is received.
#


# TBD: Should an MFFloat version of tau be added?
#      Resulting in a separate tau value for each
#      element in the MFVec3f values?

PROTO CoordinateDamper
[
    eventIn      MFVec3f      set_destination
    field        MFVec3f  initial_destination     []

    eventOut     MFVec3f          value_changed
    field        MFVec3f  initial_value          []
    eventIn      MFVec3f      set_value

    exposedField SFFloat  tau     1

    field        SFInt32  order   1

    field        SFFloat  eps     .001

    eventOut     SFBool   isActive

    eventOut     SFBool   isLoaded
]
{
    PROTO EFFS   # for connecting exposedFields with Script nodes in a spec compliant (VRML97) way.
    [
        exposedField SFFloat tau  1
    ]
       { Group {} }



    DEF EFFS EFFS
    {
        tau IS tau
    }



    DEF Worker Script
    {
        eventIn      MFVec3f      set_destination    IS     set_destination
        field        MFVec3f          input          IS initial_destination

        eventOut     MFVec3f          value_changed IS         value_changed
        field        MFVec3f  initial_value         IS initial_value
        eventIn      MFVec3f      set_value         IS     set_value

        field        SFNode           effs           USE        EFFS
        eventIn      SFFloat      set_tau
        field        SFFloat          tau            1

        field        SFInt32          order          IS         order

        field        SFFloat          eps            IS         eps



        # internal fields
        eventOut     SFBool needTimer                IS         isActive
        eventIn      SFTime tick
        field        SFTime lastTick    0


        field        MFVec3f value5   []
        field        MFVec3f value4   []
        field        MFVec3f value3   []
        field        MFVec3f value2   []
        field        MFVec3f value1   []

        field        SFBool  initialized FALSE # Contact seams to be able to send events before it calls the initialize() function.
                                               # I think these events come from other Scripts initialize() functions and
                                               # from Contacts immediate event propagation.
        field        SFBool  valueHasBeenSet FALSE # for the same problem.


        url "vrmlscript:

        function StartTimer()
        {
            if(!needTimer)
            {
                lastTick= 0;


                needTimer= true;
            }
        }

        function StopTimer()
        {
            if(needTimer)
            {
                needTimer= false;
            }
        }



        function initialize()
        {
            initialized= true;

            tau= effs.tau;

            initial_value.length= input.length;


            if(initial_value.length || input.length)
            {
                if(!valueHasBeenSet)
                {
                    // TBD: Don't know if this works in all cases.
                    value1=
                    value2=
                    value3=
                    value4=
                    value5= initial_value;
                }
            }
            else{
                // Nothing to be done here.
            }


            var NeedTimer= false;
            for(var C= 0; C<input.length; C++)
            {
                if(   input[C].x != initial_value[C].x
                   || input[C].y != initial_value[C].y
                   || input[C].z != initial_value[C].z
                  )
                {
                    NeedTimer= true;
            break;
                }
            }

            needTimer= NeedTimer;

        }

        function set_tau(t)
        {
            tau= t;
        }



        function set_destination(i)
        {
            input= i;

            value1.length=
            value2.length=
            value3.length=
            value4.length=
            value5.length=
                  i.length;

            if(!initialized)
                initial_value.length= i.length;

            StartTimer();
        }


        function set_value(o)
        {
            value1=
            value2=
            value3=
            value4=
            value5= o;
            input.length= o.length;
            if(!initialized)
                initial_value= o;
            value_changed= o;
            StartTimer();
        }




        function dynamics(dest, val, alpha)
        {
            var Val;
            var Dest;
            for(var C= 0; C<dest.length; C++ )
            {
                Val= val[C];
                Dest= dest[C];

//                Val= Dest.add(Val.subtract(Dest).multiply(alpha));
                Val.x= Dest.x + (Val.x - Dest.x) * alpha;
                Val.y= Dest.y + (Val.y - Dest.y) * alpha;
                Val.z= Dest.z + (Val.z - Dest.z) * alpha;

                val[C]= Val;



//                val[C]= dest[C].add(val[C].subtract(dest[C]).multiply(alpha));
            }
        }


        function IsDistLargerThan(a, b, Eps)
        {
            for(var C= 0; C<a.length; C++ )
            {
                var A= a[C];
                var B= b[C];

                var dist=  Math.abs(A.x - B.x)
                         + Math.abs(A.y - B.y)
                         + Math.abs(A.z - B.z)
                         ;
                if(dist > Eps)
        return true;
            }

        return false;
        }




        function tick(now)
        {
            if(!lastTick)
            {
                lastTick= now;
        return;
            }

            var delta= now - lastTick;

            var alpha= tau? Math.exp(-delta / tau) : 1;


            if(order >0)
            {
                if(tau)
                    dynamics(input,   value1, alpha);
                else
                    value1= input;
            }


            if(order > 1)
            {
                if(tau)
                    dynamics(value1, value2, alpha);
                else
                    value2= value1;
            }



            if(order > 2)
            {
                if(tau)
                    dynamics(value2, value3, alpha);
                else
                    value3= value2;
            }



            if(order > 3)
            {
                if(tau)
                    dynamics(value3, value4, alpha);
                else
                    value4= value3;
            }


            if(order > 4)
            {
                if(tau)
                    dynamics(value4, value5, alpha);
                else
                    value5= value4;
            }



    // Don't Know if this has a performance penalty.
//            var NeedContinue= false;

//            if(IsDistLargerThan(value1, input, eps))
//                NeedContinue= true;
//            else if(order > 1 && IsDistLargerThan(value2, value1, eps))
//                NeedContinue= true;
//            else if(order > 2 && IsDistLargerThan(value3, value2, eps))
//                NeedContinue= true;
//            else if(order > 3 && IsDistLargerThan(value4, value3, eps))
//                NeedContinue= true;
//            else if(order > 4 && IsDistLargerThan(value5, value4, eps))
//                NeedContinue= true;


//            if(!NeedContinue)
//            {
//                value1= value2= value3= value4= value5= input;
//                value_changed= input;
//                needTimer= false;
//        return;
//            }

            value_changed= order <= 1? value1 : (
                            order == 2? value2 : (
                            order == 3? value3 : (
                            order == 4? value4 : (
                                        value5   ))));


            ;

            lastTick= now;

        }





        "
    }

    DEF Timer TimeSensor
    {
        loop TRUE
    }

    ROUTE Worker.needTimer TO Timer.enabled
    ROUTE Timer.time       TO Worker.tick


    ROUTE EFFS.tau TO Worker.set_tau

    DEF LastNode Script
    {
        eventOut SFBool isLoaded IS isLoaded

        url "vrmlscript:

        function initialize()
        {
            isLoaded= true;
        }

        "
    }
}



# The same as CoordinateDamper, but for MFVec2f.
PROTO TexCoordDamper
[
    eventIn      MFVec2f      set_destination
    field        MFVec2f  initial_destination     []

    eventOut     MFVec2f          value_changed
    field        MFVec2f  initial_value          []
    eventIn      MFVec2f      set_value

    exposedField SFFloat  tau     1

    field        SFInt32  order   1

    field        SFFloat  eps     .001

    eventOut     SFBool   isActive

    eventOut     SFBool   isLoaded
]
{
    PROTO EFFS   # for connecting exposedFields with Script nodes in a spec compliant (VRML97) way.
    [
        exposedField SFFloat tau  1
    ]
       { Group {} }



    DEF EFFS EFFS
    {
        tau IS tau
    }



    DEF Worker Script
    {
        eventIn      MFVec2f      set_destination    IS     set_destination
        field        MFVec2f          input          IS initial_destination

        eventOut     MFVec2f          value_changed IS         value_changed
        field        MFVec2f  initial_value         IS initial_value
        eventIn      MFVec2f      set_value         IS     set_value

        field        SFNode           effs           USE        EFFS
        eventIn      SFFloat      set_tau
        field        SFFloat          tau            1

        field        SFInt32          order          IS         order

        field        SFFloat          eps            IS         eps



        # internal fields
        eventOut     SFBool needTimer                IS         isActive
        eventIn      SFTime tick
        field        SFTime lastTick    0


        field        MFVec2f value5   []
        field        MFVec2f value4   []
        field        MFVec2f value3   []
        field        MFVec2f value2   []
        field        MFVec2f value1   []

        field        SFBool  initialized FALSE # Contact seams to be able to send events before it calls the initialize() function.
                                               # I think these events come from other Scripts initialize() functions and
                                               # from Contacts immediate event propagation.
        field        SFBool  valueHasBeenSet FALSE # for the same problem.


        url "vrmlscript:

        function StartTimer()
        {
            if(!needTimer)
            {
                lastTick= 0;


                needTimer= true;
            }
        }

        function StopTimer()
        {
            if(needTimer)
            {
                needTimer= false;
            }
        }



        function initialize()
        {
            initialized= true;

            tau= effs.tau;

            initial_value.length= input.length;


            if(initial_value.length || input.length)
            {
                if(!valueHasBeenSet)
                {
                    // TBD: Don't know if this works in all cases.
                    value1=
                    value2=
                    value3=
                    value4=
                    value5= initial_value;
                }
            }
            else{
                // Nothing to be done here.
            }


            var NeedTimer= false;
            for(var C= 0; C<input.length; C++)
            {
                if(   input[C].x != initial_value[C].x
                   || input[C].y != initial_value[C].y
                  )
                {
                    NeedTimer= true;
            break;
                }
            }

            needTimer= NeedTimer;

        }

        function set_tau(t)
        {
            tau= t;
        }



        function set_destination(i)
        {


            input= i;

            value1.length=
            value2.length=
            value3.length=
            value4.length=
            value5.length=
                  i.length;

            if(!initialized)
                initial_value.length= i.length;

            StartTimer();
        }


        function set_value(o)
        {
            value1=
            value2=
            value3=
            value4=
            value5= o;
            input.length= o.length;
            if(!initialized)
                initial_value= o;
            value_changed= o;
            StartTimer();
        }





        function dynamics(dest, val, alpha)
        {
            for(var C= 0; C<dest.length; C++ )
                val[C]= dest[C].add(val[C].subtract(dest[C]).multiply(alpha));
        }


        function IsDistLargerThan(a, b, Eps)
        {
            for(var C= 0; C<a.length; C++ )
            {
                var A= a[C];
                var B= b[C];

                var dist=  Math.abs(A.x - B.x)
                         + Math.abs(A.y - B.y)
                         ;
                if(dist > Eps)
        return true;
            }

        return false;
        }




        function tick(now)
        {
            if(!lastTick)
            {
                lastTick= now;
        return;
            }

            var delta= now - lastTick;

            var alpha= tau? Math.exp(-delta / tau) : 1;


            if(order >0)
            {
                if(tau)
                    dynamics(input,   value1, alpha);
                else
                    value1= input;
            }


            if(order > 1)
            {
                if(tau)
                    dynamics(value1, value2, alpha);
                else
                    value2= value1;
            }



            if(order > 2)
            {
                if(tau)
                    dynamics(value2, value3, alpha);
                else
                    value3= value2;
            }



            if(order > 3)
            {
                if(tau)
                    dynamics(value3, value4, alpha);
                else
                    value4= value3;
            }


            if(order > 4)
            {
                if(tau)
                    dynamics(value4, value5, alpha);
                else
                    value5= value4;
            }



//            var NeedContinue= false;

//            if(IsDistLargerThan(value1, input, eps))
//                NeedContinue= true;
//            else if(order > 1 && IsDistLargerThan(value2, value1, eps))
//                NeedContinue= true;
//            else if(order > 2 && IsDistLargerThan(value3, value2, eps))
//                NeedContinue= true;
//            else if(order > 3 && IsDistLargerThan(value4, value3, eps))
//                NeedContinue= true;
//            else if(order > 4 && IsDistLargerThan(value5, value4, eps))
//                NeedContinue= true;


//            if(!NeedContinue)
//            {
//                value1= value2= value3= value4= value5= input;
//                value_changed= input;
//                needTimer= false;
//        return;
//            }

            value_changed= order <= 1? value1 : (
                            order == 2? value2 : (
                            order == 3? value3 : (
                            order == 4? value4 : (
                                        value5   ))));

            lastTick= now;
        }





        "
    }

    DEF Timer TimeSensor
    {
        loop TRUE
    }

    ROUTE Worker.needTimer TO Timer.enabled
    ROUTE Timer.time       TO Worker.tick


    ROUTE EFFS.tau TO Worker.set_tau

    DEF LastNode Script
    {
        eventOut SFBool isLoaded IS isLoaded

        url "vrmlscript:

        function initialize()
        {
            isLoaded= true;
        }

        "
    }
}








PROTO ScalarDamper
[
    eventIn      SFFloat      set_destination
    field        SFFloat  initial_destination     0

    eventOut     SFFloat          value_changed
    field        SFFloat  initial_value          0
    eventIn      SFFloat      set_value

    eventOut     SFFloat transparency_changed

    exposedField SFFloat  tau     1

    field        SFInt32  order   1

    field        SFFloat  eps     .001

    eventOut     SFBool   isActive

    eventOut     SFBool   isLoaded
]
{
    PROTO EFFS   # for connecting exposedFields with Script nodes in a spec compliant (VRML97) way.
    [
        exposedField SFFloat tau  1
    ]
       { Group {} }



    DEF EFFS EFFS
    {
        tau IS tau
    }



    DEF Worker Script
    {
        eventIn      SFFloat      set_destination    IS     set_destination
        field        SFFloat          input          IS initial_destination

        eventOut     SFFloat          value_changed IS         value_changed
        field        SFFloat  initial_value         IS initial_value
        eventIn      SFFloat      set_value         IS     set_value

        eventOut     SFFloat  transparency_changed   IS transparency_changed

        field        SFNode           effs           USE        EFFS
        eventIn      SFFloat      set_tau
        field        SFFloat          tau            1

        field        SFInt32          order          IS         order

        field        SFFloat          eps            IS         eps



        # internal fields
        eventOut     SFBool needTimer                IS         isActive
        eventIn      SFTime tick
        field        SFTime lastTick    0


        field        SFFloat value5    0
        field        SFFloat value4    0
        field        SFFloat value3    0
        field        SFFloat value2    0
        field        SFFloat value1    0

        field        SFBool  haveOutput FALSE  # This is a workaround because Contact does nt allwayscall initialize() before the first event has been received.

        url "vrmlscript:

        function StartTimer()
        {
            if(!needTimer)
            {
                lastTick= 0;
                needTimer= true;
            }
        }

        function StopTimer()
        {
            if(needTimer)
            {
                needTimer= false;
            }
        }



        function initialize()
        {
            tau= effs.tau;
            if(!haveOutput) set_value(initial_value);
            needTimer=    input != initial_value;
        }

        function set_tau(t)
        {
            tau= t;
        }



        function set_destination(i)
        {
            input= i;
            StartTimer();
        }


        function set_value(o)
        {
            haveOutput= true;
            value1= value2= value3= value4= value5= o;
            value_changed= o;
            SetTransparency();
            StartTimer();
        }



        function tick(now)
        {
            if(!lastTick)
            {
                lastTick= now;
        return;
            }

            var delta= now - lastTick;

            var alpha= Math.exp(-delta / tau);



            value1= order > 0 && tau
                       ? input   + (value1 - input) * alpha
                       : input;

            value2= order > 1 && tau					
                       ? value1 + (value2 - value1) * alpha
                       : value1;

            value3= order > 2 && tau
                       ? value2 + (value3 - value2) * alpha
                       : value2;

            value4= order > 3 && tau
                       ? value3 + (value4 - value3) * alpha
                       : value3;

            value5= order > 4 && tau
                       ? value4 + (value5 - value4) * alpha
                       : value4;


            var dist= Math.abs(value1 - input);
            if(order > 1)
            {
                var dist2= Math.abs(value2 - value1);
                if( dist2 > dist)  dist= dist2;
            }
            if(order > 2)
            {
                var dist3= Math.abs(value3 - value2);
                if( dist3 > dist)  dist= dist3;
            }
            if(order > 3)
            {
                var dist4= Math.abs(value4 - value3);
                if( dist4 > dist)  dist= dist4;
            }
            if(order > 4)
            {
                var dist5= Math.abs(value5 - value4);
                if( dist5 > dist)  dist= dist5;
            }

            if(dist < eps)
            {
                value1= value2= value3= value4= value5= input;
                value_changed= input;
                SetTransparency();
                StopTimer();
        return;
            }

            value_changed= value5;
            SetTransparency();

            lastTick= now;

        }



        function SetTransparency()
        {
            transparency_changed= 1 - value_changed * value_changed;
        }


        "
    }

    DEF Timer TimeSensor
    {
        loop TRUE
    }

    ROUTE Worker.needTimer TO Timer.enabled
    ROUTE Timer.time       TO Worker.tick


    ROUTE EFFS.tau TO Worker.set_tau

	
    DEF LastNode Script
    {
        eventOut SFBool isLoaded IS isLoaded

        url "vrmlscript:

        function initialize()
        {
            isLoaded= true;
        }

        "
    }
}


