/// compute interpolation parameters from keyframes and an iterator /// to the next keyframe (the first with time > t) static void interParams(const KeyFrameSet &keyFrames, double t, const KeyFrameSet::const_iterator &itup, double *tcur, double *vcur, double *vcurDerivRight, Natron::KeyframeType *interp, double *tnext, double *vnext, double *vnextDerivLeft, Natron::KeyframeType *interpNext) { assert(itup == keyFrames.end() || t < itup->getTime()); if (itup == keyFrames.begin()) { //if all keys have a greater time // get the first keyframe *tnext = itup->getTime(); *vnext = itup->getValue(); *vnextDerivLeft = itup->getLeftDerivative(); *interpNext = itup->getInterpolation(); *tcur = *tnext - 1.; *vcur = *vnext; *vcurDerivRight = 0.; *interp = Natron::KEYFRAME_NONE; } else if (itup == keyFrames.end()) { //if we found no key that has a greater time // get the last keyframe KeyFrameSet::const_reverse_iterator itlast = keyFrames.rbegin(); *tcur = itlast->getTime(); *vcur = itlast->getValue(); *vcurDerivRight = itlast->getRightDerivative(); *interp = itlast->getInterpolation(); *tnext = *tcur + 1.; *vnext = *vcur; *vnextDerivLeft = 0.; *interpNext = Natron::KEYFRAME_NONE; } else { // between two keyframes // get the last keyframe with time <= t KeyFrameSet::const_iterator itcur = itup; --itcur; assert(itcur->getTime() <= t); *tcur = itcur->getTime(); *vcur = itcur->getValue(); *vcurDerivRight = itcur->getRightDerivative(); *interp = itcur->getInterpolation(); *tnext = itup->getTime(); *vnext = itup->getValue(); *vnextDerivLeft = itup->getLeftDerivative(); *interpNext = itup->getInterpolation(); } }
int Curve::keyFrameIndex(double time) const { QReadLocker l(&_imp->_lock); int i = 0; double paramEps; if (_imp->xMax != INT_MAX && _imp->xMin != INT_MIN) { paramEps = NATRON_CURVE_X_SPACING_EPSILON * std::abs(_imp->xMax - _imp->xMin); } else { paramEps = NATRON_CURVE_X_SPACING_EPSILON; } for (KeyFrameSet::const_iterator it = _imp->keyFrames.begin(); it!=_imp->keyFrames.end() && (it->getTime() < time+paramEps); ++it, ++i) { if (std::abs(it->getTime() - time) < paramEps) { return i; } } return -1; }
bool Curve::getPreviousKeyframeTime(double time,KeyFrame* k) const { assert(k); QReadLocker l(&_imp->_lock); if (_imp->keyFrames.empty()) { return false; } KeyFrameSet::const_iterator upper = _imp->keyFrames.end(); for (KeyFrameSet::const_iterator it = _imp->keyFrames.begin(); it!=_imp->keyFrames.end(); ++it) { if (it->getTime() > time) { upper = it; break; } else if (it->getTime() == time) { if (it == _imp->keyFrames.begin()) { return false; } else { --it; *k = *it; return true; } } } if (upper == _imp->keyFrames.end()) { *k = *_imp->keyFrames.rbegin(); return true; } else if (upper == _imp->keyFrames.begin()) { return false; } else { ///If we reach here the previous keyframe is exactly the previous to upper because we already checked ///in the for loop that the previous key wasn't equal to the given time --upper; assert(upper->getTime() < time); *k = *upper; return true; } }
bool Curve::getNextKeyframeTime(double time,KeyFrame* k) const { assert(k); QReadLocker l(&_imp->_lock); if (_imp->keyFrames.empty()) { return false; } KeyFrameSet::const_iterator upper = _imp->keyFrames.end(); for (KeyFrameSet::const_iterator it = _imp->keyFrames.begin(); it!=_imp->keyFrames.end(); ++it) { if (it->getTime() > time) { upper = it; break; } } if (upper == _imp->keyFrames.end()) { return false; } else { *k = *upper; return true; } }
bool Curve::getNearestKeyFrameWithTime(double time,KeyFrame* k) const { assert(k); QReadLocker l(&_imp->_lock); if (_imp->keyFrames.empty()) { return false; } KeyFrameSet::const_iterator upper = _imp->keyFrames.end(); for (KeyFrameSet::const_iterator it = _imp->keyFrames.begin(); it!=_imp->keyFrames.end(); ++it) { if (it->getTime() > time) { upper = it; break; } else if (it->getTime() == time) { *k = *it; return true; } } if (upper == _imp->keyFrames.begin()) { *k = *upper; return true; } KeyFrameSet::const_iterator lower = upper; --lower; if (upper == _imp->keyFrames.end()) { *k = *lower; return true; } assert(time - lower->getTime() > 0); assert(upper->getTime() - time > 0); if ((upper->getTime() - time) < (time - lower->getTime())) { *k = *upper; } else { *k = *lower; } return true; }
double Curve::getIntegrateFromTo(double t1, double t2) const { QReadLocker l(&_imp->_lock); bool opposite = false; // the following assumes that t2 > t1. If it's not the case, swap them and return the opposite. if (t1 > t2) { opposite = true; std::swap(t1,t2); } if (_imp->keyFrames.empty()) { throw std::runtime_error("Curve has no control points!"); } assert(_imp->type == CurvePrivate::DOUBLE_CURVE); // only real-valued curves can be derived // even when there is only one keyframe, there may be tangents! //if (_imp->keyFrames.size() == 1) { // //if there's only 1 keyframe, don't bother interpolating // return (*_imp->keyFrames.begin()).getValue(); //} double tcur,tnext; double vcurDerivRight ,vnextDerivLeft ,vcur ,vnext ; Natron::KeyframeType interp ,interpNext; KeyFrame k(t1,0.); // find the first keyframe with time strictly greater than t1 KeyFrameSet::const_iterator itup; itup = _imp->keyFrames.upper_bound(k); interParams(_imp->keyFrames, t1, itup, &tcur, &vcur, &vcurDerivRight, &interp, &tnext, &vnext, &vnextDerivLeft, &interpNext); double sum = 0.; // while there are still keyframes after the current time, add to the total sum and advance while (itup != _imp->keyFrames.end() && itup->getTime() < t2) { // add integral from t1 to itup->getTime() to sum if (mustClamp()) { std::pair<double,double> minmax = getCurveYRange(); sum += Natron::integrate_clamp(tcur,vcur, vcurDerivRight, vnextDerivLeft, tnext,vnext, t1, itup->getTime(), minmax.first, minmax.second, interp, interpNext); } else { sum += Natron::integrate(tcur,vcur, vcurDerivRight, vnextDerivLeft, tnext,vnext, t1, itup->getTime(), interp, interpNext); } // advance t1 = itup->getTime(); ++itup; interParams(_imp->keyFrames, t1, itup, &tcur, &vcur, &vcurDerivRight, &interp, &tnext, &vnext, &vnextDerivLeft, &interpNext); } assert(itup == _imp->keyFrames.end() || t2 <= itup->getTime()); // add integral from t1 to t2 to sum if (mustClamp()) { std::pair<double,double> minmax = getCurveYRange(); sum += Natron::integrate_clamp(tcur,vcur, vcurDerivRight, vnextDerivLeft, tnext,vnext, t1, t2, minmax.first, minmax.second, interp, interpNext); } else { sum += Natron::integrate(tcur,vcur, vcurDerivRight, vnextDerivLeft, tnext,vnext, t1, t2, interp, interpNext); } return opposite ? -sum : sum; }
KeyFrameSet::iterator Curve::refreshDerivatives(Curve::CurveChangedReason reason, KeyFrameSet::iterator key) { // PRIVATE - should not lock double tcur = key->getTime(); double vcur = key->getValue(); double tprev, vprev, tnext, vnext, vprevDerivRight, vnextDerivLeft; Natron::KeyframeType prevType, nextType; if (key == _imp->keyFrames.begin()) { tprev = tcur; vprev = vcur; vprevDerivRight = 0.; prevType = Natron::KEYFRAME_NONE; } else { KeyFrameSet::const_iterator prev = key; --prev; tprev = prev->getTime(); vprev = prev->getValue(); vprevDerivRight = prev->getRightDerivative(); prevType = prev->getInterpolation(); //if prev is the first keyframe, and not edited by the user then interpolate linearly if (prev == _imp->keyFrames.begin() && prevType != Natron::KEYFRAME_FREE && prevType != Natron::KEYFRAME_BROKEN) { prevType = Natron::KEYFRAME_LINEAR; } } KeyFrameSet::const_iterator next = key; ++next; if (next == _imp->keyFrames.end()) { tnext = tcur; vnext = vcur; vnextDerivLeft = 0.; nextType = Natron::KEYFRAME_NONE; } else { tnext = next->getTime(); vnext = next->getValue(); vnextDerivLeft = next->getLeftDerivative(); nextType = next->getInterpolation(); KeyFrameSet::const_iterator nextnext = next; ++nextnext; //if next is thelast keyframe, and not edited by the user then interpolate linearly if (nextnext == _imp->keyFrames.end() && nextType != Natron::KEYFRAME_FREE && nextType != Natron::KEYFRAME_BROKEN) { nextType = Natron::KEYFRAME_LINEAR; } } double vcurDerivLeft,vcurDerivRight; assert(key->getInterpolation() != Natron::KEYFRAME_NONE && key->getInterpolation() != Natron::KEYFRAME_BROKEN && key->getInterpolation() != Natron::KEYFRAME_FREE); Natron::autoComputeDerivatives(prevType, key->getInterpolation(), nextType, tprev, vprev, tcur, vcur, tnext, vnext, vprevDerivRight, vnextDerivLeft, &vcurDerivLeft, &vcurDerivRight); KeyFrame newKey(*key); newKey.setLeftDerivative(vcurDerivLeft); newKey.setRightDerivative(vcurDerivRight); std::pair<KeyFrameSet::iterator,bool> newKeyIt = _imp->keyFrames.insert(newKey); // keyframe at this time exists, erase and insert again if (!newKeyIt.second) { _imp->keyFrames.erase(newKeyIt.first); newKeyIt = _imp->keyFrames.insert(newKey); assert(newKeyIt.second); } key = newKeyIt.first; if (reason != DERIVATIVES_CHANGED) { key = evaluateCurveChanged(DERIVATIVES_CHANGED,key); } return key; }
void CurveGui::nextPointForSegment(const double x, // < in curve coordinates const KeyFrameSet & keys, const bool isPeriodic, const double parametricXMin, const double parametricXMax, KeyFrameSet::const_iterator* lastUpperIt, double* x2WidgetCoords, KeyFrame* x1Key, bool* isx1Key) { // always running in the main thread assert( qApp && qApp->thread() == QThread::currentThread() ); assert( !keys.empty() ); *isx1Key = false; // If non periodic and out of curve range, draw straight lines from widget border to // the keyframe on the side if (!isPeriodic && x < keys.begin()->getTime()) { *x2WidgetCoords = keys.begin()->getTime(); double y; _imp->curveWidget->toWidgetCoordinates(x2WidgetCoords, &y); *x1Key = *keys.begin(); *isx1Key = true; return; } else if (!isPeriodic && x >= keys.rbegin()->getTime()) { *x2WidgetCoords = _imp->curveWidget->width() - 1; return; } // We're between 2 keyframes or the curve is periodic, get the upper and lower keyframes widget coordinates // Points to the first keyframe with a greater time (in widget coords) than x1 KeyFrameSet::const_iterator upperIt = keys.end(); // If periodic, bring back x in the period range (in widget coordinates) double xClamped = x; double period = parametricXMax - parametricXMin; { //KeyFrameSet::const_iterator start = keys.begin(); const double xMin = parametricXMin;// + start->getTime(); const double xMax = parametricXMax;// + start->getTime(); if ((x < xMin || x > xMax) && isPeriodic) { xClamped = std::fmod(x - xMin, period) + parametricXMin; if (xClamped < xMin) { xClamped += period; } assert(xClamped >= xMin && xClamped <= xMax); } } { KeyFrameSet::const_iterator itKeys = keys.begin(); if ( *lastUpperIt != keys.end() ) { // If we already have called this function before, start from the previously // computed iterator to avoid n square complexity itKeys = *lastUpperIt; } else { // Otherwise start from the begining itKeys = keys.begin(); } *lastUpperIt = keys.end(); for (; itKeys != keys.end(); ++itKeys) { if (itKeys->getTime() > xClamped) { upperIt = itKeys; *lastUpperIt = upperIt; break; } } } double tprev, vprev, vprevDerivRight, tnext, vnext, vnextDerivLeft; if ( upperIt == keys.end() ) { // We are in a periodic curve: we are in-between the last keyframe and the parametric xMax // If the curve is non periodic, it should have been handled in the 2 cases above: we only draw a straightline // from the widget border to the first/last keyframe assert(isPeriodic); KeyFrameSet::const_iterator start = keys.begin(); KeyFrameSet::const_reverse_iterator last = keys.rbegin(); tprev = last->getTime(); vprev = last->getValue(); vprevDerivRight = last->getRightDerivative(); tnext = std::fmod(last->getTime() - start->getTime(), period) + tprev; //xClamped += period; vnext = start->getValue(); vnextDerivLeft = start->getLeftDerivative(); } else if ( upperIt == keys.begin() ) { // We are in a periodic curve: we are in-between the parametric xMin and the first keyframe // If the curve is non periodic, it should have been handled in the 2 cases above: we only draw a straightline // from the widget border to the first/last keyframe assert(isPeriodic); KeyFrameSet::const_reverse_iterator last = keys.rbegin(); tprev = last->getTime(); //xClamped -= period; vprev = last->getValue(); vprevDerivRight = last->getRightDerivative(); tnext = std::fmod(last->getTime() - upperIt->getTime(), period) + tprev; vnext = upperIt->getValue(); vnextDerivLeft = upperIt->getLeftDerivative(); } else { // in-between 2 keyframes KeyFrameSet::const_iterator prev = upperIt; --prev; tprev = prev->getTime(); vprev = prev->getValue(); vprevDerivRight = prev->getRightDerivative(); tnext = upperIt->getTime(); vnext = upperIt->getValue(); vnextDerivLeft = upperIt->getLeftDerivative(); } double normalizeTimeRange = tnext - tprev; if (normalizeTimeRange == 0) { // Only 1 keyframe, draw a horizontal line *x2WidgetCoords = _imp->curveWidget->width() - 1; return; } assert(normalizeTimeRange > 0.); double t = ( xClamped - tprev ) / normalizeTimeRange; double P3 = vnext; double P0 = vprev; // Hermite coefficients P0' and P3' are for t normalized in [0,1] double P3pl = vnextDerivLeft / normalizeTimeRange; // normalize for t \in [0,1] double P0pr = vprevDerivRight / normalizeTimeRange; // normalize for t \in [0,1] double secondDer = 6. * (1. - t) * (P3 - P3pl / 3. - P0 - 2. * P0pr / 3.) + 6. * t * (P0 - P3 + 2 * P3pl / 3. + P0pr / 3. ); double secondDerWidgetCoord = _imp->curveWidget->toWidgetCoordinates(0, secondDer).y(); double normalizedSecondDerWidgetCoord = std::abs(secondDerWidgetCoord / normalizeTimeRange); // compute delta_x so that the y difference between the derivative and the curve is at most // 1 pixel (use the second order Taylor expansion of the function) double delta_x = std::max(2. / std::max(std::sqrt(normalizedSecondDerWidgetCoord), 0.1), 1.); // The x widget coordinate of the next keyframe double tNextWidgetCoords = _imp->curveWidget->toWidgetCoordinates(tnext, 0).x(); // The widget coordinate of the x passed in parameter but clamped to the curve period double xClampedWidgetCoords = _imp->curveWidget->toWidgetCoordinates(xClamped, 0).x(); // The real x passed in parameter in widget coordinates double xWidgetCoords = _imp->curveWidget->toWidgetCoordinates(x, 0).x(); double x2ClampedWidgetCoords = xClampedWidgetCoords + delta_x; double deltaXtoNext = (tNextWidgetCoords - xClampedWidgetCoords); // If nearby next key, clamp to it if (x2ClampedWidgetCoords > tNextWidgetCoords && deltaXtoNext > 1e-6) { // x2 is the position of the next keyframe with the period removed *x2WidgetCoords = xWidgetCoords + deltaXtoNext; x1Key->setValue(vnext); x1Key->setTime(x + (tnext - xClamped)); *isx1Key = true; } else { // just add the delta to the x widget coord *x2WidgetCoords = xWidgetCoords + delta_x; } } // nextPointForSegment