UIBezierPath lineWidth based on UIPanGestureRecognizer's velocity - ios

I'm trying to figure out how to construct a limited range of floating point values based on a UIPanGestureRecognizer's velocity. I have a minimum, or starting value of 1.0, and a maximum of 3.0 to provide a limited range for the UIBezierPath's lineWidth property.
I'm trying to figure out how to build an exponential range from 1.0 to 3.0 based accordingly on the UIPanGestureRecognizer's velocity, but am having a difficult time where I should start for mapping the values. The faster the combined x and y velocity, the smaller (down to 1.0) the lineWidth should be, and respectively the opposite up to 3.0 if the combined velocity is slower. I'm also trying to taper/smooth the line width in progress by storing a lastWidth property so the transitions aren't noticeable between subpaths.
I'd appreciate any help offered.
Working and final Code based on answer:
#property (nonatomic, assign) CGFloat lastWidth;
if (recognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint velocity = [recognizer velocityInView:self.view];
CGFloat absoluteVelocity = 1000.0 / sqrtf(pow(velocity.x, 2) + pow(velocity.y, 2));
CGFloat clampedVel = MAX(MIN(absoluteVelocity, 3.0), 1.0);
if (clampedVel > self.lastWidth)
{
clampedVel = self.lastWidth + 0.15;
}
else if (clampedVel < self.lastWidth)
{
clampedVel = self.lastWidth - 0.15;
}
self.lastWidth = clampedVel;
UIBezierPath *path = [UIBezierPath bezierPath];
path.lineCapStyle = kCGLineCapRound;
path.lineWidth = self.lastWidth;
}

So I'd use an inverted exponential function.
Start with your velocity, V(x,y). Your absolute velocity is obviously:
sqrt(pow(x, 2) + pow(y, 2));
We'll call this value "v."
Next, we want a value that is between 1 and 3 where 1 is the width where "v" is very high and 3 is the width where "v" is very low.
We can calculate that using the following exponential function:
- (CGFloat)getExponentialWidthForVeloctity(CGFloat)v {
if (v <= 1 / 3.0)
return 3;
CGFloat inverse = 1 / v;
return 1 + inverse;
}
Or this function that smooths it out a little bit
- (CGFloat)getExponentialRootWidthForVeloctity(CGFloat)v {
//play with this value to get the feel right
//The higher it is, the faster you'll have to go to get a thinner line
CGFloat rootConstantYouCanAdjust = 2;
if (pow(v, rootConstantYouCanAdjust) <= 1 / 3.0)
return 3;
CGFloat inverse = 1 / pow(v, rootConstantYouCanAdjust);
return 1 + inverse;
}
If that doesn't feel right, try a linear solution:
- (CGFloat)getLinearWidthForVelocity(CGFloat)v {
//Find this value by swiping your finger really quickly and seeing what the fastest velocity you can get is
CGFloat myExpectedMaximumVelocity = 1000;
if (v > myExpectedMaximumVelocity)
v = myExpectedMaximumVelocity;
return 3 - 2 * (v / myExpectedMaximumVelocity);
}
And finally, as a bonus, try this sqrt based function that you might find works nicely:
- (CGFloat)getSqrtWidthForVelocity(CGFloat)v {
//find the same way as above
CGFloat myExpectedMaximumVelocity = 1000;
if (v > myExpectedMaximumVelocity)
return 1;
return 3 - 2 * sqrt(v) / sqrt(myExpectedMaximumVelocity);
}
I'd be curious to know which works best! Let me know. I have a lot more functions up my sleeve, these are just some really simple ones that should get you started.

Related

Get random number from screen except rectangle

