Browse Source

Convergent corner elimination routine added to shape normalization

Viktor Chlumský 4 years ago
parent
commit
80039d7ed6
6 changed files with 91 additions and 18 deletions
  1. 9 5
      core/EdgeHolder.cpp
  2. 28 1
      core/Shape.cpp
  3. 5 0
      core/Shape.h
  4. 30 0
      core/edge-segments.cpp
  5. 9 0
      core/edge-segments.h
  6. 10 12
      core/edge-selectors.cpp

+ 9 - 5
core/EdgeHolder.cpp

@@ -26,16 +26,20 @@ EdgeHolder::~EdgeHolder() {
 }
 
 EdgeHolder & EdgeHolder::operator=(const EdgeHolder &orig) {
-    delete edgeSegment;
-    edgeSegment = orig.edgeSegment ? orig.edgeSegment->clone() : NULL;
+    if (this != &orig) {
+        delete edgeSegment;
+        edgeSegment = orig.edgeSegment ? orig.edgeSegment->clone() : NULL;
+    }
     return *this;
 }
 
 #ifdef MSDFGEN_USE_CPP11
 EdgeHolder & EdgeHolder::operator=(EdgeHolder &&orig) {
-    delete edgeSegment;
-    edgeSegment = orig.edgeSegment;
-    orig.edgeSegment = NULL;
+    if (this != &orig) {
+        delete edgeSegment;
+        edgeSegment = orig.edgeSegment;
+        orig.edgeSegment = NULL;
+    }
     return *this;
 }
 #endif

+ 28 - 1
core/Shape.cpp

@@ -1,6 +1,8 @@
 
 #include "Shape.h"
 
+#include "arithmetics.hpp"
+
 namespace msdfgen {
 
 Shape::Shape() : inverseYAxis(false) { }
@@ -36,8 +38,21 @@ bool Shape::validate() const {
     return true;
 }
 
+static void deconvergeEdge(EdgeHolder &edgeHolder, int param) {
+    {
+        const QuadraticSegment *quadraticSegment = dynamic_cast<const QuadraticSegment *>(&*edgeHolder);
+        if (quadraticSegment)
+            edgeHolder = quadraticSegment->convertToCubic();
+    }
+    {
+        CubicSegment *cubicSegment = dynamic_cast<CubicSegment *>(&*edgeHolder);
+        if (cubicSegment)
+            cubicSegment->deconverge(param, MSDFGEN_DECONVERGENCE_FACTOR);
+    }
+}
+
 void Shape::normalize() {
-    for (std::vector<Contour>::iterator contour = contours.begin(); contour != contours.end(); ++contour)
+    for (std::vector<Contour>::iterator contour = contours.begin(); contour != contours.end(); ++contour) {
         if (contour->edges.size() == 1) {
             EdgeSegment *parts[3] = { };
             contour->edges[0]->splitInThirds(parts[0], parts[1], parts[2]);
@@ -45,7 +60,19 @@ void Shape::normalize() {
             contour->edges.push_back(EdgeHolder(parts[0]));
             contour->edges.push_back(EdgeHolder(parts[1]));
             contour->edges.push_back(EdgeHolder(parts[2]));
+        } else {
+            EdgeHolder *prevEdge = &contour->edges.back();
+            for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
+                Vector2 prevDir = (*prevEdge)->direction(1).normalize();
+                Vector2 curDir = (*edge)->direction(0).normalize();
+                if (dotProduct(prevDir, curDir) < MSDFGEN_CORNER_DOT_EPSILON-1) {
+                    deconvergeEdge(*prevEdge, 1);
+                    deconvergeEdge(*edge, 0);
+                }
+                prevEdge = &*edge;
+            }
         }
+    }
 }
 
 void Shape::bound(double &l, double &b, double &r, double &t) const {

+ 5 - 0
core/Shape.h

@@ -7,6 +7,11 @@
 
 namespace msdfgen {
 
+// Threshold of the dot product of adjacent edge directions to be considered convergent.
+#define MSDFGEN_CORNER_DOT_EPSILON .000001
+// The proportional amount by which a curve's control point will be adjusted to eliminate convergent corners.
+#define MSDFGEN_DECONVERGENCE_FACTOR .000001
+
 /// Vector shape representation.
 class Shape {
 

+ 30 - 0
core/edge-segments.cpp

@@ -97,6 +97,18 @@ Vector2 CubicSegment::direction(double param) const {
     return tangent;
 }
 
+Vector2 LinearSegment::directionChange(double param) const {
+    return Vector2();
+}
+
+Vector2 QuadraticSegment::directionChange(double param) const {
+    return (p[2]-p[1])-(p[1]-p[0]);
+}
+
+Vector2 CubicSegment::directionChange(double param) const {
+    return mix((p[2]-p[1])-(p[1]-p[0]), (p[3]-p[2])-(p[2]-p[1]), param);
+}
+
 SignedDistance LinearSegment::signedDistance(Point2 origin, double &param) const {
     Vector2 aq = origin-p[0];
     Vector2 ab = p[1]-p[0];
@@ -426,4 +438,22 @@ void CubicSegment::splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeS
     part3 = new CubicSegment(point(2/3.), mix(mix(p[1], p[2], 2/3.), mix(p[2], p[3], 2/3.), 2/3.), p[2] == p[3] ? p[3] : mix(p[2], p[3], 2/3.), p[3], color);
 }
 
+EdgeSegment * QuadraticSegment::convertToCubic() const {
+    return new CubicSegment(p[0], mix(p[0], p[1], 2/3.), mix(p[1], p[2], 1/3.), p[2], color);
+}
+
+void CubicSegment::deconverge(int param, double amount) {
+    Vector2 dir = direction(param);
+    Vector2 normal = dir.getOrthonormal();
+    double h = dotProduct(directionChange(param)-dir, normal);
+    switch (param) {
+        case 0:
+            p[1] += amount*(dir+sign(h)*sqrt(fabs(h))*normal);
+            break;
+        case 1:
+            p[2] -= amount*(dir-sign(h)*sqrt(fabs(h))*normal);
+            break;
+    }
+}
+
 }

+ 9 - 0
core/edge-segments.h

@@ -25,6 +25,8 @@ public:
     virtual Point2 point(double param) const = 0;
     /// Returns the direction the edge has at the point specified by the parameter.
     virtual Vector2 direction(double param) const = 0;
+    /// Returns the change of direction (second derivative) at the point specified by the parameter.
+    virtual Vector2 directionChange(double param) const = 0;
     /// Returns the minimum signed distance between origin and the edge.
     virtual SignedDistance signedDistance(Point2 origin, double &param) const = 0;
     /// Converts a previously retrieved signed distance from origin to pseudo-distance.
@@ -53,6 +55,7 @@ public:
     LinearSegment * clone() const;
     Point2 point(double param) const;
     Vector2 direction(double param) const;
+    Vector2 directionChange(double param) const;
     SignedDistance signedDistance(Point2 origin, double &param) const;
     int scanlineIntersections(double x[3], int dy[3], double y) const;
     void bound(double &l, double &b, double &r, double &t) const;
@@ -73,6 +76,7 @@ public:
     QuadraticSegment * clone() const;
     Point2 point(double param) const;
     Vector2 direction(double param) const;
+    Vector2 directionChange(double param) const;
     SignedDistance signedDistance(Point2 origin, double &param) const;
     int scanlineIntersections(double x[3], int dy[3], double y) const;
     void bound(double &l, double &b, double &r, double &t) const;
@@ -81,6 +85,8 @@ public:
     void moveEndPoint(Point2 to);
     void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
 
+    EdgeSegment * convertToCubic() const;
+
 };
 
 /// A cubic Bezier curve.
@@ -93,6 +99,7 @@ public:
     CubicSegment * clone() const;
     Point2 point(double param) const;
     Vector2 direction(double param) const;
+    Vector2 directionChange(double param) const;
     SignedDistance signedDistance(Point2 origin, double &param) const;
     int scanlineIntersections(double x[3], int dy[3], double y) const;
     void bound(double &l, double &b, double &r, double &t) const;
@@ -101,6 +108,8 @@ public:
     void moveEndPoint(Point2 to);
     void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
 
+    void deconverge(int param, double amount);
+
 };
 
 }

