/// 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(); } }
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