Understanding CGAfflineTransform in the context of Rotation - ios

I am working on a few experiments to learn gestures and animations in iOS. Creating a Tinder-like interface is one of them. I am following this guide: http://guti.in/articles/creating-tinder-like-animations/
I understand the changing of the position of the image, but don't understand the rotation. I think I've pinpointed my problem to not understanding CGAfflineTransform. Particularly, the following code:
CGFloat rotationStrength = MIN(xDistance / 320, 1);
CGFloat rotationAngle = (CGFloat) (2 * M_PI * rotationStrength / 16);
CGFloat scaleStrength = 1 - fabsf(rotationStrength) / 4;
CGFloat scale = MAX(scaleStrength, 0.93);
CGAffineTransform transform = CGAffineTransformMakeRotation(rotationAngle);
CGAffineTransform scaleTransform = CGAffineTransformScale(transform, scale, scale);
self.draggableView.transform = scaleTransform;
Where are these values and calculations, such as: 320, 1-fabs(strength) / 4 , .93, etc, coming from? How do they contribute to the eventual rotation?
On another note, Tinder seems to use a combination of swiping and panning. Do they add a swipe gesture to the image, or do they just take into account the velocity of the pan?

That code has a lot of magic constants, most of which are likely chosen because they resulted in something that "looked good". This can make it hard to follow. It's not so much about the actual transforms, but about the values used to create them.
Let's break it down, line by line, and see if that makes it clearer.
CGFloat rotationStrength = MIN(xDistance / 320, 1);
The value 320 is likely assumed to be the width of the device (it was the portrait width of all iPhones until the 6 and 6+ came out).
This means that xDistance / 320 is a factor of how far along the the x axis (based on the name xDistance) that the user has dragged. This will be 0.0 when the user hasn't dragged any distance and 1.0 when the user has dragged 320 points.
MIN(xDistance / 320, 1) Takes the smallest value of the dragged distance factor and 1). This means that if the user drags further than 320 points (so that the distance factor would be larger than 1, the rotation strength would never be larger than 1.0. It doesn't protect agains negative values (if the user dragged to the left, xDistance would be a negative value, which would always be smaller than 1. However, I'm not sure if the guide accounted for that (since 320 is the full width, not the half width.
So, the first line is a factor between 0 and 1 (assuming no negative values) of how much rotation should be applied.
CGFloat rotationAngle = (CGFloat) (2 * M_PI * rotationStrength / 16);
The next line calculates the actual angle of rotation. The angle is specified in radians. Since 2π is a full circle (360°), the rotation angle is ranging from 0 and 1/16 of a full circle (22.5°). Th value 1/16 is likely chosen because it "looked good".
The two lines together means that as the user drags further, the view rotates more.
CGFloat scaleStrength = 1 - fabsf(rotationStrength) / 4;
From the variable name, it would look like it would calculate how much the view should scale. But it's actually calculating what scale factor the view should have. A scale of 1 means the "normal" or unscaled size. When the rotation strength is 0 (when the xDistance is 0), the scale strength will be 1 (unscaled). As rotation strength increase, approaching 1, this scale factor approaches 0.75 (since that's 1 - 1/4).
fabsf is simply the floating point absolute value (fabsf(-0.3) is equal to 0.3)
CGFloat scale = MAX(scaleStrength, 0.93);
On the next line, the actual scale factor is calculated. It's simply the largest value of the scaleStrength and 0.93 (scaled down to 93%). The value 0.93 is completely arbitrary and is likely just what the author found appealing.
Since the scale strength ranges from 1 to 0.75 and the scale factor is never smaller than 0.93, the scale factor only changes for the first third of the xDistance. All scale strength values in the next two thirds will be smaller than 0.93 and thus won't change the scale factor.
With the scaleFactor and rotationAngle calculated as above, the view is first rotated (by that angle) and then scaled down (by that scale factor).
Summary
So, in short. As the view is dragged to the right (as xDistance approaches 320 points), The view linearly rotates from 0° to 22.5° over the full drag and scales from 100% to 93% over the first third of the drag (and then stays at 93% for the remainder of the drag gesture).

Related

Using CATransform3DRotate with perspective: how to correct the 2D size increase?

I'm trying to create a paper folding effect in Swift using CALayers and CATransform3DRotate. There are some libraries out there, but those are pretty outdated and don't fit my needs (they don't have symmetric folds, for example).
My content view controller will squeeze to the right half side of the screen, revealing the menu at the left side.
Everything went well, until I applied perspective: then the dimensions I calculate are not correct anymore.
To explain the problem, I created a demo to show you what I'm doing.
This the content view controller with three squares. I will use three folds, so each square will be on a separate fold.
The even folds will get anchor point (0, 0.5) and the odd folds will get anchor point (1, 0.5), plus they'll receive a shadow.
When fully folded, the content view will be half of the screen's width.
On an iPhone 7, each fold/plane will be 125 points unfolded and 62.5 points fully folded when looked at.
To calculate the rotation needed to achieve this 62.5 points width, we can use a trigonometric function. To illustrate, look at this top-down view:
We know the original plane size (125) and the 2D width (62.5), so we can calculate the angle α using arccos:
let angle = acos(width / originalWidth)
The result is 1.04719755 rad or 60 degrees.
When using this formula with CATransform3DRotate, I get the correct result:
Now for the problem: when I add perspective, my calculation isn't correct anymore. The planes are bigger. Probably because of the now different projection.
You can see the planes are now overlapping and being clipped.
I reconstructed the desired result on the right by playing with the angle, but the correction needed is not consistent, unfortunately.
Here's the code I use. It works perfectly without perspective.
// Loop layers
for i in 0..<self.layers.count {
// Get layer
let layer = self.layers[i]
// Get dimensions
let width = self.frame.size.width / CGFloat(self.numberOfFolds)
let originalWidth = self.sourceView.frame.size.width / CGFloat(self.numberOfFolds)
// Calculate angle
let angle = acos(width / originalWidth)
// Set transform
layer.transform = CATransform3DIdentity
layer.transform.m34 = 1.0 / -500
layer.transform = CATransform3DRotate(layer.transform, angle * (i % 2 == 0 ? -1 : 1), 0, 1, 0)
// Update position
if i % 2 == 0 {
layer.position = CGPoint(x: (width * CGFloat(i)), y: layer.position.y)
} else {
layer.position = CGPoint(x: (width * CGFloat(i + 1)), y: layer.position.y)
}
}
So my question is: how do I achieve the desired result? Do I need to correct the angle, or should I calculate the projected/2D width differently?
Thanks in advance! :)

Processing cv::RotatedRect width and height

I need to define a rotated rectangle from its 4 corners. The rotated rectangle is defined by a center point, a size couple (width, height), and an angle.
How is it decided which size is the height, and which one is the width?
The width is not the length of the most horizontal edge, is it? E.g. if the angle is bigger than 90°, does it swap?
height should be the largest side, width is the other one, and angle is the rotation angle (in degrees) in a clockwise direction.
Otherwise, you can get an equivalent rectangle with height and width swapped, rotated by 90 degrees.
You can use minAreaRect to find a RotatedRect:
vector<Point> pts = {pt1, pt2, pt3, pt4}
RotatedRect box = minAreaRect(pts);
// Be sure that largest side is the height
if (box.size.width > box.size.height)
{
swap(box.size.width, box.size.height);
box.angle += 90.f;
}
Ok, with Miki's help, and with some tests, I got it clearer...
It seems that the rotated rectangle is an upright rectangle (width and height are clearly defined, then)... that is rotated!
In image coords, y is directed to the bottom, the angle is given clockwise. In usual math coords (y to the top), the angle is given counter-clockwise. Then, it fits with c++ <math.h> included atan2(y,x) function for example (except that it returns radians).
Then, to summarize, if we consider one given edge of the rectangle (two corners), its length can be considered as the width if we retrieve the angle with atan2 on its y difference and x difference. Something like:
Point pt1, pt2, pt3, pt4;
RotatedRect rect;
rect.center = (pt1 + pt2 + pt3 + pt4)/4;
// assuming the points are already sorted
rect.size.width = distance(pt1, pt2); // sqrt(...)
rect.size.height = distance(pt2, pt3);
rect.angle = atan2(pt2.y-pt1.y, pt2.x-pt1.x);
and this can be improved with width being the mean value of dist(pt1,pt2) and dist(pt3,pt4) for example. The same for height.
angle can also be calculated as being the mean value of atan for (pt1, pt2) and atan for (pt3, pt4).

Calculate real width based on picture, knowing distance

I know the distance between the camera and the object
I know the type of camera used
I know the width in pixel on the picture
Can I figure the real life width of the object?
you have to get the angle of camera. For example, iphone 5s is 61.4 in vertical and 48.0 horizontal. call it alpha.
then you calculate the width of object by this way:
viewWidth = distance * tan(alpha / 2) * 2;
objWidth = viewWidth * (imageWidth / screenWidth)

Rounding Corners: How to calculate Fillet radius?

How do I find the maximum rounding I can apply to either corner for any amount of rounding on the other corner?
Answers to questions from the comments:
1) The inner and outer large arcs (those that are 90 degrees wide here) always have the same center
2) When asking for the maximum rounding that you can do, what are the constraints on the other, smaller circle? Does it need to be at least some radius? Otherwise you are doing to end up with just one rounding.
One of the two rounding circle's radius is given. There are no other constraints other than the maximum of the other circle which I just can't find.
If the "fixed" corner that I refer to has zero rounding then I'm searching for the maximum rouding that can be applied with only the other corner.
3) What constitutes as the maximum rounding? Are you trying to choose between the two examples above? Or is finding either of those cases considered a solution?
Either of the shown cases is a perfect solution. E.g. in the first image the the radius of the smaller circle might be given. Then I'm looking for the maximum radius of the larger one.
These images are just examples for perfect solutions.
4) is there any constraints on the two arcs? What happens if the arcs can't fit a full circle? Would the answer be the largest that fits?
How exactly do you mean that the arcs can't fit a full circle?
The all circles are perfect circles, but I can't figure out the max size of the rounding possible, or how to calculate it's position. Here's some images that describe the problem.
Given that the origin of the coordinate system is at the center point of the inner and outer large arcs...
For the first case where the large circle is tangent to the outer edge, the center point of the large circle is
x = R cos(t) / (1 + cos(t))
y = R sin(t) / (1 + cos(t))
where R is the radius of the outer arc segment, and t is the angle between the x-axis and the ray from the origin through the center of the large circle.
For the second case where the large circle is tangent to the inner edge, the center point of the large circle is
x = R cos(t) / (1 - cos(t))
y = R sin(t) / (1 - cos(t))
where R is the radius of the inner arc segment, and t is the angle...
In both cases, the radius of the circle is equal to its x coordinate. The range of t is between some minimum angle and PI/2. At PI/2, the circle is vanishingly small. At the minimum angle, the y value is equal to the opposite radius. In other words, for the first case where the large circle is tangent to the outer edge, the minimum angle is such that y is equal to the inner radius. Whereas if the circle is tangent to the inner edge, the minimum angle is such that y is equal to the outer radius. It can be proven mathematically that the minimum angle is the same for both cases (tangent to inner and tangent to outer both have the same minimum angle for a given inner and outer radius). However, computing the minimum angle is a bit of a challenge. The only way I know how to do it is by playing the high/low game, e.g.
- (CGFloat)computeAngleForOuterTangentGivenY:(CGFloat)Y
{
CGFloat y;
double high = M_PI_2;
double low = 0;
double mid = M_PI_4;
while ( high - low > 1e-9 )
{
y = (self.outerRadius * sin( mid )) / (1.0 + cos( mid ));
if ( y > Y )
high = mid;
else
low = mid;
mid = (low + high) / 2.0;
}
return( mid );
}
- (CGFloat)computeAngleForInnerTangentGivenY:(CGFloat)Y
{
CGFloat y;
double high = M_PI_2;
double low = 0;
double mid = M_PI_4;
while ( high - low > 1e-9 )
{
y = (self.innerRadius * sin( mid )) / (1.0 - cos( mid ));
if ( y > Y )
low = mid;
else
high = mid;
mid = (low + high) / 2.0;
}
return( mid );
}
It takes about 30 passes for the loop to converge to an answer.
To find the coordinates of the small circle, note that the small circle has the same y value as the large circle, and is tangent to the opposite edge of the arc segment. Therefore, compute the angle t for the small circle based on its y value using the appropriate high/low algorithm, and then compute the x value using the formulas above.
QED
The question isn't posed correctly without showing both ends of the line segment. Suppose for a moment that each line segment is a data structure that maintains not only the end points, but also cap radius in each point, and also knows the angle going out to the next endpoint that this line will attach to. Each cap radius will subtract from the length of the line segment that has to be stroked as a rectangle. Assume you have a line of interest between points B and C, where B joins to another (longer) segment A, and C joins to another (longer) segment D. If line BC is length 10, with cap radius B and cap radius C both set to 4, then you will only render rectangle of length 2 for the straight part of the line segment, while length 4 is used to draw the arc to A, and another length 4 is used to draw the arc to D.
Furthermore, the maximum cap radius for C is constrained not only by BC and B's cap radius, but also by CD and D's cap radius.

Looping character around width of screen?

What I am trying to do is lets say my character moves off the right side of the screen, I want it to come back around from the left part of the screen with the same Y coordinate. I am using Cocos2D also. I am currently trying to do this in my UIAccelerometer method but it does not seem to work.
Can someone show me what I should do instead?
Thanks!
This will change your character's position if it leaves the left or right side of the screen to the other side, without modifying the y coordinate.
CGSize size = [CCDirector sharedDirector].winSize;
CGPoint pos = character.position;
if (pos.x >= size.width)
pos.x -= size.width;
else if (pos.x < 0.0f)
pos.x += size.width;
character.position = pos;
The reason why I add or subtract the width rather than setting the x coordinate directly to 0 or width is that the character may be moving faster than 1 pixel per frame. That means if he's moving fast and moves let's say from X coordinate 479 to 495 in one frame, then he should be set to X coordinate 15 on the other side to make sure the velocity of the character is unaffected.

Resources