I am developing an app where the user should be able to draw lines with the finger. The user can enable dashed lines, but I am having a strange problem when trying to do so. If the user drags the finger slowly a solid line appears, but when doing it fast the dashes appears. I would like the dashes to appear no matter what when enabled. The problem is shown on the gif below:
I am collecting the touch points with a UIPanGestureRecognizer:
-(void)dragGestureCaptured:(UIPanGestureRecognizer *)gesture
{
NSValue* touchPoint = [NSValue valueWithCGPoint:[gesture locationInView:self.drawingView]];
if(gesture.state == UIGestureRecognizerStateBegan)
{
[self initializePreviousPointValues:[touchPoint CGPointValue]];
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
EBLog(#"Dragging ends..");
return;
}
previousPoint2 = previousPoint1;
previousPoint1 = currentPoint;
currentPoint = [touchPoint CGPointValue];
CGPoint mid1 = midPoint(previousPoint1, previousPoint2);
CGPoint mid2 = midPoint(currentPoint, previousPoint1);
[self.drawingView addDrawColor:self.currentDrawColor];
[self.drawingView.controlPoints addObject:[NSValue valueWithCGPoint:previousPoint1]];
[self.drawingView.points addObject:[NSValue valueWithCGPoint:mid2]];
[self.drawingView.movePoints addObject:[NSValue valueWithCGPoint:mid1]];
[self.drawingView setNeedsDisplay];
}
The drawingView uses the controlsPoints, points and movePoints to draw the lines:
- (void)drawLinesInContext:(CGContextRef)context
{
for (int i = 0; i < [self.points count]; i++)
{
CGPoint movePoint = [[self.movePoints objectAtIndex:i] CGPointValue];
CGPoint controlPoint = [[self.controlPoints objectAtIndex:i] CGPointValue];
CGPoint point = [[self.points objectAtIndex:i] CGPointValue];
CGContextMoveToPoint(context, movePoint.x, movePoint.y);
CGContextAddQuadCurveToPoint(context, controlPoint.x, controlPoint.y, point.x, point.y);
NSNumber* colorHash = [self.colorHashes objectAtIndex:i];
UIColor* col = [self.colorsForHashValue objectForKey:colorHash];
CGFloat red = 0.0f, blue = 0.0f, green = 0.0f, alpha = 1.0f;
[col getRed:&red green:&green blue:&blue alpha:&alpha];
if (alpha == 0.0f)
{
penWidth = 15.0f;
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
}
else
{
penWidth = 5.0f;
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, alpha);
}
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, penWidth);
CGContextSetShouldAntialias(context, false);
CGContextSetAllowsAntialiasing(context, false);
if (self.drawDashedLines)
{
CGFloat dashLengths[] = {10.0f, 10.0f};
CGContextSetLineDash(context, 0.0f, dashLengths, 2);
}
CGContextStrokePath(context);
}
}
drawLinesInContext: is invoked in drawRect:.
What am I doing wrong, since the dashes don't appear when dragging slowly?
I have already seen the Drawing a dashed line with CGContextSetLineDash and tried the CGPathAddLineToPoint to see if it makes a difference, but it didn't. I was experiencing the exact same problem.
The problem is that you draw separate paths between each subsequent points, and
each path starts with a new dash pattern. If the finger is moved slowly, you draw
lots of very short path. It the path is shorter than 10 points (the first
dash length which is drawn) then no dash pattern is seen at all.
The following pseudo-code hopefully shows the idea how to solve this. Instead of
for i = 1 ... N {
move to point[i-1]
curve to point[i]
set line dash
stroke path
}
it should be
move to point[0]
for i = 1 ... N {
curve to point[i]
}
set line dash
stroke path
Related
I am drawing a rect using UIBezierPath. The following code creates the rect in my view controller:
#interface ViewController ()
{
TargetAreaView *targetView; //TargetAreaView is a subclass of UIView
UIView *detectionView;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGRect frame = self.view.frame;
frame.size.height = frame.size.height - 200;
detectionView = [[UIView alloc]initWithFrame:frame];
targetView = [[TargetAreaView alloc]initWithFrame:detectionView.frame];
targetView.backgroundColor = [UIColor clearColor];
[self.view addSubview:targetView];
// Do any additional setup after loading the view, typically from a nib.
}
I am using the bezier path to draw the rect in TargetAreaView class.
I have used the following code in the UIView subclass:(TargetAreaView)
#interface TargetAreaView : UIView
{
NSMutableArray *points;
CGRect superViewFrame;
CGPoint selectedPoint;
}
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
superViewFrame = frame;
points = [[NSMutableArray alloc]init];
CGPoint startPoint = CGPointMake(superViewFrame.size.width / 2 - 200/2, superViewFrame.size.height / 2 - 200/2);
CGPoint heightPoint1 = CGPointMake(startPoint.x, startPoint.y + 200);
CGPoint widthPoint1 = CGPointMake(startPoint.x + 200, startPoint.y + 200);
CGPoint heightPoint2 = CGPointMake(startPoint.x + 200, startPoint.y);
CGPoint widthPoint2 = CGPointMake(startPoint.x, startPoint.y);
[points addObject:[NSValue valueWithCGPoint:startPoint]];
[points addObject:[NSValue valueWithCGPoint:heightPoint1]];
[points addObject:[NSValue valueWithCGPoint:widthPoint1]];
[points addObject:[NSValue valueWithCGPoint:heightPoint2]];
[points addObject:[NSValue valueWithCGPoint:widthPoint2]];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
if (points && points.count > 0)
{
[self drawTargetArea:points];
[self setNeedsDisplay];
}
}
-(void)drawTargetArea:(NSMutableArray *)savedPoints
{
UIBezierPath *aPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
for (int idx = 0; idx < savedPoints.count; idx++)
{
if (idx == 0)
{
NSValue *startPoint = [savedPoints objectAtIndex:idx];
[aPath moveToPoint:startPoint.CGPointValue]; // Starting Point
}
else
{
NSValue *drawPoints = [savedPoints objectAtIndex:idx];
[aPath addLineToPoint:drawPoints.CGPointValue]; // Add lines from previous to current point
}
}
[aPath closePath]; // close the final path
aPath.lineWidth = 10.0f;
[[UIColor redColor] setStroke];
[aPath stroke];
}
The above UIView subclass code generates the rect as shown below:
I have written the logic to move the corners of rect to change its shape as shown below:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint startTouchPoint = [touch locationInView:self]; //get the user touch position and check it is nearer in 10px range to any of the position in rect
for (NSValue *aPoint in points)
{
CGPoint currentPoint = aPoint.CGPointValue;
CGFloat distance = [self touchPositionDifference:startTouchPoint WithCornerPosition:currentPoint]; // Calculate the distance between user touch position and rect points
if (distance < 10)
{
selectedPoint = currentPoint; // get the nearest point in a global variable
break;
}
}
}
-(CGFloat)touchPositionDifference:(CGPoint)touchPosition WithCornerPosition:(CGPoint)cornnerPosition
{
CGFloat dx= touchPosition.x - cornnerPosition.x;
CGFloat dy= touchPosition.y - cornnerPosition.y;
CGFloat distance= sqrt(dx*dx + dy*dy);
return distance; // calculate and return distance between two points
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchEndPoint = [touch locationInView:self];
for (int idx = 0; idx < points.count; idx++)
{
NSValue *aPoint = [points objectAtIndex:idx];
CGPoint currentPoint = aPoint.CGPointValue;
if (CGPointEqualToPoint(currentPoint, selectedPoint))
{
[points replaceObjectAtIndex:idx withObject:[NSValue valueWithCGPoint:touchEndPoint]]; // get the global point value and touch ended value and replace the new value with old value
}
}
[self setNeedsDisplay];// and draw the shape with new points
}
I can able to get the output like following
I have return code to get the bezier path as follows:
-(UIBezierPath *)getPath
{
if (points.count <= 0) return nil;
NSArray *oldPoints = points;
UIBezierPath *aPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
CGPoint p1 = [[oldPoints objectAtIndex:0] CGPointValue];
[aPath moveToPoint:CGPointMake(p1.x, p1.y)];
for (uint i=1; i<oldPoints.count; i++)
{
CGPoint p = [[oldPoints objectAtIndex:i] CGPointValue];
[aPath addLineToPoint:CGPointMake(p.x, p.y)];
}
[aPath closePath];
return aPath; //This returns me a bezier path of the already drawn shape
}
I am getting the entire view(within that view i am having the irregular shape View is represented in green color and the shape is represented in red border color) as shown below
I want to get that particular shape alone(the irregular shape) As shown below
How can I achieve this? Am I following the right path?
You can use the following code to draw the above mentioned shape.
- (void)drawCanvas1WithFrame: (CGRect)frame
{
//// Color Declarations
UIColor* color = [UIColor colorWithRed: 1 green: 0 blue: 0 alpha: 1];
//// Bezier Drawing
UIBezierPath* bezierPath = UIBezierPath.bezierPath;
[bezierPath moveToPoint: CGPointMake(CGRectGetMinX(frame) + 77.5, CGRectGetMinY(frame) + 39.5)];
[bezierPath addCurveToPoint: CGPointMake(CGRectGetMinX(frame) + 130.5, CGRectGetMinY(frame) + 14.5) controlPoint1: CGPointMake(CGRectGetMinX(frame) + 130.5, CGRectGetMinY(frame) + 14.5) controlPoint2: CGPointMake(CGRectGetMinX(frame) + 130.5, CGRectGetMinY(frame) + 14.5)];
[bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 122.5, CGRectGetMinY(frame) + 75.5)];
[bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 61.5, CGRectGetMinY(frame) + 110.5)];
[bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 77.5, CGRectGetMinY(frame) + 39.5)];
[UIColor.greenColor setFill];
[bezierPath fill];
[color setStroke];
bezierPath.lineWidth = 1;
[bezierPath stroke];
}
I am new to Core Graphics and trying to understand why text labels I draw in CGRect form an elliptical arc when images I draw using the same coordinates form a circular arc.
The original code by Arthur Knopper creates circles wherever the screen is touched. By removing the touches method, I have been able to generate a series of small circles (dots) along a circular arc (uber circle). Each dot is centred on the perimeter of the uber circle (as shown below).
In order to label each dot I use the same point coordinates I used for placing the dot. However text labels form an elliptical arc even though dots form a circular arc (as shown below). Labels are also hidden by the dots when dots are filled. The reason for this is a complete mystery.
As a novice I am probably missing something basic in Core Graphics. If anyone could explain what that is and what I need to do to make both arcs circular and place labels on top of the dots I’d be most grateful.
Thanks.
Here is the code.
circleView.h
NSMutableArray *totalCircles;
int dotCount, limit;
float uberX, uberY, uberRadius, uberAngle, labelX,
labelY,dotRadius, dotsFilled, sectors, x, y;
CGPoint dotPosition;
CGRect boxBoundary;
CGContextRef context;
}
- (void)demo;
#end
And ...
-#implementation iOSCircleView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
totalCircles = [[NSMutableArray alloc] init];
// Set background color
self.backgroundColor = [UIColor whiteColor];
}
return self;
} // frame a view for drawing
- (void)drawRect:(CGRect)rect {
[self demo];
}
- (void)demo {
context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 0.5);
uberX = 120;
uberY = 160;
uberRadius = 30;
sectors = 16;
uberAngle = (2.0 * PI) / sectors;
dotRadius = 20;
dotsFilled = FALSE;
for (dotCount = 1; dotCount <= sectors; dotCount++)
{
// Create a new iOSCircle Object
iOSCircle *newCircle = [[iOSCircle alloc] init];
newCircle.circleRadius = dotRadius;
[self setSectorDotCoordinates]; // make new point for each dot
dotPosition = CGPointMake(x,y); // create each dot
NSLog(#"Circle%i: %#", dotCount, NSStringFromCGPoint(dotPosition));
[self autoLabel]; // text hides behind the dots
newCircle.circleCentre = dotPosition; // place each dot on the frame
[totalCircles addObject:newCircle];
[self setNeedsDisplay];
}
CGContextSetShadowWithColor(context, CGSizeMake(-3 , 2), 4.0, [UIColor clearColor].CGColor);
dotCount = 1;
for (iOSCircle *circle in totalCircles) {
CGContextAddArc(context, circle.circleCentre.x, circle.circleCentre.y, circle.circleRadius, 0.0, M_PI * 2.0, YES); // draw the circles
NSLog(#"Dot %i Filled %i ", dotCount, dotsFilled);
switch (dotsFilled) {
case 1:
CGContextSetFillColorWithColor(context, [[UIColor cyanColor] CGColor]);
//CGContextDrawPath(context, kCGPathFillStroke);
break;
default:
//CGContextStrokePath(context); // draw dot outline
break;
}
dotCount++;
}
} // draw circular dots in circular patterns
- (void)setSectorDotCoordinates {
x = uberX + (uberRadius * cos(uberAngle *dotCount) * 2);
y = uberY + (uberRadius * sin(uberAngle *dotCount) * 2);
} // calculate dot coordinates along a circular arc
- (void)autoLabel {
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
boxBoundary = CGRectMake(x-dotRadius, y-dotRadius, x+dotRadius, y+dotRadius);
[[NSString stringWithFormat:#"%i",dotCount] drawInRect:boxBoundary withFont:[UIFont systemFontOfSize:24] lineBreakMode:NSLineBreakByCharWrapping alignment:NSTextAlignmentCenter];
}
Change the boxBoundary in autoLabel, CGRectMake creates a rectangle with one point coordinates and width and height, not two points:
(void)autoLabel {
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
boxBoundary = CGRectMake(x-dotRadius, y-dotRadius, dotRadius*2, dotRadius*2);
[[NSString stringWithFormat:#"%i",dotCount] drawInRect:boxBoundary
withFont:[UIFont systemFontOfSize:24]
lineBreakMode:NSLineBreakByCharWrapping alignment:NSTextAlignmentCenter];
}
In your code the "boxes" containing the texts where bigger and bigger when you where going to the right. (the width and height were not fixed)
My updated code show labels that match the drawing order but text is still hidden when dots are filled.
I suspect I need to construct a path to write text in front of the dots and it’s already apparent that something like CGPathMoveToPoint is needed to start drawing from the 12 O'clock position.
Here’s the updated code. The first part draws and renders the dots
context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 0.5);
uberX = 160;
uberY = 240;
uberRadius = 52;
sectors = 16;
uberAngle = ((2.0 * PI) / sectors);
NSLog(#"%f %f %f %f", uberX, uberY, uberRadius, uberAngle);
dotRadius = 20;
dotsFilled = FALSE;
textOffset = 4; // add to y to centre the label
for (dotCount = 1; dotCount <= 4 /*sectors*/; dotCount++)
{
// Create a new iOSCircle Object
iOSCircle *newCircle = [[iOSCircle alloc] init];
newCircle.circleRadius = dotRadius;
[self setSectorDotCoordinates]; // create a new point for each dot
dotPosition = CGPointMake(x,y); // create each dot
NSLog(#"Circle%i: %#", dotCount, NSStringFromCGPoint(dotPosition));
newCircle.circleCentre = dotPosition; // place each dot on the frame
[totalCircles addObject:newCircle];
[self setNeedsDisplay];
}
CGContextSetShadowWithColor(context, CGSizeMake(-3 , 2), 4.0, [UIColor clearColor].CGColor);
dotCount = 1;
for (iOSCircle *circle in totalCircles) {
CGContextAddArc(context, circle.circleCentre.x, circle.circleCentre.y, circle.circleRadius, 0.0, M_PI * 2.0, YES);
// draw the circles
NSLog(#"Dot %i Filled %i ", dotCount, dotsFilled);
switch (dotsFilled) {
case 1:
CGContextSetFillColorWithColor(context, [[UIColor cyanColor] CGColor]);
CGContextDrawPath(context, kCGPathFillStroke);
break;
default:
CGContextStrokePath(context); // draw dot outline
break;
}
[self setSectorDotCoordinates]; // find point coordinates for each dot
dotCount++;
}
The code that draws the labels follow immediately afterwards.
// draw labels
for (dotCount = 1; dotCount <= sectors; dotCount++)
{
// Create a new iOSCircle Object
iOSCircle *newCircle = [[iOSCircle alloc] init];
newCircle.circleRadius = dotRadius;
[self setSectorDotCoordinates]; // find point coordinates for each dot
dotPosition = CGPointMake(x,y); // use point coordinates for label
[self autoLabel]; // THIS SHOWS TEXT BEHIND THE DOTS
}
I am looking for an output as in this image Expected Result
I need an outline to my stroke. My code is as follows
- (void)awakeFromNib {
self.strokeArray = [NSMutableArray array];
self.layerIndex = 0;
self.isSolid = false;
self.path = [[UIBezierPath alloc] init];
self.innerPath = [[UIBezierPath alloc] init];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[self.path moveToPoint:p];
[self.innerPath moveToPoint:p];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[self.path addLineToPoint:p];
[self.innerPath addLineToPoint:p];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[self.path addLineToPoint:p];
[self.innerPath addLineToPoint:p];
[self drawBitmap];
[self setNeedsDisplay];
[self.path removeAllPoints];
[self.innerPath removeAllPoints];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
- (void)drawRect:(CGRect)rect
{
[self.incrementalImage drawInRect:rect];
[self.brushColor setStroke];
self.path.lineWidth = self.brushWidth;
if(self.isEraser)
[self.path strokeWithBlendMode:kCGBlendModeClear alpha:0.0];
else
[self.path stroke];
self.innerPath.lineWidth = self.brushWidth - 10;
[[UIColor clearColor] setStroke];
[self.innerPath strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
}
- (void)drawBitmap
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (!self.incrementalImage)
{
CGContextClearRect(context, CGRectMake(0,0,self.bounds.size.width,self.bounds.size.height));
}
[self.incrementalImage drawAtPoint:CGPointZero];
[self.brushColor setStroke];
self.path.lineWidth = self.brushWidth;
if(self.isEraser)
[self.path strokeWithBlendMode:kCGBlendModeClear alpha:0.0];
else
[self.path stroke];
self.innerPath.lineWidth = self.brushWidth - 10;
[[UIColor clearColor] setStroke];
[self.innerPath strokeWithBlendMode:kCGBlendModeClear alpha:1.0];
self.incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
On screen what i get is as per this image, Actual Result
I understand that blend mode 'clear' gives an eraser effect. What i want is that the stroke should have a solid outline on the sides and be clear in the middle. It should not blend into the path right below it.The path below it should still be visible.
How can i achieve this result?
Have a look at this question: Generate a CGPath from another path's line width outline
I guess that's what you need: creating a polygon of the line and stroke that instead of the line. Use CGPathCreateCopyByStrokingPath to get a path of the polygon and stroke it.
OK, since CGPathCreateCopyByStrokingPath seems to be a bit buggy, you could create your own implementation. Something like the following algorithm, which assumes you have a NSArray of CGPoint packed in NSValue objects.
It works pretty well until you start adding short overlapping lines which could of course easily happen while drawing. You could reduce this effect by only adding points in touchesMoved that have a larger distance (lineWidth/2?) to the previous added point. Another drawback is that the lines are simply straight (no kCGLineJoinRound or kCGLineCapRound) but maybe you could adjust the algorithm to do that.
+ (UIBezierPath*)polygonForLine:(NSArray *)points withLineWidth:(CGFloat)lineWidth{
UIBezierPath *path = [UIBezierPath bezierPath];
//stores the starting point to close the path
CGPoint startPoint;
//get the points to a c-array for easier access
CGPoint cpoints[points.count];
int numPoints = 0;
for(NSValue *v in points){
cpoints[numPoints++] = [v CGPointValue];
}
//store the last intersection to apply it for the next segement
BOOL hasIntersection = NO;
CGPoint intersectionPoint;
for (int i=0;i<numPoints-1;i++){
//get the current line segment
CGPoint p1 = cpoints[i];
CGPoint p2 = cpoints[i+1];
CGPoint l1p1,l1p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l1p1,&l1p2);
//if there had been an intersection with the previous segement, start here to get a nice outline
if(hasIntersection){
l1p1 = intersectionPoint;
}
//is there a next segment?
if(i+2<numPoints){
//get the next line segment
p1 = cpoints[i+1];
p2 = cpoints[i+2];
CGPoint l2p1,l2p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l2p1,&l2p2);
//calculate the intersection point with the current line segment
hasIntersection = getLineIntersection(l1p1, l1p2, l2p1, l2p2, &intersectionPoint);
//if they intersect, the current linesegment has to end here to get a nice outline
if(hasIntersection){
l1p2 = intersectionPoint;
}
}
//write the current linesegment to the path
if(i==0){
//first point, move to it and store it for closing the path later on
startPoint = l1p1;
[path moveToPoint:startPoint];
}else{
[path addLineToPoint:l1p1];
}
[path addLineToPoint:l1p2];
}
//now do the same for the other side of the future polygon
hasIntersection = NO;//reset intersections
for (int i=numPoints-1;i>0;i--){
//get the current line segment
CGPoint p1 = cpoints[i];
CGPoint p2 = cpoints[i-1];
CGPoint l1p1,l1p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l1p1,&l1p2);
//if there had been an intersection with the previous segement, start here to get a nice outline
if(hasIntersection){
l1p1 = intersectionPoint;
}
//is there a next segment?
if(i-2>=0){
//get the next line segment
p1 = cpoints[i-1];
p2 = cpoints[i-2];
CGPoint l2p1,l2p2;
getOffsetLineSegmentForPoints(p1,p2,lineWidth,&l2p1,&l2p2);
//calculate the intersection point with the current line segment
hasIntersection = getLineIntersection(l1p1, l1p2, l2p1, l2p2, &intersectionPoint);
//if they intersect, the current linesegment has to end here to get a nice outline
if(hasIntersection){
l1p2 = intersectionPoint;
}
}
//write the current linesegment to the path
[path addLineToPoint:l1p1];
[path addLineToPoint:l1p2];
}
//close the path
[path addLineToPoint:startPoint];
//we're done
return path;
}
void getOffsetLineSegmentForPoints(CGPoint p1, CGPoint p2, CGFloat lineWidth, CGPoint *linep1, CGPoint *linep2){
CGPoint offset = CGPointSub(p2, p1);
offset = CGPointNorm(offset);
offset = CGPointOrthogonal(offset);
offset = CGPointMultiply(offset, lineWidth/2);
(*linep1) = CGPointAdd(p1, offset);
(*linep2) = CGPointAdd(p2, offset);
}
CGPoint CGPointSub(CGPoint p1, CGPoint p2){
return CGPointMake(p1.x-p2.x, p1.y-p2.y);
}
CGPoint CGPointAdd(CGPoint p1, CGPoint p2){
return CGPointMake(p1.x+p2.x, p1.y+p2.y);
}
CGFloat CGPointLength(CGPoint p){
return sqrtf(powf(p.x,2)+powf(p.y,2));
}
CGPoint CGPointNorm(CGPoint p){
CGFloat length = CGPointLength(p);
if(length==0)
return CGPointZero;
return CGPointMultiply(p, 1/length);
}
CGPoint CGPointMultiply(CGPoint p, CGFloat f){
return CGPointMake(p.x*f, p.y*f);
}
CGPoint CGPointOrthogonal(CGPoint p){
return CGPointMake(p.y, -p.x);
}
BOOL getLineIntersection(CGPoint l1p1, CGPoint l1p2, CGPoint l2p1,
CGPoint l2p2, CGPoint *intersection)
{
CGPoint s1 = CGPointSub(l1p2, l1p1);
CGPoint s2 = CGPointSub(l2p2, l2p1);
float determinant = (-s2.x * s1.y + s1.x * s2.y);
if(determinant==0)
return NO;
CGPoint l2p1l1p1 = CGPointSub(l1p1, l2p1);
float s, t;
s = (-s1.y * l2p1l1p1.x + s1.x * l2p1l1p1.y) / determinant;
t = ( s2.x * l2p1l1p1.y - s2.y * l2p1l1p1.x) / determinant;
if (s >= 0 && s <= 1 && t >= 0 && t <= 1){
if (intersection != NULL){
(*intersection).x = l1p1.x + (t * s1.x);
(*intersection).y = l1p1.y + (t * s1.y);
}
return YES;
}
return NO;
}
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 want to draw shape of small land on view by taking latitude and longitude at the corner of land.
I have wrote following code. For now I took hard core values.
- (void)drawRect:(CGRect)rect {
CGSize screenSize = [UIScreen mainScreen].applicationFrame.size;
SCALE = MIN(screenSize.width, screenSize.height) / (2.0 * EARTH_RADIUS);
OFFSET = MIN(screenSize.width, screenSize.height) / 2.0;
CGPoint latLong1 = {18.626103, 73.805023};
CGPoint latLong2 = {18.626444, 73.804884};
CGPoint latLong3 = {18.626226, 73.804969};
CGPoint latLong4 = {18.626103, 73.805023};
NSMutableArray *points=[NSMutableArray arrayWithObjects:[NSValue valueWithCGPoint:[self convertLatLongCoord:latLong1]],[NSValue valueWithCGPoint:[self convertLatLongCoord:latLong2]], [NSValue valueWithCGPoint:[self convertLatLongCoord:latLong3]],[NSValue valueWithCGPoint:[self convertLatLongCoord:latLong4]],nil];
CGContextRef ctx = UIGraphicsGetCurrentContext();
for(int i=0;i<points.count;i++)
{
// CGPoint newCoord = [self convertLatLongCoord:latLong];
NSValue *val = [points objectAtIndex:i];
CGPoint newCoord = [val CGPointValue];
if(i == 0)
{
// move to the first point
CGContextMoveToPoint(ctx, newCoord.x, newCoord.y);
}
else
{
CGContextAddLineToPoint(ctx, newCoord.x, newCoord.y);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 1);
CGContextSetStrokeColorWithColor(ctx, [[UIColor redColor] CGColor]);
}
}
CGContextStrokePath(ctx);
}
Below is method which converts lat long into x,y co-ordinates.
- (CGPoint)convertLatLongCoord:(CGPoint)latLong
{
CGFloat x = EARTH_RADIUS * cos(latLong.x) * cos(latLong.y) * SCALE + OFFSET;
CGFloat y = EARTH_RADIUS * cos(latLong.x) * sin(latLong.y) * SCALE + OFFSET;
return CGPointMake(x, y);
}
My problem is when I took small land(e.g house land) area lat long its shape is not visible on view after draw. How I can show maximise shape of land on view.
Thanks in advance.