mirror of https://github.com/Chlumsky/msdfgen.git
Experimental method to resolve convergent corners (WIP)
This commit is contained in:
parent
8c6fb790b5
commit
0356d48930
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<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 ¶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 <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>;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 BaseSegment>
|
||||
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);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue