Skip to content

Instantly share code, notes, and snippets.

@ahayman
Last active October 5, 2015 15:48
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save ahayman/2830483 to your computer and use it in GitHub Desktop.
Save ahayman/2830483 to your computer and use it in GitHub Desktop.
Core Graphics Beveling Routine
//
// CGPathBevel.h
//
// Created by Aaron Hayman on 6/21/12.
// Copyright (c) 2012 FlexileSoft, LLC. All rights reserved.
//
#import <Foundation/Foundation.h>
void bevelPath(CGPathRef path, CGContextRef context, CGFloat bevelDepth, CGColorRef highlight, CGColorRef shadow, CGFloat lightSourceAngle, BOOL evenOddShadows, BOOL eofFill);
//
// CGPathBevel.m
// iDB
//
// Created by Aaron Hayman on 6/21/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "CGPathBevel.h"
#import <QuartzCore/QuartzCore.h>
#define BevelGradSpread .2f
typedef struct{
CGFloat a;
CGFloat b;
CGFloat c;
CGPoint pointA;
CGPoint pointB;
} LineDef;
typedef struct{
CGPoint point;
CGPoint ctrlPoint1;
CGPoint ctrlPoint2;
CGPathElementType type;
CGPoint bevPoint;
CGFloat incidentTheta;
CGFloat bisectingTheta;
BOOL prevShadow;
BOOL shadow;
BOOL nextShadow;
CGPoint shadowBisector;
} PathElement;
typedef struct{
PathElement **elements;
NSUInteger count;
CGPoint lastMove;
CGPoint lastPoint;
NSUInteger size;
} PathElements;
typedef struct{
void **objects;
NSUInteger count;
} PointerArray;
typedef struct{
CGPoint *points;
NSUInteger count;
} PointArray;
CGColorSpaceRef defaultColorSpace(void);
CGFloat PointTheta(CGPoint);
PathElement * NewPathElement(CGPathElementType type);
PathElements * NewPathElementArray(void);
PointerArray * NewPointerArray(void);
void AddToArray(PointerArray *array, void * object);
void AddPathElement(PathElement *element, PathElements *elements);
void GetPathElements(void *info, const CGPathElement *element);
void FindBoundingRect (void *info, const CGPathElement *element);
void SetBisectorsAndFocalPointsForPathElements(PathElements *elements, CGPathRef path, CGFloat lightSourceAngle, BOOL evenOddShadows, BOOL eofFill);
void BevelSubpath(PathElements *subPathElements, CGContextRef context, CGFloat bevelSize, CGColorRef highlight, CGColorRef shadow);
CGColorSpaceRef defaultColorSpace(){
static CGColorSpaceRef space = NULL;
if (space == NULL){
space = CGColorSpaceCreateDeviceRGB();
}
return space;
}
CGFloat PointTheta(CGPoint point){
//This assumes an origin of {0, 0} and returns a theta for the given point
return atan2f(point.y, point.x);
}
PathElement * NewPathElement(CGPathElementType type){
PathElement *pathElement = malloc(sizeof(PathElement));
pathElement->type = type;
pathElement->point = pathElement->bevPoint = pathElement->ctrlPoint1 = pathElement->ctrlPoint2 = pathElement->shadowBisector = CGPointZero;
pathElement->incidentTheta = pathElement->bisectingTheta = CGFLOAT_MAX;
pathElement->prevShadow = pathElement->nextShadow = pathElement->shadow = NO;
return pathElement;
}
PathElements * NewPathElementArray(){
PathElements *elements = malloc(sizeof(PathElements));
elements->elements = NULL;
elements->count = elements->size = 0;
elements->lastMove = elements->lastPoint = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
return elements;
}
PointerArray * NewPointerArray(){
PointerArray *array = malloc(sizeof(PointerArray));
array->count = 0;
array->objects = NULL;
return array;
}
void AddToArray(PointerArray *array, void *object){
array->count++;
array->objects = realloc(array->objects, array->count * sizeof(void *));
array->objects[array->count - 1] = object;
}
void AddPathElement(PathElement *element, PathElements *elements){
elements->count++;
if (elements->count > elements->size){
elements->elements = realloc(elements->elements, elements->count * sizeof(PathElement *));
elements->size = elements->count;
}
elements->elements[elements->count - 1] = element;
}
void GetPathElements(void *info, const CGPathElement *element){
PathElements *elements = (PathElements *)info;
CGPoint *points = element->points;
PathElement *newElement = NewPathElement(element->type);
BOOL (^CGPointsEqual)(CGPoint, CGPoint) = ^BOOL(CGPoint p1, CGPoint p2){
//Using a tolerance to determine point equality. Some default CG shapes include supurflous path elements that I don't care to replicate
CGFloat tolerance = 100;
NSUInteger p1X = p1.x * tolerance;
NSUInteger p1Y = p1.y * tolerance;
NSUInteger p2X = p2.x * tolerance;
NSUInteger p2Y = p2.y * tolerance;
return (p1X > p2X - tolerance && p1X < p2X + tolerance && p1Y > p2Y - tolerance && p1Y < p2Y + tolerance);
};
switch (newElement->type) {
case kCGPathElementMoveToPoint:
newElement->point = points[0];
elements->lastMove = points[0];
break;
case kCGPathElementAddLineToPoint:
newElement->point = points[0];
break;
case kCGPathElementAddCurveToPoint:
newElement->point = points[2];
newElement->ctrlPoint1 = points[0];
newElement->ctrlPoint2 = points[1];
break;
case kCGPathElementAddQuadCurveToPoint:
newElement->point = points[1];
newElement->ctrlPoint1 = points[0];
break;
case kCGPathElementCloseSubpath:
newElement->point = elements->lastMove;
break;
}
//Remove superfluous elements
if (elements->count > 0 && CGPointsEqual(newElement->point, elements->lastPoint)){
free(newElement);
return;
}
//If a path doesn't begin with moveToPoint, it begins at {0,0}, so we create a 'moveToPoint' element representing this
if (elements->count == 0 && element->type != kCGPathElementMoveToPoint){
PathElement *firstElement = NewPathElement(kCGPathElementMoveToPoint);
firstElement->point = CGPointZero;
elements->lastMove = CGPointZero;
AddPathElement(firstElement, elements);
AddPathElement(newElement, elements);
elements->lastPoint = newElement->point;
} else {
elements->lastPoint = newElement->point;
AddPathElement(newElement, elements);
}
}
void SetBisectorsAndFocalPointsForPathElements(PathElements *elements, CGPathRef path, CGFloat lightSourceAngle, BOOL evenOddShadows, BOOL eofFill){
if (elements->count < 3) return;
PointArray *focalPoints = malloc(sizeof(PointArray));
focalPoints->count = 0;
focalPoints->points = NULL;
LineDef (^LineDefForPoints)(CGPoint, CGPoint) = ^LineDef(CGPoint p1, CGPoint p2){
LineDef line = {0,0,0, p1, p2};
line.a = p2.y - p1.y;
line.b = p1.x - p2.x;
line.c = line.a*p1.x + line.b*p1.y;
return line;
};
CGFloat (^CalculateTheta) (CGPoint, CGPoint, CGPoint, CGFloat *) = ^CGFloat (CGPoint endPoint1, CGPoint centerPoint, CGPoint endPoint2, CGFloat *biTheta){
//normalize end points
endPoint1 = CGPointMake(endPoint1.x - centerPoint.x, endPoint1.y - centerPoint.y);
endPoint2 = CGPointMake(endPoint2.x - centerPoint.x, endPoint2.y - centerPoint.y);
//grab our line thetas
CGFloat theta1 = PointTheta(endPoint1);
CGFloat theta2 = PointTheta(endPoint2);
//grab bisecting angle
*biTheta = (fmaxf(theta1, theta2) - fminf(theta1, theta2)) / 2 + fminf(theta1, theta2);
//Determine if angle is pointing into the shape, it not, rotate is 180°
centerPoint.x += cosf(*biTheta);
centerPoint.y += sinf(*biTheta);
if (!CGPathContainsPoint(path, NULL, centerPoint, eofFill))
*biTheta += (*biTheta < M_PI) ? M_PI : -M_PI;
//take the least difference in thetas for the angle of incidence
theta1 = (fmaxf(theta1, theta2) - fminf(theta1, theta2));
if (theta1 > M_PI) theta1 = fabsf((M_PI * 2) - theta1);
return theta1;
};
LineDef (^CalculateBisector)(CGPoint, CGFloat) = ^(CGPoint centerPoint, CGFloat bisectingTheta){
//Calculate a another point on the line
CGPoint biPoint = CGPointMake(cosf(bisectingTheta) * 10, sinf(bisectingTheta) * 10);
//de-normalize biPoint
biPoint.x += centerPoint.x;
biPoint.y += centerPoint.y;
return LineDefForPoints(biPoint, centerPoint);
};
//Takes two LineDef's and returns the intersection of the lines
CGPoint (^CalculateFocalPoint)(LineDef, LineDef) = ^(LineDef l1, LineDef l2){
//m is essentially the diff in line slopes
CGFloat m = (l1.a * l2.b) - (l2.a * l1.b);
if (m == 0)
return CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
else {
CGPoint rPoint = CGPointMake(((l2.b * l1.c) - (l1.b * l2.c)) / m, ((l1.a * l2.c) - (l2.a * l1.c)) / m);
//Round to the nearest tenth.
rPoint.x *= 10;
rPoint.y *= 10;
rPoint.x = roundf(rPoint.x);
rPoint.y = roundf(rPoint.y);
rPoint.x /= 10;
rPoint.y /= 10;
return rPoint;
}
};
CGPoint (^LineIntersect)(LineDef, LineDef) = ^(LineDef l1, LineDef l2){
CGFloat m = (l1.a * l2.b) - (l2.a * l1.b);
if (m == 0)
return CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
else {
return CGPointMake(((l2.b * l1.c) - (l1.b * l2.c)) / m, ((l1.a * l2.c) - (l2.a * l1.c)) / m);
}
};
BOOL (^CGPointsEqual)(CGPoint, CGPoint) = ^BOOL(CGPoint p1, CGPoint p2){
CGFloat tolerance = 100;
NSUInteger p1X = p1.x * tolerance;
NSUInteger p1Y = p1.y * tolerance;
NSUInteger p2X = p2.x * tolerance;
NSUInteger p2Y = p2.y * tolerance;
return (p1X > p2X - tolerance && p1X < p2X + tolerance && p1Y > p2Y - tolerance && p1Y < p2Y + tolerance);
};
void (^AddFocalPoint)(CGPoint) = ^(CGPoint focalPoint){
if (focalPoint.x == CGFLOAT_MAX) return;
if (!CGPathContainsPoint(path, NULL, focalPoint, eofFill)) return;
focalPoints->count++;
focalPoints->points = realloc(focalPoints->points, focalPoints->count * sizeof(CGPoint));
focalPoints->points[focalPoints->count - 1] = focalPoint;
};
PathElement *cElement = nil;
PathElement *pElement = nil;
PathElement *p2Element = nil;
PathElement *poppedElement = nil;
LineDef prevBisector = {CGFLOAT_MAX, CGFLOAT_MAX, CGFLOAT_MAX};
LineDef curBisector = prevBisector;
LineDef firstBisector = prevBisector;
PathElements *updateQueue = NewPathElementArray();
CGPoint cPoint, pPoint, p2Point;
cPoint = pPoint = p2Point = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
CGFloat theta = CGFLOAT_MAX;
int totalCount, escapeCount, cIndex;
cIndex = totalCount = 0;
escapeCount = elements->count * 2;
while (theta == CGFLOAT_MAX) {
totalCount++;
if (pElement)
p2Element = pElement;
if (cElement)
pElement = cElement;
cElement = elements->elements[cIndex];
theta = cElement->bisectingTheta;
if (cElement && pElement && p2Element){
//While cPoint == cElement.point, there is no path progression, queue all elements at the same point to update when we do move
if (!CGPointsEqual(cElement->point, cPoint)){
cPoint = cElement->point;
if (updateQueue->count < 1){
pPoint = pElement->point;
p2Point = p2Element->point;
}
pElement->incidentTheta = CalculateTheta(p2Point, pPoint, cPoint, &(pElement->bisectingTheta));
//If there are elements in queue, update them with the current theta
for (int i = 0; i < updateQueue->count; i++){
poppedElement = updateQueue->elements[i];
poppedElement->incidentTheta = pElement->incidentTheta;
poppedElement->bisectingTheta = pElement->bisectingTheta;
}
updateQueue->count = 0;
//Check previous element and calculate bisector/focalpoint
curBisector = CalculateBisector(pPoint, pElement->bisectingTheta);
if (prevBisector.a != CGFLOAT_MAX)
AddFocalPoint(CalculateFocalPoint(prevBisector, curBisector));
prevBisector = curBisector;
if (firstBisector.a == CGFLOAT_MAX) firstBisector = curBisector;
} else {
//On the first queued item, we need to progress the point cache so that it's accurate
if (theta == CGFLOAT_MAX){
if (updateQueue->count < 1){
p2Point = pPoint;
pPoint = cPoint;
}
AddPathElement(pElement, updateQueue);
} else {
for (int i = 0; i < updateQueue->count; i++){
poppedElement = updateQueue->elements[i];
poppedElement->incidentTheta = pElement->incidentTheta;
poppedElement->bisectingTheta = pElement->bisectingTheta;
}
}
}
}
cIndex++;
if (cIndex >= elements->count)
cIndex = 0;
//If the path is too small (points too close together) this loop could run forever
if (totalCount > escapeCount)
break;
}
//The above will miss checking the intersection of the first and last bisectors
//....yes, I could find a way to include it in the loop but why do that when it's so easy to do this:
AddFocalPoint(CalculateFocalPoint(curBisector, firstBisector));
//Setup the LineDefs to create shadows
LineDef lines[elements->count - 1];
CGPoint prev, current;
current = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);
for (int i = 0; i < elements->count; i++){
prev = current;
current = elements->elements[i]->point;
if (i != 0){
lines[i - 1] = LineDefForPoints(prev, current);
}
}
//Define Point of Light Source
CGRect boundingRect = CGPathGetBoundingBox(path);
CGPoint lightSource = CGPointMake(CGRectGetMidX(boundingRect), CGRectGetMidY(boundingRect));
//A random number designed to avoid scenarios where a light ray shines perfectly through the ends of a line...this seems to indicate I've made an error somewhere but damn...it works so I'm keeping it for now
CGFloat lightDistance = fmaxf(boundingRect.size.width, boundingRect.size.height) * 1.879896f;
lightSource.y += sinf(lightSourceAngle) * lightDistance;
lightSource.x += cosf(lightSourceAngle) * lightDistance;
BOOL (^PointOnLine)(CGPoint, LineDef) = ^BOOL (CGPoint linePoint, LineDef line){
return (linePoint.y >= fminf(line.pointA.y, line.pointB.y) &&
linePoint.y <= fmaxf(line.pointA.y, line.pointB.y) &&
linePoint.x >= fminf(line.pointA.x, line.pointB.x) &&
linePoint.x <= fmaxf(line.pointA.x, line.pointB.x));
};
for (int i = 0; i < elements->count - 1; i++){
cElement = elements->elements[i];
//Each element will test shadows for 3 points: The normal point (used for curves) and two points, one to either side of the normal point (used for line shadows)
CGPoint pointA = cElement->point;
LineDef prevLightRay, nextLightRay, lightRay;
lightRay = LineDefForPoints(lightSource, pointA);
CGPoint pointB = (i == 0) ? elements->elements[elements->count - 2]->point : elements->elements[i - 1]->point;
theta = PointTheta(CGPointMake(pointB.x - pointA.x, pointB.y - pointA.y));
pointA.y += sinf(theta);
pointA.x += cosf(theta);
prevLightRay = LineDefForPoints(lightSource, pointA);
//Move point slightly closer to next point
pointA = cElement->point;
pointB = elements->elements[(i == elements->count - 1) ? 1 : i + 1]->point;
theta = PointTheta(CGPointMake(pointB.x - pointA.x, pointB.y - pointA.y));
pointA.y += sinf(theta);
pointA.x += cosf(theta);
nextLightRay = LineDefForPoints(lightSource, pointA);
LineDef currentLine;
CGPoint currentIntersect;
int prevOcclusionCount = 0, nextOcclusionCount = 0, occlusionCount = 0;
for (int si = 0; si < elements->count - 1; si++){
currentLine = lines[si];
currentIntersect = LineIntersect(prevLightRay, currentLine);
//Check if the intersection occurs within the line bounds
if (si != (i == 0 ? elements->count < 4 ? 2 : elements->count - 2 : i - 1) &&
currentIntersect.x != CGFLOAT_MAX &&
PointOnLine(currentIntersect, currentLine))
{
//If the intersection occurs within the bounds of the lightRay, the line occludes the point
if (PointOnLine(currentIntersect, prevLightRay)){
prevOcclusionCount++;
}
}
currentIntersect = LineIntersect(lightRay, currentLine);
if (si != (i == 0 ? elements->count < 4 ? 2 : elements->count - 2 : i - 1) &&
si != i &&
currentIntersect.x != CGFLOAT_MAX &&
PointOnLine(currentIntersect, currentLine))
{
//If the intersection occurs within the bounds of the lightRay, the line occludes the point
if (PointOnLine(currentIntersect, lightRay)){
occlusionCount++;
}
//Else the point casts a shadow on the line
else {
elements->elements[si]->shadowBisector = currentIntersect; //Note: this will replace previous shadows, maybe I'll change that later
}
}
currentIntersect = LineIntersect(nextLightRay, currentLine);
//Check if the intersection occurs within the line bounds
if (si != i &&
currentIntersect.x != CGFLOAT_MAX &&
PointOnLine(currentIntersect, currentLine))
{
//If the intersection occurs within the bounds of the lightRay, the line occludes the point
if (PointOnLine(currentIntersect, nextLightRay)){
nextOcclusionCount++;
}
}
}
//If there are Even occlusions, there's no shadow
cElement->prevShadow = evenOddShadows ? (prevOcclusionCount % 2 == 1) : (prevOcclusionCount > 0);
cElement->nextShadow = evenOddShadows ? (nextOcclusionCount % 2 == 1) : (nextOcclusionCount > 0);
cElement->shadow = (occlusionCount > 0);
}
pElement = elements->elements[0];
cElement = elements->elements[elements->count - 1];
cElement->prevShadow = pElement->prevShadow;
cElement->shadow = pElement->shadow;
cElement->nextShadow = pElement -> nextShadow;
//Free up memory
free(updateQueue->elements);
free(updateQueue);
free(focalPoints->points);
free(focalPoints);
}
void BevelSubpath(PathElements *subPathElements, CGContextRef context, CGFloat bevelSize, CGColorRef highlight, CGColorRef shadow){
CGPoint (^ScaledCtrlPoint)(CGPoint, CGPoint, CGPoint, CGPoint, CGPoint) = ^CGPoint (CGPoint refPoint1, CGPoint refPoint2, CGPoint bevPoint1, CGPoint bevPoint2, CGPoint ctrlPoint){
CGPoint translation = CGPointMake(bevPoint1.x - refPoint1.x, bevPoint1.y - refPoint1.y);
//Normalize control point to refPoint1
ctrlPoint.x -= refPoint1.x; ctrlPoint.y -= refPoint1.y;
//Get the distances between reference points and bevelled points and calculate the scale
CGFloat refHyp = hypotf(refPoint2.x - refPoint1.x, refPoint2.y - refPoint1.y);
CGFloat bevHyp = hypotf(bevPoint2.x - bevPoint1.x, bevPoint2.y - bevPoint1.y);
CGFloat scale = (bevHyp / refHyp);
//Create transform and apply to control point
CGAffineTransform txfm = CGAffineTransformMakeScale(scale, scale);
txfm = CGAffineTransformTranslate(txfm, translation.x, translation.y);
ctrlPoint = CGPointApplyAffineTransform(ctrlPoint, txfm);
//De normalize the new control point
ctrlPoint.x += refPoint1.x;
ctrlPoint.y += refPoint1.y;
return ctrlPoint;
};
//This will calculate the hypotenuse size based on the incident angle, and then generate the bevelled point
CGPoint (^BevelPoint)(CGPoint, CGFloat, CGFloat) = ^CGPoint (CGPoint refPoint, CGFloat bisectingTheta, CGFloat incidentTheta){
CGFloat hypBevel = bevelSize / sinf(incidentTheta / 2);
return CGPointMake((refPoint.x + (cosf(bisectingTheta) * hypBevel)), (refPoint.y + (sinf(bisectingTheta) * hypBevel)));
};
void (^CalculateOffsets)(CGPoint *fromOffset, CGPoint *toOffset, CGPoint from, CGPoint to) = ^(CGPoint *fromOffset, CGPoint *toOffset, CGPoint from, CGPoint to){
//This calculates an offset to remove seams that occur when two shapes are diagonally butted against each other
//This isn't perfect, and on larger bevel depths with semi-transparent fills a seam may still occur due to the overlap
CGFloat offset = .015f;
if (to.x < from.x){
toOffset->x = -offset;
fromOffset->x = offset;
}
if (to.x > from.x){
toOffset->x = offset;
fromOffset->x = -offset;
}
if (to.y < from.y){
toOffset->y = -offset;
fromOffset->y = offset;
}
if (to.y > from.y){
toOffset->y = offset;
fromOffset->y = -offset;
}
};
void (^FillPath)(CGPathRef, CGPoint, BOOL, CGPoint, BOOL, CGPoint) = ^(CGPathRef fillPath, CGPoint point1, BOOL shadow1, CGPoint point2, BOOL shadow2, CGPoint shadowBisector){
//Calculate the exactly where the transition boundary occurs on the shape
//Yes, this was a PITA to figure out and write
CGContextSaveGState(context);
CGContextAddPath(context, fillPath);
if (shadow1 != shadow2){
CGContextClip(context);
CGFloat boundary = .5f;
if (!CGPointEqualToPoint(shadowBisector, CGPointZero)){
CGFloat tDist = hypotf(point2.x - point1.x, point2.y - point1.y);
CGFloat sDist = hypotf(shadowBisector.x - point1.x, shadowBisector.y - point1.y);
boundary = 1 - (sDist / tDist);
if (boundary < 0 || boundary > 1) boundary = .5f;
}
//Use Boundary to figure out where the gradient starts/ends
CGFloat gStart = fmaxf(0.0f, boundary - BevelGradSpread);
CGFloat gEnd = fminf(1.0f, boundary + BevelGradSpread);
//Draw the gradient
CGFloat locations[4] = {0.0f, gStart, gEnd, 1.0f};
CGColorRef p1Color = (!shadow1) ? highlight : shadow;
CGColorRef p2Color = (!shadow2) ? highlight : shadow;
CGColorRef colorRefs[4] = {p1Color, p1Color, p2Color, p2Color};
CFArrayRef colors = CFArrayCreate(NULL, (const void**)colorRefs, 4, &kCFTypeArrayCallBacks);
CGGradientRef grad = CGGradientCreateWithColors(defaultColorSpace(), colors, locations);
CGContextDrawLinearGradient(context, grad, point1, point2, kCGGradientDrawsAfterEndLocation | kCGGradientDrawsBeforeStartLocation);
CGGradientRelease(grad);
CFRelease(colors);
} else {
CGContextSetFillColorWithColor(context, (!shadow1) ? highlight : shadow);
CGContextFillPath(context);
}
CGContextRestoreGState(context);
};
PathElement *prevElement, *originalElement, *pathElement;
prevElement = originalElement = pathElement = subPathElements->elements[0];
CGPoint curPoint, bevPoint, curCtrlPoint, curBevCtrlPoint, curCtrlPoint2, curBevCtrlPoint2, prevPoint, prevBevPoint, fOff, tOff;
CGMutablePathRef path;
for (int i = 0; i < subPathElements->count; i++){
pathElement = subPathElements->elements[i];
switch (pathElement->type) {
case kCGPathElementMoveToPoint:{
pathElement->bevPoint = BevelPoint(pathElement->point, pathElement->bisectingTheta, pathElement->incidentTheta);
originalElement = pathElement;
break;
}
case kCGPathElementAddLineToPoint:
//calculate the bevel
curPoint = pathElement->point;
prevPoint = prevElement->point;
prevBevPoint = prevElement->bevPoint;
pathElement->bevPoint = bevPoint = BevelPoint(pathElement->point, pathElement->bisectingTheta, pathElement->incidentTheta);
//determine offset to Cover seams
fOff = CGPointZero;
tOff = CGPointZero;
CalculateOffsets(&fOff, &tOff, prevElement->point, pathElement->point);
//create path for bevelled side
path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, prevPoint.x + fOff.x, prevPoint.y + fOff.y);
CGPathAddLineToPoint(path, NULL, prevBevPoint.x + fOff.x, prevBevPoint.y + fOff.y);
CGPathAddLineToPoint(path, NULL, bevPoint.x + tOff.x, bevPoint.y + tOff.y);
CGPathAddLineToPoint(path, NULL, curPoint.x + tOff.x, curPoint.y + tOff.y);
CGPathCloseSubpath(path);
FillPath(path, curPoint, pathElement->prevShadow, prevPoint, prevElement->nextShadow, pathElement->shadowBisector);
CGPathRelease(path);
break;
case kCGPathElementAddCurveToPoint:{
//get prev vars
prevPoint = prevElement->point;
prevBevPoint = prevElement->bevPoint;
//calculate the bevel
curPoint = pathElement->point;
pathElement->bevPoint = bevPoint = BevelPoint(curPoint, pathElement->bisectingTheta, pathElement->incidentTheta);
//scale control points
curCtrlPoint = pathElement->ctrlPoint1;
curCtrlPoint2 = pathElement->ctrlPoint2;
//Note: ctrlPoints are reversed, cause we're going in the other direction
curBevCtrlPoint = ScaledCtrlPoint(curPoint, prevPoint, bevPoint, prevBevPoint, curCtrlPoint2);
curBevCtrlPoint2 = ScaledCtrlPoint(curPoint, prevPoint, bevPoint, prevBevPoint, curCtrlPoint);
//create the path
fOff = CGPointZero;
tOff = CGPointZero;
CalculateOffsets(&fOff, &tOff, prevPoint, curPoint);
path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, prevPoint.x + fOff.x, prevPoint.y + fOff.y);
CGPathAddCurveToPoint(path, NULL, curCtrlPoint.x, curCtrlPoint.y, curCtrlPoint2.x, curCtrlPoint2.y, curPoint.x + tOff.x, curPoint.y + tOff.y);
CGPathAddLineToPoint(path, NULL, bevPoint.x + tOff.x, bevPoint.y + tOff.y);
CGPathAddCurveToPoint(path, NULL, curBevCtrlPoint.x + fOff.x, curBevCtrlPoint.y + fOff.y, curBevCtrlPoint2.x + tOff.x, curBevCtrlPoint2.y + tOff.y, prevBevPoint.x +fOff.x, prevBevPoint.y + fOff.y);
CGPathCloseSubpath(path);
//fill the path
FillPath(path, curPoint, pathElement->shadow, prevPoint, prevElement->shadow, pathElement->shadowBisector);
CGPathRelease(path);
break;
}
case kCGPathElementAddQuadCurveToPoint:
//get prev vars
prevPoint = prevElement->point;
prevBevPoint = prevElement->bevPoint;
//calculate the bevel
curPoint = pathElement->point;
pathElement->bevPoint = bevPoint = BevelPoint(curPoint, pathElement->bisectingTheta, pathElement->incidentTheta);
//scale control point
curCtrlPoint = pathElement->ctrlPoint1;
curBevCtrlPoint = ScaledCtrlPoint(curPoint, prevPoint, bevPoint, prevBevPoint, curCtrlPoint);
//create path
fOff = CGPointZero;
tOff = CGPointZero;
CalculateOffsets(&fOff, &tOff, prevPoint, curPoint);
path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, prevPoint.x + fOff.x, prevPoint.y + fOff.y);
CGPathAddQuadCurveToPoint(path, NULL, curCtrlPoint.x, curCtrlPoint.y, curPoint.x + tOff.x, curPoint.y + tOff.y);
CGPathAddLineToPoint(path, NULL, bevPoint.x + tOff.x, bevPoint.y + tOff.y);
CGPathAddQuadCurveToPoint(path, NULL, curBevCtrlPoint.x, curBevCtrlPoint.y, prevBevPoint.x +fOff.x, prevBevPoint.y + fOff.y);
CGPathCloseSubpath(path);
//fill the path
FillPath(path, curPoint, pathElement->shadow, prevPoint, prevElement->shadow, pathElement->shadowBisector);
CGPathRelease(path);
break;
case kCGPathElementCloseSubpath:
//Get theta
curPoint = originalElement->point;
bevPoint = originalElement->bevPoint;
prevPoint = prevElement->point;
prevBevPoint = prevElement->bevPoint;
//create path for bevelled side
fOff = CGPointZero;
tOff = CGPointZero;
CalculateOffsets(&fOff, &tOff, prevPoint, curPoint);
path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, prevPoint.x + fOff.x, prevPoint.y + fOff.y);
CGPathAddLineToPoint(path, NULL, prevBevPoint.x + fOff.x, prevBevPoint.y + fOff.y);
CGPathAddLineToPoint(path, NULL, bevPoint.x + tOff.x, bevPoint.y + tOff.y);
CGPathAddLineToPoint(path, NULL, curPoint.x + tOff.x, curPoint.y + tOff.y);
CGPathCloseSubpath(path);
FillPath(path, curPoint, pathElement->prevShadow, prevPoint, prevElement->nextShadow, pathElement->shadowBisector);
CGPathRelease(path);
break;
}
prevElement = pathElement;
}
}
void bevelPath(CGPathRef path, CGContextRef context, CGFloat bevelDepth, CGColorRef highlight, CGColorRef shadow, CGFloat lightSourceAngle, BOOL evenOddShadows, BOOL eofFill){
if (bevelDepth <= 0 || !highlight || !shadow) return;
//Grab the all the elements in the path
PathElements *pathElements = NewPathElementArray();
CGPathApply(path, pathElements, GetPathElements);
//Split out the subpaths into separate linked lists, stored in an array
PathElement *element;
PathElements *subPath;
PointerArray *subPaths = NewPointerArray();
for (int i = 0; i < pathElements->count; i++){
element = pathElements->elements[i];
//Sepearete out subpaths into individual stacks
if (i == 0 || element->type == kCGPathElementMoveToPoint){
subPath = NewPathElementArray();
AddToArray(subPaths, subPath);
}
AddPathElement(element, subPath);
}
//Perform the actual bevelling
for (int i = 0; i < subPaths->count; i++){
subPath = subPaths->objects[i];
SetBisectorsAndFocalPointsForPathElements(subPath, path, lightSourceAngle, evenOddShadows, eofFill);
BevelSubpath(subPath, context, bevelDepth, highlight, shadow);
}
//Free memory
for (int i = 0; i < subPaths->count; i++){
subPath = subPaths->objects[i];
free(subPath->elements);
free(subPath);
}
free(subPaths->objects);
free(subPaths);
for (int i = 0; i < pathElements->size; i++){
free(pathElements->elements[i]);
}
free(pathElements->elements);
free(pathElements);
}
@ahayman
Copy link
Author

ahayman commented Aug 19, 2013

I'll take a look at at when I get some time. Oddly, all that I know about ray tracing is entirely what I made up. Essentially, I count the intersection of lines. There's a lot more than could be done, and I'm sure there's better ways to do it. Unfortunately, I'm in a bit of a crunch time right now with my own development, so this'll probably wait a little while.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment