Created
February 19, 2013 00:39
-
-
Save rizo/4982063 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// IRMMainView.m | |
// IRMPathIntersection | |
// | |
// Created by Rizo Isrof on 2/13/13. | |
// Copyright (c) 2013 IRM. All rights reserved. | |
// | |
#import "IRMMainView.h" | |
#import "GTREdgeShapes.h" | |
#import "IRMGeometry.h" | |
#import "NSBezierPath+Geometry.h" | |
#define $debugRect(rect) \ | |
NSLog(@"%s: {%f, %f, %f, %f}", __PRETTY_FUNCTION__, rect.origin.x, rect.origin.y,\ | |
rect.size.width, rect.size.height) | |
struct GTRPolygon | |
{ | |
CGPoint *points; | |
NSUInteger count; | |
}; | |
typedef struct GTRPolygon GTRPolygon; | |
CGPathRef GTRCreateCircle(CGPoint center, float radius) | |
{ | |
CGMutablePathRef path = CGPathCreateMutable(); | |
CGRect circleRect = (CGRect) { | |
center.x - radius, center.y - radius, | |
radius * 2.0f, radius * 2.0f | |
}; | |
CGPathAddEllipseInRect(path, NULL, circleRect); | |
return path; | |
} | |
CGFloat GTRCubicBezierPathInterpolation(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) | |
{ | |
CGFloat C1 = (d - (3.0 * c) + (3.0 * b) - a); | |
CGFloat C2 = ((3.0 * c) - (6.0 * b) + (3.0 * a)); | |
CGFloat C3 = ((3.0 * b) - (3.0 * a)); | |
CGFloat C4 = a; | |
return (C1 * t * t * t + C2 * t * t + C3 * t + C4); | |
} | |
CGPoint GTRQuadraticBezierPathInterpolation(CGFloat t, CGPoint points[static 3]) | |
{ | |
CGFloat a = (1.0 - t) * (1.0 - t); | |
CGFloat b = (2.0 * t * (1.0 - t)); | |
CGFloat c = t * t; | |
return (CGPoint) { | |
(a * points[0].x) + (b * points[1].x) + (c * points[2].x), | |
(a * points[0].y) + (b * points[1].y) + (c * points[2].y) | |
}; | |
} | |
CGFloat GTRCubicBezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) | |
{ | |
CGFloat C1 = (d - (3.0 * c) + (3.0 * b) - a); | |
CGFloat C2 = ((3.0 * c) - (6.0 * b) + (3.0 * a)); | |
CGFloat C3 = ((3.0 * b) - (3.0 * a)); | |
return ((3.0 * C1 * t * t ) + ( 2.0 * C2 * t ) + C3); | |
} | |
static const CGFloat GTRBezierPathFlatness = 0.05; | |
GTRPolygon * | |
GTRPolygonByFlatteningCubicBezierPath(CGPoint p1, CGPoint p2, CGPoint p3, CGPoint p4) | |
{ | |
// TODO: Check special cases. | |
GTRPolygon *polygon = NULL; | |
polygon->points = malloc(sizeof(CGPoint)); | |
polygon->count = 0; | |
for (CGFloat t = 0.0f; t <= 1.00001f; t += GTRBezierPathFlatness) | |
{ | |
polygon->points[polygon->count++] = (CGPoint) { | |
GTRCubicBezierPathInterpolation(t, p1.x, p2.x, p3.x, p4.x), | |
GTRCubicBezierPathInterpolation(t, p1.y, p2.y, p3.y, p4.y) | |
}; | |
} | |
return polygon; | |
} | |
GTRPolygon * | |
GTRPolygonByFlatteningQuadraticBezierPath(CGPoint *points) | |
{ | |
// TODO: Check special cases. | |
GTRPolygon *polygon = (GTRPolygon *)malloc(sizeof(GTRPolygon)); | |
polygon->points = (CGPoint *)malloc(sizeof(CGPoint) * (1.00001f / GTRBezierPathFlatness) + 1); | |
polygon->count = 0; | |
for (CGFloat t = 0.0f; t <= 1.00001f; t += GTRBezierPathFlatness) | |
{ | |
polygon->points[polygon->count++] = GTRQuadraticBezierPathInterpolation(t, points); | |
NSLog(@">>> %f %f", polygon->points[polygon->count - 1].x, polygon->points[polygon->count - 1].y); | |
} | |
(polygon->count == (1.00001f / GTRBezierPathFlatness) + 1) | |
? NSLog(@"Wrong number of polylines.") | |
: NULL; | |
return polygon; | |
} | |
CALayer* drawPoint(CALayer *parent, CGPoint cp) | |
{ | |
NSLog(@"%s %f %f", __PRETTY_FUNCTION__, cp.x, cp.y); | |
CALayer *cpl = [CALayer layer]; | |
cpl.backgroundColor = CGColorCreateGenericRGB(1.0f, 0.0f, 0.0f, 0.7f); | |
cpl.position = cp; | |
cpl.cornerRadius = 5; | |
cpl.bounds = CGRectMake(cp.x, cp.y, 10, 10); | |
[parent addSublayer:cpl]; | |
return cpl; | |
} | |
void drawRect(CALayer *parent, CGRect rect) | |
{ | |
NSLog(@"%s %f %f %f %f", __PRETTY_FUNCTION__, rect.origin.x, rect.origin.y, | |
rect.size.width, rect.size.height); | |
CALayer *cpl = [CALayer layer]; | |
cpl.borderColor = CGColorCreateGenericRGB(1.0f, 0.0f, 0.0f, 0.7f); | |
cpl.borderWidth = 1; | |
cpl.frame = rect; | |
[parent addSublayer:cpl]; | |
} | |
@interface GTRNodeShape : CALayer | |
@property CGPathRef path; | |
@property CGPoint center; | |
@end | |
@implementation GTRNodeShape | |
static const float GTRNodeShapeRadius = 30.0f; | |
- (id)initWithCenter:(CGPoint)center | |
{ | |
NSLog(@"%s", __PRETTY_FUNCTION__); | |
if (self = [super init]) | |
{ | |
NSLog(@">> %f %f", self.position.x, self.position.y); | |
self.center = center; | |
// Calculate the node origin. | |
CGPoint origin = (CGPoint) { | |
center.x - GTRNodeShapeRadius, | |
center.y - GTRNodeShapeRadius | |
}; | |
// Calculate the node-s frame rectangle. | |
self.frame = CGRectMake(origin.x, origin.y, GTRNodeShapeRadius * 2, GTRNodeShapeRadius * 2); | |
self.bounds = CGRectInset((CGRect) { origin, self.frame.size }, -2, -2); | |
self.path = GTRCreateCircle(center, 30.0f); | |
self.zPosition = 2; | |
[self setNeedsDisplay]; | |
[self debugLayer]; | |
} | |
return self; | |
} | |
- (void)debugLayer | |
{ | |
// Position | |
drawPoint(self, self.position); | |
// Frame | |
drawRect(self, self.frame); | |
} | |
- (void)drawInContext:(CGContextRef)context | |
{ | |
NSLog(@"%s", __PRETTY_FUNCTION__); | |
// Set the color space. | |
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); | |
CGContextSetFillColorSpace(context, colorSpace); | |
CGContextSetStrokeColorSpace(context, colorSpace); | |
CGColorSpaceRelease(colorSpace); | |
// Node fill. | |
CGFloat fillColor[] = {1.0f, 1.0f, 1.0f, 0.7f}; | |
CGContextAddPath(context, self.path); | |
CGContextSetFillColor(context, fillColor); | |
CGContextFillPath(context); | |
// Node stroke. | |
CGFloat strokeColor[] = {0.0f, 0.0f, 0.0f, 1.0f}; | |
CGContextAddPath(context, self.path); | |
CGContextSetStrokeColor(context, strokeColor); | |
CGContextSetLineWidth(context, 2.0f); | |
CGContextStrokePath(context); | |
} | |
@end | |
@interface GTREdgeShape : CAShapeLayer | |
{ | |
CGPoint _controlPoints[3]; | |
} | |
@property GTRNodeShape* startNode; | |
@property GTRNodeShape* endNode; | |
@property (nonatomic) CGPoint startPoint; | |
@property (nonatomic) CGPoint controlPoint; | |
@property (nonatomic) CGPoint endPoint; | |
@end | |
@implementation GTREdgeShape | |
- (id)initFrom:(GTRNodeShape*)startNode | |
to:(GTRNodeShape*)endNode | |
controlPoint:(CGPoint)globalControlPoint | |
{ | |
NSLog(@"%s", __PRETTY_FUNCTION__); | |
if (self = [super init]) | |
{ | |
// List of the bezier curve control points (in global coordinates). | |
_controlPoints[0] = startNode.center; | |
_controlPoints[1] = globalControlPoint; | |
_controlPoints[2] = endNode.center; | |
// Though there is no path calculated yet, the bounding box of the path can be easily | |
// obtained with the provided control points. | |
CGRect boundingBox = IRMRectWithPoints(3, _controlPoints); | |
// Layer Geometry | |
self.anchorPoint = CGPointZero; | |
self.position = boundingBox.origin; | |
self.bounds = (CGRect) { CGPointZero, boundingBox.size }; | |
self.zPosition = 1; | |
// Translate the control points to the local coordinates system. | |
_controlPoints[0] = IRMPointSubtractPoint(_controlPoints[0], self.position); | |
_controlPoints[1] = IRMPointSubtractPoint(_controlPoints[1], self.position); | |
_controlPoints[2] = IRMPointSubtractPoint(_controlPoints[2], self.position); | |
// Create the edge path (in local coordinates). | |
self.path = GTRQuadraticBezierPathCreateWithPoints(_controlPoints); | |
// Style | |
self.fillColor = CGColorCreateGenericGray(0.0f, 0.0f); | |
self.strokeColor = CGColorCreateGenericGray(0.0f, 0.2f); | |
self.lineWidth = 2.0f; | |
[self setNeedsDisplay]; | |
[self debugLayer]; | |
} | |
return self; | |
} | |
- (CGPoint)convertToLocalPoint:(CGPoint)globalPoint | |
{ | |
return (CGPoint) { | |
globalPoint.x - self.position.x, | |
globalPoint.y - self.position.y | |
}; | |
} | |
- (CGPoint)convertToGlobalPoint:(CGPoint)localPoint | |
{ | |
return (CGPoint) { | |
localPoint.x + self.position.x, | |
localPoint.y + self.position.y | |
}; | |
} | |
- (void)drawInContext:(CGContextRef)context | |
{ | |
CGContextClearRect(context, CGPathGetBoundingBox(self.path)); | |
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); | |
CGContextSetFillColorSpace(context, colorSpace); | |
CGContextSetStrokeColorSpace(context, colorSpace); | |
CGColorSpaceRelease(colorSpace); | |
CGFloat strokeColor[] = {0.0f, 0.0f, 1.0f, 1.0f}; | |
CGContextSetStrokeColor(context, strokeColor); | |
// The rendering problem comes from here! | |
GTRPolygon *p = GTRPolygonByFlatteningQuadraticBezierPath(_controlPoints); | |
CGContextStrokeLineSegments(context, p->points, p->count); | |
} | |
- (void)setControlPoint:(CGPoint)globalControlPoint | |
{ | |
[CATransaction begin]; | |
[CATransaction setDisableActions:YES]; | |
_controlPoints[0] = IRMPointAddPoint(_controlPoints[0], self.position); | |
_controlPoints[1] = globalControlPoint; | |
_controlPoints[2] = IRMPointAddPoint(_controlPoints[2], self.position); | |
CGRect boundingBox = IRMRectWithPoints(3, _controlPoints); | |
self.position = boundingBox.origin; | |
self.bounds = (CGRect) { CGPointZero, boundingBox.size }; | |
_controlPoints[0] = IRMPointSubtractPoint(_controlPoints[0], self.position); | |
_controlPoints[1] = IRMPointSubtractPoint(_controlPoints[1], self.position); | |
_controlPoints[2] = IRMPointSubtractPoint(_controlPoints[2], self.position); | |
self.path = GTRQuadraticBezierPathCreateWithPoints(_controlPoints); | |
[CATransaction commit]; | |
[self setNeedsDisplay]; | |
} | |
- (void)debugLayer | |
{ | |
self.borderWidth = 1.0f; | |
self.borderColor = CGColorCreateGenericRGB(1.0f, 0.0f, 0.0f, 0.7f); | |
} | |
@end | |
@implementation IRMMainView | |
- (void)awakeFromNib | |
{ | |
NSLog(@"%s", __PRETTY_FUNCTION__); | |
// Setup the root layer. | |
CALayer *rootLayer = [CALayer layer]; | |
rootLayer.name = @"rootLayer"; | |
rootLayer.backgroundColor = CGColorCreateGenericRGB(0.9f, 0.9f, 1.0f, 1.0f); | |
// Geometrical Constants | |
CGPoint p1 = CGPointMake(40, 80); | |
CGPoint p2 = CGPointMake(300, 300); | |
CGPoint p3 = CGPointMake(700, 300); | |
// Setup the nodes layer. | |
self.startNode = [[GTRNodeShape alloc] initWithCenter:p1]; | |
[rootLayer addSublayer:self.startNode]; | |
self.endNode = [[GTRNodeShape alloc] initWithCenter:p3]; | |
[rootLayer addSublayer:self.endNode]; | |
// Setup the edge layer. | |
self.edge = [[GTREdgeShape alloc] initFrom:self.startNode | |
to:self.endNode | |
controlPoint:p2]; | |
[rootLayer addSublayer:self.edge]; | |
// Position Marker | |
self.positionMarker = [CATextLayer layer]; | |
self.positionMarker.string = @"(X, Y)"; | |
self.positionMarker.position = CGPointZero; | |
self.positionMarker.frame = CGRectMake(0, 0, 225, 40); | |
self.positionMarker.backgroundColor = CGColorCreateGenericGray(0.0f, 0.3f); | |
[rootLayer addSublayer:self.positionMarker]; | |
self.layer = rootLayer; | |
self.wantsLayer = YES; | |
[[self window] makeFirstResponder: self]; | |
[[self window] setAcceptsMouseMovedEvents: YES]; | |
} | |
- (void)mouseDragged:(NSEvent *)event | |
{ | |
CGPoint p = [self convertPoint:[event locationInWindow] fromView:nil]; | |
self.edge.controlPoint = p; | |
[CATransaction setDisableActions:YES]; | |
self.positionMarker.string = [NSString stringWithFormat:@"(%.1f, %.1f)", p.x, p.y]; | |
} | |
- (void)mouseMoved:(NSEvent *)event | |
{ | |
CGPoint p = [self convertPoint:[event locationInWindow] fromView:nil]; | |
[CATransaction setDisableActions:YES]; | |
self.positionMarker.string = [NSString stringWithFormat:@"(%.1f, %.1f)", p.x, p.y]; | |
} | |
- (BOOL)acceptsFirstResponder | |
{ | |
return YES; | |
} | |
@end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment