text: #include <math.h>
  
  #include "gthreeanimationaction.h"
  #include "gthreeanimationmixer.h"
  #include "gthreeprivate.h"
  #include "gthreepropertymixerprivate.h"
  
  typedef struct {
    GthreeAnimationMixer *mixer;
    GthreeAnimationClip *clip;
    GthreeObject *local_root;
    GPtrArray *interpolants; // GthreeInterpolant
    GPtrArray *property_bindings; // GthreePropertyMixer
  
    GthreeInterpolantSettings *interpolant_settings;
    GthreeInterpolant *time_scale_interpolant;
    GthreeInterpolant *weight_interpolant;
    GthreeLoopMode loop_mode;
    int loop_count;
  
    // global mixer time when the action is to be started
    // unset upon start of the action
    float start_time;
    gboolean start_time_set;
  
    // scaled local time of the action
    // gets clamped or wrapped to 0..clip.duration according to loop
    float time;
  
    float time_scale;
    float effective_time_scale;
  
    float weight;
    float effective_weight;
  
    // no. of repetitions when looping
    int repetitions; // <0 == infinite
  
    gboolean paused; // true -> zero effective time scale
    gboolean enabled; // false -> zero effective weight
  
    gboolean clamp_when_finished; // keep feeding the last frame?
    gboolean zero_slope_at_start; // for smooth interpolation w/o separate
    gboolean zero_slope_at_end;   // clips for start, loop and end
  
    GthreeAnimationActionMixerData mixer_data;
  
  } GthreeAnimationActionPrivate;
  
  G_DEFINE_TYPE_WITH_PRIVATE (GthreeAnimationAction, gthree_animation_action, G_TYPE_OBJECT)
  
  static void
  maybe_object_unref (GObject *object)
  {
    if (object)
      g_object_unref (object);
  }
  
  static void
  gthree_animation_action_init (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    priv->interpolants = g_ptr_array_new_with_free_func (g_object_unref);
    priv->property_bindings = g_ptr_array_new_with_free_func ((GDestroyNotify)maybe_object_unref);
  
    priv->loop_mode = GTHREE_LOOP_MODE_REPEAT;
    priv->loop_count = -1;
  
    priv->start_time = 0;
    priv->start_time_set = FALSE;
  
    priv->time_scale = 1.0;
    priv->effective_time_scale = 1.0;
  
    priv->weight = 1.0;
    priv->effective_weight = 1.0;
  
    priv->repetitions = -1;
  
    priv->paused = FALSE;
    priv->enabled = TRUE;
  
    priv->clamp_when_finished = FALSE;
  
    priv->zero_slope_at_end = TRUE;
    priv->zero_slope_at_start = TRUE;
  
    priv->interpolant_settings = gthree_interpolant_settings_new ();
    gthree_interpolant_settings_set_start_ending_mode (priv->interpolant_settings, GTHREE_ENDING_MODE_ZERO_CURVATURE);
    gthree_interpolant_settings_set_end_ending_mode (priv->interpolant_settings, GTHREE_ENDING_MODE_ZERO_CURVATURE);
  
    _gthree_animation_mixer_init_action_data (&priv->mixer_data);
  }
  
  static void
  gthree_animation_action_finalize (GObject *obj)
  {
    GthreeAnimationAction *action = GTHREE_ANIMATION_ACTION (obj);
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    g_clear_object (&priv->mixer);
    g_clear_object (&priv->clip);
    g_clear_object (&priv->local_root);
  
    g_clear_object (&priv->weight_interpolant);
    g_clear_object (&priv->time_scale_interpolant);
  
    g_ptr_array_unref (priv->interpolants);
    g_ptr_array_unref (priv->property_bindings);
  
    G_OBJECT_CLASS (gthree_animation_action_parent_class)->finalize (obj);
  }
  
  static void
  gthree_animation_action_class_init (GthreeAnimationActionClass *klass)
  {
    G_OBJECT_CLASS (klass)->finalize = gthree_animation_action_finalize;
  }
  
  GthreeAnimationAction *
  gthree_animation_action_new (GthreeAnimationMixer *mixer,
                               GthreeAnimationClip *clip,
                               GthreeObject *local_root)
  {
    GthreeAnimationAction *action = g_object_new (GTHREE_TYPE_ANIMATION_ACTION, NULL);
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    int n_tracks, i;
  
    priv->mixer = g_object_ref (mixer);
    priv->clip = g_object_ref (clip);
    if (local_root)
      priv->local_root = g_object_ref (local_root);
  
    n_tracks = gthree_animation_clip_get_n_tracks (clip);
  
    for (i = 0; i < n_tracks; i++)
      {
        GthreeKeyframeTrack *track = gthree_animation_clip_get_track (clip, i);
        GthreeInterpolant *interpolant = gthree_keyframe_track_create_interpolant (track);
        gthree_interpolant_set_settings (interpolant, priv->interpolant_settings);
        g_ptr_array_add (priv->interpolants, interpolant);
      }
  
    g_ptr_array_set_size (priv->property_bindings, n_tracks);
  
    return action;
  }
  
  GthreeAnimationActionMixerData *
  _gthree_animation_action_get_mixer_data (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return &priv->mixer_data;
  }
  
  void
  gthree_animation_action_play (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    _gthree_animation_mixer_activate_action (priv->mixer, action);
  }
  
  
  void
  gthree_animation_action_stop (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    _gthree_animation_mixer_deactivate_action (priv->mixer, action);
    gthree_animation_action_reset (action);
  }
  
  void
  gthree_animation_action_reset (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    priv->paused = FALSE;
    priv->enabled = TRUE;
    priv->time = 0; // restart_clip
    priv->loop_count = -1;
    priv->start_time = 0;
    priv->start_time_set = FALSE;
  
    gthree_animation_action_stop_fading (action);
    gthree_animation_action_stop_warping (action);
  }
  
  gboolean
  gthree_animation_action_is_running (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    return
      priv->enabled &&
      !priv->paused &&
      priv->time_scale != 0 &&
      !priv->start_time_set &&
      _gthree_animation_mixer_is_active_action (priv->mixer, action);
  }
  
  gboolean
  gthree_animation_action_is_scheduled (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    return
      _gthree_animation_mixer_is_active_action (priv->mixer, action);
  }
  
  void
  gthree_animation_action_start_at (GthreeAnimationAction *action,
                                    float time)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    priv->start_time = time;
    priv->start_time_set = TRUE;
  }
  
  /* < 0 repetitions == infinite */
  void
  gthree_animation_action_set_loop_mode (GthreeAnimationAction *action,
                                         GthreeLoopMode loop_mode,
                                         int repetitions)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    priv->loop_mode = loop_mode;
    priv->repetitions = repetitions;
  }
  
  void
  gthree_animation_action_set_enabled (GthreeAnimationAction *action,
                                       gboolean enabled)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    priv->enabled = enabled;
  }
  
  gboolean
  gthree_animation_action_get_enabled (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->enabled;
  }
  
  void
  gthree_animation_action_set_paused (GthreeAnimationAction *action,
                                      gboolean paused)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    priv->paused = paused;
  }
  
  gboolean
  gthree_animation_action_get_paused (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->paused;
  }
  
  void
  gthree_animation_action_set_time (GthreeAnimationAction *action,
                                    float time)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    priv->time = time;
  }
  
  float
  gthree_animation_action_get_time (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->time;
  }
  
  void
  gthree_animation_action_set_effective_weight (GthreeAnimationAction *action,
                                                float weight)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    priv->weight = weight;
    priv->effective_weight = priv->enabled ? weight : 0;
  
    gthree_animation_action_stop_fading (action);
  }
  
  float
  gthree_animation_action_get_effective_weight (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->effective_weight;
  }
  
  float
  gthree_animation_action_get_weight (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->weight;
  }
  
  void
  gthree_animation_action_fade_in (GthreeAnimationAction *action, float duration)
  {
    _gthree_animation_action_schedule_fading (action, duration, 0, 1);
  }
  
  void
  gthree_animation_action_fade_out (GthreeAnimationAction *action, float duration)
  {
    _gthree_animation_action_schedule_fading (action, duration, 1, 0);
  }
  
  void
  gthree_animation_action_cross_fade_from (GthreeAnimationAction *action,
                                           GthreeAnimationAction *fade_out_action,
                                           float duration,
                                           gboolean warp)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    GthreeAnimationActionPrivate *fade_out_priv = gthree_animation_action_get_instance_private (fade_out_action);
  
    gthree_animation_action_fade_out (fade_out_action, duration);
    gthree_animation_action_fade_in (action, duration);
  
    if (warp)
      {
        float fade_in_duration = gthree_animation_clip_get_duration (priv->clip);
        float fade_out_duration = gthree_animation_clip_get_duration (fade_out_priv->clip);
  
        float start_end_ratio = fade_out_duration / fade_in_duration;
        float end_start_ratio = fade_in_duration / fade_out_duration;
  
        gthree_animation_action_warp (fade_out_action, 1.0, start_end_ratio, duration);
        gthree_animation_action_warp (action, end_start_ratio, 1.0, duration);
      }
  }
  
  void
  gthree_animation_action_cross_fade_to (GthreeAnimationAction *action,
                                         GthreeAnimationAction *fade_in_action,
                                         float duration,
                                         gboolean warp)
  {
    gthree_animation_action_cross_fade_from (fade_in_action, action, duration, warp);
  }
  
  void
  gthree_animation_action_stop_fading (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    GthreeInterpolant *weight_interpolant = priv->weight_interpolant;
  
    if (weight_interpolant != NULL)
      {
        priv->weight_interpolant = NULL;
        _gthree_animation_mixer_take_back_control_interpolant (priv->mixer, weight_interpolant);
      }
  }
  
  void
  gthree_animation_action_set_effective_time_scale (GthreeAnimationAction *action,
                                                    float time_scale)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    priv->time_scale = time_scale;
    priv->effective_time_scale = priv->paused ? 0 : time_scale;
  
    gthree_animation_action_stop_warping (action);
  }
  
  // return the time scale considering warping and .paused
  float
  gthree_animation_action_get_effective_time_scale (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->effective_time_scale;
  }
  
  float
  gthree_animation_action_get_time_scale (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->time_scale;
  }
  
  void
  gthree_animation_action_set_duration (GthreeAnimationAction *action,
                                        float duration)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    priv->time_scale = gthree_animation_clip_get_duration (priv->clip) / duration;
  
    gthree_animation_action_stop_warping (action);
  }
  
  void
  gthree_animation_action_sync_with (GthreeAnimationAction *action,
                                     GthreeAnimationAction *other_action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    GthreeAnimationActionPrivate *other_priv = gthree_animation_action_get_instance_private (action);
  
    priv->time = other_priv->time;
    priv->time_scale = other_priv->time_scale;
  
    gthree_animation_action_stop_warping (action);
  }
  
  void
  gthree_animation_action_halt (GthreeAnimationAction *action,
                                float duration)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    gthree_animation_action_warp (action, priv->effective_time_scale, 0, duration);
  }
  
  void
  gthree_animation_action_warp (GthreeAnimationAction *action,
                                float start_time_scale,
                                float end_time_scale,
                                float duration)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    float now = gthree_animation_mixer_get_time (priv->mixer);
    GthreeInterpolant *interpolant = priv->time_scale_interpolant;
    float time_scale = priv->time_scale;
    float *times, *values;
  
    if (interpolant == NULL)
      {
        interpolant = _gthree_animation_mixer_lend_control_interpolant (priv->mixer);
        priv->time_scale_interpolant = interpolant;
      }
  
    times = gthree_attribute_array_peek_float (gthree_interpolant_get_parameter_positions (interpolant));
    values = gthree_attribute_array_peek_float (gthree_interpolant_get_sample_values (interpolant));
  
    times[0] = now;
    times[1] = now + duration;
    values[0] = start_time_scale / time_scale;
    values[1] = end_time_scale / time_scale;
  }
  
  void
  gthree_animation_action_stop_warping (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    GthreeInterpolant *time_scale_interpolant = priv->time_scale_interpolant;
  
    if (time_scale_interpolant)
      {
        priv->time_scale_interpolant = NULL;
        _gthree_animation_mixer_take_back_control_interpolant (priv->mixer, time_scale_interpolant);
      }
  
  }
  
  GthreeAnimationMixer *
  gthree_animation_action_get_mixer (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->mixer;
  }
  
  GthreeAnimationClip *
  gthree_animation_action_get_clip (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->clip;
  }
  
  GthreeObject *
  gthree_animation_action_get_root (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
  
    return priv->local_root ? priv->local_root : gthree_animation_mixer_get_root (priv->mixer);
  }
  
  void
  _gthree_animation_action_update (GthreeAnimationAction *action,
                                   float time,
                                   float delta_time,
                                   float time_direction,
                                   int accu_index)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    float start_time;
    float clip_time;
    float weight;
    int i;
  
    // called by the mixer
  
    if (!priv->enabled)
      {
        // call ._updateWeight() to update ._effectiveWeight
        _gthree_animation_action_update_weight (action, time);
        return;
      }
  
    start_time = priv->start_time;
    if (priv->start_time_set)
      {
        // check for scheduled start of action
        float time_running = (time - start_time) * time_direction;
        if (time_running < 0 || time_direction == 0)
          return; // yet to come / don't decide when delta = 0
  
        // start
        priv->start_time = 0;
        priv->start_time_set = FALSE;
        delta_time = time_direction * time_running;
      }
  
    // apply time scale and advance time
    delta_time *= _gthree_animation_action_update_time_scale (action, time);
    clip_time = _gthree_animation_action_update_time (action, delta_time);
  
    // note: update_time may disable the action resulting in
    // an effective weight of 0
    weight = _gthree_animation_action_update_weight (action, time);
  
    if (weight > 0)
      {
        for (i = 0; i < priv->interpolants->len; i++)
          {
            GthreeInterpolant *interpolant = g_ptr_array_index (priv->interpolants, i);
            GthreePropertyMixer *property_mixer = g_ptr_array_index (priv->property_bindings, i);
            GthreeAttributeArray *result = gthree_interpolant_evaluate (interpolant, clip_time);
  
            gthree_property_mixer_accumulate (property_mixer, result, accu_index, weight);
          }
      }
  }
  
  float
  _gthree_animation_action_update_weight (GthreeAnimationAction *action,
                                          float time)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    float weight = 0;
    GthreeInterpolant *interpolant;
  
    if (priv->enabled)
      {
        weight = priv->weight;
        interpolant = priv->weight_interpolant;
        if (interpolant != NULL)
          {
            GthreeAttributeArray *interpolant_value_array = gthree_interpolant_evaluate (interpolant, time);
            float interpolant_value = gthree_attribute_array_get_float_at (interpolant_value_array, 0, 0);
            GthreeAttributeArray *parameter_positions = gthree_interpolant_get_parameter_positions (interpolant);
  
            weight *= interpolant_value;
  
            if (time > gthree_attribute_array_get_float_at (parameter_positions, 1, 0))
              {
                gthree_animation_action_stop_fading (action);
                if (interpolant_value == 0)
                  priv->enabled = FALSE; // faded out, disable
              }
          }
      }
  
    priv->effective_weight = weight;
    return weight;
  }
  
  float
  _gthree_animation_action_update_time_scale (GthreeAnimationAction *action,
                                              float time)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    float time_scale = 0;
    GthreeInterpolant *interpolant;
  
    if (!priv->paused)
      {
        time_scale = priv->time_scale;
        interpolant = priv->time_scale_interpolant;
        if (interpolant != NULL)
          {
            GthreeAttributeArray *interpolant_value_array = gthree_interpolant_evaluate (interpolant, time);
            float interpolant_value = gthree_attribute_array_get_float_at (interpolant_value_array, 0, 0);
            GthreeAttributeArray *parameter_positions = gthree_interpolant_get_parameter_positions (interpolant);
  
            time_scale *= interpolant_value;
  
            if (time > gthree_attribute_array_get_float_at (parameter_positions, 1, 0))
              {
                gthree_animation_action_stop_warping (action);
                if (time_scale == 0)
                  priv->paused = TRUE; // motion has halted, pause
                else
                  priv->time_scale = time_scale; // warp done - apply final time scale
              }
          }
      }
  
    priv->effective_time_scale = time_scale;
    return time_scale;
  }
  
  float
  _gthree_animation_action_update_time (GthreeAnimationAction *action,
                                        float delta_time)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    float time = priv->time + delta_time;
    float duration = gthree_animation_clip_get_duration (priv->clip);
    GthreeLoopMode loop = priv->loop_mode;
    int loop_count = priv->loop_count;
    gboolean ping_pong = loop == GTHREE_LOOP_MODE_PINGPONG;
  
    if (delta_time == 0)
      {
        if (loop_count == -1)
          return time;
        return ( ping_pong && (loop_count & 1) == 1) ? duration - time : time;
      }
  
    if (loop == GTHREE_LOOP_MODE_ONCE)
      {
        if (loop_count == -1)
          {
            // just started
            priv->loop_count = 0;
            _gthree_animation_action_set_endings(action, TRUE, TRUE, FALSE);
          }
  
        {
          gboolean handled_stop = FALSE;
          if (time >= duration)
            {
              time = duration;
            }
          else if ( time < 0 )
            {
              time = 0;
            }
          else
            {
              priv->time = time;
              handled_stop = TRUE;
            }
  
          if (!handled_stop)
            {
              if (priv->clamp_when_finished)
                priv->paused = TRUE;
              else
                priv->enabled = FALSE;
              priv->time = time;
  
              _gthree_animation_mixer_displatch_finished (priv->mixer, action, delta_time < 0 ? - 1 : 1);
            }
        }
      }
    else
      {
        // repetitive Repeat or PingPong
        if (loop_count == -1)
          {
            // just started
            if (delta_time >= 0)
              {
                loop_count = 0;
                _gthree_animation_action_set_endings (action, TRUE, priv->repetitions == 0, ping_pong);
              }
            else
              {
                // when looping in reverse direction, the initial
                // transition through zero counts as a repetition,
                // so leave loopCount at -1
                _gthree_animation_action_set_endings (action, priv->repetitions == 0, TRUE, ping_pong);
              }
          }
  
        if (time >= duration || time < 0)
          {
            int pending;
            // wrap around
            float loop_delta = floorf (time / duration); // signed
            time -= duration * loop_delta;
  
            loop_count += (int) fabsf(loop_delta);
  
            if (priv->repetitions < 0)
              pending = G_MAXINT; // priv->repetitions == -1 => infinite repetitions
            else
              pending = priv->repetitions - loop_count;
            if (pending <= 0)
              {
                // have to stop (switch state, clamp time, fire event)
                if (priv->clamp_when_finished)
                  priv->paused = TRUE;
                else
                  priv->enabled = FALSE;
  
                time = delta_time > 0 ? duration : 0;
                priv->time = time;
  
                _gthree_animation_mixer_displatch_finished (priv->mixer, action, delta_time > 0 ? 1 : - 1);
              }
            else
              {
                // keep running
                if (pending == 1)
                  {
                    // entering the last round
                    gboolean at_start = delta_time < 0;
                    _gthree_animation_action_set_endings (action, at_start, !at_start, ping_pong);
                  }
                else
                  {
                    _gthree_animation_action_set_endings (action, FALSE, FALSE, ping_pong);
                  }
  
                priv->loop_count = loop_count;
                priv->time = time;
                _gthree_animation_mixer_displatch_loop (priv->mixer, action, (int) loop_delta);
              }
          }
        else
          {
            priv->time = time;
          }
  
        if ( ping_pong && (loop_count & 1) == 1)
          {
            // invert time for the "pong round"
            return duration - time;
          }
      }
  
    return time;
  }
  
  void
  _gthree_animation_action_set_endings (GthreeAnimationAction *action,
                                        gboolean at_start,
                                        gboolean at_end,
                                        gboolean ping_pong)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    GthreeInterpolantSettings *settings = priv->interpolant_settings;
  
    if (ping_pong)
      {
        gthree_interpolant_settings_set_start_ending_mode (settings, GTHREE_ENDING_MODE_ZERO_SLOPE);
        gthree_interpolant_settings_set_end_ending_mode (settings, GTHREE_ENDING_MODE_ZERO_SLOPE);
      }
    else
      {
        // assuming for LoopOnce atStart == atEnd == true
        if (at_start)
          gthree_interpolant_settings_set_start_ending_mode (settings,
                                                             priv->zero_slope_at_start ? GTHREE_ENDING_MODE_ZERO_SLOPE : GTHREE_ENDING_MODE_ZERO_CURVATURE);
        else
          gthree_interpolant_settings_set_start_ending_mode (settings,
                                                             GTHREE_ENDING_MODE_WRAP_AROUND);
  
        if (at_end)
          gthree_interpolant_settings_set_end_ending_mode (settings,
                                                           priv->zero_slope_at_end ? GTHREE_ENDING_MODE_ZERO_SLOPE : GTHREE_ENDING_MODE_ZERO_CURVATURE);
        else
          gthree_interpolant_settings_set_end_ending_mode (settings,
                                                           GTHREE_ENDING_MODE_WRAP_AROUND);
      }
  }
  
  void
  _gthree_animation_action_schedule_fading (GthreeAnimationAction *action,
                                            float duration,
                                            float weight_now,
                                            float weight_then)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    GthreeInterpolant *interpolant = priv->weight_interpolant;
    float now = gthree_animation_mixer_get_time (priv->mixer);
    float *times, *values;
  
    if (interpolant == NULL)
      {
        interpolant = _gthree_animation_mixer_lend_control_interpolant (priv->mixer);
        priv->weight_interpolant = interpolant;
      }
  
    times = gthree_attribute_array_peek_float (gthree_interpolant_get_parameter_positions (interpolant));
    values = gthree_attribute_array_peek_float (gthree_interpolant_get_sample_values (interpolant));
  
    times[0] = now;
    times[1] = now + duration;
    values[0] = weight_now;
    values[1] = weight_then;
  }
  
  GPtrArray *
  _gthree_animation_action_get_property_bindings (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    return priv->property_bindings;
  }
  
  GPtrArray *
  _gthree_animation_action_get_interpolants (GthreeAnimationAction *action)
  {
    GthreeAnimationActionPrivate *priv = gthree_animation_action_get_instance_private (action);
    return priv->interpolants;
  }
  