Skip to content

Instantly share code, notes, and snippets.

@rizo
Created February 19, 2013 00:39
Show Gist options
  • Save rizo/4982063 to your computer and use it in GitHub Desktop.
Save rizo/4982063 to your computer and use it in GitHub Desktop.
//
// 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