Skip to content

Commit

Permalink
fix animation of Beziers with non-broken tangents
Browse files Browse the repository at this point in the history
  • Loading branch information
devernay committed Apr 9, 2021
1 parent e71e391 commit 09cd3a9
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 15 additions & 5 deletions Engine/Bezier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,7 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam,
double fry,
bool isLeft,
bool moveBoth,
bool breakTangents,
bool onlyFeather)
{
///only called on the main-thread
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -1950,6 +1954,7 @@ Bezier::moveBezierPointInternal(BezierCP* cpParam,
rightF = Transform::matApply(invTrans, rightF);
(fp)->setRightBezierStaticPosition(useGuiCurve, rightF.x, rightF.y);
}
(fp)->setBroken(breakTangents);
}
}

Expand All @@ -1968,13 +1973,15 @@ 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);
leftF = Transform::matApply(trans, leftF);
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) {
Expand All @@ -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);
}
}
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Engine/Bezier.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);


Expand Down
67 changes: 63 additions & 4 deletions Engine/BezierCP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

#include <QtCore/QThread>
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>

#include "Engine/Bezier.h"
#include "Engine/KnobTypes.h"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -214,16 +216,39 @@ 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

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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 6 additions & 2 deletions Engine/BezierCP.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<double>* times) const;
Expand Down
2 changes: 2 additions & 0 deletions Engine/BezierCPPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -70,6 +71,7 @@ struct BezierCPPrivate
, y(0)
, guiX(0)
, guiY(0)
, broken(false)
, curveLeftBezierX()
, curveRightBezierX()
, curveLeftBezierY()
Expand Down
22 changes: 21 additions & 1 deletion Engine/BezierCPSerialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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<class Archive>
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions Engine/RotoUndoCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down

0 comments on commit 09cd3a9

Please sign in to comment.