From 08173443c5bbdf27d1dfb0236f67c4de4aefb567 Mon Sep 17 00:00:00 2001 From: Michael Galetzka Date: Wed, 15 Feb 2017 12:32:55 +0100 Subject: [PATCH 1/5] added rotation to vector --- core/Vector2.cpp | 9 +++++++++ core/Vector2.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/core/Vector2.cpp b/core/Vector2.cpp index 896963f..eca6435 100644 --- a/core/Vector2.cpp +++ b/core/Vector2.cpp @@ -49,6 +49,15 @@ Vector2 Vector2::project(const Vector2 &vector, bool positive) const { return t*n; } +Vector2 Vector2::rotateAround(const Vector2 ¢er, 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::operator const void*() const { return x || y ? this : NULL; } diff --git a/core/Vector2.h b/core/Vector2.h index 47ca637..1ebfb75 100644 --- a/core/Vector2.h +++ b/core/Vector2.h @@ -33,6 +33,8 @@ 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 ¢er, double angleDegree) const; operator const void *() const; bool operator!() const; bool operator==(const Vector2 &other) const; From 376064b0ded8479479615ac4927c5acd6cddb9c7 Mon Sep 17 00:00:00 2001 From: Michael Galetzka Date: Wed, 15 Feb 2017 12:33:32 +0100 Subject: [PATCH 2/5] added arc handling to SVG importer --- ext/import-svg.cpp | 163 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/ext/import-svg.cpp b/ext/import-svg.cpp index 6ae3134..86229da 100644 --- a/ext/import-svg.cpp +++ b/ext/import-svg.cpp @@ -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(v); + return true; + } + return false; +} + static void consumeOptionalComma(const char *&pathDef) { while (*pathDef == ' ') ++pathDef; @@ -59,6 +70,154 @@ static void consumeOptionalComma(const char *&pathDef) { ++pathDef; } +static double toDegrees(double rad) { + return rad * 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 = std::fmod(xAxisRotation, 360.0) * M_PI / 180; + 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(std::ceil(std::abs(angleExtent) / 90.0)); + angleStart = std::fmod(angleStart, 360.0) * 180 / M_PI; + angleExtent = std::fmod(angleExtent, 360.0) * 180 / M_PI; + 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 maxCoord(std::max(arcEnd.x, prevNode.x), std::max(arcEnd.y, prevNode.y)); + Point2 minCoord(std::min(arcEnd.x, prevNode.x), std::min(arcEnd.y, prevNode.y)); + Point2 unit = maxCoord - minCoord; + 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) * unit.x; + controlPoint[0].y = (dy + controlLength * dx) * unit.y; + angle += angleIncrement; + dx = std::cos(angle); + dy = std::sin(angle); + controlPoint[1].x = (dx + controlLength * dy) * unit.x; + controlPoint[1].y = (dy - controlLength * dx) * unit.y; + if (i == segmentCount - 1) { + // to prevent rounding errors + bezEndpoint = arcEnd; + } else { + bezEndpoint.x = dx; + bezEndpoint.y = dy; + } + contour.addEdge(new CubicSegment(startPoint, controlPoint[0] + center, controlPoint[1] + center, bezEndpoint)); + // rotated + // 45.371329,19.126326 + // 40.339192,21.715938 + // 28.431021,18.144102 + // 18.311195,8.2382273 + + // not rotated + // 53.27246,0.13737911 + // 51.581384,5.5381897 + // 40.674998,11.505877 + // 26.516017,11.751778 + startPoint = bezEndpoint; + } + + return arcEnd; +} + static bool buildFromPath(Shape &shape, const char *pathDef) { char nodeType; Point2 prevNode(0, 0); @@ -138,7 +297,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); } From 869084420b49fdd54e26421ba53297cc5f6ba4d6 Mon Sep 17 00:00:00 2001 From: Michael Galetzka Date: Wed, 15 Feb 2017 12:55:24 +0100 Subject: [PATCH 3/5] fixed arc rotation problems --- ext/import-svg.cpp | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/ext/import-svg.cpp b/ext/import-svg.cpp index 86229da..d8d954f 100644 --- a/ext/import-svg.cpp +++ b/ext/import-svg.cpp @@ -177,41 +177,31 @@ static Point2 handleArc(const char *&pathDef, char nodeType, Point2 prevNode, Co double controlLength = 4.0 / 3.0 * std::sin(angleIncrement / 2.0) / (1.0 + std::cos(angleIncrement / 2.0)); Point2 startPoint = prevNode; - Point2 maxCoord(std::max(arcEnd.x, prevNode.x), std::max(arcEnd.y, prevNode.y)); - Point2 minCoord(std::min(arcEnd.x, prevNode.x), std::min(arcEnd.y, prevNode.y)); - Point2 unit = maxCoord - minCoord; 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) * unit.x; - controlPoint[0].y = (dy + controlLength * dx) * unit.y; + 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) * unit.x; - controlPoint[1].y = (dy - controlLength * dx) * unit.y; + 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; - } else { - bezEndpoint.x = dx; - bezEndpoint.y = dy; } - contour.addEdge(new CubicSegment(startPoint, controlPoint[0] + center, controlPoint[1] + center, bezEndpoint)); - // rotated - // 45.371329,19.126326 - // 40.339192,21.715938 - // 28.431021,18.144102 - // 18.311195,8.2382273 - // not rotated - // 53.27246,0.13737911 - // 51.581384,5.5381897 - // 40.674998,11.505877 - // 26.516017,11.751778 + contour.addEdge(new CubicSegment(startPoint, controlPoint[0], controlPoint[1], bezEndpoint)); startPoint = bezEndpoint; } From 2c1df9430c622fbd294cd5eb3b4b7037ff276be0 Mon Sep 17 00:00:00 2001 From: Michael Galetzka Date: Wed, 15 Feb 2017 12:57:03 +0100 Subject: [PATCH 4/5] added origin rotation to vector --- core/Vector2.cpp | 8 ++++++++ core/Vector2.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/core/Vector2.cpp b/core/Vector2.cpp index eca6435..900f718 100644 --- a/core/Vector2.cpp +++ b/core/Vector2.cpp @@ -58,6 +58,14 @@ Vector2 Vector2::rotateAround(const Vector2 ¢er, double angleDegree) const { 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; } diff --git a/core/Vector2.h b/core/Vector2.h index 1ebfb75..67e7e16 100644 --- a/core/Vector2.h +++ b/core/Vector2.h @@ -35,6 +35,8 @@ struct Vector2 { 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 ¢er, 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; From 46a7c09aea05110c951507891caed178d869a672 Mon Sep 17 00:00:00 2001 From: Michael Galetzka Date: Wed, 15 Feb 2017 13:02:28 +0100 Subject: [PATCH 5/5] extracted rad and degree conversion --- ext/import-svg.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ext/import-svg.cpp b/ext/import-svg.cpp index d8d954f..8738825 100644 --- a/ext/import-svg.cpp +++ b/ext/import-svg.cpp @@ -71,7 +71,11 @@ static void consumeOptionalComma(const char *&pathDef) { } static double toDegrees(double rad) { - return rad * M_PI / 180.0; + 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) { @@ -114,7 +118,7 @@ static Point2 handleArc(const char *&pathDef, char nodeType, Point2 prevNode, Co return prevNode; } - double xAxisRotationRad = std::fmod(xAxisRotation, 360.0) * M_PI / 180; + double xAxisRotationRad = toRad(std::fmod(xAxisRotation, 360.0)); double cosRotation = std::cos(xAxisRotationRad); double sinRotation = std::sin(xAxisRotationRad); Point2 mid = (prevNode - arcEnd) / 2.0; @@ -171,8 +175,8 @@ static Point2 handleArc(const char *&pathDef, char nodeType, Point2 prevNode, Co // generate bezier curves for a unit circle that covers the given arc angles int segmentCount = static_cast(std::ceil(std::abs(angleExtent) / 90.0)); - angleStart = std::fmod(angleStart, 360.0) * 180 / M_PI; - angleExtent = std::fmod(angleExtent, 360.0) * 180 / M_PI; + 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));