Prechádzať zdrojové kódy

Experimental method to resolve convergent corners (WIP)

Viktor Chlumský 5 rokov pred
rodič
commit
0356d48930
4 zmenil súbory, kde vykonal 158 pridanie a 13 odobranie
  1. 20 1
      core/Shape.cpp
  2. 95 0
      core/edge-segments.cpp
  3. 28 0
      core/edge-segments.h
  4. 15 12
      core/edge-selectors.cpp

+ 20 - 1
core/Shape.cpp

@@ -1,6 +1,8 @@
 
 #include "Shape.h"
 
+#include "arithmetics.hpp"
+
 namespace msdfgen {
 
 Shape::Shape() : inverseYAxis(false) { }
@@ -37,7 +39,7 @@ bool Shape::validate() const {
 }
 
 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 +47,24 @@ 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) {
+                    Vector2 prevTendency = (*prevEdge)->directionTendency(1, 1);
+                    Vector2 curTendency = (*edge)->directionTendency(0, -1);
+                    int winding = nonZeroSign(crossProduct(prevTendency, curTendency));
+                    EdgeSegment *newEdge = (*prevEdge)->makeDivergent(0, winding);
+                    if (newEdge) *prevEdge = newEdge;
+                    newEdge = (*edge)->makeDivergent(winding, 0);
+                    if (newEdge) *edge = newEdge;
+                }
+                prevEdge = &*edge;
+            }
         }
