I have some points like
(x1,y1), (x2,y2), (x3,y3)...
Now I want to draw a chart with smooth curve?
I'm trying to draw as below
-(void)drawPrices
{
NSInteger count = self.prices.count;
UIBezierPath *path = [UIBezierPath bezierPath];
path.lineCapStyle = kCGLineCapRound;
for(int i=0; i<count-1; i++)
{
CGPoint controlPoint[2];
CGPoint p = [self pointWithIndex:i inData:self.prices];
if(i==0)
{
[path moveToPoint:p];
}
CGPoint nextPoint, previousPoint, m;
nextPoint = [self pointWithIndex:i+1 inData:self.prices];
previousPoint = [self pointWithIndex:i-1 inData:self.prices];
if(i > 0) {
m.x = (nextPoint.x - previousPoint.x) / 2;
m.y = (nextPoint.y - previousPoint.y) / 2;
} else {
m.x = (nextPoint.x - p.x) / 2;
m.y = (nextPoint.y - p.y) / 2;
}
controlPoint[0].x = p.x + m.x * 0.2;
controlPoint[0].y = p.y + m.y * 0.2;
// Second control point
nextPoint = [self pointWithIndex:i+2 inData:self.prices];
previousPoint = [self pointWithIndex:i inData:self.prices];
p = [self pointWithIndex:i + 1 inData:self.prices];
m = zeroPoint;
if(i < self.prices.count - 2) {
m.x = (nextPoint.x - previousPoint.x) / 2;
m.y = (nextPoint.y - previousPoint.y) / 2;
} else {
m.x = (p.x - previousPoint.x) / 2;
m.y = (p.y - previousPoint.y) / 2;
}
controlPoint[1].x = p.x - m.x * 0.2;
controlPoint[1].y = p.y - m.y * 0.2;
[path addCurveToPoint:p controlPoint1:controlPoint[0] controlPoint2:controlPoint[1]];
}
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.path = path.CGPath;
lineLayer.lineWidth = LINE_WIDTH;
lineLayer.strokeColor = _priceColor.CGColor;
lineLayer.fillColor = [UIColor clearColor].CGColor;
[self.layer addSublayer:lineLayer];
}
but in some situation, the line will "go back" like
Is there any better way to do that?
I've got you another solution which I used to success.
It requires SpriteKit, which has a fantastic tool called SKKeyFrameSequence which is capable of spline interpolation as shown in this tutorial provided by Apple.
So the idea is that you'll create the correct SKKeyFrameSequence object like this (assuming your data is in an array of (CGFloat, CGFloat) (x, y) tuples):
let xValues = data.map { $0.0 }
let yValues = data.map { $0.1 }
let sequence = SKKeyFrameSequence(keyFrameValues: yValues,
times: xValues.map { NSNumber($0) })
sequence.interpolationMode = .spline
let xMin = xValues.min()!
let xMax = xValues.max()!
Then, if you divide the interpolated spline into 200 pieces (change this value if you want, but for me this resulted in smooth waves to human eyes), you can draw a path consisting of small lines.
var splinedValues = [(CGFloat, CGFloat)]()
stride(from: xMin, to: xMax, by: (xMax - xMin) / 200).forEach {
splinedValues.append((CGFloat($0),
sequence.sample(atTime: CGFloat($0)) as! CGFloat))
}
Then finally the path (I will use SwiftUI, but you can use UIKit the same way too.):
Path { path in
path.move(to: CGPoint(x: splinedValues[0].0, y: splinedValues[0].1))
for splineValue in splinedValues.dropFirst() {
path.addLine(to: CGPoint(x: splineValue.0, y: splineValue.1))
}
}
For the values
[(1, 4), (2, 6), (3, 7), (4, 5), (5, 3), (6, -1), (7, -2), (8, -2.5), (9, -2), (10, 0), (11, 4)]
I've gotten a graph like this with the method described above: (I added the points too to evaluate the result better)
I find the answer at Draw Graph curves with UIBezierPath
And try implement with the code
+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points
{
UIBezierPath *path = [UIBezierPath bezierPath];
NSValue *value = points[0];
CGPoint p1 = [value CGPointValue];
[path moveToPoint:p1];
if (points.count == 2) {
value = points[1];
CGPoint p2 = [value CGPointValue];
[path addLineToPoint:p2];
return path;
}
for (NSUInteger i = 1; i < points.count; i++) {
value = points[i];
CGPoint p2 = [value CGPointValue];
CGPoint midPoint = midPointForPoints(p1, p2);
[path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)];
[path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)];
p1 = p2;
}
return path;
}
static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) {
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) {
CGPoint controlPoint = midPointForPoints(p1, p2);
CGFloat diffY = abs(p2.y - controlPoint.y);
if (p1.y < p2.y)
controlPoint.y += diffY;
else if (p1.y > p2.y)
controlPoint.y -= diffY;
return controlPoint;
}
Thanks user1244109 ^_^.
Related
Can we avoid the extra bezier curve line after the last point in Bezier graph? Is there any property for that?
We need just end-end curve graph, no extra curve. Please share the solutions/suggestion. Update: Please check the code below,
- (void)drawRect:(CGRect)rect {
UIBezierPath *line = [UIBezierPath bezierPath];
NSValue *value = points[0];
CGPoint p1 = [value CGPointValue];
[line moveToPoint:p1];if (points.count == 2) {
value = points[1];
CGPoint p2 = [value CGPointValue];
[line addLineToPoint:p2];
return line;
}
for (NSUInteger i = 1; i < points.count; i++) {
value = points[i];
CGPoint p2 = [value CGPointValue];
CGPoint midPoint = midPointForPoints(p1, p2);
[line addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)];
[line addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)];
p1 = p2;
}
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = self.bounds;
pathLayer.path = line.CGPath;
pathLayer.strokeColor = self.color.CGColor;
pathLayer.fillColor = nil;
pathLayer.opacity = self.lineAlpha;
pathLayer.lineWidth = self.lineWidth;
pathLayer.lineJoin = kCALineJoinBevel;
pathLayer.lineCap = kCALineCapRound;
[self.layer addSublayer:pathLayer];}
static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) {
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);}
Thanks!
Lets assume we have a 2D space (to simplify situation), and layer S and layer C, where C is sublayer of S.
The conversion process must affect bounds, position of C, transform of C, sublayersTransform of S, anchorPoint of C. My guess was the next:
CGAffineTransform transformToChild(CALayer *S, CALayer *C) {
CGFloat txa = - C.bounds.origin.x - C.bounds.size.width * C.anchorPoint.x;
CGFloat tya = - C.bounds.origin.y - C.bounds.size.height * C.anchorPoint.y;
CGFloat txb = C.position.x;
CGFloat tyb = C.position.y;
CGAffineTransform sublayerTransform = CATransform3DGetAffineTransform(S.sublayerTransform);
CGAffineTransform fromS = CGAffineTransformTranslate(sublayerTransform, txb, tyb);
fromS = CGAffineTransformConcat(fromS, C.affineTransform);
fromS = CGAffineTransformTranslate(fromS, txa, tya);
return fromS;
}
But this is not working when transform of the child layer is not identity (e.g. in case of rotation to M_PI_2 angle).
Whole code with layers:
CALayer *l1 = [CALayer new];
l1.frame = CGRectMake(-40, -40, 80, 80);
l1.bounds = CGRectMake(40, 40, 80, 80);
CALayer *l2 = [CALayer new];
l2.frame = CGRectMake(50, 40, 20, 20);
l2.bounds = CGRectMake(40, 40, 20, 20);
CGAffineTransform t2 = CGAffineTransformMakeRotation(M_PI / 2);
l2.affineTransform = t2;
[l1 addSublayer:l2];
CGAffineTransform toL2 = transformToChild(l1, l2);
CGPoint p = CGPointApplyAffineTransform(CGPointMake(70, 50), toL2);
NSLog(#"Custom Point %#", [NSValue valueWithCGPoint:p]);
p = [l1 convertPoint:CGPointMake(70, 50) toLayer:l2];
NSLog(#"CoreAnimation Point %#", [NSValue valueWithCGPoint:p]);
Comparison to system results:
Custom Point NSPoint: {-50, 80}
CoreAnimation Point NSPoint: {50, 40}
There's an old mailing list thread with some details about this here:
http://lists.apple.com/archives/quartz-dev/2008/Mar/msg00086.html
http://lists.apple.com/archives/quartz-dev/2008/Mar/msg00087.html
those messages are quite old, so e.g. they don't include the effects of the geometryFlipped property, which was added more recently, but that would just add another term onto the merged matrix.
So, I found this way of point conversion to and from sublayer's coordinate space, which works correctly with sublayer's non-identity transformation. sublayersTransform of the super layer is not covered here, but I think it would be not hard to extend these functions to support it.
CGPoint pointToChild(CALayer *C, CGPoint p) {
CGFloat txa = - C.bounds.origin.x - C.bounds.size.width * C.anchorPoint.x;
CGFloat tya = - C.bounds.origin.y - C.bounds.size.height * C.anchorPoint.y;
CGFloat txb = C.position.x;
CGFloat tyb = C.position.y;
p.x -= txb;
p.y -= tyb;
p = CGPointApplyAffineTransform(p, CGAffineTransformInvert(C.affineTransform));
if (C.isGeometryFlipped) {
CGAffineTransform flip = CGAffineTransformMakeScale(1.0f, -1.0f);
flip = CGAffineTransformTranslate(flip, 0, C.bounds.size.height * (2.0f * C.anchorPoint.y - 1.0f));
p = CGPointApplyAffineTransform(p, CGAffineTransformInvert(flip));
}
p.x -= txa;
p.y -= tya;
return p;
}
CGPoint pointFromChild(CALayer *C, CGPoint p) {
CGFloat txb = - C.bounds.origin.x - C.bounds.size.width * C.anchorPoint.x;
CGFloat tyb = - C.bounds.origin.y - C.bounds.size.height * C.anchorPoint.y;
CGFloat txa = C.position.x;
CGFloat tya = C.position.y;
p.x += txb;
p.y += tyb;
if (C.isGeometryFlipped) {
CGAffineTransform flip = CGAffineTransformMakeScale(1.0f, -1.0f);
flip = CGAffineTransformTranslate(flip, 0, C.bounds.size.height * (2.0f * C.anchorPoint.y - 1.0f));
p = CGPointApplyAffineTransform(p, flip);
}
p = CGPointApplyAffineTransform(p, C.affineTransform);
p.x += txa;
p.y += tya;
return p;
}
I am trying hands at Core Animation on iphone.
Details of what I have done so far:
I am using layers and CAKeyFrameAnimation using path.
I have created a layer with Contents set to a bitmap file of a fly which I want to animate on a spiral path. The centre point of the spiral path is at CGPoint (150,150). The end point of the spiral path is a radius of 100.0f.
What I want to achieve:
I further want to increase the radius to a value so that the spiral can go beyond the bounds of the view frame, but when it reaches the bound, I desire the fly to trace back the path.
EDIT: (adding code):
-(IBAction) clickToAnimate:(id) sender
{
//create a path to animate the fly
CGFloat minRadius = 5.0f;
CGFloat maxRadius = 100.0f;
CGFloat radiusOffset = 5.0f;
float i;
int count =1;
int remainder;
curvePath = CGPathCreateMutable();
//this looping mimics the "spiral path"
for (i = minRadius; i <= maxRadius; i = i + 5.0f)
{
remainder = count % 2;
if (remainder == 0)
{
CGPathAddArc(curvePath, NULL, currentPoint.x, currentPoint.y, i, M_PI / 2, 1.5 * M_PI, NO);
}
else
{
CGPathAddArc(curvePath, NULL, currentPoint.x, currentPoint.y + radiusOffset, i, 1.5 * M_PI , M_PI / 2, NO);
}
count = count +1;
}
//add timer
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(checkCoordinatesOnTimer) userInfo:self repeats:YES];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.calculationMode = kCAAnimationPaced;
animation.path = curvePath;
animation.duration = 25.0f;
animation.autoreverses = YES;
animation.fillMode = kCAFillModeFrozen;
CGPathRelease(curvePath);
[animationLayer addAnimation:animation forKey:#"position"];
}
Approach taken:
I set up a timer, which would check the co-ordinates of the animation layer every half second. The co-ordinates would be checked against the bounding x and y co-ordinates. If the co-ordinates are found to cross the boundary, we will disable the timer and start with the reverse animation process.
The reverse animation process would first check the angle from horizontal at which the animation is when it crosses the boundary. Using this angle and radius from center point, create a path for reverse animation. Details can be found in code below.
- (void) checkCoordinatesOnTimer
{
if (!presentLayer)
{
presentLayer =[[CALayer alloc] init];
}
presentLayer = [animationLayer presentationLayer];
CGPoint curPt;
curPt.x = presentLayer.position.x;
curPt.y = presentLayer.position.y;
currRadius = sqrt(pow(fabsf(curPt.x - centerPoint.x), 2) + pow(fabsf(curPt.y - centerPoint.y), 2));
if ((curPt.x >= CGRectGetMaxX(self.view.frame)) || (curPt.y >= CGRectGetMaxY(self.view.frame)))
{
[timer invalidate];
[self reverseAnimation:curPt];
}
}
-(void) reverseAnimation:(CGPoint)curPoint
{
CGFloat angle = (CGFloat)[self calculateAngleInRadians:curPoint];
maxRadius = currRadius;
float i;
int count =1;
int remainder;
BOOL firstLap = YES;
CGPathRelease(curvePath);
curvePath = CGPathCreateMutable();
for (i = maxRadius; i >= minRadius; i = i - 5.0f)
{
remainder = count % 2 ;
if (firstLap)
{
if ((angle >= (M_PI/2)) && (angle <= 1.5 *M_PI))
//2nd and third quard,
//angle to change from final angle to 90
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y, i, angle, 1.5 * M_PI, FALSE);
}
else
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y, i, angle, M_PI / 2 , FALSE);
}
firstLap = NO;
continue;
}
else
{
if (remainder == 0)
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y, i, 1.5* M_PI, M_PI / 2, FALSE);
}
else
{
CGPathAddArc(curvePath, NULL, centerPoint.x, centerPoint.y + radiusOffset, i, M_PI / 2 ,1.5* M_PI, FALSE);
}
}
count = count +1;
}
animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.calculationMode =kCAAnimationPaced; // for even pacing of animation in segments.
animation.path = curvePath;
animation.duration = 40.0f;
animation.fillMode = kCAFillModeForwards;
CGPathRelease(curvePath);
[animationLayer addAnimation:animation forKey:#"position"];
}
-(CGFloat) calculateAngleInRadians:(CGPoint) pt
{
CGFloat xLen = fabsf(centerPoint.x - pt.x);
CGFloat yLen = fabsf(centerPoint.y - pt.y);
double value = yLen/xLen;
double angle = atan(value);
double angleInDeg = ( 180 * angle/ M_PI);
double finalAngle;
if ((pt.x > centerPoint.x) || (pt.y > centerPoint.y)) {
finalAngle = angle;
}
else if ((centerPoint.x > pt.x) || (pt.y > centerPoint.y))
{
finalAngle = M_PI/2 + angle;
}
else if ((centerPoint.x > pt.x) || (centerPoint.y > pt.y))
{
finalAngle = M_PI + angle;
}
else {
finalAngle = (1.5 * M_PI) + angle;
}
return finalAngle;
}
Using the code below I am drawing lines from triangle strips of varying sizes. At the point of the final triangle I would like to add a GLpoint primitive so it looks like the line has a rounded end. How would I calculate the correct diameter for the GLpoint based upon the size of the final triangle? Please see attached image demonstrating what I have at the moment (the point is much too large).
- (void)pan:(UIPanGestureRecognizer *)p {
CGPoint v = [p velocityInView:self.view];
CGPoint l = [p locationInView:self.view];
float distance = sqrtf((l.x - previousPoint.x) * (l.x - previousPoint.x) + (l.y - previousPoint.y) * (l.y - previousPoint.y));
float velocityMagnitude = sqrtf(v.x*v.x + v.y*v.y);
float clampedVelocityMagnitude = clamp(VELOCITY_CLAMP_MIN, VELOCITY_CLAMP_MAX, velocityMagnitude);
float normalizedVelocity = (clampedVelocityMagnitude - VELOCITY_CLAMP_MIN) / (VELOCITY_CLAMP_MAX - VELOCITY_CLAMP_MIN);
float lowPassFilterAlpha = STROKE_WIDTH_SMOOTHING;
float newThickness = (STROKE_WIDTH_MAX - STROKE_WIDTH_MIN) * normalizedVelocity + STROKE_WIDTH_MIN;
penThickness = penThickness * lowPassFilterAlpha + newThickness * (1 - lowPassFilterAlpha);
glBindVertexArrayOES(vertexArrayTriangles);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferTriangles);
if ([p state] == UIGestureRecognizerStateBegan)
{
previousPoint = l;
previousMidPoint = l;
NISignaturePoint startPoint = {
{ (l.x / self.view.bounds.size.width * 2. - 1), ((l.y / self.view.bounds.size.height) * 2.0 - 1) * -1, 0}, {0,0,0}
};
previousVertex = startPoint;
previousThickness = penThickness;
addVertexTriangles(&lengthTriangles, startPoint);
addVertexTriangles(&lengthTriangles, previousVertex);
} else if ([p state] == UIGestureRecognizerStateChanged) {
CGPoint mid = CGPointMake((l.x + previousPoint.x) / 2.0, (l.y + previousPoint.y) / 2.0);
if (distance > QUADRATIC_DISTANCE_TOLERANCE) {
// Plot quadratic bezier instead of line
unsigned int i;
int segments = (int) distance / 1.5;
float startPenThickness = previousThickness;
float endPenThickness = penThickness;
previousThickness = penThickness;
for (i = 0; i < segments; i++)
{
penThickness = startPenThickness + ((endPenThickness - startPenThickness) / segments) * i;
double t = (double)i / (double)segments;
double a = pow((1.0 - t), 2.0);
double b = 2.0 * t * (1.0 - t);
double c = pow(t, 2.0);
double x = a * previousMidPoint.x + b * previousPoint.x + c * mid.x;
double y = a * previousMidPoint.y + b * previousPoint.y + c * mid.y;
NISignaturePoint v = {
{
(x / self.view.bounds.size.width * 2. - 1),
((y / self.view.bounds.size.height) * 2.0 - 1) * -1,
0
},
strokeColor
};
[self addTriangleStripPointsForPrevious:previousVertex next:v];
previousVertex = v;
}
}
previousPoint = l;
previousMidPoint = mid;
}
else if (p.state == UIGestureRecognizerStateEnded | p.state == UIGestureRecognizerStateCancelled)
{
addVertexTriangles(&lengthTriangles, previousVertex);
addVertexTriangles(&lengthTriangles, previousVertex);
glBindVertexArrayOES(vertexArrayPoints);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferPoints);
NISignaturePoint startPoint = {
{previousVertex.vertex.x, previousVertex.vertex.y, 0}, strokeColor, ???
};
addVertexPoints(&lengthPoints, startPoint);
penThickness = STROKE_WIDTH_MIN;
previousThickness = penThickness;
}
}
- (void)addTriangleStripPointsForPrevious:(NISignaturePoint)previous next:(NISignaturePoint)next {
float toTravel = penThickness / 2.0;
//NSLog(#"add tri");
for (int i = 0; i < 2; i++) {
GLKVector3 p = perpendicular(previous, next);
GLKVector3 p1 = next.vertex;
GLKVector3 ref = GLKVector3Add(p1, p);
float distance = GLKVector3Distance(p1, ref);
float difX = p1.x - ref.x;
float difY = p1.y - ref.y;
float ratio = -1.0 * (toTravel / distance);
difX = difX * ratio;
difY = difY * ratio;
NISignaturePoint stripPoint = {
{ p1.x + difX, p1.y + difY, 0.0 },
strokeColor
};
addVertexTriangles(&lengthTriangles, stripPoint);
toTravel *= -1;
}
}
You might include UIGestureRecognizerStateChanged, UIGestureRecognizerStateEnded, and UIGestureRecognizerStateCancelled as one case to draw the line, but then detect the later two cases to add the ending point with diameter equal to 0.5+endPenThickness
- (void)pan:(UIPanGestureRecognizer *)p {
CGPoint v = [p velocityInView:self.view];
CGPoint l = [p locationInView:self.view];
float distance = sqrtf((l.x - previousPoint.x) * (l.x - previousPoint.x) + (l.y - previousPoint.y) * (l.y - previousPoint.y));
float velocityMagnitude = sqrtf(v.x*v.x + v.y*v.y);
float clampedVelocityMagnitude = clamp(VELOCITY_CLAMP_MIN, VELOCITY_CLAMP_MAX, velocityMagnitude);
float normalizedVelocity = (clampedVelocityMagnitude - VELOCITY_CLAMP_MIN) / (VELOCITY_CLAMP_MAX - VELOCITY_CLAMP_MIN);
float lowPassFilterAlpha = STROKE_WIDTH_SMOOTHING;
float newThickness = (STROKE_WIDTH_MAX - STROKE_WIDTH_MIN) * normalizedVelocity + STROKE_WIDTH_MIN;
penThickness = penThickness * lowPassFilterAlpha + newThickness * (1 - lowPassFilterAlpha);
glBindVertexArrayOES(vertexArrayTriangles);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferTriangles);
if ([p state] == UIGestureRecognizerStateBegan)
{
previousPoint = l;
previousMidPoint = l;
NISignaturePoint startPoint = {
{ (l.x / self.view.bounds.size.width * 2. - 1), ((l.y / self.view.bounds.size.height) * 2.0 - 1) * -1, 0}, {0,0,0}
};
previousVertex = startPoint;
previousThickness = penThickness;
addVertexTriangles(&lengthTriangles, startPoint);
addVertexTriangles(&lengthTriangles, previousVertex);
} else {
// UIGestureRecognizerStateChanged, UIGestureRecognizerStateEnded, and UIGestureRecognizerStateCancelled
CGPoint mid = CGPointMake((l.x + previousPoint.x) / 2.0, (l.y + previousPoint.y) / 2.0);
if (distance > QUADRATIC_DISTANCE_TOLERANCE) {
// Plot quadratic bezier instead of line
unsigned int i;
int segments = (int) distance / 1.5;
float startPenThickness = previousThickness;
float endPenThickness = penThickness;
previousThickness = penThickness;
for (i = 0; i < segments; i++)
{
penThickness = startPenThickness + ((endPenThickness - startPenThickness) / segments) * i;
double t = (double)i / (double)segments;
double a = pow((1.0 - t), 2.0);
double b = 2.0 * t * (1.0 - t);
double c = pow(t, 2.0);
double x = a * previousMidPoint.x + b * previousPoint.x + c * mid.x;
double y = a * previousMidPoint.y + b * previousPoint.y + c * mid.y;
NISignaturePoint v = {
{
(x / self.view.bounds.size.width * 2. - 1),
((y / self.view.bounds.size.height) * 2.0 - 1) * -1,
0
},
strokeColor
};
[self addTriangleStripPointsForPrevious:previousVertex next:v];
previousVertex = v;
}
}
previousPoint = l;
previousMidPoint = mid;
if (p.state == UIGestureRecognizerStateEnded | p.state == UIGestureRecognizerStateCancelled)
{
glBindVertexArrayOES(vertexArrayPoints);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferPoints);
NISignaturePoint startPoint = {
{previousVertex.vertex.x, previousVertex.vertex.y, 0}, strokeColor, endPenThickness / 2.
};
addVertexPoints(&lengthPoints, startPoint);
penThickness = STROKE_WIDTH_MIN;
previousThickness = penThickness;
}
}
}
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.