mirror of
https://github.com/darlinghq/darling-cocotron.git
synced 2024-11-23 12:09:51 +00:00
494 lines
16 KiB
Objective-C
494 lines
16 KiB
Objective-C
/* Copyright (c) 2007 Christopher J. W. Lloyd
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
this software and associated documentation files (the "Software"), to deal in
|
|
the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
#import <Onyx2D/O2Exceptions.h>
|
|
#import <Onyx2D/O2MutablePath.h>
|
|
#import <Onyx2D/O2Path.h>
|
|
#import <math.h>
|
|
|
|
// ellipse to 4 spline bezier, http://www.tinaja.com/glib/ellipse4.pdf
|
|
void O2MutablePathEllipseToBezier(O2Point *cp, O2Float x, O2Float y,
|
|
O2Float xrad, O2Float yrad)
|
|
{
|
|
O2Float magic = 0.551784;
|
|
O2Float xmag = xrad * magic;
|
|
O2Float ymag = yrad * magic;
|
|
size_t i = 0;
|
|
|
|
cp[i++] = O2PointMake(-xrad, 0);
|
|
|
|
cp[i++] = O2PointMake(-xrad, -ymag);
|
|
cp[i++] = O2PointMake(-xmag, -yrad);
|
|
cp[i++] = O2PointMake(0, -yrad);
|
|
|
|
cp[i++] = O2PointMake(xmag, -yrad);
|
|
cp[i++] = O2PointMake(xrad, -ymag);
|
|
cp[i++] = O2PointMake(xrad, 0);
|
|
|
|
cp[i++] = O2PointMake(xrad, ymag);
|
|
cp[i++] = O2PointMake(xmag, yrad);
|
|
cp[i++] = O2PointMake(0, yrad);
|
|
|
|
cp[i++] = O2PointMake(-xmag, yrad);
|
|
cp[i++] = O2PointMake(-xrad, ymag);
|
|
cp[i++] = O2PointMake(-xrad, 0);
|
|
|
|
for (i = 0; i < 13; i++) {
|
|
cp[i].x += x;
|
|
cp[i].y += y;
|
|
}
|
|
}
|
|
|
|
@implementation O2MutablePath
|
|
|
|
// Bezier and arc to bezier algorithms from: Windows Graphics Programming by
|
|
// Feng Yuan
|
|
#if 0
|
|
static void bezier(O2GState *self,double x1,double y1,double x2, double y2,double x3,double y3,double x4,double y4){
|
|
// Ax+By+C=0 is the line (x1,y1) (x4,y4);
|
|
double A=y4-y1;
|
|
double B=x1-x4;
|
|
double C=y1*(x4-x1)-x1*(y4-y1);
|
|
double AB=A*A+B*B;
|
|
|
|
if((A*x2+B*y2+C)*(A*x2+B*y2+C)<AB &&
|
|
(A*x3+B*y3+C)*(A*x3+B*y3+C)<AB){
|
|
[self moveToPoint:x1:y1];
|
|
[self addLineToPoint:x4:y4];
|
|
return;
|
|
}
|
|
else {
|
|
double x12=x1+x2;
|
|
double y12=y1+y2;
|
|
double x23=x2+x3;
|
|
double y23=y2+y3;
|
|
double x34=x3+x4;
|
|
double y34=y3+y4;
|
|
double x1223=x12+x23;
|
|
double y1223=y12+y23;
|
|
double x2334=x23+x34;
|
|
double y2334=y23+y34;
|
|
double x=x1223+x2334;
|
|
double y=y1223+y2334;
|
|
|
|
bezier(self,x1,y1,x12/2,y12/2,x1223/4,y1223/4,x/8,y/8);
|
|
bezier(self,x/8,y/8,x2334/4,y2334/4,x34/2,y34/2,x4,y4);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
O2MutablePathRef O2PathCreateMutable(void) {
|
|
return [[O2MutablePath allocWithZone: NULL] initWithOperators: NULL
|
|
numberOfElements: 0
|
|
points: NULL
|
|
numberOfPoints: 0];
|
|
}
|
|
|
|
- initWithOperators: (unsigned char *) elements
|
|
numberOfElements: (size_t) numberOfElements
|
|
points: (O2Point *) points
|
|
numberOfPoints: (size_t) numberOfPoints
|
|
{
|
|
O2PathInitWithOperators(self, elements, numberOfElements, points,
|
|
numberOfPoints);
|
|
_capacityOfElements = numberOfElements;
|
|
_capacityOfPoints = numberOfPoints;
|
|
return self;
|
|
}
|
|
|
|
- copyWithZone: (NSZone *) zone {
|
|
return O2PathInitWithOperators([O2Path allocWithZone: zone], _elements,
|
|
_numberOfElements, _points, _numberOfPoints);
|
|
}
|
|
|
|
void O2PathReset(O2MutablePathRef self) {
|
|
self->_numberOfElements = 0;
|
|
self->_numberOfPoints = 0;
|
|
}
|
|
|
|
static inline void expandOperatorCapacity(O2MutablePath *self, size_t delta) {
|
|
if (self->_numberOfElements + delta > self->_capacityOfElements) {
|
|
self->_capacityOfElements = MAX(1, self->_capacityOfElements);
|
|
|
|
while (self->_numberOfElements + delta > self->_capacityOfElements)
|
|
self->_capacityOfElements *= 2;
|
|
|
|
self->_elements =
|
|
NSZoneRealloc(NULL, self->_elements, self->_capacityOfElements);
|
|
}
|
|
}
|
|
|
|
static inline void expandPointCapacity(O2MutablePath *self, size_t delta) {
|
|
if (self->_numberOfPoints + delta > self->_capacityOfPoints) {
|
|
self->_capacityOfPoints = MAX(1, self->_capacityOfPoints);
|
|
|
|
while (self->_numberOfPoints + delta > self->_capacityOfPoints)
|
|
self->_capacityOfPoints *= 2;
|
|
|
|
self->_points = NSZoneRealloc(
|
|
NULL, self->_points, self->_capacityOfPoints * sizeof(O2Point));
|
|
}
|
|
}
|
|
|
|
void O2PathMoveToPoint(O2MutablePathRef self, const O2AffineTransform *matrix,
|
|
O2Float x, O2Float y)
|
|
{
|
|
O2Point point = O2PointMake(x, y);
|
|
|
|
if (matrix != NULL)
|
|
point = O2PointApplyAffineTransform(point, *matrix);
|
|
|
|
expandOperatorCapacity(self, 1);
|
|
expandPointCapacity(self, 1);
|
|
self->_elements[self->_numberOfElements++] = kO2PathElementMoveToPoint;
|
|
self->_points[self->_numberOfPoints++] = point;
|
|
}
|
|
|
|
void O2PathAddLineToPoint(O2MutablePathRef self,
|
|
const O2AffineTransform *matrix, O2Float x, O2Float y)
|
|
{
|
|
O2Point point = O2PointMake(x, y);
|
|
|
|
if (matrix != NULL)
|
|
point = O2PointApplyAffineTransform(point, *matrix);
|
|
|
|
expandOperatorCapacity(self, 1);
|
|
expandPointCapacity(self, 1);
|
|
self->_elements[self->_numberOfElements++] = kO2PathElementAddLineToPoint;
|
|
self->_points[self->_numberOfPoints++] = point;
|
|
}
|
|
|
|
void O2PathAddCurveToPoint(O2MutablePathRef self,
|
|
const O2AffineTransform *matrix, O2Float cp1x,
|
|
O2Float cp1y, O2Float cp2x, O2Float cp2y, O2Float x,
|
|
O2Float y)
|
|
{
|
|
O2Point cp1 = O2PointMake(cp1x, cp1y);
|
|
O2Point cp2 = O2PointMake(cp2x, cp2y);
|
|
O2Point endPoint = O2PointMake(x, y);
|
|
|
|
if (matrix != NULL) {
|
|
cp1 = O2PointApplyAffineTransform(cp1, *matrix);
|
|
cp2 = O2PointApplyAffineTransform(cp2, *matrix);
|
|
endPoint = O2PointApplyAffineTransform(endPoint, *matrix);
|
|
}
|
|
|
|
expandOperatorCapacity(self, 1);
|
|
expandPointCapacity(self, 3);
|
|
self->_elements[self->_numberOfElements++] = kO2PathElementAddCurveToPoint;
|
|
self->_points[self->_numberOfPoints++] = cp1;
|
|
self->_points[self->_numberOfPoints++] = cp2;
|
|
self->_points[self->_numberOfPoints++] = endPoint;
|
|
}
|
|
|
|
void O2PathAddQuadCurveToPoint(O2MutablePathRef self,
|
|
const O2AffineTransform *matrix, O2Float cpx,
|
|
O2Float cpy, O2Float x, O2Float y)
|
|
{
|
|
O2Point cp1 = O2PointMake(cpx, cpy);
|
|
O2Point endPoint = O2PointMake(x, y);
|
|
|
|
if (matrix != NULL) {
|
|
cp1 = O2PointApplyAffineTransform(cp1, *matrix);
|
|
endPoint = O2PointApplyAffineTransform(endPoint, *matrix);
|
|
}
|
|
|
|
expandOperatorCapacity(self, 1);
|
|
expandPointCapacity(self, 2);
|
|
self->_elements[self->_numberOfElements++] =
|
|
kO2PathElementAddQuadCurveToPoint;
|
|
self->_points[self->_numberOfPoints++] = cp1;
|
|
self->_points[self->_numberOfPoints++] = endPoint;
|
|
}
|
|
|
|
void O2PathCloseSubpath(O2MutablePathRef self) {
|
|
expandOperatorCapacity(self, 1);
|
|
self->_elements[self->_numberOfElements++] = kO2PathElementCloseSubpath;
|
|
}
|
|
|
|
void O2PathAddLines(O2MutablePathRef self, const O2AffineTransform *matrix,
|
|
const O2Point *points, size_t count)
|
|
{
|
|
size_t i;
|
|
|
|
if (count == 0)
|
|
return;
|
|
|
|
O2PathMoveToPoint(self, matrix, points[0].x, points[0].y);
|
|
for (i = 1; i < count; i++)
|
|
O2PathAddLineToPoint(self, matrix, points[i].x, points[i].y);
|
|
}
|
|
|
|
void O2PathAddRect(O2MutablePathRef self, const O2AffineTransform *matrix,
|
|
O2Rect rect)
|
|
{
|
|
// The line order is correct per documentation, do not change.
|
|
O2PathMoveToPoint(self, matrix, O2RectGetMinX(rect), O2RectGetMinY(rect));
|
|
O2PathAddLineToPoint(self, matrix, O2RectGetMaxX(rect),
|
|
O2RectGetMinY(rect));
|
|
O2PathAddLineToPoint(self, matrix, O2RectGetMaxX(rect),
|
|
O2RectGetMaxY(rect));
|
|
O2PathAddLineToPoint(self, matrix, O2RectGetMinX(rect),
|
|
O2RectGetMaxY(rect));
|
|
O2PathCloseSubpath(self);
|
|
}
|
|
|
|
void O2PathAddRects(O2MutablePathRef self, const O2AffineTransform *matrix,
|
|
const O2Rect *rects, size_t count)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
O2PathAddRect(self, matrix, rects[i]);
|
|
}
|
|
|
|
void O2PathAddArc(O2MutablePathRef self, const O2AffineTransform *matrix,
|
|
O2Float x, O2Float y, O2Float radius, O2Float startRadian,
|
|
O2Float endRadian, BOOL clockwise)
|
|
{
|
|
startRadian = fmod(startRadian, M_PI * 2);
|
|
endRadian = fmod(endRadian, M_PI * 2);
|
|
|
|
if (clockwise) {
|
|
O2Float tmp = startRadian;
|
|
startRadian = endRadian;
|
|
endRadian = tmp;
|
|
}
|
|
|
|
if (endRadian < startRadian) {
|
|
endRadian += M_PI * 2;
|
|
}
|
|
|
|
O2Float radiusx = radius, radiusy = radius;
|
|
double remainder = ABS(endRadian - startRadian);
|
|
double delta = M_PI_2; // 90 degrees
|
|
size_t i;
|
|
O2Point points[4 * ((int) ceil(remainder / delta) + 1)];
|
|
size_t pointsIndex = 0;
|
|
|
|
for (; remainder > 0; startRadian += delta, remainder -= delta) {
|
|
double sweepangle = (remainder > delta) ? delta : remainder;
|
|
double XY[8];
|
|
double B = sin(sweepangle / 2);
|
|
double C = cos(sweepangle / 2);
|
|
double A = 1 - C;
|
|
double X = A * 4 / 3;
|
|
double Y = B - X * (1 - A) / B;
|
|
double s = sin(startRadian + sweepangle / 2);
|
|
double c = cos(startRadian + sweepangle / 2);
|
|
|
|
XY[0] = C;
|
|
XY[1] = -B;
|
|
XY[2] = C + X;
|
|
XY[3] = -Y;
|
|
XY[4] = C + X;
|
|
XY[5] = Y;
|
|
XY[6] = C;
|
|
XY[7] = B;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
points[pointsIndex].x =
|
|
x + (XY[i * 2] * c - XY[i * 2 + 1] * s) * radiusx;
|
|
points[pointsIndex].y =
|
|
y + (XY[i * 2] * s + XY[i * 2 + 1] * c) * radiusy;
|
|
pointsIndex++;
|
|
}
|
|
}
|
|
|
|
if (clockwise) {
|
|
// just reverse points
|
|
for (i = 0; i < pointsIndex / 2; i++) {
|
|
O2Point tmp;
|
|
|
|
tmp = points[i];
|
|
points[i] = points[(pointsIndex - 1) - i];
|
|
points[(pointsIndex - 1) - i] = tmp;
|
|
}
|
|
}
|
|
|
|
if (pointsIndex > 0) {
|
|
if (O2PathIsEmpty(self)) {
|
|
O2PathMoveToPoint(self, matrix, points[0].x, points[0].y);
|
|
} else {
|
|
O2PathAddLineToPoint(self, matrix, points[0].x, points[0].y);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < pointsIndex; i += 4) {
|
|
O2PathAddCurveToPoint(self, matrix, points[i + 1].x, points[i + 1].y,
|
|
points[i + 2].x, points[i + 2].y, points[i + 3].x,
|
|
points[i + 3].y);
|
|
}
|
|
}
|
|
|
|
void O2PathAddArcToPoint(O2MutablePathRef self, const O2AffineTransform *matrix,
|
|
O2Float tx1, O2Float ty1, O2Float tx2, O2Float ty2,
|
|
O2Float radius)
|
|
{
|
|
#if 0
|
|
// ignores arc and draws a sharp corner
|
|
O2PathAddLineToPoint(self,matrix,tx1,ty1);
|
|
#else
|
|
if (self->_numberOfPoints == 0)
|
|
return;
|
|
|
|
O2Point start = self->_points[self->_numberOfPoints - 1];
|
|
O2Point mid = O2PointMake(tx1, ty1);
|
|
O2Point end = O2PointMake(tx2, ty2);
|
|
|
|
if (matrix != NULL) {
|
|
// If a matrix is specified, either the start point needs to be
|
|
// transformed, or the arc needs to be transformed. Since we really want
|
|
// the result curve transformed to get non-uniform scale/skew results
|
|
// to come out correctly, we just back-transform the start point. But
|
|
// the assumption here is that the start point was transformed using the
|
|
// same matrix as is being passed in, otherwise we have to track the
|
|
// last point and the matrix that goes with it which doesn't seem tidy.
|
|
// Further testing required for this case.
|
|
|
|
start = O2PointApplyAffineTransform(start,
|
|
O2AffineTransformInvert(*matrix));
|
|
}
|
|
|
|
double x1 = start.x - mid.x;
|
|
double y1 = start.y - mid.y;
|
|
double x2 = end.x - mid.x;
|
|
double y2 = end.y - mid.y;
|
|
double n1 = sqrt(x1 * x1 + y1 * y1);
|
|
double n2 = sqrt(x2 * x2 + y2 * y2);
|
|
|
|
O2Float startAngle = acos(x1 / n1);
|
|
if (y1 < 0)
|
|
startAngle = M_PI * 2 - startAngle;
|
|
|
|
O2Float endAngle = acos(x2 / n2);
|
|
if (y2 < 0)
|
|
endAngle = M_PI * 2 - endAngle;
|
|
|
|
O2Float angleBetweenLines = (endAngle - startAngle);
|
|
int clockwise = 1;
|
|
O2Float clockwiseRotate = 0;
|
|
|
|
if (angleBetweenLines < 0)
|
|
angleBetweenLines = M_PI * 2 + angleBetweenLines;
|
|
|
|
CGFloat arcAngle = M_PI - angleBetweenLines;
|
|
|
|
if (angleBetweenLines > M_PI) {
|
|
arcAngle = M_PI * 2 - angleBetweenLines;
|
|
clockwise = 0;
|
|
clockwiseRotate = M_PI;
|
|
}
|
|
|
|
/* The triangle formed by the center of the arc, the midpoint and where the
|
|
radius and tangent line meet
|
|
|
|
Using the law of sines we can figure the other two sides of the triangle
|
|
using the radius and the angles.
|
|
|
|
The radius meets the tangent at 90 degrees.
|
|
The tangent meets the mid->center line at half the angle between the
|
|
tangents The radius meets the mid->center line at 180 - the other angles
|
|
*/
|
|
CGFloat angleOfRadiusAndTangent = M_PI / 2;
|
|
CGFloat angleOfTangentAndLineToCenter = angleBetweenLines / 2.0;
|
|
|
|
CGFloat midToCenterLength = (radius * sin(angleOfRadiusAndTangent)) /
|
|
sin(angleOfTangentAndLineToCenter);
|
|
|
|
CGPoint center;
|
|
center.x = cos(startAngle + angleBetweenLines / 2 + clockwiseRotate) *
|
|
midToCenterLength;
|
|
center.y = sin(startAngle + angleBetweenLines / 2 + clockwiseRotate) *
|
|
midToCenterLength;
|
|
center.x += mid.x;
|
|
center.y += mid.y;
|
|
|
|
CGFloat arcStartAngle;
|
|
CGFloat arcEndAngle;
|
|
|
|
if (clockwise) {
|
|
arcStartAngle = (M_PI * 2 - (M_PI / 2 - startAngle));
|
|
arcEndAngle = (arcStartAngle - arcAngle);
|
|
} else {
|
|
arcStartAngle = (M_PI * 2 - (M_PI / 2 - startAngle)) + clockwiseRotate;
|
|
arcEndAngle = (arcStartAngle - arcAngle) + clockwiseRotate;
|
|
}
|
|
|
|
O2PathAddArc(self, matrix, center.x, center.y, radius, arcStartAngle,
|
|
arcEndAngle, clockwise);
|
|
#endif
|
|
}
|
|
|
|
void O2PathAddEllipseInRect(O2MutablePathRef self,
|
|
const O2AffineTransform *matrix, O2Rect rect)
|
|
{
|
|
O2Float xradius = rect.size.width / 2;
|
|
O2Float yradius = rect.size.height / 2;
|
|
O2Float x = rect.origin.x + xradius;
|
|
O2Float y = rect.origin.y + yradius;
|
|
O2Point cp[13];
|
|
size_t i;
|
|
|
|
O2MutablePathEllipseToBezier(cp, x, y, xradius, yradius);
|
|
|
|
O2PathMoveToPoint(self, matrix, cp[0].x, cp[0].y);
|
|
for (i = 1; i < 13; i += 3)
|
|
O2PathAddCurveToPoint(self, matrix, cp[i].x, cp[i].y, cp[i + 1].x,
|
|
cp[i + 1].y, cp[i + 2].x, cp[i + 2].y);
|
|
O2PathCloseSubpath(self);
|
|
}
|
|
|
|
void O2PathAddPath(O2MutablePathRef self, const O2AffineTransform *matrix,
|
|
O2PathRef path)
|
|
{
|
|
size_t opsCount = O2PathNumberOfElements(path);
|
|
const unsigned char *ops = O2PathElements(path);
|
|
size_t pointCount = O2PathNumberOfPoints(path);
|
|
const O2Point *points = O2PathPoints(path);
|
|
size_t i;
|
|
|
|
expandOperatorCapacity(self, opsCount);
|
|
expandPointCapacity(self, pointCount);
|
|
|
|
for (i = 0; i < opsCount; i++)
|
|
self->_elements[self->_numberOfElements++] = ops[i];
|
|
|
|
if (matrix == NULL) {
|
|
for (i = 0; i < pointCount; i++)
|
|
self->_points[self->_numberOfPoints++] = points[i];
|
|
} else {
|
|
for (i = 0; i < pointCount; i++)
|
|
self->_points[self->_numberOfPoints++] =
|
|
O2PointApplyAffineTransform(points[i], *matrix);
|
|
}
|
|
}
|
|
|
|
void O2PathApplyTransform(O2MutablePathRef self, const O2AffineTransform matrix)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < self->_numberOfPoints; i++)
|
|
self->_points[i] =
|
|
O2PointApplyAffineTransform(self->_points[i], matrix);
|
|
}
|
|
|
|
@end
|