Why doesn't CGPathIsRect work? Why doesn't CGPathContainsPoint work? - ios

I am making a CGPoint and a CGPathRef then trying to find if the CGPoint is inside the CGPathRef. Here is the code:
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 0, 0);
CGPathMoveToPoint(path, NULL, 200, 0);
CGPathMoveToPoint(path, NULL, 200, 200);
CGPathMoveToPoint(path, NULL, 0, 200);
CGPathCloseSubpath(path);
CGPoint hitPoint = CGPointMake(77, 77);
if ( CGPathIsEmpty(path) )
NSLog(#"Path Is Empty!");
else
{
if ( CGPathIsRect(path, NULL) )
NSLog(#"Path is a Rectangle!");
else
{
NSLog(#"Path is NOT a Rectangle!");
if (CGPathContainsPoint(path, NULL, hitPoint, FALSE)) // FALSE or TRUE - same result
NSLog(#"Hit Point Inside: x=%f, y=%f", hitPoint.x, hitPoint.y);
else
NSLog(#"Hit Point Outside: x=%f, y=%f", hitPoint.x, hitPoint.y);
}
}
The output reads:
Path is NOT a Rectangle!
Hit Point Outside: x=77.000000, y=77.000000
The path obviously is a rectangle and the point is inside the closed path. Please clue me in on what I am doing wrong here.

CGRectIsPath only returns true if the path was created by CGPathCreateWithRect (with a transform parameter that doesn't rotate or skew the rectangle), or if the path was created by CGPathCreateMutable and had a single rectangle added to it with CGPathAddRect.
It would be much more work for it to figure out whether any arbitrary path is exactly a rectangle. The path might contain bezier curve segments that are actually straight lines, or sides that are built from consecutive straight line segments.
If you need to detect whether any arbitrary path is actually just a rectangle, you will have to do it yourself using CGPathApply. It will be complicated.
As for why your point-inside test isn't working: you need to use CGPathAddLineToPoint to create the sides of your rectangle:
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 0, 0);
CGPathAddLineToPoint(path, NULL, 200, 0);
CGPathAddLineToPoint(path, NULL, 200, 200);
CGPathAddLineToPoint(path, NULL, 0, 200);
CGPathCloseSubpath(path);

Related

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

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);

Cannot create two CGPathAddArc on the same CGMutablePathRef

I'm trying to understand why i'm seeing only one of mine CGPathAddArc.
Code :
var r: CGRect = self.myView.bounds
var lay: CAShapeLayer = CAShapeLayer()
var path: CGMutablePathRef = CGPathCreateMutable()
CGPathAddArc(path, nil, 30, 30, 30, 0, (360 * CGFloat(M_PI))/180, true )
CGPathAddArc(path, nil, 70, 30, 30, 0, (360 * CGFloat(M_PI))/180, true )
CGPathAddRect(path, nil, r2)
CGPathAddRect(path, nil, r)
lay.path = path
lay.fillRule = kCAFillRuleEvenOdd
self.myView.layer.mask = lay
result :
Any suggestions? Thanks!
If you push down the command key and click on CGPathAddArc function, you will see documentation.
/* Note that using values very near 2π can be problematic. For example,
setting `startAngle' to 0, `endAngle' to 2π, and `clockwise' to true will
draw nothing. (It's easy to see this by considering, instead of 0 and 2π,
the values ε and 2π - ε, where ε is very small.) Due to round-off error,
however, it's possible that passing the value `2 * M_PI' to approximate
2π will numerically equal to 2π + δ, for some small δ; this will cause a
full circle to be drawn.
If you want a full circle to be drawn clockwise, you should set
`startAngle' to 2π, `endAngle' to 0, and `clockwise' to true. This avoids
the instability problems discussed above. */
Setting startAngle to 0, endAngle to 2π, and clockwise to true will
draw nothing. If you want a full circle to be drawn clockwise, you should set
startAngle to 2π, endAngle to 0, and clockwise to true. So that you can see all circles.
What you need to do is debug. How? Well, when in doubt, your first step should be to simplify. In this case, you should start by testing your code outside the context of a mask and a fill rule. When you do, you'll see that the arcs are in fact both present. I ran this reduced version of your code:
let lay = CAShapeLayer()
lay.frame = CGRectMake(20,20,400,400)
let path = CGPathCreateMutable()
CGPathAddArc(path, nil, 30, 30, 30, 0,
(360 * CGFloat(M_PI))/180, true)
CGPathAddArc(path, nil, 70, 30, 30, 0,
(360 * CGFloat(M_PI))/180, true)
lay.path = path
self.view.layer.addSublayer(lay)
And this is what I got:
As you can see, both arcs are present. So your results must be due to some complication beyond the drawing of the arcs.
If we add the fill rule...
lay.fillRule = kCAFillRuleEvenOdd
...we get this:
And if we introduce the mask element...
// self.view.layer.addSublayer(lay)
self.view.layer.mask = lay
...we get this:
Thus, using basic tests, you should be able to convince yourself of what this part of your code does. You can now introduce more and more of your actual code until you start getting undesirable results, and then you'll know what's causing the problem.

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.

Assertion failed when use bodyWithPolygonFromPath: method to egg shape

I am simulating physics to an egg shape SKSprite node.
However, there is assertion failed error occur:
Assertion failed: (edge.LengthSquared() > 1.19209290e-7F * 1.19209290e-7F), function Set, file /SourceCache/PhysicsKit_Sim/PhysicsKit-6.5.4/PhysicsKit/Box2D/Collision/Shapes/b2PolygonShape.cpp, line 180.
the following is the code:
self.egg = [SKSpriteNode spriteNodeWithImageNamed:IMAGE_NAME_EGG];
[self.egg setScale:0.2];
self.egg.position = CGPointMake(self.size.width/2,self.size.height - self.egg.size.height/2);
self.egg.name = IMAGE_NAME_EGG;
CGPoint startPoint = CGPointMake(0, self.egg.size.height*0.4);
CGPoint endPoint = CGPointMake(self.egg.size.width, startPoint.y);
CGPoint controlPointLeft = CGPointMake(startPoint.x, self.egg.size.height);
CGPoint controlPointRight = CGPointMake(endPoint.x, controlPointLeft.y);
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathMoveToPoint(pathRef, NULL, startPoint.x, startPoint.y);
CGPathAddQuadCurveToPoint(pathRef, NULL, controlPointLeft.x, controlPointLeft.y, self.egg.size.width/2, controlPointLeft.y);
CGPathAddQuadCurveToPoint(pathRef, NULL, controlPointRight.x, controlPointRight.y, endPoint.x,endPoint.y);
CGPathAddArc(pathRef, NULL, self.egg.size.width/2, startPoint.y, self.egg.size.width/2, 0, M_PI, NO);
self.egg.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:pathRef];
self.egg.physicsBody.dynamic = YES;
self.egg.physicsBody.categoryBitMask = eggCategory;
self.egg.physicsBody.contactTestBitMask = rabbitCategory;
self.egg.physicsBody.collisionBitMask = rabbitCategory;
self.egg.physicsBody.allowsRotation = YES;
[self addChild:self.egg];
What's wrong? can anyone help me to fix? thank you very much!
when you use " bodyWithPolygonFromPath: ", you mut make sure that the path meats the following conditions:
1). convex polygonal path
2). counterclockwise winding
3). no self intersections.
//The points are specified relative to the owning node’s origin.
you can find it in apply class reference for SKPhysicsBody here:
https://developer.apple.com/library/ios/documentation/SpriteKit/Reference/SKPhysicsBody_Ref/Reference/Reference.html#//apple_ref/occ/clm/SKPhysicsBody/bodyWithPolygonFromPath:
According to the assertion, one of the edges is shorter than the minimum size expected by sprite kit.
Assertion failed: (edge.LengthSquared() > 1.19209290e-7F * 1.19209290e-7F)
Check your path coordinates and ensure it's not too small.
I had this error on an older iOS7 iPhone when attempting to make a triangular path. It turns out that adding a final line to bring the path back to the starting point led to the error (though newer iOS versions allow this.) IE:
CGPathMoveToPoint(path, nil, self.size.width/2, self.size.height/2)
CGPathAddLineToPoint(path, nil, -self.size.width/2, 0.0)
CGPathAddLineToPoint(path, nil, self.size.width/2, -self.size.height/2)
CGPathAddLineToPoint(path, nil, self.size.width/2, self.size.height/2) //REMOVED THIS LINE TO FIX
CGPathCloseSubpath(path)
This explains why there was an edge that was too small to evaluate - the edge between two of the same points is 0!

Getting the outer path only of a 'merged' Core Graphics path

I have created a path in Core Graphics to draw a heart shape, however, to do this I have created a diamond shape and 2 circles, this image looks great, but I just want to know if it is possible to get the 'actual' heart shape from this CGPath/CGContext.
Here is the code to draw a heart (for anyone interested), this shape is the one I want to create as the connection between the heart 'humps' and the pointed triangle are smooth:
CGPathAddEllipseInRect(heartPath, NULL, CGRectMake(0, 0, 20, 20));
CGPathAddEllipseInRect(heartPath, NULL, CGRectMake(20, 0, 20, 20));
CGPathMoveToPoint(heartPath, NULL, 37.5, 16.5);
CGPathAddLineToPoint(heartPath, NULL, 37.5, 16.5);
CGPathAddLineToPoint(heartPath, NULL, 20, 37.5);
CGPathAddLineToPoint(heartPath, NULL, 2.5, 16.5);
CGPathAddLineToPoint(heartPath, NULL, 20, 10);
CGPathCloseSubpath(heartPath);
Produces this
And this code:
CGPathAddArc(heartPath, NULL, 10, 10, 10, M_PI, 0, false); // Left hump
CGPathAddArc(heartPath, NULL, 30, 10, 10, M_PI, 0, false); // Right hump
CGPathAddLineToPoint(heartPath, NULL, 20, 37.5); // Pointy end
CGPathCloseSubpath(heartPath);
Produces this
Is this possible and easy to implement? If so how?
Thanks in advance!
There is no public function to compute the union of multiple paths (nor any private function as far as I know). Computing the union of bezier paths is a hard, complicated problem.
Depending on what you want to do, there may be easier ways. What do you want to do with the union path?
Instead of drawing 3 different shapes, you should draw a single shape by doing something like this:
CGPathAddArc(heartPath, NULL, 10, 10, 10, M_PI, 0, false); // Left hump
CGPathAddArc(heartPath, NULL, 30, 10, 10, M_PI, 0, false); // Right hump
CGPathAddLineToPoint(heartPath, NULL, 20, 37.5); // Pointy end
CGPathCloseSubpath(heartPath);
I'm not positive all the values are correct, but basically you want to draw only part of the circle, so use CGPathAddArc() twice, and then a line to the point at the pointy end, and then close it.

Resources