+    }
 }
 
 void Shape::bound(double &l, double &b, double &r, double &t) const {

+ 95 - 0
core/edge-segments.cpp

@@ -6,6 +6,13 @@
 
 namespace msdfgen {
 
+Vector2 EdgeSegment::directionTendency(double param, int polarity) const {
+    Vector2 dir = direction(param);
+    Vector2 normal = dir.getOrthonormal();
+    double h = dotProduct(directionChange(param)-dir, normal);
+    return dir+polarity*sign(h)*sqrt(fabs(h))*normal;
+}
+
 void EdgeSegment::distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const {
     if (param < 0) {
         Vector2 dir = direction(0).normalize();
@@ -97,6 +104,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 +445,80 @@ 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 * LinearSegment::makeDivergent(int dStart, int dEnd) {
+    return new DivergentEdgeSegment<LinearSegment>(*this, dStart, dEnd);
+}
+
+EdgeSegment * QuadraticSegment::makeDivergent(int dStart, int dEnd) {
+    return new DivergentEdgeSegment<QuadraticSegment>(*this, dStart, dEnd);
+}
+
+EdgeSegment * CubicSegment::makeDivergent(int dStart, int dEnd) {
+    return new DivergentEdgeSegment<CubicSegment>(*this, dStart, dEnd);
+}
+
+template <class BaseSegment>
+DivergentEdgeSegment<BaseSegment>::DivergentEdgeSegment(const BaseSegment &base, int dStart, int dEnd) : BaseSegment(base), dStart(dStart), dEnd(dEnd) { }
+
+template <class BaseSegment>
+SignedDistance DivergentEdgeSegment<BaseSegment>::signedDistance(Point2 origin, double &param) const {
+    SignedDistance d = BaseSegment::signedDistance(origin, param);
+    if (
+        (dStart && param < 0 && dStart*crossProduct(origin-BaseSegment::point(0), BaseSegment::direction(0)) > 0) ||
+        (dEnd && param > 1 && dEnd*crossProduct(origin-BaseSegment::point(1), BaseSegment::direction(1)) > 0)
+    )
+        d.dot = sqrt(d.dot);
+    return d;
+}
+
+template <class BaseSegment>
+void DivergentEdgeSegment<BaseSegment>::distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const {
+    #define P_LEN (sizeof(BaseSegment::p)/sizeof(*BaseSegment::p))
+    Vector2 qa, ab, ac, br;
+    if (P_LEN >= 3 && dStart && param < 0) {
+        ab = BaseSegment::p[2]-BaseSegment::p[1];
+        ac = BaseSegment::p[2]-BaseSegment::p[0];
+        br = BaseSegment::p[1]-BaseSegment::p[0]-ab;
+        qa = BaseSegment::p[0]-ac-origin;
+    } else if (P_LEN >= 3 && dEnd && param > 1) {
+        qa = BaseSegment::p[P_LEN-1]-origin;
+        ab = BaseSegment::p[P_LEN-1]-BaseSegment::p[P_LEN-2];
+        ac = BaseSegment::p[P_LEN-1]-BaseSegment::p[P_LEN-3];
+        br = ac-2*ab;
+    }
+    #undef P_LEN
+    if (ac) {
+        double a = dotProduct(br, br);
+        double b = 3*dotProduct(ab, br);
+        double c = 2*dotProduct(ab, ab)+dotProduct(qa, br);
+        double d = dotProduct(qa, ab);
+        double t[3];
+        int solutions = solveCubic(t, a, b, c, d);
+        for (int i = 0; i < solutions; ++i) {
+            if ((param < 0 && t[i] < 1) || (param > 1 && t[i] > 0)) {
+                Point2 qe = qa+2*t[i]*ab+t[i]*t[i]*br;
+                double pseudoDistance = nonZeroSign(crossProduct(ac, qe))*qe.length();
+                if (fabs(pseudoDistance) <= fabs(distance.distance)) {
+                    distance.distance = pseudoDistance;
+                    distance.dot = 0;
+                }
+            }
+        }
+    } else
+        BaseSegment::distanceToPseudoDistance(distance, origin, param);
+}
+
+template <class BaseSegment>
+EdgeSegment * DivergentEdgeSegment<BaseSegment>::makeDivergent(int dStart, int dEnd) {
+    if (dStart)
+        this->dStart = dStart;
+    if (dEnd)
+        this->dEnd = dEnd;
+    return NULL;
+}
+
+template class DivergentEdgeSegment<LinearSegment>;
+template class DivergentEdgeSegment<QuadraticSegment>;
+template class DivergentEdgeSegment<CubicSegment>;
+
 }

+ 28 - 0
core/edge-segments.h

@@ -11,6 +11,9 @@ namespace msdfgen {
 #define MSDFGEN_CUBIC_SEARCH_STARTS 4
 #define MSDFGEN_CUBIC_SEARCH_STEPS 4
 
+// Threshold of the dot product of adjacent edge directions to be considered convergent.
+#define MSDFGEN_CORNER_DOT_EPSILON 0.000001
+
 /// An abstract edge segment.
 class EdgeSegment {
 
@@ -25,6 +28,10 @@ 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 direction tendency vector that can resolve cases where directions converge at a corner.
+    virtual Vector2 directionTendency(double param, int polarity) const;
     /// 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.
@@ -40,6 +47,8 @@ public:
     virtual void moveEndPoint(Point2 to) = 0;
     /// Splits the edge segments into thirds which together represent the original edge.
     virtual void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const = 0;
+    /// Converts convergent segment to a divergent segment. If NULL is returned, the object is already divergent and has been updated.
+    virtual EdgeSegment * makeDivergent(int dStart, int dEnd) = 0;
 
 };
 
@@ -53,6 +62,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;
@@ -60,6 +70,7 @@ public:
     void moveStartPoint(Point2 to);
     void moveEndPoint(Point2 to);
     void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
+    EdgeSegment * makeDivergent(int dStart, int dEnd);
 
 };
 
@@ -73,6 +84,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;
@@ -80,6 +92,7 @@ public:
     void moveStartPoint(Point2 to);
     void moveEndPoint(Point2 to);
     void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
+    EdgeSegment * makeDivergent(int dStart, int dEnd);
 
 };
 
@@ -93,6 +106,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;
@@ -100,6 +114,20 @@ public:
     void moveStartPoint(Point2 to);
     void moveEndPoint(Point2 to);
     void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
+    EdgeSegment * makeDivergent(int dStart, int dEnd);
+
+};
+
+template <class BaseSegment>
+class DivergentEdgeSegment : public BaseSegment {
+
+public:
+    int dStart, dEnd;
+
+    explicit DivergentEdgeSegment(const BaseSegment &base, int dStart, int dEnd);
+    SignedDistance signedDistance(Point2 origin, double &param) const;
+    void distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const;
+    EdgeSegment * makeDivergent(int dStart, int dEnd);
 
 };
 

+ 15 - 12
core/edge-selectors.cpp

@@ -38,19 +38,22 @@ TrueDistanceSelector::DistanceType TrueDistanceSelector::distance() const {
 
 PseudoDistanceSelectorBase::EdgeCache::EdgeCache() : absDistance(0), edgeDomainDistance(0), pseudoDistance(0) { }
 
-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));
+static double cornerEdgeDomainDistance(const EdgeSegment *a, const EdgeSegment *b, const Point2 &p, int polarity) {
+    Vector2 aDir = a->direction(1).normalize(true);
+    Vector2 bDir = b->direction(0).normalize(true);
+    if (dotProduct(aDir, bDir) < MSDFGEN_CORNER_DOT_EPSILON-1) {
+        Vector2 aTen = a->directionTendency(1, 1);
+        Vector2 bTen = b->directionTendency(0, -1);
+        return nonZeroSign(crossProduct(aTen, bTen))*fabs(SignedDistance::INFINITE.distance);
     }
+    return polarity*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)
+        return cornerEdgeDomainDistance(prevEdge, edge, p, -1);
+    else if (param > 1)
+        return cornerEdgeDomainDistance(edge, nextEdge, p, 1);
     return 0;
 }