As my tile says that I want to get random number for origin (X-Axis & y-Axis) so in my whole screen in iPad landscape I have 1 rectangle, I want to get random number for origin which out of this rectangle, so obiously I want to get random number for X-Axis between max and min and same as for Y-Axis.
I tried with following answers but not helpful for me.
Generate Random Numbers Between Two Numbers in Objective-C
Generate a random float between 0 and 1
Generate random number in range in iOS?
For more clear see below image
In above image I just want to find random number (for origin) of GREEN screen. How can I achieve it ?
Edited
I had tried.
int randNum = rand() % ([max intValue] - [min intValue]) + [min intValue];
Same for both X-Axis & y-Axis.
If the blue exclusion rectangle is not "too large" compared to the green screen rectangle
then the easiest solution is to
create a random point inside the green rectangle,
check if the point lies inside the blue rectangle, and
repeat the process if necessary.
That would look like:
CGRect greenRect = ...;
CGRect blueRect = ...;
CGPoint p;
do {
p = CGPointMake(greenRect.origin.x + arc4random_uniform(greenRect.size.width),
greenRect.origin.y + arc4random_uniform(greenRect.size.height));
} while (CGRectContainsPoint(blueRect, p));
If I remember correctly, the expected number of iterations is G/(G - B), where G is
the area of the green rectangle and B is the area of the blue rectangle.
What if you first determined x within the green rectangle like this:
int randomX = arc4random()%greenRectangle.frame.size.width;
int randomY; // we'll do y later
Then check if this is inside the blue rectangle:
if(randomX < blueRectangle.frame.origin.x && randomX > (blueRectangle.frame.origin.x + blueRectangle.frame.size.width))
{
//in this case we are outside the rectangle with the x component
//so can randomly generate any y like this:
randomY = arc4random()%greenRectangle.frame.size.height;
}
//And if randomX is in the blue rectangle then we can use the space either before or after it:
else
{
//randomly decide if you are going to use the range to the left of blue rectangle or to the right
BOOL shouldPickTopRange = arc4random()%1;
if(shouldPickTopRange)
{
//in this case y can be any point before the start of blue rectangle
randomY = arc4random()%blueRectangle.frame.origin.y;
}
else
{
//in this case y can be any point after the blue rectangle
int minY = blueRectangle.frame.origin.y + blueRectangle.frame.size.height;
int maxY = greenRectangle.frame.size.height;
randomY = arc4random()%(maxY - minY + 1) + minY;
}
}
Then your random point would be:
CGPoint randomPoint = CGPointMake(randomX, randomY);
The only thing missing above is to check if your blue rectangle sits at y = 0 or at the very bottom of green rectangle.
[Apologies I did this with OS X, translation is straightforward]
A non-iterative solution:
- (NSPoint) randomPointIn:(NSRect)greenRect excluding:(NSRect)blueRect
{
// random point on green x-axis
int x = arc4random_uniform(NSWidth(greenRect)) + NSMinX(greenRect);
if (x < NSMinX(blueRect) || x > NSMaxX(blueRect))
{
// to the left or right of the blue, full height available
int y = arc4random_uniform(NSHeight(greenRect)) + NSMinY(greenRect);
return NSMakePoint(x, y);
}
else
{
// within the x-range of the blue, avoid it
int y = arc4random_uniform(NSHeight(greenRect) - NSHeight(blueRect)) + NSMinY(greenRect);
if (y >= NSMinY(blueRect))
{
// not below the blue, step over it
y += NSHeight(blueRect);
}
return NSMakePoint(x, y);
}
}
This picks a random x-coord in the range of green. If that point is outside the range of blue it picks a random y-coord in the range of green; otherwise it reduces the y range by the height of blue, produces a random point, and then increases it if required to avoid blue.
There are other solutions based on picking a uniform random point in the available area (green - blue) and then adjusting, but the complexity isn't worth it I think (I haven't done the stats).
Addendum
OK folk seem concerned over uniformity, so here is the algorithm mentioned in my last paragraph. We're picking an "point" with integer coords so the number of points to pick from is the green area minus the blue area. Pick a point randomly in this range. Now place it into one of the rectangles below, left, right or above the blue:
// convenience
int RectArea(NSRect r) { return (int)NSWidth(r) * (int)NSHeight(r); }
- (NSPoint) randomPointIn:(NSRect)greenRect excluding:(NSRect)blueRect
{
// not we are using "points" with integer coords so the
// bottom left point is 0,0 and the top right (width-1, height-1)
// you can adjust this to suit
// the number of points to pick from is the diff of the areas
int availableArea = RectArea(greenRect) - RectArea(blueRect);
int pointNumber = arc4random_uniform(availableArea);
// now "just" locate pointNumber into the available space
// we consider four rectangles, one each full width above and below the blue
// and one each to the left and right of the blue
int belowArea = NSWidth(greenRect) * (NSMinY(blueRect) - NSMinY(greenRect));
if (pointNumber < belowArea)
{
return NSMakePoint(pointNumber % (int)NSWidth(greenRect) + NSMinX(greenRect),
pointNumber / (int)NSWidth(greenRect) + NSMinY(greenRect));
}
// not below - consider to left
pointNumber -= belowArea;
int leftWidth = NSMinX(blueRect) - NSMinX(greenRect);
int leftArea = NSHeight(blueRect) * leftWidth;
if (pointNumber < leftArea)
{
return NSMakePoint(pointNumber % leftWidth + NSMinX(greenRect),
pointNumber / leftWidth + NSMinY(blueRect));
}
// not left - consider to right
pointNumber -= leftArea;
int rightWidth = NSMaxX(greenRect) - NSMaxX(blueRect);
int rightArea = NSHeight(blueRect) * rightWidth;
if (pointNumber < rightArea)
{
return NSMakePoint(pointNumber % rightWidth + NSMaxX(blueRect),
pointNumber / rightWidth + NSMinY(blueRect));
}
// it must be above
pointNumber -= rightArea;
return NSMakePoint(pointNumber % (int)NSWidth(greenRect) + NSMinX(greenRect),
pointNumber / (int)NSWidth(greenRect) + NSMaxY(blueRect));
}
This is uniform, but whether it is worth it you'll have to decide.
Okay. This was bothering me, so I did the work. It's a lot of source code, but computationally lightweight and probabilistically correct (haven't tested).
With all due respect to #MartinR, I think this is superior insofar as it doesn't loop (consider the case where the contained rect covers a very large portion of the outer rect). And with all due respect to #CRD, it's a pain, but not impossible to get the desired probabilities. Here goes:
// Find a random position in rect, excluding a contained rect called exclude
//
// It looks terrible, but it's just a lot of bookkeeping.
// Divide rect into 8 regions, like a tic-tac-toe board, excluding the center square
// Reading left to right, top to bottom, call these: A,B,C,D, (no E, it's the center) F,G,H,I
// The random point must be in one of these regions, choose by throwing a random dart, using
// cumulative probabilities to choose. The likelihood that the dart will be in regions A-I is
// the ratio of each's area to the total (less the center)
// With a target rect, correctly selected, we can easily pick a random point within it.
+ (CGPoint)pointInRect:(CGRect)rect excluding:(CGRect)exclude {
// find important points in the grid
CGFloat xLeft = CGRectGetMinX(rect);
CGFloat xCenter = CGRectGetMinX(exclude);
CGFloat xRight = CGRectGetMaxX(exclude);
CGFloat widthLeft = exclude.origin.x-CGRectGetMinX(rect);
CGFloat widthCenter = exclude.size.width;
CGFloat widthRight = CGRectGetMaxY(rect)-CGRectGetMaxX(exclude);
CGFloat yTop = CGRectGetMinY(rect);
CGFloat yCenter = exclude.origin.y;
CGFloat yBottom = CGRectGetMaxY(exclude);
CGFloat heightTop = exclude.origin.y-CGRectGetMinY(rect);
CGFloat heightCenter = exclude.size.height;
CGFloat heightBottom = CGRectGetMaxY(rect)-CGRectGetMaxY(exclude);
// compute the eight regions
CGFloat areaA = widthLeft * heightTop;
CGFloat areaB = widthCenter * heightTop;
CGFloat areaC = widthRight * heightTop;
CGFloat areaD = widthLeft * heightCenter;
CGFloat areaF = widthRight * heightCenter;
CGFloat areaG = widthLeft * heightBottom;
CGFloat areaH = widthCenter * heightBottom;
CGFloat areaI = widthRight * heightBottom;
CGFloat areaSum = areaA+areaB+areaC+areaD+areaF+areaG+areaH+areaI;
// compute the normalized probabilities
CGFloat pA = areaA/areaSum;
CGFloat pB = areaB/areaSum;
CGFloat pC = areaC/areaSum;
CGFloat pD = areaD/areaSum;
CGFloat pF = areaF/areaSum;
CGFloat pG = areaG/areaSum;
CGFloat pH = areaH/areaSum;
// compute cumulative probabilities
CGFloat cumB = pA+pB;
CGFloat cumC = cumB+pC;
CGFloat cumD = cumC+pD;
CGFloat cumF = cumD+pF;
CGFloat cumG = cumF+pG;
CGFloat cumH = cumG+pH;
// now pick which region we're in, using cumulatvie probabilities
// whew, maybe we should just use MartinR's loop. No No, we've come too far!
CGFloat dart = uniformRandomUpTo(1.0);
CGRect targetRect;
// top row
if (dart < pA) {
targetRect = CGRectMake(xLeft, yTop, widthLeft, heightTop);
} else if (dart >= pA && dart < cumB) {
targetRect = CGRectMake(xCenter, yTop, widthCenter, heightTop);
} else if (dart >= cumB && dart < cumC) {
targetRect = CGRectMake(xRight, yTop, widthRight, heightTop);
}
// middle row
else if (dart >= cumC && dart < cumD) {
targetRect = CGRectMake(xRight, yCenter, widthRight, heightCenter);
} else if (dart >= cumD && dart < cumF) {
targetRect = CGRectMake(xLeft, yCenter, widthLeft, heightCenter);
}
// bottom row
else if (dart >= cumF && dart < cumG) {
targetRect = CGRectMake(xLeft, yBottom, widthLeft, heightBottom);
} else if (dart >= cumG && dart < cumH) {
targetRect = CGRectMake(xCenter, yBottom, widthCenter, heightBottom);
} else {
targetRect = CGRectMake(xRight, yBottom, widthRight, heightBottom);
}
// yay. pick a point in the target rect
CGFloat x = uniformRandomUpTo(targetRect.size.width) + CGRectGetMinX(targetRect);
CGFloat y = uniformRandomUpTo(targetRect.size.height)+ CGRectGetMinY(targetRect);
return CGPointMake(x, y);
}
float uniformRandomUpTo(float max) {
return max * arc4random_uniform(RAND_MAX) / RAND_MAX;
}
Try this code, Worked for me.
-(CGPoint)randomPointInRect:(CGRect)r
{
CGPoint p = r.origin;
p.x += arc4random_uniform((u_int32_t) CGRectGetWidth(r));
p.y += arc4random_uniform((u_int32_t) CGRectGetHeight(r));
return p;
}
I don't like piling onto answers. However, the provided solutions do not work, so I feel obliged to chime in.
Martin's is fine, and simple... which may be all you need. It does have one major problem though... finding the answer when the inner rectangle dominates the containing rectangle could take quite a long time. If it fits your domain, then always choose the simplest solution that works.
jancakes solution is not uniform, and contains a fair amount of bias.
The second solution provided by dang just plain does not work... because arc4_random takes and returns uint32_t and not a floating point value. Thus, all generated numbers should fall into the first box.
You can address that by using drand48(), but it's not a great number generator, and has bias of its own. Furthermore, if you look at the distribution generated by that method, it has heavy bias that favors the box just to the left of the "inner box."
You can easily test the generation... toss a couple of UIViews in a controller, add a button handler that plots 100000 "random" points and you can see the bias clearly.
So, I hacked up something that is not elegant, but does provide a uniform distribution of random numbers in the larger rectangle that are not in the contained rectangle.
You can surely optimize the code and make it a bit easier to read...
Caveat: Will not work if you have more than 4,294,967,296 total points. There are multiple solutions to this, but this should get you moving in the right direction.
- (CGPoint)randomPointInRect:(CGRect)rect
excludingRect:(CGRect)excludeRect
{
excludeRect = CGRectIntersection(rect, excludeRect);
if (CGRectEqualToRect(excludeRect, CGRectNull)) {
return CGPointZero;
}
CGPoint result;
uint32_t rectWidth = rect.size.width;
uint32_t rectHeight = rect.size.height;
uint32_t rectTotal = rectHeight * rectWidth;
uint32_t excludeWidth = excludeRect.size.width;
uint32_t excludeHeight = excludeRect.size.height;
uint32_t excludeTotal = excludeHeight * excludeWidth;
if (rectTotal == 0) {
return CGPointZero;
}
if (excludeTotal == 0) {
uint32_t r = arc4random_uniform(rectHeight * rectWidth);
result.x = r % rectWidth;
result.y = r /rectWidth;
return result;
}
uint32_t numValidPoints = rectTotal - excludeTotal;
uint32_t r = arc4random_uniform(numValidPoints);
uint32_t numPointsAboveOrBelowExcludedRect =
(rectHeight * excludeWidth) - excludeTotal;
if (r < numPointsAboveOrBelowExcludedRect) {
result.x = (r % excludeWidth) + excludeRect.origin.x;
result.y = r / excludeWidth;
if (result.y >= excludeRect.origin.y) {
result.y += excludeHeight;
}
} else {
r -= numPointsAboveOrBelowExcludedRect;
uint32_t numPointsLeftOfExcludeRect =
rectHeight * excludeRect.origin.x;
if (r < numPointsLeftOfExcludeRect) {
uint32_t rowWidth = excludeRect.origin.x;
result.x = r % rowWidth;
result.y = r / rowWidth;
} else {
r -= numPointsLeftOfExcludeRect;
CGFloat startX =
excludeRect.origin.x + excludeRect.size.width;
uint32_t rowWidth = rectWidth - startX;
result.x = (r % rowWidth) + startX;
result.y = r / rowWidth;
}
}
return result;
}

