diff --git a/core/Shape.cpp b/core/Shape.cpp index a0df08e..2cb6bdf 100644 --- a/core/Shape.cpp +++ b/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::iterator contour = contours.begin(); contour != contours.end(); ++contour) + for (std::vector::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::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 { diff --git a/core/edge-segments.cpp b/core/edge-segments.cpp index c49827d..f6d7b60 100644 --- a/core/edge-segments.cpp +++ b/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 ¶m) 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(*this, dStart, dEnd); +} + +EdgeSegment * QuadraticSegment::makeDivergent(int dStart, int dEnd) { + return new DivergentEdgeSegment(*this, dStart, dEnd); +} + +EdgeSegment * CubicSegment::makeDivergent(int dStart, int dEnd) { + return new DivergentEdgeSegment(*this, dStart, dEnd); +} + +template +DivergentEdgeSegment::DivergentEdgeSegment(const BaseSegment &base, int dStart, int dEnd) : BaseSegment(base), dStart(dStart), dEnd(dEnd) { } + +template +SignedDistance DivergentEdgeSegment::signedDistance(Point2 origin, double ¶m) 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 +void DivergentEdgeSegment::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 +EdgeSegment * DivergentEdgeSegment::makeDivergent(int dStart, int dEnd) { + if (dStart) + this->dStart = dStart; + if (dEnd) + this->dEnd = dEnd; + return NULL; +} + +template class DivergentEdgeSegment; +template class DivergentEdgeSegment; +template class DivergentEdgeSegment; + } diff --git a/core/edge-segments.h b/core/edge-segments.h index 539ce71..8410fd9 100644 --- a/core/edge-segments.h +++ b/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 ¶m) 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 ¶m) 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 ¶m) 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 ¶m) 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 DivergentEdgeSegment : public BaseSegment { + +public: + int dStart, dEnd; + + explicit DivergentEdgeSegment(const BaseSegment &base, int dStart, int dEnd); + SignedDistance signedDistance(Point2 origin, double ¶m) const; + void distanceToPseudoDistance(SignedDistance &distance, Point2 origin, double param) const; + EdgeSegment * makeDivergent(int dStart, int dEnd); }; diff --git a/core/edge-selectors.cpp b/core/edge-selectors.cpp index f74b449..a695f15 100644 --- a/core/edge-selectors.cpp +++ b/core/edge-selectors.cpp @@ -38,19 +38,22 @@ 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, 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) { - 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, -1); + else if (param > 1) + return cornerEdgeDomainDistance(edge, nextEdge, p, 1); return 0; }