diff --git a/core/Contour.cpp b/core/Contour.cpp index ae0a7c3..ca80d3c 100644 --- a/core/Contour.cpp +++ b/core/Contour.cpp @@ -80,4 +80,11 @@ int Contour::winding() const { return sign(total); } +void Contour::reverse() { + for (int i = (int) edges.size()/2; i > 0; --i) + EdgeHolder::swap(edges[i-1], edges[edges.size()-i]); + for (std::vector::iterator edge = edges.begin(); edge != edges.end(); ++edge) + (*edge)->reverse(); +} + } diff --git a/core/Contour.h b/core/Contour.h index c0b82b4..f79b269 100644 --- a/core/Contour.h +++ b/core/Contour.h @@ -26,6 +26,8 @@ public: void boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const; /// Computes the winding of the contour. Returns 1 if positive, -1 if negative. int winding() const; + /// Reverses the sequence of edges on the contour. + void reverse(); }; diff --git a/core/EdgeHolder.cpp b/core/EdgeHolder.cpp index 3a0157a..1a8c5f6 100644 --- a/core/EdgeHolder.cpp +++ b/core/EdgeHolder.cpp @@ -3,6 +3,12 @@ namespace msdfgen { +void EdgeHolder::swap(EdgeHolder &a, EdgeHolder &b) { + EdgeSegment *tmp = a.edgeSegment; + a.edgeSegment = b.edgeSegment; + b.edgeSegment = tmp; +} + EdgeHolder::EdgeHolder() : edgeSegment(NULL) { } EdgeHolder::EdgeHolder(EdgeSegment *segment) : edgeSegment(segment) { } diff --git a/core/EdgeHolder.h b/core/EdgeHolder.h index 427de02..c4c5be7 100644 --- a/core/EdgeHolder.h +++ b/core/EdgeHolder.h @@ -9,6 +9,9 @@ namespace msdfgen { class EdgeHolder { public: + /// Swaps the edges held by a and b. + static void swap(EdgeHolder &a, EdgeHolder &b); + EdgeHolder(); EdgeHolder(EdgeSegment *segment); EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE); diff --git a/core/Shape.cpp b/core/Shape.cpp index cddb622..8d6f47c 100644 --- a/core/Shape.cpp +++ b/core/Shape.cpp @@ -1,6 +1,7 @@ #include "Shape.h" +#include #include "arithmetics.hpp" namespace msdfgen { @@ -125,4 +126,58 @@ int Shape::edgeCount() const { return total; } +void Shape::orientContours() { + struct Intersection { + double x; + int direction; + int contourIndex; + + static int compare(const void *a, const void *b) { + return sign(reinterpret_cast(a)->x-reinterpret_cast(b)->x); + } + }; + + const double ratio = .5*(sqrt(5)-1); // an irrational number to minimize chance of intersecting a corner or other point of interest + std::vector orientations(contours.size()); + std::vector intersections; + for (int i = 0; i < (int) contours.size(); ++i) { + if (!orientations[i] && !contours[i].edges.empty()) { + // Find an Y that crosses the contour + double y0 = contours[i].edges.front()->point(0).y; + double y1 = y0; + for (std::vector::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge) + y1 = (*edge)->point(1).y; + for (std::vector::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge) + y1 = (*edge)->point(ratio).y; // in case all endpoints are in a horizontal line + double y = mix(y0, y1, ratio); + // Scanline through whole shape at Y + double x[3]; + int dy[3]; + for (int j = 0; j < (int) contours.size(); ++j) { + for (std::vector::const_iterator edge = contours[j].edges.begin(); edge != contours[j].edges.end(); ++edge) { + int n = (*edge)->scanlineIntersections(x, dy, y); + for (int k = 0; k < n; ++k) { + Intersection intersection = { x[k], dy[k], j }; + intersections.push_back(intersection); + } + } + } + qsort(&intersections[0], intersections.size(), sizeof(Intersection), &Intersection::compare); + // Disqualify multiple intersections + for (int j = 1; j < (int) intersections.size(); ++j) + if (intersections[j].x == intersections[j-1].x) + intersections[j].direction = intersections[j-1].direction = 0; + // Inspect scanline and deduce orientations of intersected contours + for (int j = 0; j < (int) intersections.size(); ++j) + if (intersections[j].direction) + orientations[intersections[j].contourIndex] += 2*((j&1)^(intersections[j].direction > 0))-1; + intersections.clear(); + } + } + // Reverse contours that have the opposite orientation + for (int i = 0; i < (int) contours.size(); ++i) + if (orientations[i] < 0) + contours[i].reverse(); +} + } diff --git a/core/Shape.h b/core/Shape.h index 45535ce..7539921 100644 --- a/core/Shape.h +++ b/core/Shape.h @@ -47,6 +47,8 @@ public: void scanline(Scanline &line, double y) const; /// Returns the total number of edge segments int edgeCount() const; + /// Assumes its contours are unoriented (even-odd fill rule). Attempts to orient them to conform to the non-zero winding rule. + void orientContours(); }; diff --git a/core/edge-segments.cpp b/core/edge-segments.cpp index f99dc9d..26c1b3f 100644 --- a/core/edge-segments.cpp +++ b/core/edge-segments.cpp @@ -46,6 +46,10 @@ QuadraticSegment::QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor ed } CubicSegment::CubicSegment(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : EdgeSegment(edgeColor) { + if ((p1 == p0 || p1 == p3) && (p2 == p0 || p2 == p3)) { + p1 = mix(p0, p3, 1/3.); + p2 = mix(p0, p3, 2/3.); + } p[0] = p0; p[1] = p1; p[2] = p2; @@ -139,18 +143,18 @@ SignedDistance QuadraticSegment::signedDistance(Point2 origin, double ¶m) co param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir); { epDir = direction(1); - double distance = nonZeroSign(crossProduct(epDir, p[2]-origin))*(p[2]-origin).length(); // distance from B - if (fabs(distance) < fabs(minDistance)) { - minDistance = distance; + double distance = (p[2]-origin).length(); // distance from B + if (distance < fabs(minDistance)) { + minDistance = nonZeroSign(crossProduct(epDir, p[2]-origin))*distance; param = dotProduct(origin-p[1], epDir)/dotProduct(epDir, epDir); } } for (int i = 0; i < solutions; ++i) { if (t[i] > 0 && t[i] < 1) { Point2 qe = p[0]+2*t[i]*ab+t[i]*t[i]*br-origin; - double distance = nonZeroSign(crossProduct(direction(t[i]), qe))*qe.length(); - if (fabs(distance) <= fabs(minDistance)) { - minDistance = distance; + double distance = qe.length(); + if (distance <= fabs(minDistance)) { + minDistance = nonZeroSign(crossProduct(direction(t[i]), qe))*distance; param = t[i]; } } @@ -175,9 +179,9 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir); { epDir = direction(1); - double distance = nonZeroSign(crossProduct(epDir, p[3]-origin))*(p[3]-origin).length(); // distance from B - if (fabs(distance) < fabs(minDistance)) { - minDistance = distance; + double distance = (p[3]-origin).length(); // distance from B + if (distance < fabs(minDistance)) { + minDistance = nonZeroSign(crossProduct(epDir, p[3]-origin))*distance; param = dotProduct(epDir-(p[3]-origin), epDir)/dotProduct(epDir, epDir); } } @@ -193,9 +197,9 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const if (t <= 0 || t >= 1) break; qe = qa+3*t*ab+3*t*t*br+t*t*t*as; - double distance = nonZeroSign(crossProduct(direction(t), qe))*qe.length(); - if (fabs(distance) < fabs(minDistance)) { - minDistance = distance; + double distance = qe.length(); + if (distance < fabs(minDistance)) { + minDistance = nonZeroSign(crossProduct(direction(t), qe))*distance; param = t; } } @@ -381,6 +385,27 @@ void CubicSegment::bound(double &l, double &b, double &r, double &t) const { pointBounds(point(params[i]), l, b, r, t); } +void LinearSegment::reverse() { + Point2 tmp = p[0]; + p[0] = p[1]; + p[1] = tmp; +} + +void QuadraticSegment::reverse() { + Point2 tmp = p[0]; + p[0] = p[2]; + p[2] = tmp; +} + +void CubicSegment::reverse() { + Point2 tmp = p[0]; + p[0] = p[3]; + p[3] = tmp; + tmp = p[1]; + p[1] = p[2]; + p[2] = tmp; +} + void LinearSegment::moveStartPoint(Point2 to) { p[0] = to; } diff --git a/core/edge-segments.h b/core/edge-segments.h index 6eca0f1..9ec3cbc 100644 --- a/core/edge-segments.h +++ b/core/edge-segments.h @@ -36,6 +36,8 @@ public: /// Adjusts the bounding box to fit the edge segment. virtual void bound(double &l, double &b, double &r, double &t) const = 0; + /// Reverses the edge (swaps its start point and end point). + virtual void reverse() = 0; /// Moves the start point of the edge segment. virtual void moveStartPoint(Point2 to) = 0; /// Moves the end point of the edge segment. @@ -60,6 +62,7 @@ public: int scanlineIntersections(double x[3], int dy[3], double y) const; void bound(double &l, double &b, double &r, double &t) const; + void reverse(); void moveStartPoint(Point2 to); void moveEndPoint(Point2 to); void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const; @@ -81,6 +84,7 @@ public: int scanlineIntersections(double x[3], int dy[3], double y) const; void bound(double &l, double &b, double &r, double &t) const; + void reverse(); void moveStartPoint(Point2 to); void moveEndPoint(Point2 to); void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const; @@ -104,6 +108,7 @@ public: int scanlineIntersections(double x[3], int dy[3], double y) const; void bound(double &l, double &b, double &r, double &t) const; + void reverse(); void moveStartPoint(Point2 to); void moveEndPoint(Point2 to); void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;