+ 10 - 12
core/edge-selectors.cpp

@@ -38,19 +38,17 @@ TrueDistanceSelector::DistanceType TrueDistanceSelector::distance() const {
 
 PseudoDistanceSelectorBase::EdgeCache::EdgeCache() : absDistance(0), edgeDomainDistance(0), pseudoDistance(0) { }
 
+static double cornerEdgeDomainDistance(const EdgeSegment *a, const EdgeSegment *b, const Point2 &p) {
+    Vector2 aDir = a->direction(1).normalize(true);
+    Vector2 bDir = b->direction(0).normalize(true);
+    return dotProduct(p-b->point(0), (aDir+bDir).normalize(true));
+}
+
 double PseudoDistanceSelectorBase::edgeDomainDistance(const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge, const Point2 &p, double param) {
-    if (param < 0) {
-        Vector2 prevEdgeDir = -prevEdge->direction(1).normalize(true);
-        Vector2 edgeDir = edge->direction(0).normalize(true);
-        Vector2 pointDir = p-edge->point(0);
-        return dotProduct(pointDir, (prevEdgeDir-edgeDir).normalize(true));
-    }
-    if (param > 1) {
-        Vector2 edgeDir = -edge->direction(1).normalize(true);
-        Vector2 nextEdgeDir = nextEdge->direction(0).normalize(true);
-        Vector2 pointDir = p-edge->point(1);
-        return dotProduct(pointDir, (nextEdgeDir-edgeDir).normalize(true));
-    }
+    if (param < 0)
+        return -cornerEdgeDomainDistance(prevEdge, edge, p);
+    else if (param > 1)
+        return cornerEdgeDomainDistance(edge, nextEdge, p);
     return 0;
 }