Skip to content

Commit

Permalink
Added algorithm to fix contour windings, low level optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
Chlumsky committed Oct 17, 2020
1 parent b22f79e commit fe910c8
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 12 deletions.
7 changes: 7 additions & 0 deletions core/Contour.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,11 @@ int Contour::winding() const {
return sign(total);
}

void Contour::reverse() {
for (int i = (int) edges.size()/2; i > 0; --i)
EdgeHolder::swap(edges[i-1], edges[edges.size()-i]);
for (std::vector<EdgeHolder>::iterator edge = edges.begin(); edge != edges.end(); ++edge)
(*edge)->reverse();
}

}
2 changes: 2 additions & 0 deletions core/Contour.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class Contour {
void boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const;
/// Computes the winding of the contour. Returns 1 if positive, -1 if negative.
int winding() const;
/// Reverses the sequence of edges on the contour.
void reverse();

};

Expand Down
6 changes: 6 additions & 0 deletions core/EdgeHolder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@

namespace msdfgen {

void EdgeHolder::swap(EdgeHolder &a, EdgeHolder &b) {
EdgeSegment *tmp = a.edgeSegment;
a.edgeSegment = b.edgeSegment;
b.edgeSegment = tmp;
}

EdgeHolder::EdgeHolder() : edgeSegment(NULL) { }

EdgeHolder::EdgeHolder(EdgeSegment *segment) : edgeSegment(segment) { }
Expand Down
3 changes: 3 additions & 0 deletions core/EdgeHolder.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ namespace msdfgen {
class EdgeHolder {

public:
/// Swaps the edges held by a and b.
static void swap(EdgeHolder &a, EdgeHolder &b);

EdgeHolder();
EdgeHolder(EdgeSegment *segment);
EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE);
Expand Down
55 changes: 55 additions & 0 deletions core/Shape.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

#include "Shape.h"

#include <algorithm>
#include "arithmetics.hpp"

namespace msdfgen {
Expand Down Expand Up @@ -125,4 +126,58 @@ int Shape::edgeCount() const {
return total;
}

void Shape::orientContours() {
struct Intersection {
double x;
int direction;
int contourIndex;

static int compare(const void *a, const void *b) {
return sign(reinterpret_cast<const Intersection *>(a)->x-reinterpret_cast<const Intersection *>(b)->x);
}
};

const double ratio = .5*(sqrt(5)-1); // an irrational number to minimize chance of intersecting a corner or other point of interest
std::vector<int> orientations(contours.size());
std::vector<Intersection> intersections;
for (int i = 0; i < (int) contours.size(); ++i) {
if (!orientations[i] && !contours[i].edges.empty()) {
// Find an Y that crosses the contour
double y0 = contours[i].edges.front()->point(0).y;
double y1 = y0;
for (std::vector<EdgeHolder>::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge)
y1 = (*edge)->point(1).y;
for (std::vector<EdgeHolder>::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge)
y1 = (*edge)->point(ratio).y; // in case all endpoints are in a horizontal line
double y = mix(y0, y1, ratio);
// Scanline through whole shape at Y
double x[3];
int dy[3];
for (int j = 0; j < (int) contours.size(); ++j) {
for (std::vector<EdgeHolder>::const_iterator edge = contours[j].edges.begin(); edge != contours[j].edges.end(); ++edge) {
int n = (*edge)->scanlineIntersections(x, dy, y);
for (int k = 0; k < n; ++k) {
Intersection intersection = { x[k], dy[k], j };
intersections.push_back(intersection);
}
}
}
qsort(&intersections[0], intersections.size(), sizeof(Intersection), &Intersection::compare);
// Disqualify multiple intersections
for (int j = 1; j < (int) intersections.size(); ++j)
if (intersections[j].x == intersections[j-1].x)
intersections[j].direction = intersections[j-1].direction = 0;
// Inspect scanline and deduce orientations of intersected contours
for (int j = 0; j < (int) intersections.size(); ++j)
if (intersections[j].direction)
orientations[intersections[j].contourIndex] += 2*((j&1)^(intersections[j].direction > 0))-1;
intersections.clear();
}
}
// Reverse contours that have the opposite orientation
for (int i = 0; i < (int) contours.size(); ++i)
if (orientations[i] < 0)
contours[i].reverse();
}

}
2 changes: 2 additions & 0 deletions core/Shape.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class Shape {
void scanline(Scanline &line, double y) const;
/// Returns the total number of edge segments
int edgeCount() const;
/// Assumes its contours are unoriented (even-odd fill rule). Attempts to orient them to conform to the non-zero winding rule.
void orientContours();

};

Expand Down
49 changes: 37 additions & 12 deletions core/edge-segments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ QuadraticSegment::QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor ed
}

CubicSegment::CubicSegment(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : EdgeSegment(edgeColor) {
if ((p1 == p0 || p1 == p3) && (p2 == p0 || p2 == p3)) {
p1 = mix(p0, p3, 1/3.);
p2 = mix(p0, p3, 2/3.);
}
p[0] = p0;
p[1] = p1;
p[2] = p2;
Expand Down Expand Up @@ -139,18 +143,18 @@ SignedDistance QuadraticSegment::signedDistance(Point2 origin, double &param) co
param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir);
{
epDir = direction(1);
double distance = nonZeroSign(crossProduct(epDir, p[2]-origin))*(p[2]-origin).length(); // distance from B
if (fabs(distance) < fabs(minDistance)) {
minDistance = distance;
double distance = (p[2]-origin).length(); // distance from B
if (distance < fabs(minDistance)) {
minDistance = nonZeroSign(crossProduct(epDir, p[2]-origin))*distance;
param = dotProduct(origin-p[1], epDir)/dotProduct(epDir, epDir);
}
}
for (int i = 0; i < solutions; ++i) {
if (t[i] > 0 && t[i] < 1) {
Point2 qe = p[0]+2*t[i]*ab+t[i]*t[i]*br-origin;
double distance = nonZeroSign(crossProduct(direction(t[i]), qe))*qe.length();
if (fabs(distance) <= fabs(minDistance)) {
minDistance = distance;
double distance = qe.length();
if (distance <= fabs(minDistance)) {
minDistance = nonZeroSign(crossProduct(direction(t[i]), qe))*distance;
param = t[i];
}
}
Expand All @@ -175,9 +179,9 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double &param) const
param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir);
{
epDir = direction(1);
double distance = nonZeroSign(crossProduct(epDir, p[3]-origin))*(p[3]-origin).length(); // distance from B
if (fabs(distance) < fabs(minDistance)) {
minDistance = distance;
double distance = (p[3]-origin).length(); // distance from B
if (distance < fabs(minDistance)) {
minDistance = nonZeroSign(crossProduct(epDir, p[3]-origin))*distance;
param = dotProduct(epDir-(p[3]-origin), epDir)/dotProduct(epDir, epDir);
}
}
Expand All @@ -193,9 +197,9 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double &param) const
if (t <= 0 || t >= 1)
break;
qe = qa+3*t*ab+3*t*t*br+t*t*t*as;
double distance = nonZeroSign(crossProduct(direction(t), qe))*qe.length();
if (fabs(distance) < fabs(minDistance)) {
minDistance = distance;
double distance = qe.length();
if (distance < fabs(minDistance)) {
minDistance = nonZeroSign(crossProduct(direction(t), qe))*distance;
param = t;
}
}
Expand Down Expand Up @@ -381,6 +385,27 @@ void CubicSegment::bound(double &l, double &b, double &r, double &t) const {
pointBounds(point(params[i]), l, b, r, t);
}

void LinearSegment::reverse() {
Point2 tmp = p[0];
p[0] = p[1];
p[1] = tmp;
}

void QuadraticSegment::reverse() {
Point2 tmp = p[0];
p[0] = p[2];
p[2] = tmp;
}

void CubicSegment::reverse() {
Point2 tmp = p[0];
p[0] = p[3];
p[3] = tmp;
tmp = p[1];
p[1] = p[2];
p[2] = tmp;
}

void LinearSegment::moveStartPoint(Point2 to) {
p[0] = to;
}
Expand Down
5 changes: 5 additions & 0 deletions core/edge-segments.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class EdgeSegment {
/// Adjusts the bounding box to fit the edge segment.
virtual void bound(double &l, double &b, double &r, double &t) const = 0;

/// Reverses the edge (swaps its start point and end point).
virtual void reverse() = 0;
/// Moves the start point of the edge segment.
virtual void moveStartPoint(Point2 to) = 0;
/// Moves the end point of the edge segment.
Expand All @@ -60,6 +62,7 @@ class LinearSegment : public EdgeSegment {
int scanlineIntersections(double x[3], int dy[3], double y) const;
void bound(double &l, double &b, double &r, double &t) const;

void reverse();
void moveStartPoint(Point2 to);
void moveEndPoint(Point2 to);
void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
Expand All @@ -81,6 +84,7 @@ class QuadraticSegment : public EdgeSegment {
int scanlineIntersections(double x[3], int dy[3], double y) const;
void bound(double &l, double &b, double &r, double &t) const;

void reverse();
void moveStartPoint(Point2 to);
void moveEndPoint(Point2 to);
void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
Expand All @@ -104,6 +108,7 @@ class CubicSegment : public EdgeSegment {
int scanlineIntersections(double x[3], int dy[3], double y) const;
void bound(double &l, double &b, double &r, double &t) const;

void reverse();
void moveStartPoint(Point2 to);
void moveEndPoint(Point2 to);
void splitInThirds(EdgeSegment *&part1, EdgeSegment *&part2, EdgeSegment *&part3) const;
Expand Down

0 comments on commit fe910c8

Please sign in to comment.