Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Tweenable CGAffineTransform & CATransform3D #9

Closed
wants to merge 13 commits into from
Closed

Added Tweenable CGAffineTransform & CATransform3D #9

wants to merge 13 commits into from

Conversation

iDonJose
Copy link
Contributor

@iDonJose iDonJose commented Dec 2, 2017

CGAffineTransform and CATransform3D should be Tweenable.
It may produce strange animation for complex transformations, but for simple ones as animating a translation, a scaling or a rotation it is great.

Example :

let action = InterpolationAction(from: self.transform,
                                         to: CATransform3DMakeScale(value, value, 1),
                                         duration: duration,
                                         easing: easing,
                                         update: { [weak self] in self?.transform = $0 })

Use theses types as Tweenable, for instance allows you to easily
interpolate from a linear transformation matrix to the identity matrix.
@SteveBarnegren
Copy link
Owner

I definitely think that conformance for CGAffineTransform and CATransform3D should be included out of the box.

I haven't done too much work with matrices, but I assume that strange animations can occur because there is more than one way in which a matrix can describe certain positions? So in that case the route between to transforms can be mathematically correct, but not what the user might expect?

@iDonJose
Copy link
Contributor Author

I had a closer look to this subject.

The linear interpolation that I was using only gave good looking results on scaling and translation transforms. This is because they are affine transforms.
Rotation transforms are built with cosinus and sinus and though are not linear. But its angle given a specific axis is.
Any other transforms are too complex.

The idea is to build a component that derives scaling, translation, rotation axis and rotation angle from a basic transform. Then we can tween on the component's variables. Here the components will be Transform2D and Transform3D.

How we use it :

let action = InterpolationAction(from: self.transform.tweenable, // or Transform2D(self.transform)
                                         to: newTransfrom.tweenable,
                                         duration: duration,
                                         easing: easing,
                                         update: { [weak self] in self?.transform = $0.cgAffinetransform })
let action = InterpolationAction(from: self.layer.transform.tweenable, // or Transform3D(self.layer.transform)
                                         to: newTransfrom.tweenable,
                                         duration: duration,
                                         easing: easing,
                                         update: { [weak self] in self?.layer.transform = $0.caTransform3D })�

We could also keep CGAffinetransform and CATransform3D Tweenable and use under the hood these components. But this implies that we have to derived translation, scaling and rotation parameters on every frame which is rather expensive.

This would look like :

extension CGAffineTransform {
   public func lerp(t: Double, end: CGAffineTransform) -> CGAffineTransform {
       return Transform2D(self).lerp(t: t, end: Transform2D(end))
    }
}

@SteveBarnegren
Copy link
Owner

Thanks for putting so much work in to this, the example looks awesome!

I get how you're using the nested Vector types and angle in Transform2D and Transform3D, and then using them to construct a CGAffineTransform or CATransform3D. The api for that is really clear.

Can you explain a little about what happens when you initialise one of those from a UIKit transform, for instance Transform2D with CGAffineTransform? It looks like you extract the translation and scale information, and then compare with the original to see if the transform contains a rotation?

I get a little lost at that point, and I'm not sure how the transposed computed variable works?

Also, when you say 'If provided transform doesn't result from a translation, a scaling and a rotation, the resulting matrix is undetermined', can you clarify what that means?

@iDonJose
Copy link
Contributor Author

iDonJose commented Jan 9, 2018

Hey @SteveBarnegren ! Sorry for this long unavailable period.

When initializing a Transform3D, what it does is :

  • Extracing scale, translation then rotation, referring to this link

  • As extracting rotation is an expensive task, we check if the scale and translation matches the given matrix, and if it does we know that there is no rotation.
    Here I forgot translation, I'm going to fix this.

  • Then we extract the last transform and check if it could be a rotation matrix :

    • A rotation matrix is inversible and its inverse is equal to the transposed matrix. So M^-1 * M = Identity or Mt * M = Identity
    • And det(M) = 1
      Once these conditions are met we can start extracting rotation parameters. See Wikipedia's intro.

Lastly, the input matrix can be any other transformation that is not a combination of translation, scaling and rotation. In that case it will return a transform with the current extracted translation and scaling which is not a close match of the input. The tweening won't be continuous here.

We can either use a failable init when the transform is not a combination of translation, scaling and rotation.

@iDonJose iDonJose closed this Jan 16, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants