@@ -379,40 +379,123 @@ void Keyframe::SetJsonValue(Json::Value root) {
379379// Get the fraction that represents how many times this value is repeated in the curve
380380// This is depreciated and will be removed soon.
381381Fraction Keyframe::GetRepeatFraction (int64_t index) const {
382- // Is index a valid point?
383- if (index >= 1 && (index + 1 ) < GetLength ()) {
384- int64_t current_value = GetLong (index);
385- int64_t previous_repeats = 0 ;
386- int64_t next_repeats = 0 ;
387-
388- // Loop backwards and look for the next unique value
389- for (int64_t i = index; i > 0 ; --i) {
390- if (GetLong (i) == current_value) {
391- // Found same value
392- previous_repeats++;
382+ // Frame numbers (index) outside of the "defined" range of this
383+ // keyframe result in a 1/1 default value.
384+ if (index < 1 || (index + 1 ) >= GetLength ()) {
385+ return Fraction (1 ,1 );
386+ }
387+ assert (Points.size () > 1 ); // Due to ! ((index + 1) >= GetLength) there are at least two points!
388+
389+ // First, get the value at the given frame and the closest point
390+ // to the right.
391+ int64_t const current_value = GetLong (index);
392+ std::vector<Point>::const_iterator const candidate =
393+ std::lower_bound (begin (Points), end (Points), static_cast <double >(index), IsPointBeforeX);
394+ assert (candidate != end (Points)); // Due to the (index + 1) >= GetLength check above!
395+
396+ // Calculate how many of the next values are going to be the same:
397+ int64_t next_repeats = 0 ;
398+ std::vector<Point>::const_iterator i = candidate;
399+ // If the index (frame number) is the X coordinate of the closest
400+ // point, then look at the segment to the right; the "current"
401+ // segement is not interesting because we're already at the last
402+ // value of it.
403+ if (i->co .X == index) {
404+ ++i;
405+ }
406+ // Skip over "constant" (when rounded) segments.
407+ bool all_constant = true ;
408+ for (; i != end (Points); ++i) {
409+ if (current_value != round (i->co .Y )) {
410+ all_constant = false ;
411+ break ;
412+ }
413+ }
414+ if (! all_constant) {
415+ // Found a point which defines a segment which will give a
416+ // different value than the current value. This means we
417+ // moved at least one segment to the right, thus we cannot be
418+ // at the first point.
419+ assert (i != begin (Points));
420+ Point const left = *(i - 1 );
421+ Point const right = *i;
422+ // Binary search for the first value which is different from
423+ // the current value.
424+ bool const increasing = current_value < round (i->co .Y );
425+ int64_t start = left.co .X ;
426+ int64_t stop = right.co .X ;
427+ while (start < stop) {
428+ int64_t const mid = (start + stop + 1 ) / 2 ;
429+ double const value = InterpolateBetween (left, right, mid, 0.01 );
430+ bool const smaller = round (value) <= current_value;
431+ bool const larger = round (value) >= current_value;
432+ if ((increasing && smaller) || (!increasing && larger)) {
433+ start = mid;
393434 } else {
394- // Found non repeating value, no more repeats found
395- break ;
435+ stop = mid - 1 ;
396436 }
397437 }
438+ next_repeats = start - index;
439+ } else {
440+ // All values to the right are the same!
441+ next_repeats = Points.back ().co .X - index;
442+ }
398443
399- // Loop forwards and look for the next unique value
400- for (int64_t i = index + 1 ; i < GetLength (); ++i) {
401- if (GetLong (i) == current_value) {
402- // Found same value
403- next_repeats++;
444+ // Now look to the left, to the previous values.
445+ all_constant = true ;
446+ i = candidate;
447+ if (i != begin (Points)) {
448+ // The binary search below assumes i to be the left point;
449+ // candidate is the right point of the current segment
450+ // though. So change this if possible. If this branch is NOT
451+ // taken, then we're at/before the first point and all is
452+ // constant!
453+ --i;
454+ }
455+ int64_t previous_repeats = 0 ;
456+ // Skip over constant (when rounded) segments!
457+ for (; i != begin (Points); --i) {
458+ if (current_value != round (i->co .Y )) {
459+ all_constant = false ;
460+ break ;
461+ }
462+ }
463+ // Special case when skipped until the first point, but the first
464+ // point is actually different. Will not happen if index is
465+ // before the first point!
466+ if (current_value != round (i->co .Y )) {
467+ assert (i != candidate);
468+ all_constant = false ;
469+ }
470+ if (! all_constant) {
471+ // There are at least two points, and we're not at the end,
472+ // thus the following is safe!
473+ Point const left = *i;
474+ Point const right = *(i + 1 );
475+ // Binary search for the last value (seen from the left to
476+ // right) to be different than the current value.
477+ bool const increasing = current_value > round (left.co .Y );
478+ int64_t start = left.co .X ;
479+ int64_t stop = right.co .X ;
480+ while (start < stop) {
481+ int64_t const mid = (start + stop + 1 ) / 2 ;
482+ double const value = InterpolateBetween (left, right, mid, 0.01 );
483+ bool const smaller = round (value) < current_value;
484+ bool const larger = round (value) > current_value;
485+ if ((increasing && smaller) || (!increasing && larger)) {
486+ start = mid;
404487 } else {
405- // Found non repeating value, no more repeats found
406- break ;
488+ stop = mid - 1 ;
407489 }
408490 }
409-
410- int64_t total_repeats = previous_repeats + next_repeats;
411- return Fraction (previous_repeats, total_repeats);
491+ previous_repeats = index - start;
492+ } else {
493+ // Every previous value is the same (rounded) as the current
494+ // value.
495+ previous_repeats = index;
412496 }
413- else
414- // return a blank coordinate
415- return Fraction (1 ,1 );
497+ int64_t total_repeats = previous_repeats + next_repeats;
498+ return Fraction (previous_repeats, total_repeats);
416499}
417500
418501// Get the change in Y value (from the previous Y value)
0 commit comments