Create Direction Arrows (arrow heads) on polyline in obj c - ios

I have been searching for a quite while but couldn't find any answer to this, anyways, I am working on Google Maps for iOS using Obj C and have drawn routes (polyline) using multiple coordinates provided to me by the server in the form of an array. But the problem is that I want to show arrow heads on that line so that the direction can be seen on the map. Please help.

Here's a function which draws a nice little arrow line. It has some parameters you can tweak:
void TRDrawLineWithArrow(CGContextRef CXT, CGPoint FROMPOINT, CGPoint TOPOINT, CGFloat WIDTH, CGFloat ARROWSIZEMULTIPLE)
{
CGFloat rise = TOPOINT.y - FROMPOINT.y;
CGFloat run = TOPOINT.x - FROMPOINT.x;
// trig
CGFloat length = sqrt(rise*rise + run+run);
CGFloat angle = atan2(rise, run);
// the length of our arrowhead
CGFloat arrowLen = WIDTH*ARROWSIZEMULTIPLE;
// push graphics context
CGContextSaveGState(CXT);
// transform context according to line's origin and angle
CGContextTranslateCTM(CXT, FROMPOINT.x, FROMPOINT.y);
CGContextRotateCTM(CXT, angle);
// draw straight line
CGContextMoveToPoint(CXT, 0, -WIDTH/2.);
CGContextAddLineToPoint(CXT, 0, WIDTH/2.);
CGContextAddLineToPoint(CXT, length-arrowLen, WIDTH/2.);
// draw arrowhead
CGContextAddLineToPoint(CXT, length-arrowLen, (WIDTH*ARROWSIZEMULTIPLE)/2.);
CGContextAddLineToPoint(CXT, length, 0);
CGContextAddLineToPoint(CXT, length-arrowLen, -(WIDTH*ARROWSIZEMULTIPLE)/2.);
CGContextAddLineToPoint(CXT, length-arrowLen, -WIDTH/2.);
CGContextAddLineToPoint(CXT, 0, -WIDTH/2.);
// fill the path
CGContextFillPath(CXT);
// pop graphics context
CGContextRestoreGState(CXT);
}
You would call it from a UIView like this:
CGContextRef cxt = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(cxt, [UIColor blackColor].CGColor);
TRDrawLineWithArrow(cxt, CGPointMake(10,10), CGPointMake(300,100), 5, 3);

Related

How can I track a point on a texture in OpenGL ES1?

In my iOS application I have a texture applied to a sphere rendered in OpenGLES1. The sphere can be rotated by the user. How can I track where a given point on the texture is in 2D space at any given time?
For example, given point (200, 200) on a texture that's 1000px x 1000px, I'd like to place a UIButton on top of my OpenGL view that tracks the point as the sphere is manipulated.
What's the best way to do this?
On my first attempt, I tried to use a color-picking technique where I have a separate sphere in an off-screen framebuffer that uses a black texture with a red square at point (200, 200). Then, I used glReadPixels() to track the position of the red square and I moved my button accordingly. Unfortunately, grabbing all the pixel data and iterating it 60 times a second just isn't possible for obvious performance reasons. I tried a number of ways to optimize this hack (eg: iterating only the red pixels, iterating every 4th red pixel, etc), but it just didn't prove to be reliable.
I'm an OpenGL noob, so I'd appreciate any guidance. Is there a better solution? Thanks!
I think it's easier to keep track of where your ball is instead of searching for it with pixels. Then just have a couple of functions to translate your ball's coordinates to your view's coordinates (and back), then set your subview's center to the translated coordinates.
CGPoint translatePointFromGLCoordinatesToUIView(CGPoint coordinates, UIView *myGLView){
//if your drawing coordinates were between (horizontal {-1.0 -> 1.0} vertical {-1 -> 1})
CGFloat leftMostGLCoord = -1;
CGFloat rightMostGLCoord = 1;
CGFloat bottomMostGLCoord = -1;
CGFloat topMostGLCoord = 1;
CGPoint scale;
scale.x = (rightMostGLCoord - leftMostGLCoord) / myGLView.bounds.size.width;
scale.y = (topMostGLCoord - bottomMostGLCoord) / myGLView.bounds.size.height;
coordinates.x -= leftMostGLCoord;
coordinates.y -= bottomMostGLCoord;
CGPoint translatedPoint;
translatedPoint.x = coordinates.x / scale.x;
translatedPoint.y =coordinates.y / scale.y;
//flip y for iOS coordinates
translatedPoint.y = myGLView.bounds.size.height - translatedPoint.y;
return translatedPoint;
}
CGPoint translatePointFromUIViewToGLCoordinates(CGPoint pointInView, UIView *myGLView){
//if your drawing coordinates were between (horizontal {-1.0 -> 1.0} vertical {-1 -> 1})
CGFloat leftMostGLCoord = -1;
CGFloat rightMostGLCoord = 1;
CGFloat bottomMostGLCoord = -1;
CGFloat topMostGLCoord = 1;
CGPoint scale;
scale.x = (rightMostGLCoord - leftMostGLCoord) / myGLView.bounds.size.width;
scale.y = (topMostGLCoord - bottomMostGLCoord) / myGLView.bounds.size.height;
//flip y for iOS coordinates
pointInView.y = myGLView.bounds.size.height - pointInView.y;
CGPoint translatedPoint;
translatedPoint.x = leftMostGLCoord + (pointInView.x * scale.x);
translatedPoint.y = bottomMostGLCoord + (pointInView.y * scale.y);
return translatedPoint;
}
In my app I choose to use the iOS coordinate system for my drawing too. I just apply a projection matrix to my whole glkView the reconciles the coordinate system.
static GLKMatrix4 GLKMatrix4MakeIOSCoordsWithSize(CGSize screenSize){
GLKMatrix4 matrix4 = GLKMatrix4MakeScale(
2.0 / screenSize.width,
-2.0 / screenSize.height,
1.0);
matrix4 = GLKMatrix4Translate(matrix4,-screenSize.width / 2.0, -screenSize.height / 2.0, 0);
return matrix4;
}
This way you don't have to translate anything.