Finding an angle with 3 CGPoints

In my application, a user taps 3 times and an angle will be created by the 3 points that were tapped. It draws the angle perfectly. I am trying to calculate the angle at the second tap, but I think I am doing it wrong (probably a math error). I haven't covered this in my calculus class yet, so I am going off of a formula on wikipedia.
http://en.wikipedia.org/wiki/Law_of_cosines
Here is what I am trying:
Note: First, Second, and Third are CGPoints created at the user's tap.
CGFloat xDistA = (second.x - third.x);
CGFloat yDistA = (second.y - third.y);
CGFloat a = sqrt((xDistA * xDistA) + (yDistA * yDistA));
CGFloat xDistB = (first.x - third.x);
CGFloat yDistB = (first.y - third.y);
CGFloat b = sqrt((xDistB * xDistB) + (yDistB * yDistB));
CGFloat xDistC = (second.x - first.x);
CGFloat yDistC = (second.y - first.y);
CGFloat c = sqrt((xDistC * xDistC) + (yDistC * yDistC));
CGFloat angle = acos(((a*a)+(b*b)-(c*c))/((2*(a)*(b))));
NSLog(#"FULL ANGLE IS: %f, ANGLE IS: %.2f",angle, angle);
Sometimes, it gives the angle as 1 which doesn't make sense to me. Can anyone explain why this is, or how to fix it please?
Not sure if this is the main problem but it is a problem
Your answer gives the angle at the wrong point:
To get the angle in green (which is probably angle you want based on your variable names "first", "second" and "third), use:
CGFloat angle = acos(((a*a)+(c*c)-(b*b))/((2*(a)*(c))));
Here's a way that circumvents the law of cosines and instead calculates the angles of the two vectors. The difference between the angles is the searched value:
CGVector vec1 = { first.x - second.x, first.y - second.y };
CGVector vec2 = { third.x - second.x, third.y - second.y };
CGFloat theta1 = atan2f(vec1.dy, vec1.dx);
CGFloat theta2 = atan2f(vec2.dy, vec2.dx);
CGFloat angle = theta1 - theta2;
NSLog(#"angle: %.1f°, ", angle / M_PI * 180);
Note the atan2 function that takes the x and y components as separate arguments and thus avoids the 0/90/180/270° ambiguity.
The cosine formula implementation looks right; did you take into account that acos() returns the angle in radians, not in degrees? In order to convert into degrees, multiply the angle by 180 and divide by Pi (3.14159...).
The way I have done it is to calculate the two angles separately using atan2(y,x) then using this function.
static inline double
AngleDiff(const double Angle1, const double Angle2)
{
double diff = 0;
diff = fabs(Angle1 - Angle2);
if (diff > <Pi>) {
diff = (<2Pi>) - diff;
}
return diff;
}
The function deals in radians, but you can change <Pi> to 180 and <2Pi> to 360
Using this answer to compute angle of the vector:
CGFloat angleForVector(CGFloat dx, CGFloat dy) {
return atan2(dx, -dy) * 180.0/M_PI;
}
// Compute angle at point Corner, that is between AC and BC:
CGFloat angle = angleForVector(A.x - Corner.x, A.y - Corner.y)
- angleForVector(B.x - Corner.x, B.y - Corner.y);
NSLog(#"FULL ANGLE IS: %f, ANGLE IS: %.2f",angle, angle);

Calculus to convert Y coordinates for purpose of updating bpm in a metronome

I'm in the course of developing a metronome for iPad. I'm using CGAffineTransformRotate for the metronomeArm animation, NSTimer(I'm not interested in great precision) for sound and a UIPanGestureRecognizer for dragging the metronomeWeight on the metronomeArm.
My problem is that I don't know how to update the bpm by dragging the weight using the pan. For now I have this : metronomeWeight.center.y is 240 and the default bpm for this position is 80.The weight goes from top 140 to a maximum of 450. I have implemented this method but it is not correct :
-(void)updateBPM
{
CGFloat weightYPosition = metronomeWeight.center.y;
NSUInteger newBPM = (weightYPosition/3);
self.bpm = newBPM;
}
and the selector for the pan is this :
-(void)handlePan:(UIPanGestureRecognizer*)gesture
{
CGPoint translation = [gesture translationInView:metronomeArm];
CGPoint location = [gesture locationInView:metronomeArm];
NSLog(#"miscarea pe oy are valoare de: %f", location.y);
CGPoint newCenter = CGPointMake(metronomeArm.frame.size.width/2, gesture.view.center.y + translation.y );
if (newCenter.y >= 140 && newCenter.y <= 450)
{
gesture.view.center = newCenter;
[gesture setTranslation:CGPointZero inView:metronomeArm];
[self updateBPMFromWeightLocation];
tempoLabel.text = [NSString stringWithFormat:#"%d", self.bpm];
NSLog(#"metronomeWeight position : %f ",metronomeWeight.center.y);
}
}
The sound and animation update but not as desired, meaning that the lower limit bpm should be 225 and the upper one should be 1. In my case they are 150 and 46 respectively.
My calculations are not good, so it will be fantastic if you can help me solve this problem... I have looked at apple's metronome project for days and can't understand how they do this...
Thanks
The new updateBPM method thanks to #zimmryan suggestion
-(void)updateBPMFromWeightLocation
{
CGFloat weightYPosition = metronomeWeight.center.y;
float lengthInM = ((weightYPosition - 140) * 0.00041333);
float time = 2 * M_PI * sqrt(lengthInM / 9.8);
NSUInteger newBPM = floor(60.0 / time);
self.bpm = newBPM;
}
From my understanding of physics and calculus, the equation for the period of a pendulum is T=2pi sqrt(l/g) where T is time in seconds, l is length in meters, and g is gravity.
You are picking a base point of 290 (pixels) and a BPM of 120. A BPM of 120 converts to a period of .5 seconds. So T = .5. Solving the equation you get .062 for l, or 6.2cm.
But your length is not in cm it is in pixels s now you have to convert it. Since your range is from 140 to 350, your zero point is 350. So first you take 350 - 390 to get an offset of 60. Now create your equation of 60pixels * k = .062 so your k = .001033
Your final function should read
-(void)updateBPM
{
CGFloat weightYPosition = metronomeWeight.center.y;
float lengthInM = ((350 - weightYPosition) * .001033);
float time = 2 * M_PI * sqrt(lengthInM / 9.8);
NSUInteger newBPM = floor(60 / time);
self.bpm = newBPM;
}
or
-(void)updateBPM
{
self.bpm = floor(60 / (2 * M_PI * sqrt(((350 - metronomeWeight.center.y) * .001033) / 9.8)));
}

Scale a Bezier Curve Proportionally - calculate control points

I'm trying to take a bezier curve (any arbitrary curve in Core Graphics) and shrink (or expand) it proportionally given another two end points. I have an approach that sort of works, but it ends up 'flattening' out the curves, and not retaining the shape exactly. Maybe I've messed up the code or logic, but I have the two original points along with the control point(s). Given another set of end points I want to calculate the appropriate control points to produce the same shape between the new end points.
Here's the main code that will calculate 1 control point:
CGPoint (^ScaledCtrlPoint)(CGPoint, CGPoint, CGPoint, CGPoint, CGPoint) = ^CGPoint (CGPoint refPoint1, CGPoint refPoint2, CGPoint bevPoint1, CGPoint bevPoint2, CGPoint ctrlPoint){
//Normalize points to refPoint1
refPoint2.x -= refPoint1.x; refPoint2.y -= refPoint1.y;
ctrlPoint.x -= refPoint1.x; ctrlPoint.y -= refPoint1.y;
//Normalize bevPoints to bevPoint1
bevPoint2.x -= bevPoint1.x; bevPoint2.y -= bevPoint1.y;
//Calculate control point angle
CGFloat theta = PointTheta(refPoint2);
CGFloat refHyp = (refPoint2.y != 0.0f) ? refPoint2.y / sinf(theta) : refPoint2.x / cosf(theta);
theta = PointTheta(bevPoint2);
CGFloat bevHyp = (bevPoint2.y != 0.0f) ? bevPoint2.y / sinf(theta) : bevPoint2.x / cosf(theta);
theta = PointTheta(ctrlPoint);
CGFloat ctrlHyp = (ctrlPoint.y != 0.0f) ? ctrlPoint.y / sinf(theta) : ctrlPoint.x / cosf(theta);
ctrlHyp *= (bevHyp / refHyp);
return CGPointMake(bevPoint1.x + cosf(theta) * ctrlHyp, bevPoint1.y + sinf(theta) * ctrlHyp);
};
The bevPoints are the new points I'm using to calculate the new control point. The refPoints and ctrlPoint are the original points of the bezier curve. As you can see, I'm trying to scale the ctrlPoint down (could also work up) by the same ratio as the the original end points are to the new end points.
I also use another function, which I use to calculate incident angles. It's pretty simple:
CGFloat PointTheta(CGPoint point){
//This assumes an origin of {0, 0} and returns a theta for the given point
CGFloat theta = atanf(point.y / point.x);
//Using arc tan requires some adjustment depending on the point quadrant
if (point.x == 0.0f) theta = (point.y >= 0.0f) ? M_PI_2 : M_PI + M_PI_2;
else if (point.x < 0.0f) theta += M_PI;
else if (point.x > 0.0f && point.y < 0.0f) theta += (M_PI * 2);
return theta;
}
I would compute the CGAffineTransform with parameters
(a, b, -b, a, tx, ty)
(i.e. a transform without skewing) that maps the old endpoints to the new endpoints, and then apply this transform to the old control point to get the new control point.
The condition that the 2 old endpoints are mapped to the 2 new endpoints gives 4 equations for a, b, tx, ty, and these equations can even be solved without trigonometric functions.

Animation with an image in iPad

Hi I have an image like a round top of a table.
I want to move it clockwise when ever user swipes from left to right and counter clockwise when user swipes from right to left.
Like moving a round table top in real time.
How can I do this in the app?
I am using the following code for rotation. Its from the TrackBall example.
The problem I am having is the when ever the image spins, it changes its position.
- (CATransform3D)rotationTransformForLocation:(CGPoint)location
{
CGFloat trackBallCurrentPoint[3] = {location.x - trackBallCenter.x, location.y - trackBallCenter.y, 0.0f};
if(fabs(trackBallCurrentPoint[0] - trackBallStartPoint[0]) < kTol && fabs(trackBallCurrentPoint[1] - trackBallStartPoint[1]) < kTol)
{
return CATransform3DIdentity;
}
CGFloat dist = trackBallCurrentPoint[0] * trackBallCurrentPoint[0] + trackBallCurrentPoint[1] * trackBallCurrentPoint[1];
if(dist > trackBallRadius * trackBallRadius)
{
// outside the center of the sphere so make it zero
trackBallCurrentPoint[2] = 0.0f;
}
else
{
trackBallCurrentPoint[2] = sqrt(trackBallRadius * trackBallRadius - dist);
}
// cross product yields the rotation vector
CGFloat rotationVector[3];
rotationVector[0] = trackBallStartPoint[1] * trackBallCurrentPoint[2] - trackBallStartPoint[2] * trackBallCurrentPoint[1];
rotationVector[1] = -trackBallStartPoint[0] * trackBallCurrentPoint[2] + trackBallStartPoint[2] * trackBallCurrentPoint[0];
rotationVector[2] = trackBallStartPoint[0] * trackBallCurrentPoint[1] - trackBallStartPoint[1] * trackBallCurrentPoint[0];
// calc the angle between the current point vector and the starting point vector
// use arctan so we get all eight quadrants instead of just the positive ones
// cos(a) = (start . current) / (||start|| ||current||)
// sin(a) = (||start X current||) / (||start|| ||current||)
// a = atan2(sin(a), cos(a))
CGFloat startLength = sqrt(trackBallStartPoint[0] * trackBallStartPoint[0] + trackBallStartPoint[1] * trackBallStartPoint[1] + trackBallStartPoint[2] * trackBallStartPoint[2]);
CGFloat currentLength = sqrt(trackBallCurrentPoint[0] * trackBallCurrentPoint[0] + trackBallCurrentPoint[1] * trackBallCurrentPoint[1] + trackBallCurrentPoint[2] * trackBallCurrentPoint[2]);
CGFloat startDotCurrent = trackBallStartPoint[0] * trackBallCurrentPoint[0] + trackBallStartPoint[1] * trackBallCurrentPoint[1] + trackBallStartPoint[2] * trackBallCurrentPoint[2]; // (start . current)
// start X current we have already calcualted in the rotation vector
CGFloat rotationLength = sqrt(rotationVector[0] * rotationVector[0] + rotationVector[1] * rotationVector[1] + rotationVector[2] * rotationVector[2]);
CGFloat angle = atan2(rotationLength / (startLength * currentLength), startDotCurrent / (startLength * currentLength));
// normalize the rotation vector
rotationVector[0] = rotationVector[0] / rotationLength;
rotationVector[1] = rotationVector[1] / rotationLength;
rotationVector[2] = rotationVector[2] / rotationLength;
CATransform3D rotationTransform = CATransform3DMakeRotation(angle, rotationVector[0], rotationVector[1], rotationVector[2]);
return CATransform3DConcat(baseTransform, rotationTransform);
}
Thanks in advance.
Take a look at a question I posed... you might be trying to do the same thing (I don't think the question covered it, but after getting rotation working I implemented pan gesture to allow the user to spin the disc in either direction)
How to rotate a flat object around its center in perspective view?

Resources