Drawing an Arrow with NSBezierPath between two Points - objective-c

So I'm trying to generate a NSBezierPath that looks like an arrow between two points, which can lie anywhere on the view, so the startPoint can be larger or smaller than the endpoint.
The arrow is updated while a user drags the mouse like in a drawing app.
I already figured out, that I probably have to use transformations and some math to do trigonometric and have come up with this implementation:
+(NSBezierPath *)arrowWithStart:(NSPoint)startPoint andEnd:(NSPoint)endPoint{
NSBezierPath* path = [NSBezierPath bezierPath];
CGFloat width = endPoint.x - startPoint.x;
CGFloat height = endPoint.y - startPoint.y;
CGFloat angle = atan2(width, height);
NSAffineTransform *tr = [NSAffineTransform transform];
[tr translateXBy:startPoint.x yBy:startPoint.y];
[tr scaleXBy:width yBy:height];
[tr rotateByDegrees:angle];
[path moveToPoint:CGPointZero];
[path lineToPoint:CGPointMake(0.75, 0.7)];
[path lineToPoint:CGPointMake(0.8, 0.65)];
[path lineToPoint:CGPointMake(1, 1)];
[path lineToPoint:CGPointMake(0.65, 0.8)];
[path lineToPoint:CGPointMake(0.7, 0.75)];
[path closePath];
[path transformUsingAffineTransform:tr];
return path;
}
This Code generates pretty nice arrows, when the points are some kind of diagonal,
like
(0,0)
(-2,-2)
to each other, but when the points are getting nearer to a horizontal or vertical line, like
(2,3)
(5,3)
the result becomes a straight line without an arrowhead.
So I think I'm doing something wrong in the transformation Matrix.
If somebody knows where I'm making a mistake it would be great.

What you need is an affine transformation that transforms the point (0, 0) to startPoint and (1, 1) to endPoint. This transformation can be computed directly:
CGFloat tx = startPoint.x;
CGFloat ty = startPoint.y;
CGFloat a = ((endPoint.x - startPoint.x) + (endPoint.y - startPoint.y))/2.;
CGFloat b = (-(endPoint.x - startPoint.x) + (endPoint.y - startPoint.y))/2.;
NSAffineTransformStruct transformStruct = { a, b, -b, a, tx, ty };
NSAffineTransform *tr = [NSAffineTransform transform];
[tr setTransformStruct:transformStruct];
Explanation:
NSAffineTransformStruct transformStruct = { a, b, -b, a, tx, ty };
describes a general combination of translation, scaling and rotation, i.e. an affine transformation without shearing. The requirements (0, 0) -> startPoint, (1, 1) -> endPoint give the equations
startPoint.x = 0 * a + 0 * (-b) + tx
startPoint.y = 0 * b + 0 * a + ty
endPoint.x = 1 * a + 1 * (-b) + tx
endPoint.y = 1 * b + 1 * a + tx
and solving these equations for a, b, tx, ty gives above solution. (See Transform Mathematics in the "Cocoa Drawing Guide" for more information.)
The problem with your original code is that
atan2 takes y as first argument, so atan2(height, width) would compute the angle.
For horizontal or vertical lines, width or height and therefore one scaling factor is zero, this causes the straight lines without arrowhead.

Related

Calculate an intercept velocity between two SKSpriteNodes

I created a "ship" node to move along the circular path as follows:
self.orbit = [OrbitManager getCirclePathWithCenter:centerRealPt radius:radius startingAngle:angelFromCenter isClockwise:self.isClockwise];
SKAction* orbitAction = [SKAction followPath:self.orbit asOffset:NO orientToPath:YES speed:300];
[self.ship.node runAction:orbitAction];
and I have a cannon which shoots a bullet by applying a velocity to it as follows:
bullet.node.physicsBody.velocity = [ActionHelper getVelocityFrom:bullet.node toNodeB:self.target speed:bullet.speed];
as the ship is moving along the path. But the bullet will miss every time. How can I calculate the position which the cannon should aim at, with a given speed?
This is my Objective-C (it is actually a C function) Solution to fire a projectile in to a moving target.
You can look at the derivations In this SO topic
This will give you a hit point and an angle to shoot,
you can simply translate it to velocity because you know the angle and a projectile speed, it will be something like:
`CGVector Velocity = CGVectorMake(speed * cos(theta), speed * sin(theta));`
BOOL calculateAngleToShoot(CGVector targetVelocity, CGPoint targetPosition, CGPoint selfPos,CGFloat projectileSpeed,CGFloat *theta, CGPoint * hitPoint)
{
CGFloat dx = targetPosition.x - selfPos.x;
CGFloat dy = targetPosition.y - selfPos.y;
CGVector v = targetVelocity;
CGFloat a = v.dx * v.dx + v.dy * v.dy - projectileSpeed * projectileSpeed;
CGFloat b = 2 * (v.dx * dx + v.dy * dy);
CGFloat c = v.dx * v.dx + v.dy * v.dy;
CGFloat q = b * b - 4 * a * c;
if (q < 0)
{
//Dead End;
return NO;
}
CGFloat t = ((a < 0 ? -1 : 1) * sqrt(q) - b) / (2 * a);
// Aim for where the target will be after time t
dx += t * v.dx;
dy += t * v.dy;
*theta = atan2(dy, dx);
*hitPoint = CGPointMake(targetPosition.x + v.dx * t, targetPosition.y + v.dy * t);
return YES;
}
After some investigation I got how to get the answer
first I need to get the distance(d) between the target and the center
and the time for the bullet from center to the target.
since the ship is moving along the circle, so the radius is also equals to distance(d)
CGFloat timeToArriveTarget = bullet.speed/distance;
CGFloat angularSpeed = bullet.speed/distance;
Find the angle moved within this period of time
CGFloat angle = angularSpeed * timeToArriveTarget;
CGFloat x = self.target.position.x;
CGFloat y = self.target.position.y;
CGFloat a = bullet.node.position.x;
CGFloat b = bullet.node.position.y;
and finally using this formula:
details are give by this link https://math.stackexchange.com/a/266837
CGPoint targetPt = CGPointMake((x - a) * cos(angle) - (y - b) * sin(angle) + a, (x - a) * sin(angle) + (y - b) * cos(angle) + b);
bullet.node.physicsBody.velocity = [ActionHelper getVelocityFrom:bullet.node.position toPtB:targetPt speed:bullet.speed];
the getVelocity function is given by
+(CGVector)getVelocityFrom:(CGPoint)ptA toPtB:(CGPoint)ptB speed:(CGFloat)speed{
CGPoint targetPosition = ptB;
CGPoint currentPosition = ptA;
double angle = [MathHelper getRotateAngleFrom:currentPosition toTargetPosition:targetPosition];
double velocityX = speed * cos(angle);
double velocityY = speed * sin(angle);
CGVector newVelocty = CGVectorMake(velocityX, velocityY);
return newVelocty;
}

iBeacon Trilateration - Vector Math

I'm having a bit of trouble with Trilateration using iBeacon's. I found a formula using vector math in this thread, and I implemented it in my project. However it seems to be giving me odd numbers.
It seems like it works most of the time, however I've noticed that sometimes it will give me extremely similar answers when using distances that are vastly different, which seems odd to me.
CGPoint pointA = [[curBlufiPoints objectAtIndex:0] CGPointValue];
CGPoint pointB = [[curBlufiPoints objectAtIndex:1] CGPointValue];
CGPoint pointC = [[curBlufiPoints objectAtIndex:2] CGPointValue];
/*Point A = (40, 612)
Point B = (379, 788)
Point C = (0, 352)*/
float distanceA = [[distances objectAtIndex:0] floatValue];
float distanceB = [[distances objectAtIndex:1] floatValue];
float distanceC = [[distances objectAtIndex:2] floatValue];
/*distanceA = 13.535637
distanceB = 46.931522
distanceC = 51.585461
----OR----
distanceA = 349.9057;
distanceB = 352.84134;
distanceC = 353.37515;*/
CGPoint P2MinusP1 = CGPointMake(pointB.x - pointA.x, pointB.y - pointA.y);
CGPoint P3MinusP1 = CGPointMake(pointC.x - pointA.x, pointC.y - pointA.y);
CGFloat magP2MinusP1 = sqrt(pow((P2MinusP1.x), 2) + pow((P2MinusP1.y), 2));
CGPoint eX = CGPointMake(P2MinusP1.x / magP2MinusP1, P2MinusP1.y / magP2MinusP1);
CGFloat i = eX.x * P3MinusP1.x + eX.y * P3MinusP1.y;
CGPoint eXTimesI = CGPointMake(eX.x * i, eX.y * i);
CGPoint P3MinusP1MinusITimesEX = CGPointMake(P3MinusP1.x - eXTimesI.x, P3MinusP1.y - eXTimesI.y);
CGFloat magP3MinusP1MinusITimesEX = sqrt(pow(P3MinusP1MinusITimesEX.x, 2) + pow(P3MinusP1MinusITimesEX.y, 2));
CGPoint eY = CGPointMake(P3MinusP1MinusITimesEX.x / magP3MinusP1MinusITimesEX, P3MinusP1MinusITimesEX.y / magP3MinusP1MinusITimesEX);
CGFloat j = eY.x * P3MinusP1.x + eY.y * P3MinusP1.y;
CGFloat x = (pow(distanceA, 2) - pow(distanceB, 2) + pow(magP2MinusP1, 2)) / (2 * magP2MinusP1);
CGFloat y = (pow(distanceA, 2) - pow(distanceC, 2) + pow(i, 2) + pow(j, 2)) / (2 * j) - (i * x) / j;
CGPoint finalPoint = CGPointMake(pointA.x + x * eX.x + y * eY.x, pointA.y + x * eX.y + y * eY.y);
NSLog(#"%f %f %f %f", finalPoint.x, finalPoint.y);
//finalPoint.x = ~343
//finalPoint.y = ~437
As you can see from the values I've commented in the code above, when I am using the same points, but different distances I end up with the same result. It doesn't make sense to me how they can both have around the same answer. If the point (343, 437), (the answer I get for both sets of input) is 13.5 units away from point (40, 612), how can the same point also be 349.9 units away?
I'm not sure where my math has gone wrong but I think that something about calculating x and y is where the problems comes in. I've discovered that the lines "pow(distanceA, 2) - pow(distanceB, 2)" and "pow(distanceA, 2) - pow(distanceC, 2)" give me approximately the same answer for both sets of numbers, which is why x and y end up being the same regardless of which set of numbers I use.
I don't think that this should be the case, any help would be greatly appreciated, thank you!
I tried your code with a sample (and bad looking code test).
The testing code is here.
To debug, I used the "3D" new feature of XCode 6, to see the points and circle that were out of the screen bounds, because I didn't want to do translation (recentering the plane, etc.)
The main issue (with your first sample test (#[#(13.535637), #(46.931522), #(51.585461)];) is that the circles aren't overlapping. There is NO intersection. So you can't guess the position.
If you draw a circle with center pointA, and radius distanceA, and do it for point B and C, you'll need to find the intersection to geolocalize, which is clearly illustrated on the wiki-related article on the linked question.
I added a "check" method to see if they overlap correctly, with a "allowed mistake", because we're doing some maths with float and there could be a few rounding issue.
The check method consist on recalculating the distance between the finalPoint (coordinate found) and each point. The question is now if they differs (a lot?) from the original ones.
Screenshot with first set of distances
Screenshot with second set of distances

Bezier curve algorithm in objective-c needs a tweak

I asked a quick question Bezier curve algorithm in objective-c that seemed to solve my issues. I'm asking this new question as I think its different enough rather than repurposing the old one.
I have what seems like a working Bezier Curve algorithm but when compared to built in NSBezierPath version there are some major issues. It looks as though certain types of curves are very much distorted.
You can see from the above image the differences, the red line is my function and the lighter color is the built in version. I am not expecting and exat match, pixel for pixel, but as you can see the red lines go way off course at times.
The first method I'm listing is what calls the 2 Bezier methods, it shows that the inputs are the same to both versions.
- (void)MakeBezier
{
int x1 = [self getMegaNumber:2];
int y1 = self.frame.size.height - [self getMegaNumber:2];
int x2 = [self getMegaNumber:2];
int y2 = self.frame.size.height - [self getMegaNumber:2];
int x3 = [self getMegaNumber:2];
int y3 = self.frame.size.height - [self getMegaNumber:2];
int x4 = [self getMegaNumber:2];
int y4 = self.frame.size.height - [self getMegaNumber:2];
int cnt = [self getMegaNumber:2];
NSBezierPath *bezierPath = [[NSBezierPath alloc] init];
[bezierPath setLineWidth:1.0f];
[bezierPath moveToPoint:NSMakePoint(x1, y1)];
[bezierPath curveToPoint:NSMakePoint(x4, y4) controlPoint1:NSMakePoint(x2, y2) controlPoint2:NSMakePoint(x3, y3)];
// Draw path to image with build in NSBezierPath
[self drawPath:bezierPath fill:NO];
// Draw path with custom algorithm
[self drawBezierFrom:NSMakePoint(x1, y1) to:NSMakePoint(x4, y4) controlA:NSMakePoint(x2, y2) controlB:NSMakePoint(x3, y3) sections:cnt color:4];
}
This next method is the custom algorithm thats used to draw the red lines in the sample image.
- (void)drawBezierFrom:(NSPoint)from to:(NSPoint)to controlA:(NSPoint)a controlB:(NSPoint)b sections:(NSUInteger)cnt color:(NSUInteger)color
{
float qx, qy;
float q1, q2, q3, q4;
int lastx = - 1, lasty;
int plotx, ploty;
float t = 0.0;
while (t <= 1)
{
q1 = t*t*t*-1 + t*t*3 + t*-3 + 1;
q2 = t*t*t*3 + t*t*-6 + t*3;
q3 = t*t*t*-3 + t*t*3;
q4 = t*t*t;
qx = q1*from.x + q2*a.x + q3*to.x + q4*b.x;
qy = q1*from.y + q2*a.y + q3*to.y + q4*b.y;
plotx = round(qx);
ploty = round(qy);
if (lastx != -1)
[self drawLineFrom:NSMakePoint(lastx, lasty) to:NSMakePoint(plotx, ploty) color:color];
else
[self drawLineFrom:NSMakePoint(from.x, from.y) to:NSMakePoint(plotx, ploty) color:color];
lastx = plotx;
lasty = ploty;
t = t + (1.0/(cnt + 0.0f));
}
[self drawLineFrom:NSMakePoint(lastx, lasty) to:NSMakePoint(to.x, to.y) color:color];
}
So my question is; is the custom algorithm way off or is it just missing an edge case for specific kinds of lines, or something else? Either way any help in fixing the algorithm would be very appreciated. To reiterate, I am not looking for a pixel perfect match, but I am expecting the curves to lineup together.
Looking at the Wikipedia page here it seems that your coefficients for the q1-q4 are incorrect. Shouldn't they be symmetric?
It also seems that to.x and b.x should be swapped:
qx = q1*from.x + q2*a.x + q3*to.x + q4*b.x;
qy = ...

How to draw circular bars?

I am devleoping a game with cocos2d-iphone.
I want to great a circular health bar. Think of Kingdom Hearts or something.
I am able to draw circles with ccDrawLine, but they are full circles. Basically, I need to be able to draw up to a certain circumference value to represent the health properly. However, I am not really sure about this. Any ideas?
I had a quick look at the code for ccDrawCircle. If I was approaching this, I'd probably start by modifying the way the for loop works (maybe by playing with coef or segs) so that it stops early.
void ccDrawCircle( CGPoint center, float r, float a, NSUInteger segs, BOOL drawLineToCenter)
{
int additionalSegment = 1;
if (drawLineToCenter)
additionalSegment++;
const float coef = 2.0f * (float)M_PI/segs;
GLfloat *vertices = calloc( sizeof(GLfloat)*2*(segs+2), 1);
if( ! vertices )
return;
for(NSUInteger i=0;i<=segs;i++)
{
float rads = i*coef;
GLfloat j = r * cosf(rads + a) + center.x;
GLfloat k = r * sinf(rads + a) + center.y;
vertices[i*2] = j * CC_CONTENT_SCALE_FACTOR();
vertices[i*2+1] =k * CC_CONTENT_SCALE_FACTOR();
}
vertices[(segs+1)*2] = center.x * CC_CONTENT_SCALE_FACTOR();
vertices[(segs+1)*2+1] = center.y * CC_CONTENT_SCALE_FACTOR();
// Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY
// Needed states: GL_VERTEX_ARRAY,
// Unneeded states: GL_TEXTURE_2D, GL_TEXTURE_COORD_ARRAY, GL_COLOR_ARRAY
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, vertices);
glDrawArrays(GL_LINE_STRIP, 0, (GLsizei) segs+additionalSegment);
// restore default state
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_TEXTURE_2D);
free( vertices );
}
CGContextAddArc() will do the trick. An example explains everything.
CGContextAddArc(CGFloat centerX, CGFloat centerY, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise);
I'm not quite sure about the order of the parameters, you'd better google it or let XCode do the work for you.

help to calculate atan2 properly

I need to calculate the angle between lines. I need to calculate atan. So I am using such code
static inline CGFloat angleBetweenLinesInRadians2(CGPoint line1Start, CGPoint line1End)
{
CGFloat dx = 0, dy = 0;
dx = line1End.x - line1Start.x;
dy = line1End.y - line1Start.y;
NSLog(#"\ndx = %f\ndy = %f", dx, dy);
CGFloat rads = fabs(atan2(dy, dx));
return rads;
}
But I can't get over 180 degrees(( After 179 deg going 178..160..150 and so on.
I need to rotate on 360 degrees. How can I do it? What's wrong?
maby this helps:
//Tells the receiver when one or more fingers associated with an event move within a view or window.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSArray *Touches = [touches allObjects];
UITouch *first = [Touches objectAtIndex:0];
CGPoint b = [first previousLocationInView:[self imgView]]; //prewious position
CGPoint c = [first locationInView:[self imgView]]; //current position
CGFloat rad1 = angleBetweenLinesInRadians2(center, b); //first angel
CGFloat rad2 = angleBetweenLinesInRadians2(center, c); //second angel
CGFloat radAngle = fabs(rad2 - rad1); //angel between two lines
if (tempCount <= gradus)
{
[imgView setTransform: CGAffineTransformRotate([imgView transform], radAngle)];
tempCount += radAngle;
}
}
atan2 returns results in [-180,180] (or -pi, pi in radians). To get results from 0,360 use:
float radians = atan2(dy, dx);
if (radians < 0) {
radians += M_PI*2.0f;
}
It should be noted that it is typical to express rotations in [-pi,pi] and thusly you can just use the result of atan2 without worrying about the sign.
Remove the fabs call and simply make it:
CGFloat rads = atan2(dy, dx);
Use this function in Swift. This makes sure the angle from "fromPoint" to "toPoint" lands between 0 to <360 (not including 360). Please note, the following function assumes that CGPointZero is at top left corner.
func getAngle(fromPoint: CGPoint, toPoint: CGPoint) -> CGFloat {
let dx: CGFloat = fromPoint.x - toPoint.x
let dy: CGFloat = fromPoint.y - toPoint.y
let twoPi: CGFloat = 2 * CGFloat(M_PI)
let radians: CGFloat = (atan2(dy, -dx) + twoPi) % twoPi
return radians * 360 / twoPi
}
For the case where the origin is at the bottom left corner
let twoPi = 2 * Float(M_PI)
let radians = (atan2(-dy, -dx) + twoPi) % twoPi
let angle = radians * 360 / twoPi
Your problem is that the result of atan2 is between -180 and +180 degrees.
If you want it to be between 0 and 360 then move the result to sure be a positive value, and then do a modulo. For example:
let angle = fmod(atan2(dx,dy) + .pi * 2, .pi * 2)