Merge commit '46a7c09aea05110c951507891caed178d869a672'

* commit '46a7c09aea05110c951507891caed178d869a672':
  extracted rad and degree conversion
  added origin rotation to vector
  fixed arc rotation problems
  added arc handling to SVG importer
  added rotation to vector
This commit is contained in:
Christopher Kohnert 2017-05-31 13:46:54 -07:00
commit cba7d65285
3 changed files with 177 additions and 1 deletions

View File

@ -49,6 +49,23 @@ Vector2 Vector2::project(const Vector2 &vector, bool positive) const {
return t*n;
}
Vector2 Vector2::rotateAround(const Vector2 &center, double angleDegree) const {
Vector2 result = *this - center;
double s = std::sin(angleDegree * M_PI / 180);
double c = std::cos(angleDegree * M_PI / 180);
result.x = result.x * c - result.y * s;
result.y = result.x * s + result.y * c;
return result + center;
}
Vector2 Vector2::rotate(double angleDegree) const {
double s = std::sin(angleDegree * M_PI / 180);
double c = std::cos(angleDegree * M_PI / 180);
double newX = x * c - y * s;
double newY = x * s + y * c;
return Vector2(newX, newY);
}
Vector2::operator const void*() const {
return x || y ? this : NULL;
}

View File

