Browse Source

Added algorithm to fix contour windings, low level optimization

Viktor Chlumský 4 năm trước cách đây
mục cha
commit
fe910c8b5a
8 tập tin đã thay đổi với 117 bổ sung12 xóa
  1. 7 0
      core/Contour.cpp
  2. 2 0
      core/Contour.h
  3. 6 0
      core/EdgeHolder.cpp
  4. 3 0
      core/EdgeHolder.h
  5. 55 0
      core/Shape.cpp
  6. 2 0
      core/Shape.h
  7. 37 12
      core/edge-segments.cpp
  8. 5 0
      core/edge-segments.h

+ 7 - 0
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<EdgeHolder>::iterator edge = edges.begin(); edge != edges.end(); ++edge)
+        (*edge)->reverse();
+}
+
 }

+ 2 - 0
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();
 
 };
 

+ 6 - 0
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) { }

+ 3 - 0
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);

+ 55 - 0
core/Shape.cpp

@@ -1,6 +1,7 @@
 
 #include "Shape.h"
 
+#include <algorithm>
 #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<const Intersection *>(a)->x-reinterpret_cast<const Intersection *>(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<int> orientations(contours.size());
+    std::vector<Intersection> 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<EdgeHolder>::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge)
+                y1 = (*edge)->point(1).y;
+            for (std::vector<EdgeHolder>::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<EdgeHolder>::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();
+}
+
 }

+ 2 - 0
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();
 
 };
 

+ 37 - 12
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 &param) 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 &param) 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 &param) 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;
 }

+ 5 - 0
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;