diff --git a/core/EdgeHolder.cpp b/core/EdgeHolder.cpp index 581dff4..3a0157a 100644 --- a/core/EdgeHolder.cpp +++ b/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 diff --git a/core/Shape.cpp b/core/Shape.cpp index a0df08e..cddb622 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) { } @@ -36,8 +38,21 @@ bool Shape::validate() const { return true; } +static void deconvergeEdge(EdgeHolder &edgeHolder, int param) { + { + const QuadraticSegment *quadraticSegment = dynamic_cast(&*edgeHolder); + if (quadraticSegment) + edgeHolder = quadraticSegment->convertToCubic(); + } + { + CubicSegment *cubicSegment = dynamic_cast(&*edgeHolder); + if (cubicSegment) + cubicSegment->deconverge(param, MSDFGEN_DECONVERGENCE_FACTOR); + } +} + 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 +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::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 { diff --git a/core/Shape.h b/core/Shape.h index 97c17f6..45535ce 100644 --- a/core/Shape.h +++ b/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 { diff --git a/core/edge-segments.cpp b/core/edge-segments.cpp index c49827d..f99dc9d 100644 --- a/core/edge-segments.cpp +++ b/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 ¶m) 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; + } +} + } diff --git a/core/edge-segments.h b/core/edge-segments.h index 539ce71..6eca0f1 100644 --- a/core/edge-segments.h +++ b/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 ¶m) 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 ¶m) 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 ¶m) 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 ¶m) 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); + }; } diff --git a/core/edge-selectors.cpp b/core/edge-selectors.cpp index f74b449..c9edd97 100644 --- a/core/edge-selectors.cpp +++ b/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; }