Are there any core or other framework methods to return a CGPath or collection of some type that represents the Fill portion of a path object?
I am looking for something that would basically do the opposite of CGPathCreateCopyByStrokingPath.
With the help of an article on low-level-text-rendering I was able to come up with a path representing each letter of the alphabet.
The problem is that the path is the outline of the letter and what I would actually like to have is a path that represents only the fill portion of the letter instead of the stroke. For example the letter "A" would just consist of 3 distinct lines.
I am currently trying to work around this problem with class that acts much like an over complicated 16 segment display. Here is some sample code from that effort for anyone who is interested:
const NSUInteger segmentsPerDimension = 4;
const CGFloat oneOverTheNumberOfSegments = 1.0 / segmentsPerDimension;
#implementation PathSegments
- (instancetype)initWithRect:(CGRect)rect {
self = [super init];
[self setSegmentBounds:rect];
_segments = [[NSMutableArray alloc] init];
...
[self addVerticalSegments];
...
return self;
}
- (void)addVerticalSegments {
for (NSUInteger i = 0; i <= segmentsPerDimension; i++) {
[self createColumnSegmentsForRow:i];
}
}
- (void)createColumnSegmentsForRow:(NSUInteger)row {
for (NSUInteger i = 0; i < segmentsPerDimension; i++) {
CGFloat x = _segmentBounds.size.width * oneOverTheNumberOfSegments * row;
CGFloat yEnd = _segmentBounds.size.height * oneOverTheNumberOfSegments * i;
CGFloat yStart = _segmentBounds.size.height * oneOverTheNumberOfSegments * (i + 1);
[self addLineSegmentWithXStart:x YStart:yStart XEnd:x YEnd:yEnd];
}
}
...
#end
I have determined that it does not make sense to return the fill portion of a path. While a path can be defined in such a way to draw a circle, rectangle or custom shape by filling an outline, it would be ambiguous to copy the fill portion to another path because it would simply be the same path you started with.
A segmented path which is then combined into one full path using GPathAddPath(...) seems to be the way to go on this one. With a series lines and curves you can roll your own letter shapes.
A snippet based on the assumption you have defined a set of points to use as segments of the path:
_generatedSegmentPath = CGPathCreateMutable();
CGMutablePathRef subPath = CGPathCreateMutable();
...
if (CGPathIsEmpty(subPath)) {
CGPoint start = [[points objectAtIndex:startIndex] CGPointValue];
CGPathMoveToPoint(subPath, nil, start.x, start.y);
}
if (points.count == 2) {
CGPoint point = [[points objectAtIndex:startIndex + indexChange] CGPointValue];
CGPathAddLineToPoint(subPath, nil, point.x, point.y);
}
else {
CGPoint controlPoint = [[points objectAtIndex:startIndex + indexChange] CGPointValue];
CGPoint endPoint = [[points objectAtIndex:startIndex + (2 * indexChange)] CGPointValue];
CGPathAddQuadCurveToPoint(subPath, nil, controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
}
Then to combined the subpaths together:
CGPathAddPath(_generatedSegmentPath, nil, subPath);
Related
I have to create a simulation in landscape which displays a sine function and a node which moves on the given path (the sine wave). So far I've created the sine wave with a simple sine wave with it's period (2*PI).
The question is how do I create the simulation of an infinite wave and the node moving up and down ?
My code so far is:
(CGMutablePathRef)sineWithAmplitude:(CGFloat)amp frequency:(CGFloat)freq
width:(CGFloat)width centered:(BOOL)centered
andNumPoints:(NSInteger)numPoints {
CGFloat offsetX = 0;
CGFloat offsetY = amp;
// Center the sinusoid within the shape node
if (centered) {
offsetX = -width/2.0;
offsetY = 0;
}
CGMutablePathRef path = CGPathCreateMutable();
// Move to the starting point
CGPathMoveToPoint(path, nil, offsetX, offsetY);
CGFloat xIncr = width / (numPoints-1);
// Construct the sinusoid
for (int i=1;i<numPoints;i++) {
CGFloat y = (amp * sin(2*M_PI*freq*i/(numPoints-1))*multiplier);
CGPathAddLineToPoint(path, nil, i*xIncr+offsetX, y+offsetY);
}
return path;
And the update method:
/* Called before each frame is rendered */
self.scaleMode = SKSceneScaleModeResizeFill;
// Create an SKShapeNode
SKShapeNode *node = [SKShapeNode node];
node.position = CGPointMake(x, y);
// Assign to the path attribute
node.path = [self sineWithAmplitude:20.0 frequency:frequency1 width:400.0 centered:YES andNumPoints:70];
//node.frame.size.height
node.strokeColor = [SKColor blackColor];
[self addChild:node];
x = x+400;
Until now I only display a wave, but the result should move right and look like the image.
The circle is the node that goes up and down while the wave simulates too. Any help will be greatly appreciated.
I have a question related to cutting up an image to know which part is tapped by the user. It's a rather simple exercise for some, I think, but I've been cracking my head over this and can't find a proper way.
What I would like to do is tap a body part on this image and have the device tell me what body part it is:
But I have absolutely no clue on how to program this.
I've been thinking on setting an array of outline CGPoints for every body part, but how to get those? Photoshop coordinates? And then calculate the touched point onto the closest CGPoint?
Somebody gave me the idea to cut up the image in the different zones, different images for every body part. But here the problem is that every image is a rectangle, which makes it very hard to know what exactly got touched. Would also need an array of points, especially for the overlapping images.
Another person told me this would only be possible using SpriteKit.
Let me know, smart peeps :)
UPDATE:
I figured it out! I'm making a lot of arrays that contain the points to the hand, the legs, ... An example:
CGPoint p1 = CGPointMake(x + 432, y + 200);
CGPoint p2 = CGPointMake(x + 523, y + 188);
CGPoint p3 = CGPointMake(x + 530, y + 277);
CGPoint p4 = CGPointMake(x + 432, y + 277);
CGPoint p5 = CGPointMake(x + 523, y + 367);
CGPoint p6 = CGPointMake(x + 432, y + 354);
CGPoint p7 = CGPointMake(x + 325, y + 355);
CGPoint p8 = CGPointMake(x + 296, y + 362);
NSArray *handL = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:p1],
[NSValue valueWithCGPoint:p2],
[NSValue valueWithCGPoint:p3],
[NSValue valueWithCGPoint:p4], nil];
[shapes addObject:#{#"name":NSLocalizedString(#"Left Hand", #""), #"points":handL}];
NSArray *handR = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:p5],
[NSValue valueWithCGPoint:p6],
[NSValue valueWithCGPoint:p7],
[NSValue valueWithCGPoint:p8], nil];
[shapes addObject:#{#"name":NSLocalizedString(#"Right Hand", #""), #"points": handR}];
Afterwards in I read these values and layer them:
NSArray *shapes = [[NumbersHelper sharedNumbersHelper] getScreenThreeShapes];
for (int outside = 0; outside < [shapes count]; outside++) {
CAShapeLayer *shape = [[CAShapeLayer alloc] init];
[self.scrollView.layer addSublayer:shape];
shape.opacity = 0.5;
shape.lineWidth = 2;
shape.lineJoin = kCALineJoinRound;
NSArray *points = [[[shapes valueForKey:#"points"] allObjects] objectAtIndex:outside];
UIBezierPath *path = [[UIBezierPath alloc] init];
for (int inside = 0; inside < points.count; inside++) {
if (inside == 0) {
[path moveToPoint:[[points objectAtIndex:inside] CGPointValue]];
} else {
[path addLineToPoint:[[points objectAtIndex:inside] CGPointValue]];
}
}
[path closePath];
shape.path = [path CGPath];
shape.name = [[[shapes valueForKey:#"name"] allObjects] objectAtIndex:outside];
}
When the user taps the view I do this:
- (void)tappedOnView:(UITapGestureRecognizer *)sender
{
if ([sender locationInView:self.scrollView].x > 2048 && [sender locationInView:self.scrollView].x < 3072) {
screenTwoTouchedPoint = [sender locationInView:self.scrollView];
CGPoint touchPoint = [sender locationInView:self.scrollView];
NSString *name;
for (id sublayers in self.scrollView.layer.sublayers) {
if ([sublayers isKindOfClass:[CAShapeLayer class]]) {
CAShapeLayer *shape = sublayers;
if (CGPathContainsPoint(shape.path, 0, touchPoint, YES)) {
name = shape.name;
break;
}
}
}
[self tappedBody:sender onPart:name];
}
}
A crude but simple way would be to have transparent buttons over various body parts. In case a body part is not covered properly by a single button, add multiple smaller buttons.
You can add tag to the buttons to identify which button/body part got tapped or connect them to unique Actions.
A solution that worked for me. Create a 2d array for the entire size of the image. If the image is 250x250, your array would be the same.
Fill this array with NSObjects, having and int bodyInteger
Then drag your finger on the hand of the image, and in your touchesMoved take the co-ordinates from location, and pick the object from your 2d array by using the co-ordinates and setting its bodyInteger to 1.
Now when someone puts finger on hand, you take the respective object from 2d array and see what bodyInteger it has.
If you have memory constraints, you can create a structure instead of an NSObject and save only for the points that are within the body.
Alternatively, you can create a boundary for each body part and save it. When user touches with finger, you can see if the point is within a saved polygon or not.
I want to make UBersense like app (http://blog.ubersense.com/2013/01/03/how-to-use-the-drawing-tools-in-ubersense/), there i need to draw two line with some angle, after that i can adjust the angle between two line by dragging any line or intersection point.
can you guys please provide me some idea or code snippet.
screenshots url:
https://picasaweb.google.com/yunusm7/AppScreenshots#slideshow/5952787957718627714
Thanks in advance.
You have a construction with three points, one point is an angle point, and two others are just vertices. First of all you should create a new class like this:
#interface MyAngle : NSObject {
}
#property (nonatomic) CGPoint p1;
#property (nonatomic) CGPoint p2;
#property (nonatomic) CGPoint v; // this is an angle point
#end
You can use the default implementation of this without any tricks with such sample init:
- (id)init {
if (self = [super init]) {
p1 = CGPointMake(1,0);
p2 = CGPointMake(0,1);
v = CGPointZero;
}
return self;
}
But also as I understood you need to know the value of the angle. You can do this using the following way:
- (CGFloat)valueOfAngle {
CGPoint v1 = CGPointMake(p1.x-v.x, p1.y-v.y);
CGPoint v2 = CGPointMake(p2.x-v.x, p2.y-v.y);
CGFloat scalarProduct = v1.x*v2.x + v1.y*v2.y;
CGFloat lengthProduct = sqrt(v1.x*v1.x + v1.y*v1.y)*sqrt(v2.x*v2.x + v2.y*v2.y);
CGFloat fraction = scalarProduct / lengthProduct;
if (fraction < -1) fraction = -1;
if (fraction > 1) fraction = 1;
return acos(fraction);
}
If you want to obtain angles more than 180 degrees you should change the code above a little. But there are too much information about how to do this in the Internet, so I will skip this part.
Then you need to create an instance of MyAngle in your viewController. Let it be called "angle". Knowing coordinates of every three points if enough do draw it (!!!). Implement drawRect method in a view that will contain the MyAngle instance (I strongly recommend do to this on your own subclass of UIView):
- (void)drawRect {
[super drawRect];
// set options of drawing
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat red[4] = {1.0f, 0.0f, 0.0f, 1.0f};
CGContextSetLineWidth(c, 3.0);
CGContextSetStrokeColor(c, red);
// draw an angle directly
CGContextBeginPath(c);
CGContextMoveToPoint(c, angle.p1.x, angle.p1.y);
CGContextAddLineToPoint(c, angle.v.x, angle.v.y);
CGContextAddLineToPoint(c, angle.p2.x, angle.p2.y);
CGContextStrokePath(c);
// draw circles around vertices (like on the screenshot you provided)
CGFloat R = 7.0f;
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(angle.p1.x - R, angle.p1.y - R, 2*R, 2*R));
CGContextStrokePath(c);
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(angle.p2.x - R, angle.p2.y - R, 2*R, 2*R));
CGContextStrokePath(c);
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(angle.v.x - R, angle.v.y - R, 2*R, 2*R));
CGContextStrokePath(c);
}
And that's all you need to know for drawing what you want! You can change the stroke color or radius of three circles if you want.
Then you need to have a possibility to change the locations of your angle's points. For this you can just implement panGestureRecognizer in your viewController's viewDidLoad method like this:
UIPanGestureRecognizer *pan = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveAngle:)] autorelease];
pan.delegate = self;
[self.view addGestureRecognizer:pan];
Implement UIGestureRecognizerDelegate method:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint p = [gestureRecognizer locationInView:self.view];
CGFloat d1 = sqrt((p.x-angle.p1.x)*(p.x-angle.p1.x) + (p.y-angle.p1.y)*(p.y-angle.p1.y);
CGFloat d2 = sqrt((p.x-angle.p2.x)*(p.x-angle.p2.x) + (p.y-angle.p2.y)*(p.y-angle.p2.y);
CGFloat d3 = sqrt((p.x-angle.v.x)*(p.x-angle.v.x) + (p.y-angle.v.y)*(p.y-angle.v.y);
// just check if we touched the screen near some of angle's points
CGFloat tolerance = 15.0f;
return (d1 < tolerance) || (d2 < tolerance) || (d3 < tolerance);
}
return YES;
}
and tagret's selector (also in your viewController):
- (void)moveAngle:(UIPanGestureRecognizer *)gr {
CGPoint p = [gr locationInView:self.view];
if (gr.state == UIGestureRecognizerStateBegan) {
CGFloat d1 = sqrt((p.x-angle.p1.x)*(p.x-angle.p1.x) + (p.y-angle.p1.y)*(p.y-angle.p1.y);
CGFloat d2 = sqrt((p.x-angle.p2.x)*(p.x-angle.p2.x) + (p.y-angle.p2.y)*(p.y-angle.p2.y);
CGFloat d3 = sqrt((p.x-angle.v.x)*(p.x-angle.v.x) + (p.y-angle.v.y)*(p.y-angle.v.y);
// pointToMove is your int variable
CGFloat tolerance = 15.0f;
if (d1 < tolerance) {
pointToMove = 1;
}
else if (d2 < tolerance) {
pointToMove = 2;
}
else {
pointToMove = 3;
}
}
else {
if (pointToMove == 1) {
angle.p1 = loc;
}
else if (pointToMove == 2) {
angle.p2 = loc;
}
else {
angle.v = loc;
}
[yourCustomView setNeedsDisplay];
[yourLabel setText:[NSString stringWithFormat:#"%.3f", [angle valueOfangle]*180/PI]];
}
}
Maybe I skip some evident things, but I think it should be enough for you to begin writing some code.
I have some troubles when I try to draw lines with cocos2d! I store points, got from touchMoved method, in a NSMutableArray, and pass that array to a subclass of CCNode called Lines that I use to draw the lines from the array of points. The problem is that the line is not smooth when I swipe slowly, but when I swipe faster, the line is way much smoother. See the pictures below :
Slow Swipe :
Fast Swipe :
I tried to solve the problem with ccpDistance, which calculate the distance between the last saved point and if it's not far enough I don't save it. I also tried to draw little circles at each saved positions, but this isn't really nice neither. Here's my code :
In my GameScene :
- (void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
if (ccpDistance(lastPoint, location) > 10) {
//SAVE THE POINT
[linePoints addObject:[NSValue valueWithCGPoint:location]];
[line updatePoints:linePoints];
lastPoint = location;
}
}
And my Line Class :
- (void) updatePoints:(NSMutableArray *)_point
{
points = _point;
}
- (void) draw
{
if ([points count] > 0) {
ccGLEnable(GL_LINE_STRIP);
ccDrawColor4B(209, 75, 75, 255);
float lineWidth = 6.0 * CC_CONTENT_SCALE_FACTOR();
glLineWidth(lineWidth);
int count = [points count];
for (int i = 0; i < (count - 1); i++){
CGPoint pos1 = [[points objectAtIndex:i] CGPointValue];
CGPoint pos2 = [[points objectAtIndex:i+1] CGPointValue];
ccDrawLine(pos1, pos2);
ccDrawSolidCircle(pos2, 2.5, 20);
}
}
}
Also, is there something in my code that could be done better to improve performance? Right now I don't have any problems even with 1000+ points, but just in case...
Any help will be greatly appreciated! Thanks in advance!
Ok, I found a website explaining really clearly how to do smooth lines, and it worked wonderfully! There's still anti-aliasing that's left to do, but maybe I will never do, since it looks really great on retina devices. Here's the website : Drawing Smooth Lines with Cocos2D
And here's the result :
Also, for those interested in the finished code, here it is :
Line.m
- (void) drawCurPoint:(CGPoint)curPoint PrevPoint:(CGPoint)prevPoint
{
float lineWidth = 6.0;
ccColor4F red = ccc4f(209.0/255.0, 75.0/255.0, 75.0/255.0, 1.0);
//These lines will calculate 4 new points, depending on the width of the line and the saved points
CGPoint dir = ccpSub(curPoint, prevPoint);
CGPoint perpendicular = ccpNormalize(ccpPerp(dir));
CGPoint A = ccpAdd(prevPoint, ccpMult(perpendicular, lineWidth / 2));
CGPoint B = ccpSub(prevPoint, ccpMult(perpendicular, lineWidth / 2));
CGPoint C = ccpAdd(curPoint, ccpMult(perpendicular, lineWidth / 2));
CGPoint D = ccpSub(curPoint, ccpMult(perpendicular, lineWidth / 2));
CGPoint poly[4] = {A, C, D, B};
//Then draw the poly, and a circle at the curPoint to get smooth corners
ccDrawSolidPoly(poly, 4, red);
ccDrawSolidCircle(curPoint, lineWidth/2.0, 20);
}
- (void) draw
{
if ([points count] > 0) {
ccGLEnable(GL_LINE_STRIP);
ccColor4F red = ccc4f(209.0/255.0, 75.0/255.0, 75.0/255.0, 1.0);
ccDrawColor4F(red.r, red.g, red.b, red.a);
float lineWidth = 6.0 * CC_CONTENT_SCALE_FACTOR();
glLineWidth(lineWidth);
int count = [points count];
for (int i = 0; i < (count - 1); i++){
CGPoint pos1 = [[points objectAtIndex:i] CGPointValue];
CGPoint pos2 = [[points objectAtIndex:i+1] CGPointValue];
[self drawCurPoint:pos2 PrevPoint:pos1];
}
}
}
As for the GameScene, nothing changed there (See the question for the code)! Note that you can change the line if (ccpDistance(lastPoint, location) > X), where X is the minimum distance between two points before the game saves another one. The lower X is, the smoother the line will be, but you will have way more points in your array, which could affect performance!
Anyway, thank you guys for your suggestions and your help, it helped me to get in the right way!
I think that you could smooth up your line drawing with some averaging.
- (void) updatePoints:(NSMutableArray *)_point
{
points = _point;
int count = [points count];
for (int i = 3; i < (count - 4); i++) {
CGPoint pos1 = [[points objectAtIndex:i - 2] CGPointValue];
CGPoint pos2 = [[points objectAtIndex:i - 1] CGPointValue];
CGPoint pos3 = [[points objectAtIndex:i] CGPointValue];
CGPoint pos4 = [[points objectAtIndex:i + 1] CGPointValue];
CGPoint pos5 = [[points objectAtIndex:i + 2] CGPointValue];
CGFloat xpos = (pos1.x + pos2.x + 2 * pos3.x + pos4.x + pos5.x)/6;
...
(now calcuclate ypos similarly and store the point into an array)
}
}
I am trying to smoothen my hand drawn Bezier curves, but not able to achieve, it, I got the code of smoothing the curve from the book written by erica sudan , which I tried to implement , but dont know why I am not able to produce a smooth curve, below is my code , I request all of you to please go through it ..
- (void)drawRect:(CGRect)rect
{
myPath.pathColor = [UIColor redColor];
for (BezeirPath *_path in m_pathArray)
{
[_path.pathColor setStroke];
[_path.bezeirPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
m_previousPoint1 = [mytouch previousLocationInView:self];
m_previousPoint2 = [mytouch previousLocationInView:self];
m_currentPoint = [mytouch locationInView:self];
CGPoint controlPoint = CGPointMake(m_previousPoint1.x+(m_previousPoint1.x - m_previousPoint2.x), m_previousPoint1.y +(m_previousPoint1.y - m_previousPoint2.y));
[myPath.bezeirPath addQuadCurveToPoint:m_currentPoint controlPoint:controlPoint];
self.previousPoint3 = controlPoint;
[myPath.bezeirPath smoothedPath:myPath.bezeirPath :40];//Called the smoothing function.
[self setNeedsDisplay];
}
//Smoothing function
-(UIBezierPath*)smoothedPath:(UIBezierPath*)bpath: (NSInteger) granularity
{
NSArray *points = pointsFromBezierPath(bpath);
if (points.count < 4) return bpath;
// This only copies lineWidth. You may want to copy more
UIBezierPath *smoothedPath = [UIBezierPath bezierPath];
smoothedPath.lineWidth = bpath.lineWidth;
// Draw out the first 3 points (0..2)
[smoothedPath moveToPoint:POINT(0)];
for (int index = 1; index < 3; index++)
[smoothedPath addLineToPoint:POINT(index)];
// Points are interpolated between p1 and p2,
// starting with 2..3, and moving from there
for (int index = 4; index < points.count; index++)
{
CGPoint p0 = POINT(index - 3);
CGPoint p1 = POINT(index - 2);
CGPoint p2 = POINT(index - 1);
CGPoint p3 = POINT(index);
// now add n points starting at p1 + dx/dy up until p2
// using Catmull-Rom splines
for (int i = 1; i < granularity; i++)
{
float t = (float) i * (1.0f / (float) granularity);
float tt = t * t;
float ttt = tt * t;
CGPoint pi; // intermediate point
pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t +
(2*p0.x-5*p1.x+4*p2.x-p3.x)*tt +
(3*p1.x-p0.x-3*p2.x+p3.x)*ttt);
pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t +
(2*p0.y-5*p1.y+4*p2.y-p3.y)*tt +
(3*p1.y-p0.y-3*p2.y+p3.y)*ttt);
[smoothedPath addLineToPoint:pi];
}
[smoothedPath addLineToPoint:p2];
}
// finish by adding the last point
[smoothedPath addLineToPoint:POINT(points.count - 1)];
return smoothedPath;
}
I suggest watching "Building Advanced Gesture Recognizers" from WWDC 2012. You can skip the beginning - the part you are most interested in is at 38:23.