Experimental method to resolve convergent corners (WIP)

This commit is contained in:
Viktor Chlumský 2020-10-11 20:58:50 +02:00
parent 8c6fb790b5
commit 0356d48930
4 changed files with 158 additions and 13 deletions

View File

@ -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 {

View File

@ -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>;
}

View File

@ -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);
};

View File

@ -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;
}