@ -35,6 +35,10 @@ struct Vector2 {
Vector2 getOrthonormal(bool polarity = true, bool allowZero = false) const;
/// Returns a vector projected along this one.
Vector2 project(const Vector2 &vector, bool positive = false) const;
/// Returns a vector that is rotated by the angle around a center
Vector2 rotateAround(const Vector2 &center, double angleDegree) const;
/// Returns a vector that is rotated by the angle around the origin
Vector2 rotate(double angleDegree) const;
operator const void *() const;
bool operator!() const;
bool operator==(const Vector2 &other) const;

View File

@ -52,6 +52,17 @@ static bool readDouble(double &output, const char *&pathDef) {
return false;
}
static bool readBool(bool &output, const char *&pathDef) {
int shift;
int v;
if (sscanf(pathDef, "%d%n", &v, &shift) == 1) {
pathDef += shift;
output = static_cast<bool>(v);
return true;
}
return false;
}
static void consumeOptionalComma(const char *&pathDef) {
while (*pathDef == ' ')
++pathDef;
@ -59,6 +70,148 @@ static void consumeOptionalComma(const char *&pathDef) {
++pathDef;
}
static double toDegrees(double rad) {
return rad * 180.0 / M_PI;
}
static double toRad(double degrees) {
return degrees * M_PI / 180.0;
}
static Point2 handleArc(const char *&pathDef, char nodeType, Point2 prevNode, Contour &contour) {
Point2 arcEnd;
double radiusX = 0;
double radiusY = 0;
double xAxisRotation = 0;
bool largeArc = false;
bool sweep = false;
// arc syntax
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
// a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy
REQUIRE(readDouble(radiusX, pathDef));
radiusX = std::abs(radiusX);
consumeOptionalComma(pathDef);
REQUIRE(readDouble(radiusY, pathDef));
radiusY = std::abs(radiusY);
consumeOptionalComma(pathDef);
REQUIRE(readDouble(xAxisRotation, pathDef));
consumeOptionalComma(pathDef);
REQUIRE(readBool(largeArc, pathDef));
consumeOptionalComma(pathDef);
REQUIRE(readBool(sweep, pathDef));
consumeOptionalComma(pathDef);
REQUIRE(readCoord(arcEnd, pathDef));
if (nodeType == 'a') {
arcEnd += prevNode;
}
// Zero arc radius results in a straight line
if (radiusX == 0 || radiusY == 0) {
contour.addEdge(new LinearSegment(prevNode, arcEnd));
return arcEnd;
}
if (prevNode.x == arcEnd.x && prevNode.y == arcEnd.y) {
// End point matches previous node,
// which means it has a length of zero and can be ignored
return prevNode;
}
double xAxisRotationRad = toRad(std::fmod(xAxisRotation, 360.0));
double cosRotation = std::cos(xAxisRotationRad);
double sinRotation = std::sin(xAxisRotationRad);
Point2 mid = (prevNode - arcEnd) / 2.0;
// Compute transformed start point
double x1 = (cosRotation * mid.x + sinRotation * mid.y);
double y1 = (-sinRotation * mid.x + cosRotation * mid.y);
double radXsq = std::pow(radiusX, 2);
double radYsq = std::pow(radiusY, 2);
double x1sq = std::pow(x1, 2);
double y1sq = std::pow(y1, 2);
// Scale the radius if it is not big enough
double radiiCheck = x1sq / radXsq + y1sq / radYsq;
if (radiiCheck > 1) {
radiusX = std::sqrt(radiiCheck) * radiusX;
radiusY = std::sqrt(radiiCheck) * radiusY;
radXsq = std::pow(radiusX, 2);
radYsq = std::pow(radiusY, 2);
}
// Compute centre point
double sign = (largeArc == sweep) ? -1 : 1;
double sq = ((radXsq * radYsq) - (radXsq * y1sq) - (radYsq * x1sq)) / ((radXsq * y1sq) + (radYsq * x1sq));
sq = (sq < 0) ? 0 : sq;
double coefficient = sign * std::sqrt(sq);
double cx1 = coefficient * ((radiusX * y1) / radiusY);
double cy1 = coefficient * -((radiusY * x1) / radiusX);
Point2 sx = (prevNode + arcEnd) / 2.0;
double cx = sx.x + (cosRotation * cx1 - sinRotation * cy1);
double cy = sx.y + (sinRotation * cx1 + cosRotation * cy1);
Point2 center(cx, cy);
// Compute the angle start
double ux = (x1 - cx1) / radiusX;
double uy = (y1 - cy1) / radiusY;
double n = std::sqrt((ux * ux) + (uy * uy));
double p = ux;
sign = (uy < 0) ? -1.0 : 1.0;
double angleStart = toDegrees(sign * std::acos(p / n));
// Compute the angle extent
double vx = (-x1 - cx1) / radiusX;
double vy = (-y1 - cy1) / radiusY;
n = std::sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
p = ux * vx + uy * vy;
sign = (ux * vy - uy * vx < 0) ? -1.0 : 1.0;
double angleExtent = toDegrees(sign * std::acos(p / n));
if (!sweep && angleExtent > 0) {
angleExtent -= 360;
} else if (sweep && angleExtent < 0) {
angleExtent += 360;
}
// generate bezier curves for a unit circle that covers the given arc angles
int segmentCount = static_cast<int>(std::ceil(std::abs(angleExtent) / 90.0));
angleStart = toRad(std::fmod(angleStart, 360.0));
angleExtent = toRad(std::fmod(angleExtent, 360.0));
double angleIncrement = angleExtent / segmentCount;
double controlLength = 4.0 / 3.0 * std::sin(angleIncrement / 2.0) / (1.0 + std::cos(angleIncrement / 2.0));
Point2 startPoint = prevNode;
Point2 bezEndpoint;
Point2 controlPoint[2];
for (int i = 0; i < segmentCount; i++) {
double angle = angleStart + i * angleIncrement;
double dx = std::cos(angle);
double dy = std::sin(angle);
controlPoint[0].x = (dx - controlLength * dy) * radiusX;
controlPoint[0].y = (dy + controlLength * dx) * radiusY;
controlPoint[0] = controlPoint[0].rotate(xAxisRotation) + center;
angle += angleIncrement;
dx = std::cos(angle);
dy = std::sin(angle);
controlPoint[1].x = (dx + controlLength * dy) * radiusX;
controlPoint[1].y = (dy - controlLength * dx) * radiusY;
controlPoint[1] = controlPoint[1].rotate(xAxisRotation) + center;
bezEndpoint.x = dx * radiusX;
bezEndpoint.y = dy * radiusY;
bezEndpoint = bezEndpoint.rotate(xAxisRotation);
bezEndpoint = bezEndpoint + center;
if (i == segmentCount - 1) {
// to prevent rounding errors
bezEndpoint = arcEnd;
}
contour.addEdge(new CubicSegment(startPoint, controlPoint[0], controlPoint[1], bezEndpoint));
startPoint = bezEndpoint;
}
return arcEnd;
}
static bool buildFromPath(Shape &shape, const char *pathDef) {
char nodeType;
Point2 prevNode(0, 0);
@ -138,7 +291,9 @@ static bool buildFromPath(Shape &shape, const char *pathDef) {
}
contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
break;
// TODO A, a
case 'A': case 'a':
node = handleArc(pathDef, nodeType, prevNode, contour);
break;
default:
REQUIRE(false);
}