In iOS, arcs are malformed for certain start angles

I use the following code to draw an arc
double radius = 358.40001058578491;
startAngle = 0.13541347644783652;
double center_x= 684;
double center_y = 440;
std::complex<double> start1( std::polar(radius,startAngle) );
CGPoint targetStart1 = CGPointMake(start1.real() + center_x, start1.imag() +center_y);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, targetStart1.x, targetStart1.y);
CGPathAddArc(path, NULL, center_x, center_y, radius, startAngle, 0.785, 0 );
CGContextAddPath(context, path);
CGContextSetLineWidth( context, 30 );
CGContextSetStrokeColorWithColor( context, targetColor.CGColor);
CGContextStrokePath(context);
CGPathRelease(path);
If u check it in retina, it looks like this:
My arc is the green arc. I have shown the place that the start angle is with a orange line. As I have shown in the red rectangle, there is an extra thing drawn in the very beginning of the arc. This happens not for all start angles, but only for certain start angles.
Do you have any idea why it happens?
Thanks.
In your original question, you specified a literal starting point that was not quite right and, as a result, Core Graphics will draw a line from that point to the start of the arc. And because that starting point was just a few pixels away from the actual start of the arc, it results in that curious rendering you illustrate in your question.
In your revised question, you're calculating the starting point, but I might suggest calculating it programmatically like so:
CGFloat centerX = 684.0;
CGFloat centerY = 440.0;
CGFloat radius = 360.0;
CGFloat startAngle = 0.135;
CGFloat endAngle = 0.785;
CGFloat startingX = centerX + radius * cosf(startAngle);
CGFloat startingY = centerY + radius * sinf(startAngle);
CGContextMoveToPoint(context, startingX, startingY);
CGContextAddArc(context, centerX, centerY, radius, startAngle, endAngle, 0);
CGContextSetLineWidth(context, 30);
CGContextSetStrokeColorWithColor(context, targetColor.CGColor);
CGContextStrokePath(context);
When I calculated it this way, there was no rounding errors that resulted in the artifact illustrated in your original question.
Note, if you're not drawing anything before the arc, you can just omit the CGContextMoveToPoint call altogether. You only need that "move to point" call if you've drawn something before the arc and don't want the path connecting from that CGContextGetPathCurrentPoint to the start of the arc.

Creating Clickable CGPaths

I have a UIView that contains a 'pie' with irregular sized slices and I need them to be clickable regions. Through a lot of trial and error I wrote a method to produce the pie, but now I am at a loss for how to make these slices into regions. Currently this method returns void so I need to modify to return something, like a CGPath? I guess where I'm really confused is how to take what I have and put it into a CGPath and return it. Any suggestions? thanks!
-(void)addClickableRegion:(float)beginDegrees to:(float)endDegrees withContext:(CGContextRef)ctx withRadiusOf:(int)rad{
int x = self.bounds.size.height / 2;
int y = self.bounds.size.width / 2;
float red = (arc4random() % 255)/255.0;
float green = (arc4random() % 255)/255.0;
float blue = (arc4random() % 255)/255.0;
CGContextMoveToPoint(ctx, x, y);
CGContextAddArc(ctx, x, y, rad, (beginDegrees) * M_PI/180.0, (endDegrees) * M_PI/180.0, 0);
CGContextSetRGBFillColor(ctx, red, green, blue, 1.0);
CGContextFillPath(ctx);
CGContextClosePath(ctx);
}
You are already creating paths, you're just creating them in the context rather than creating them and then adding them to the context. You don't need to do that. You can use CGPathCreateMutable, CGPathAddArc and CGContextAddPath.
You can also use UIBezierPath, either via bezierPathWithCGPath: or directly (and then get the CGPath from them).
And, once you have bezier paths, you can call containsPoint: on them to hit test touches.

Changing the pattern in setLineDash:count:phase: to an arc in ios core graphics

I am drawing an ellipse
CGRect paperRect = self.bounds;
CGRect strokeRect = CGRectInset(paperRect, 5.0, 5.0);
CGContextAddEllipseInRect(context, strokeRect);
CGContextStrokePath(context);
I was hoping to change this to a jarred ellipse (see pic). In search for the best way to do this, I was wondering if there is a way to use
setLineDash:count:phase:
and pass an arc/ curve as the pattern instead of line? Or is there a better way to do this?
UPDATE
I tried the following, as suggested by Wain:
float a = strokeRect.size.width/2;
float b = strokeRect.size.height/2;
float x1,y1,x2,y2,k;
x1=CGRectGetMinX(strokeRect);
y1=CGRectGetMinY(strokeRect);
int maxAngle = 360;
CGContextBeginPath(context);
CGContextMoveToPoint(context, x1, y1);
for(int i=0;i<maxAngle;i+=30){
float cX=cos(DEGREES_TO_RADIANS(i));
float sX=sin(DEGREES_TO_RADIANS(i));
k=1/(sqrt(pow((b*cX),2) + pow(a*sX,2)));
x2=k*a*b*cos(DEGREES_TO_RADIANS(i));
y2=k*a*b*sin(DEGREES_TO_RADIANS(i));
CGContextAddArcToPoint(context, x1, y1, x2, y2, 20);
x1=x2; // make x2 the new x1
y1=y2;
}
CGContextClosePath(context);
CGContextStrokePath(context);
I can see some irregular drawings, but nothing to the extend that makes sense visually.
You don't 'pass a line' as the dash. The dash is a description of how the line is drawn.
You need to create a path (instead of the ellipse) which traces the 'jarred ellipse' shape (using CGContextAddArcToPoint) and then draw that.
The points are all on the edge of the ellipse, you can find them with the calculations described here. I haven't tried it but I guess the first tangent point could be the center point of the ellipse...

How to use GLKit to rotate about the center of a sprite off an atlas

I have a texture that follows a user's finger in GLKit. I calculate the radian to draw the angle at using arctan between the two points.
Part of the trick here is to keep the object centered underfed the finger. So i have introduced the idea of an anchor point so that things can be drawn relative to their origin or center. My goal is to move the sprite into place and then rotate. I have the following code in my renderer.
// lets adjust for our location based on our anchor point.
GLKVector2 adjustment = GLKVector2Make(self.spriteSize.width * self.anchorPoint.x,
self.spriteSize.height * self.anchorPoint.y);
GLKVector2 adjustedPosition = GLKVector2Subtract(self.position, adjustment);
GLKMatrix4 modelMatrix = GLKMatrix4Multiply(GLKMatrix4MakeTranslation(adjustedPosition.x, adjustedPosition.y, 1.0), GLKMatrix4MakeScale(adjustedScale.x, adjustedScale.y, 1));
modelMatrix = GLKMatrix4Rotate(modelMatrix, self.rotation, 0, 0, 1);
effect.transform.modelviewMatrix = modelMatrix;
effect.transform.projectionMatrix = scene.projection;
One other note is that my sprite is on a texture alias. If i take out my rotation my sprite draws correctly centered under my finger. My project matrix is GLKMatrix4MakeOrtho(0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame), 0, 1, -1); so it matches the UIkit and the view its embedded in.
I ended up having to add a little more math to calculate additional offsets before i rotate.
// lets adjust for our location based on our anchor point.
GLKVector2 adjustment = GLKVector2Make(self.spriteSize.width * self.anchorPoint.x,
self.spriteSize.height * self.anchorPoint.y);
// we need to further adjust based on our so we can calucate the adjust based on our anchor point in our image.
GLKVector2 angleAdjustment;
angleAdjustment.x = adjustment.x * cos(self.rotation) - adjustment.y * sin(self.rotation);
angleAdjustment.y = adjustment.x * sin(self.rotation) + adjustment.y * cos(self.rotation);
// now create our real position.
GLKVector2 adjustedPosition = GLKVector2Subtract(self.position, angleAdjustment);
GLKMatrix4 modelMatrix = GLKMatrix4Multiply(GLKMatrix4MakeTranslation(adjustedPosition.x, adjustedPosition.y, 1.0), GLKMatrix4MakeScale(adjustedScale.x, adjustedScale.y, 1));
modelMatrix = GLKMatrix4Rotate(modelMatrix, self.rotation, 0, 0, 1);
This will create an additional adjustment based on where in the image we want to rotate and then transform based on that. This works like a charm..
There is a similar code I used to rotate a sprite around its center
First you move it to the position, then you rotate it, then you move it back halfsprite
- (GLKMatrix4) modelMatrix {
GLKMatrix4 modelMatrix = GLKMatrix4Identity;
float radians = GLKMathDegreesToRadians(self.rotation);
modelMatrix = GLKMatrix4Multiply(
GLKMatrix4Translate(modelMatrix, self.position.x , self.position.y , 0),
GLKMatrix4MakeRotation(radians, 0, 0, 1));
modelMatrix = GLKMatrix4Translate(modelMatrix, -self.contentSize.height/2, -self.contentSize.width/2 , 0);
return modelMatrix;
}

Resources