diff --git a/src/core/TransformEffects/parenteffect.cpp b/src/core/TransformEffects/parenteffect.cpp index e84f53ade..02bd75bd0 100644 --- a/src/core/TransformEffects/parenteffect.cpp +++ b/src/core/TransformEffects/parenteffect.cpp @@ -28,19 +28,24 @@ #include "Boxes/boundingbox.h" #include "Animators/transformanimator.h" #include "Animators/qrealanimator.h" +#include "matrixdecomposition.h" ParentEffect::ParentEffect() : FollowObjectEffectBase("parent", TransformEffectType::parent) {} -void ParentEffect::applyEffect( - const qreal relFrame, - qreal& pivotX, qreal& pivotY, - qreal& posX, qreal& posY, - qreal& rot, - qreal& scaleX, qreal& scaleY, - qreal& shearX, qreal& shearY, - QMatrix& postTransform, - BoundingBox* const parent) { +void ParentEffect::applyEffect(const qreal relFrame, + qreal& pivotX, + qreal& pivotY, + qreal& posX, + qreal& posY, + qreal& rot, + qreal& scaleX, + qreal& scaleY, + qreal& shearX, + qreal& shearY, + QMatrix& postTransform, + BoundingBox* const parent) +{ Q_UNUSED(pivotX) Q_UNUSED(pivotY) Q_UNUSED(posX) @@ -51,14 +56,84 @@ void ParentEffect::applyEffect( Q_UNUSED(shearX) Q_UNUSED(shearY) - if(!isVisible()) return; + if (!isVisible() || !parent) { return; } - if(!parent) return; const auto target = targetProperty()->getTarget(); - if(!target) return; + if (!target) { return; } + const qreal absFrame = prp_relFrameToAbsFrameF(relFrame); const qreal targetRelFrame = target->prp_absFrameToRelFrameF(absFrame); - const auto targetTransAnim = target->getTransformAnimator(); - postTransform = targetTransAnim->getRelativeTransformAtFrame(targetRelFrame); + + const QMatrix targetTransform = + targetTransAnim->getRelativeTransformAtFrame(targetRelFrame); + const QPointF targetPivot = target->getPivotRelPos(targetRelFrame); + const auto targetValues = + MatrixDecomposition::decomposePivoted(targetTransform, targetPivot); + + // Get influence values with reasonable bounds to prevent extreme values + const qreal posXInfl = qBound(-10.0, mPosInfluence->getEffectiveXValue(relFrame), 10.0); + const qreal posYInfl = qBound(-10.0, mPosInfluence->getEffectiveYValue(relFrame), 10.0); + const qreal scaleXInfl = qBound(-10.0, mScaleInfluence->getEffectiveXValue(relFrame), 10.0); + const qreal scaleYInfl = qBound(-10.0, mScaleInfluence->getEffectiveYValue(relFrame), 10.0); + const qreal rotInfl = qBound(-10.0, mRotInfluence->getEffectiveValue(relFrame), 10.0); + + // Validate influence values for safety + if (!validateInfluenceValues(posXInfl, posYInfl, scaleXInfl, scaleYInfl, rotInfl)) { + return; // Skip effect if invalid values detected + } + + // Check for near-zero rotation influence + const bool zeroRotInfluence = std::abs(rotInfl) < 1e-6; + + // Apply influence to transform values using helper method + TransformValues influencedValues = targetValues; + applyInfluenceToTransform(influencedValues, targetValues, posXInfl, posYInfl, scaleXInfl, scaleYInfl); + + // Handle rotation influence with linear interpolation + qreal translationRotInfl = 1.0; + + influencedValues.fRotation = targetValues.fRotation * translationRotInfl; + + const qreal desiredRotation = zeroRotInfluence + ? -targetValues.fRotation + : targetValues.fRotation * rotInfl; + const qreal rotDeltaZero = -targetValues.fRotation; + const qreal rotDeltaFull = desiredRotation - influencedValues.fRotation; + + if (rotInfl >= 0.0 && rotInfl <= 1.0) { + const qreal t = rotInfl; + const qreal blendedDelta = rotDeltaZero + t * (rotDeltaFull - rotDeltaZero); + rot += blendedDelta; + } else if (zeroRotInfluence) { + rot += rotDeltaZero; + } else { + rot += rotDeltaFull; + } + + // Calculate final transform matrix + postTransform = influencedValues.calculate(); +} + +bool ParentEffect::validateInfluenceValues(const qreal posXInfl, const qreal posYInfl, + const qreal scaleXInfl, const qreal scaleYInfl, + const qreal rotInfl) const +{ + // Check for NaN or infinite values + return std::isfinite(posXInfl) && std::isfinite(posYInfl) && + std::isfinite(scaleXInfl) && std::isfinite(scaleYInfl) && + std::isfinite(rotInfl); +} + +void ParentEffect::applyInfluenceToTransform(TransformValues& values, + const TransformValues& targetValues, + const qreal posXInfl, const qreal posYInfl, + const qreal scaleXInfl, const qreal scaleYInfl) const +{ + values.fMoveX = targetValues.fMoveX * posXInfl; + values.fMoveY = targetValues.fMoveY * posYInfl; + + // Scale influence: interpolate between no scaling (1.0) and target scaling + values.fScaleX = 1.0 + (targetValues.fScaleX - 1.0) * scaleXInfl; + values.fScaleY = 1.0 + (targetValues.fScaleY - 1.0) * scaleYInfl; } diff --git a/src/core/TransformEffects/parenteffect.h b/src/core/TransformEffects/parenteffect.h index 1ff462f65..193ef32cd 100644 --- a/src/core/TransformEffects/parenteffect.h +++ b/src/core/TransformEffects/parenteffect.h @@ -27,6 +27,7 @@ #define PARENTEFFECT_H #include "followobjecteffectbase.h" +#include "transformvalues.h" class ParentEffect : public FollowObjectEffectBase { public: @@ -40,6 +41,16 @@ class ParentEffect : public FollowObjectEffectBase { qreal &shearX, qreal &shearY, QMatrix& postTransform, BoundingBox* const parent) override; + +private: + bool validateInfluenceValues(const qreal posXInfl, const qreal posYInfl, + const qreal scaleXInfl, const qreal scaleYInfl, + const qreal rotInfl) const; + + void applyInfluenceToTransform(TransformValues& values, + const TransformValues& targetValues, + const qreal posXInfl, const qreal posYInfl, + const qreal scaleXInfl, const qreal scaleYInfl) const; }; #endif // PARENTEFFECT_H diff --git a/src/core/TransformEffects/trackeffect.cpp b/src/core/TransformEffects/trackeffect.cpp index 6168d4e66..1a0be1b81 100644 --- a/src/core/TransformEffects/trackeffect.cpp +++ b/src/core/TransformEffects/trackeffect.cpp @@ -45,9 +45,18 @@ qreal calculateTrackAngle(const QPointF& parentPos, return trackAngle; } -void TrackEffect::setRotScaleAfterTargetChange( - BoundingBox* const oldTarget, BoundingBox* const newTarget) { - const auto parent = getFirstAncestor(); +void TrackEffect::setRotScaleAfterTargetChange(BoundingBox* const oldTarget, + BoundingBox* const newTarget) +{ + Q_UNUSED(oldTarget) + Q_UNUSED(newTarget) + + // https://github.com/friction2d/friction/issues/591 + + // I see no reason to apply the rotation here (or anywhere else) + // it only breaks the original state of the bounding box. + + /*const auto parent = getFirstAncestor(); if(!parent) return; const qreal infl = mInfluence->getEffectiveValue(); @@ -70,7 +79,7 @@ void TrackEffect::setRotScaleAfterTargetChange( } parent->startRotTransform(); - parent->rotateBy(rot); + parent->rotateBy(rot);*/ } void TrackEffect::applyEffect(