diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c524c2c93..97294c9b43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Always serialize nodes with an expression or a link, even if they have the default value. #585 - Support cloned group nodes or hard links between groups. #568 #579 #594 #598 - Default keyframe interpolation method for strokes and shapes is now "Smooth" (was "Linear"). #597 +- Fix animation of Roto Beziers with non-broken tangents. #102 ### Plugins diff --git a/Engine/Bezier.cpp b/Engine/Bezier.cpp index eb968705e2..a0f96e5084 100644 --- a/Engine/Bezier.cpp +++ b/Engine/Bezier.cpp @@ -1805,6 +1805,7 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam, double fry, bool isLeft, bool moveBoth, + bool breakTangents, bool onlyFeather) { ///only called on the main-thread @@ -1901,6 +1902,7 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam, right = Transform::matApply(invTrans, right); (cp)->setRightBezierPointAtTime(useGuiCurve, time, right.x, right.y); } + (cp)->setBroken(breakTangents); } if ( moveFeather && useFeatherPoints() ) { if (isLeft || moveBoth) { @@ -1915,6 +1917,7 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam, rightF = Transform::matApply(invTrans, rightF); (fp)->setRightBezierPointAtTime(useGuiCurve, time, rightF.x, rightF.y); } + (fp)->setBroken(breakTangents); } if (!isOnKeyframe) { keySet = true; @@ -1936,6 +1939,7 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam, right = Transform::matApply(invTrans, right); (cp)->setRightBezierStaticPosition(useGuiCurve, right.x, right.y); } + (cp)->setBroken(breakTangents); } if ( moveFeather && useFeatherPoints() ) { if (isLeft || moveBoth) { @@ -1950,6 +1954,7 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam, rightF = Transform::matApply(invTrans, rightF); (fp)->setRightBezierStaticPosition(useGuiCurve, rightF.x, rightF.y); } + (fp)->setBroken(breakTangents); } } @@ -1968,6 +1973,7 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam, left.x += lx; left.y += ly; left = Transform::matApply(invTrans, left); (cp)->setLeftBezierPointAtTime(useGuiCurve, *it2, left.x, left.y); + (cp)->setBroken(breakTangents); } if ( moveFeather && useFeatherPoints() ) { (fp)->getLeftBezierPointAtTime(useGuiCurve, *it2, ViewIdx(0), &leftF.x, &leftF.y); @@ -1975,6 +1981,7 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam, leftF.x += flx; leftF.y += fly; leftF = Transform::matApply(invTrans, leftF); (fp)->setLeftBezierPointAtTime(useGuiCurve, *it2, leftF.x, leftF.y); + (fp)->setBroken(breakTangents); } } else { if (moveControlPoint) { @@ -1983,13 +1990,15 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam, right.x += rx; right.y += ry; right = Transform::matApply(invTrans, right); (cp)->setRightBezierPointAtTime(useGuiCurve, *it2, right.x, right.y); + (cp)->setBroken(breakTangents); } if ( moveFeather && useFeatherPoints() ) { - (cp)->getRightBezierPointAtTime(useGuiCurve, *it2, ViewIdx(0), &rightF.x, &rightF.y); + (fp)->getRightBezierPointAtTime(useGuiCurve, *it2, ViewIdx(0), &rightF.x, &rightF.y); rightF = Transform::matApply(trans, rightF); rightF.x += frx; rightF.y += fry; rightF = Transform::matApply(invTrans, rightF); - (cp)->setRightBezierPointAtTime(useGuiCurve, *it2, rightF.x, rightF.y); + (fp)->setRightBezierPointAtTime(useGuiCurve, *it2, rightF.x, rightF.y); + (fp)->setBroken(breakTangents); } } } @@ -2016,7 +2025,7 @@ Bezier::moveLeftBezierPoint(int index, double dx, double dy) { - moveBezierPointInternal(NULL, NULL, index, time, dx, dy, 0, 0, dx, dy, 0, 0, true, false, false); + moveBezierPointInternal(NULL, NULL, index, time, dx, dy, 0, 0, dx, dy, 0, 0, true, false, false, false); } // moveLeftBezierPoint void @@ -2025,7 +2034,7 @@ Bezier::moveRightBezierPoint(int index, double dx, double dy) { - moveBezierPointInternal(NULL, NULL, index, time, 0, 0, dx, dy, 0, 0, dx, dy, false, false, false); + moveBezierPointInternal(NULL, NULL, index, time, 0, 0, dx, dy, 0, 0, dx, dy, false, false, false, false); } // moveRightBezierPoint void @@ -2040,9 +2049,10 @@ Bezier::movePointLeftAndRightIndex(BezierCP & cp, double fly, double frx, double fry, + bool breakTangents, bool onlyFeather) { - moveBezierPointInternal(&cp, &fp, -1, time, lx, ly, rx, ry, flx, fly, frx, fry, false, true, onlyFeather); + moveBezierPointInternal(&cp, &fp, -1, time, lx, ly, rx, ry, flx, fly, frx, fry, false, true, breakTangents, onlyFeather); } void diff --git a/Engine/Bezier.h b/Engine/Bezier.h index 7f852501d7..a02a3f745d 100644 --- a/Engine/Bezier.h +++ b/Engine/Bezier.h @@ -243,6 +243,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON double flx, double fly, double frx, double fry, bool isLeft, bool moveBoth, + bool breakTangents, bool onlyFeather); public: @@ -292,6 +293,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON double time, double lx, double ly, double rx, double ry, double flx, double fly, double frx, double fry, + bool breakTangents, bool onlyFeather); diff --git a/Engine/BezierCP.cpp b/Engine/BezierCP.cpp index 6b5ba988f1..35a77fa0ca 100644 --- a/Engine/BezierCP.cpp +++ b/Engine/BezierCP.cpp @@ -36,6 +36,7 @@ #include #include +#include #include "Engine/Bezier.h" #include "Engine/KnobTypes.h" @@ -178,9 +179,10 @@ BezierCP::setRightBezierStaticPosition(bool useGuiCurves, bool BezierCP::getLeftBezierPointAtTime(bool useGuiCurves, double time, - ViewIdx /*view*/, + ViewIdx view, double* x, - double* y) const + double* y, + bool reAlign) const { KeyFrame k; bool ret = false; @@ -214,6 +216,28 @@ BezierCP::getLeftBezierPointAtTime(bool useGuiCurves, ret = false; } + // If the CP does not have broken tangents, realign the Bezier points. + // Fixes https://github.com/NatronGitHub/Natron/issues/202 + if (reAlign && !_imp->broken) { + double px, py, qx, qy; + getPositionAtTime(useGuiCurves, time, view, &px, &py); + getRightBezierPointAtTime(useGuiCurves, time, view, &qx, &qy, false); + // The vector between the two bezier points + double vx = qx - *x; + double vy = qy - *y; + double v = std::sqrt(vx * vx + vy *vy); + // The normal vector + double nx = vy / v; + double ny = -vx / v; + // The (signed) distance from the position to the segment + double d = nx * (px - *x) + ny * (py - *y); + // displace the key point in the normal direction. + *x += d * nx; + *y += d * ny; + // Verify that d is now zero + // d = nx * (px - *x) + ny * (py - *y); + // qDebug() << 'd' << d; + } return ret; } // BezierCP::getLeftBezierPointAtTime @@ -221,9 +245,10 @@ BezierCP::getLeftBezierPointAtTime(bool useGuiCurves, bool BezierCP::getRightBezierPointAtTime(bool useGuiCurves, double time, - ViewIdx /*view*/, + ViewIdx view, double *x, - double *y) const + double *y, + bool reAlign) const { KeyFrame k; bool ret = false; @@ -257,6 +282,28 @@ BezierCP::getRightBezierPointAtTime(bool useGuiCurves, ret = false; } + // If the CP does not have broken tangents, realign the Bezier points. + // Fixes https://github.com/NatronGitHub/Natron/issues/202 + if (reAlign && !_imp->broken) { + double px, py, qx, qy; + getPositionAtTime(useGuiCurves, time, view, &px, &py); + getLeftBezierPointAtTime(useGuiCurves, time, view, &qx, &qy, false); + // The vector between the two bezier points + double vx = qx - *x; + double vy = qy - *y; + double v = std::sqrt(vx * vx + vy *vy); + // The normal vector + double nx = vy / v; + double ny = -vx / v; + // The (signed) distance from the position to the segment + double d = nx * (px - *x) + ny * (py - *y); + // displace the key point in the normal direction. + *x += d * nx; + *y += d * ny; + // Verify that d is now zero + // d = nx * (px - *x) + ny * (py - *y); + // qDebug() << 'd' << d; + } return ret; } // BezierCP::getRightBezierPointAtTime @@ -311,6 +358,18 @@ BezierCP::setRightBezierPointAtTime(bool useGuiCurves, } } +void +BezierCP::setBroken(bool broken) +{ + _imp->broken = broken; +} + +bool +BezierCP::getBroken() const +{ + return _imp->broken; +} + void BezierCP::removeAnimation(bool useGuiCurves, double currentTime) diff --git a/Engine/BezierCP.h b/Engine/BezierCP.h index ddb1092fc8..86dc238adc 100644 --- a/Engine/BezierCP.h +++ b/Engine/BezierCP.h @@ -96,6 +96,8 @@ class BezierCP void setRightBezierPointAtTime(bool useGuiCurves, double time, double x, double y); + void setBroken(bool broken); + void setStaticPosition(bool useGuiCurves, double x, double y); void setLeftBezierStaticPosition(bool useGuiCurves, double x, double y); @@ -122,10 +124,12 @@ class BezierCP bool getPositionAtTime(bool useGuiCurves, double time, ViewIdx view, double* x, double* y) const; - bool getLeftBezierPointAtTime(bool useGuiCurves, double time, ViewIdx view, double* x, double* y) const; + bool getLeftBezierPointAtTime(bool useGuiCurves, double time, ViewIdx view, double* x, double* y, bool reAlign = true) const; - bool getRightBezierPointAtTime(bool useGuiCurves, double time, ViewIdx view, double *x, double *y) const; + bool getRightBezierPointAtTime(bool useGuiCurves, double time, ViewIdx view, double *x, double *y, bool reAlign = true) const; + bool getBroken() const; + bool hasKeyFrameAtTime(bool useGuiCurves, double time) const; void getKeyframeTimes(bool useGuiCurves, std::set* times) const; diff --git a/Engine/BezierCPPrivate.h b/Engine/BezierCPPrivate.h index 2b1995da87..f21547145f 100644 --- a/Engine/BezierCPPrivate.h +++ b/Engine/BezierCPPrivate.h @@ -51,6 +51,7 @@ struct BezierCPPrivate CurvePtr guiCurveX, guiCurveY; double x, y; //< used when there is no keyframe double guiX, guiY; + bool broken; //< true if both tangents are independent ///the animation curves for the derivatives ///They do not need to be protected as Curve is a thread-safe class. @@ -70,6 +71,7 @@ struct BezierCPPrivate , y(0) , guiX(0) , guiY(0) + , broken(false) , curveLeftBezierX() , curveRightBezierX() , curveLeftBezierY() diff --git a/Engine/BezierCPSerialization.h b/Engine/BezierCPSerialization.h index 8bb424f7ad..633143565b 100644 --- a/Engine/BezierCPSerialization.h +++ b/Engine/BezierCPSerialization.h @@ -50,7 +50,8 @@ GCC_DIAG_ON(unused-parameter) #define BEZIER_CP_INTRODUCES_OFFSET 2 #define BEZIER_CP_FIX_BUG_CURVE_POINTER 3 #define BEZIER_CP_REMOVE_OFFSET 4 -#define BEZIER_CP_VERSION BEZIER_CP_REMOVE_OFFSET +#define BEZIER_CP_ADD_BROKEN 5 +#define BEZIER_CP_VERSION BEZIER_CP_ADD_BROKEN NATRON_NAMESPACE_ENTER @@ -80,6 +81,7 @@ BezierCP::save(Archive & ar, ar & ::boost::serialization::make_nvp("Right_X_animation", *_imp->curveRightBezierX); ar & ::boost::serialization::make_nvp("Right_Y", _imp->rightY); ar & ::boost::serialization::make_nvp("Right_Y_animation", *_imp->curveRightBezierY); + ar & ::boost::serialization::make_nvp("Broken", _imp->broken); } template @@ -120,6 +122,24 @@ BezierCP::load(Archive & ar, _imp->curveLeftBezierY->clone(leftCurveY); _imp->curveRightBezierX->clone(rightCurveX); _imp->curveRightBezierY->clone(rightCurveY); + + if (version >= BEZIER_CP_ADD_BROKEN) { + ar & ::boost::serialization::make_nvp("Broken", _imp->broken); + } else { + // For older projects, infer if the tangents are broken. + // Fixes https://github.com/NatronGitHub/Natron/issues/202 + + // The vector between the two bezier points + double vx = _imp->rightX - _imp->leftX; + double vy = _imp->rightY - _imp->leftY; + double v = std::sqrt(vx * vx + vy *vy); + // The normal vector + double nx = vy / v; + double ny = -vx / v; + // The (signed) distance from the position to the segment + double d = nx * (_imp->x - _imp->leftX) + ny * (_imp->y - _imp->leftY); + _imp->broken = (std::abs(d) < 1e-4); + } } else { ar & ::boost::serialization::make_nvp("X", _imp->x); CurvePtr xCurve, yCurve, leftCurveX, leftCurveY, rightCurveX, rightCurveY; diff --git a/Engine/RotoUndoCommand.cpp b/Engine/RotoUndoCommand.cpp index 2063df6197..3ff1a2bc2b 100644 --- a/Engine/RotoUndoCommand.cpp +++ b/Engine/RotoUndoCommand.cpp @@ -847,9 +847,9 @@ dragTangent(double time, if (autoKeying || isOnKeyframe) { if (left) { - cp.getBezier()->movePointLeftAndRightIndex(cp, fp, time, dx, dy, otherDiffX, otherDiffY, dx, dy, otherFpDiffX, otherFpDiffY, draggedPointIsFeather); + cp.getBezier()->movePointLeftAndRightIndex(cp, fp, time, dx, dy, otherDiffX, otherDiffY, dx, dy, otherFpDiffX, otherFpDiffY, breakTangents, draggedPointIsFeather); } else { - cp.getBezier()->movePointLeftAndRightIndex(cp, fp, time, otherDiffX, otherDiffY, dx, dy, otherFpDiffX, otherFpDiffY, dx, dy, draggedPointIsFeather); + cp.getBezier()->movePointLeftAndRightIndex(cp, fp, time, otherDiffX, otherDiffY, dx, dy, otherFpDiffX, otherFpDiffY, dx, dy, breakTangents, draggedPointIsFeather); } } }