I'm using CA to draw line segments from an array of points. For some reason, although I did not close the NSBezierPath, CAShapeLayer results in a closed shape. The following are my codes. Do anyone else have this problem?
// mappedPoints is an array of CGPoints
NSBezierPath *path = [NSBezierPath bezierPath];
[path appendBezierPathWithPoints:mappedPoints count:numPoints];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [color CGColor];
shapeLayer.lineWidth = lineWidth;
shapeLayer.fillColor = nil;
[self.layer addSublayer:shapeLayer];
I'm allowing myself to guess here that the [path CGPath] call is what's closing that path for you, as that's exactly what happened to me.
To give a bit more context - Apple's Creating a CGPathRef From an NSBezierPath Object sample code, which creates an NSBezierPath category method like CGPath above, has this in it:
// Be sure the path is closed or Quartz may not do valid hit detection.
if (!didClosePath)
CGPathCloseSubpath(path);
The reasoning behind that (from the same source):
Quartz requires paths to be closed in order to do hit detection on the
path’s fill area
If you're not interested in fill-area hit detection, it's safe to skip over that path-closing part and you'd get the expected behavior.
Related
I'm trying to draw a power function curve in a 300x300 pixel rectangle using NSBezierPath as follows:
-(void)drawPowerCurve:(float)power points:(int)numbPoints{
NSBezierPath * path = [NSBezierPath bezierPath];
[path setLineWidth: 1.0];
[[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:1.0 alpha:1.0] set];
NSPoint borderOrigin = {35.5,15.5};
NSPoint endPoint;
[path moveToPoint:borderOrigin];
for(int i = 0; i < numbPoints; i++){
endPoint.x = borderOrigin.x + (300.0/numbPoints)*(i+1);
endPoint.y = borderOrigin.y + 300.0*pow(((i+1)/(float)numbPoints), power);
[path lineToPoint:endPoint];
[path stroke];
[path moveToPoint:endPoint];
}
}
However, the curve thins out at the top end compared to the bottom end.
For example power = 1.8 and numbPoints = 50.
Also the curve doesn't look as smooth as for example curves shown in Apple's ColorSync Utility. Of course I don't know how they are drawing the curves in ColorSync. Any ideas on how to improve the look of these curves (particularly getting rid of the thining out).
Edit -- Here is a screenshot:
Move the stroke out of the loop so you only draw the curve once instead of numbPoints times as it grows. Also remove the redundant moveToPoint in the loop, lineToPoint leaves the current point at the end of the added segment.
I'am struggling to find any information about bending or extruding along path some bezier profile.
Let's say I have some path:
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointZero];
[path addLineToPoint:CGPointMake(5, 0)];
[path addLineToPoint:CGPointMake(5, 0.1)];
[path addLineToPoint:CGPointMake(0, 0.1)];
[path closePath];
(for simplicity its a box, but that profile can be something more complicated, like 3 rounded corners and some curve)
and I can extrude it to 3d object with SCNShape, like this:
SCNShape *ringShape = [SCNShape shapeWithPath: path extrusionDepth: 1];
ringShape.firstMaterial.doubleSided = YES;
SCNNode *node = [SCNNode nodeWithGeometry: pieShape];
but now I would love to bend this "box" shape to 360 degrees to form a ring shape. Is this even possible without loading external geometry?
Maybe there are some other option to make that geometry programmatically, maybe some SCNTube and then chamfer its edges with path profile ? Or some shader ?
For visual helper, this is what I mean, profile ~ 360 extrude = ring
Any pointers would be lovely, as I can't find much information about SceneKit.
I'm drawing some simple bezier paths, but I'm finding it impossible to remove the spikes created when the angle between line segments is small:
(Note: The circle is from a separate drawing operation, but I'm trying to make sure the line does not spike past the circle...).
I've tried all kinds of variations of lineCapStyle and lineJoinStyle but nothing seems to work.
In addition to what is shown below I have tried using a Miter Join with 'setMiterLimit'.
Here's my line drawing code snip:
CAShapeLayer *myShapeLayer=[CAShapeLayer layer];
UIBezierPath *myPath=[UIBezierPath bezierPath];
[myPath moveToPoint:tmpPoint];
[myPath addLineToPoint:tmpPoint];
[myPath setLineCapStyle:kCGLineCapRound];
[myPath setLineJoinStyle:kCGLineJoinRound];
myShapeLayer.path=[myPath CGPath];
myShapeLayer.strokeColor = [[UIColor yellowColor] CGColor];
myShapeLayer.fillColor = [[UIColor clearColor] CGColor];
myShapeLayer.lineWidth = 3.0;
Just in case - here's the Miter code I've used, varying the value rom 0.0 to 100.0 - all with no effect:
[myPath setLineCapStyle:kCGLineCapRound];
[myPath setLineJoinStyle:kCGLineJoinMiter];
[myPath setMiterLimit:1.0];
You should be setting lineJoin on the shape layer instead of the path:
myShapeLayer.lineJoin = kCALineJoinRound;
The confusion comes from the fact that UIBezierPath has the ability to draw the path (by calling fill and stroke on the path). The configuration of line joins and line caps on the path is only affecting this drawing.
However, since you are drawing the path using a CAShapeLayer, the configurations of both line joins and line caps should be done on the shape layer.
I am trying to create a mask region of the intersection of a circle and a rectangle.
I am starting with this code that seems to create an XOR of the circle and rectangle for the mask region but I want just a plain old AND:
- (void)addMaskToHoleViewAtX:(CGFloat) x AtY:(CGFloat) y Radius:(CGFloat) kRadius {
CGRect bounds = holeView.bounds;
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = bounds;
maskLayer.fillColor = [UIColor blackColor].CGColor;
CGRect const rect = CGRectMake(CGRectGetMidX(bounds) - kRadius/2,
CGRectGetMidY(bounds) - kRadius,
kRadius,
2 * kRadius);
UIBezierPath *pathrect = [UIBezierPath bezierPathWithRect:rect];
CGRect const circ = CGRectMake(CGRectGetMidX(bounds) - kRadius,
CGRectGetMidY(bounds) - kRadius,
2 * kRadius,
2 * kRadius);
UIBezierPath *pathcirc= [UIBezierPath bezierPathWithOvalInRect:circ];
UIBezierPath *allPaths= [[UIBezierPath alloc] init];
[allPaths appendPath:pathrect];
[allPaths appendPath:pathcirc];
[allPaths appendPath:[UIBezierPath bezierPathWithRect:bounds]];
maskLayer.path = allPaths.CGPath;
maskLayer.fillRule = kCAFillRuleEvenOdd;
holeView.layer.mask = maskLayer;
holeView.center = CGPointMake(x, y);
}
Could someone help me with the syntax to do the AND, I think I might need to use addClip but it is not obvious to me how to do that with the above code?
MY SOLUTION: It appears to me that if I were able to figure how to use addClip to solve this problem in one manner, I would not actually end up with the closed NSBezierPath of the intersection. I prefer not to do it that way as having the intersection NSBezierPath is also needed to easily determine if a point is inside the path. SOOOO, I just created the NSBezierPath of the intersection through calculations and used my derived path to append to the masklayer bounds path. It sure would be nice to have a way of actually obtaining the intersection NSBezierPath without calculations but I just had to move on. Thanks for trying.
Thanks,
Carmen
EDIT : Here is the routine I am calling to put the 'intersection' mask over my _map View. Change _map to your view if you want to try this:
- (void)addHoleSubview {
holeView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10000, 10000)];
holeView.backgroundColor = [UIColor colorWithRed:255 green:0 blue:0 alpha:0.2];
holeView.autoresizingMask = 0;
[_map addSubview:holeView];
[self addMaskToHoleViewAtX:100 AtY:100 Radius:50];
}
If you aim for the intersection of two paths you should not create a compound path. Appending a path to another path will
result in a union of both paths if using the non-zero winding number rule and both paths have the same direction - or in other words it will have the effect of an AND
result in a shape that contains only the parts of the areas surrounded by the two paths that do not overlap if using the even-odd rule - or in other words it will have the effect of an XOR.
Instead I suggest you first add the first path pathrect to your graphics context and clip
[pathrect addClip];
and then you add the second path pathcirc to your context and clip again:
[pathcirc addClip];
You can now use any filling rule within that context and it will fill the intersection (the AND) of the two paths.
I have to draw lines few times. I do this now:
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:point];
[[NSColor blackColor] set];
[path lineToPoint:point2];
[path stroke];
[NSGraphicsContext restoreGraphicsState];
}
And i call this from other class many time with other parameters:
[workspace setPoint1:someValue setPoint2:someOtherValue];
[workspace setNeedsDisplay:YES];
What i need?
I call this few times with changing parameters someValue and someOtherValue and I have to draw all lines and I want to see it. Now i see only last path. Where is the problem? How can i do this correctly?
Thank you.
If you want to draw more than one line, you'll need to... well... draw more than one line.
You'll need to come up with some way of storing all of the line segments that you want to draw, not just the most recent one. (Use NSMutableArray for this.) Once you've got that, you'll need to loop through that array and draw each line.