Path: blob/master/thirdparty/msdfgen/core/edge-segments.cpp
21334 views
1#include "edge-segments.h"23#include "arithmetics.hpp"4#include "equation-solver.h"56namespace msdfgen {78EdgeSegment *EdgeSegment::create(Point2 p0, Point2 p1, EdgeColor edgeColor) {9return new LinearSegment(p0, p1, edgeColor);10}1112EdgeSegment *EdgeSegment::create(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) {13if (!crossProduct(p1-p0, p2-p1))14return new LinearSegment(p0, p2, edgeColor);15return new QuadraticSegment(p0, p1, p2, edgeColor);16}1718EdgeSegment *EdgeSegment::create(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) {19Vector2 p12 = p2-p1;20if (!crossProduct(p1-p0, p12) && !crossProduct(p12, p3-p2))21return new LinearSegment(p0, p3, edgeColor);22if ((p12 = 1.5*p1-.5*p0) == 1.5*p2-.5*p3)23return new QuadraticSegment(p0, p12, p3, edgeColor);24return new CubicSegment(p0, p1, p2, p3, edgeColor);25}2627void EdgeSegment::distanceToPerpendicularDistance(SignedDistance &distance, Point2 origin, double param) const {28if (param < 0) {29Vector2 dir = direction(0).normalize();30Vector2 aq = origin-point(0);31double ts = dotProduct(aq, dir);32if (ts < 0) {33double perpendicularDistance = crossProduct(aq, dir);34if (fabs(perpendicularDistance) <= fabs(distance.distance)) {35distance.distance = perpendicularDistance;36distance.dot = 0;37}38}39} else if (param > 1) {40Vector2 dir = direction(1).normalize();41Vector2 bq = origin-point(1);42double ts = dotProduct(bq, dir);43if (ts > 0) {44double perpendicularDistance = crossProduct(bq, dir);45if (fabs(perpendicularDistance) <= fabs(distance.distance)) {46distance.distance = perpendicularDistance;47distance.dot = 0;48}49}50}51}5253LinearSegment::LinearSegment(Point2 p0, Point2 p1, EdgeColor edgeColor) : EdgeSegment(edgeColor) {54p[0] = p0;55p[1] = p1;56}5758QuadraticSegment::QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : EdgeSegment(edgeColor) {59p[0] = p0;60p[1] = p1;61p[2] = p2;62}6364CubicSegment::CubicSegment(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : EdgeSegment(edgeColor) {65p[0] = p0;66p[1] = p1;67p[2] = p2;68p[3] = p3;69}7071LinearSegment *LinearSegment::clone() const {72return new LinearSegment(p[0], p[1], color);73}7475QuadraticSegment *QuadraticSegment::clone() const {76return new QuadraticSegment(p[0], p[1], p[2], color);77}7879CubicSegment *CubicSegment::clone() const {80return new CubicSegment(p[0], p[1], p[2], p[3], color);81}8283int LinearSegment::type() const {84return (int) EDGE_TYPE;85}8687int QuadraticSegment::type() const {88return (int) EDGE_TYPE;89}9091int CubicSegment::type() const {92return (int) EDGE_TYPE;93}9495const Point2 *LinearSegment::controlPoints() const {96return p;97}9899const Point2 *QuadraticSegment::controlPoints() const {100return p;101}102103const Point2 *CubicSegment::controlPoints() const {104return p;105}106107Point2 LinearSegment::point(double param) const {108return mix(p[0], p[1], param);109}110111Point2 QuadraticSegment::point(double param) const {112return mix(mix(p[0], p[1], param), mix(p[1], p[2], param), param);113}114115Point2 CubicSegment::point(double param) const {116Vector2 p12 = mix(p[1], p[2], param);117return mix(mix(mix(p[0], p[1], param), p12, param), mix(p12, mix(p[2], p[3], param), param), param);118}119120Vector2 LinearSegment::direction(double param) const {121return p[1]-p[0];122}123124Vector2 QuadraticSegment::direction(double param) const {125Vector2 tangent = mix(p[1]-p[0], p[2]-p[1], param);126if (!tangent)127return p[2]-p[0];128return tangent;129}130131Vector2 CubicSegment::direction(double param) const {132Vector2 tangent = mix(mix(p[1]-p[0], p[2]-p[1], param), mix(p[2]-p[1], p[3]-p[2], param), param);133if (!tangent) {134if (param == 0) return p[2]-p[0];135if (param == 1) return p[3]-p[1];136}137return tangent;138}139140Vector2 LinearSegment::directionChange(double param) const {141return Vector2();142}143144Vector2 QuadraticSegment::directionChange(double param) const {145return (p[2]-p[1])-(p[1]-p[0]);146}147148Vector2 CubicSegment::directionChange(double param) const {149return mix((p[2]-p[1])-(p[1]-p[0]), (p[3]-p[2])-(p[2]-p[1]), param);150}151152double LinearSegment::length() const {153return (p[1]-p[0]).length();154}155156double QuadraticSegment::length() const {157Vector2 ab = p[1]-p[0];158Vector2 br = p[2]-p[1]-ab;159double abab = dotProduct(ab, ab);160double abbr = dotProduct(ab, br);161double brbr = dotProduct(br, br);162double abLen = sqrt(abab);163double brLen = sqrt(brbr);164double crs = crossProduct(ab, br);165double h = sqrt(abab+abbr+abbr+brbr);166return (167brLen*((abbr+brbr)*h-abbr*abLen)+168crs*crs*log((brLen*h+abbr+brbr)/(brLen*abLen+abbr))169)/(brbr*brLen);170}171172SignedDistance LinearSegment::signedDistance(Point2 origin, double ¶m) const {173Vector2 aq = origin-p[0];174Vector2 ab = p[1]-p[0];175param = dotProduct(aq, ab)/dotProduct(ab, ab);176Vector2 eq = p[param > .5]-origin;177double endpointDistance = eq.length();178if (param > 0 && param < 1) {179double orthoDistance = dotProduct(ab.getOrthonormal(false), aq);180if (fabs(orthoDistance) < endpointDistance)181return SignedDistance(orthoDistance, 0);182}183return SignedDistance(nonZeroSign(crossProduct(aq, ab))*endpointDistance, fabs(dotProduct(ab.normalize(), eq.normalize())));184}185186SignedDistance QuadraticSegment::signedDistance(Point2 origin, double ¶m) const {187Vector2 qa = p[0]-origin;188Vector2 ab = p[1]-p[0];189Vector2 br = p[2]-p[1]-ab;190double a = dotProduct(br, br);191double b = 3*dotProduct(ab, br);192double c = 2*dotProduct(ab, ab)+dotProduct(qa, br);193double d = dotProduct(qa, ab);194double t[3];195int solutions = solveCubic(t, a, b, c, d);196197Vector2 epDir = direction(0);198double minDistance = nonZeroSign(crossProduct(epDir, qa))*qa.length(); // distance from A199param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir);200{201double distance = (p[2]-origin).length(); // distance from B202if (distance < fabs(minDistance)) {203epDir = direction(1);204minDistance = nonZeroSign(crossProduct(epDir, p[2]-origin))*distance;205param = dotProduct(origin-p[1], epDir)/dotProduct(epDir, epDir);206}207}208for (int i = 0; i < solutions; ++i) {209if (t[i] > 0 && t[i] < 1) {210Point2 qe = qa+2*t[i]*ab+t[i]*t[i]*br;211double distance = qe.length();212if (distance <= fabs(minDistance)) {213minDistance = nonZeroSign(crossProduct(ab+t[i]*br, qe))*distance;214param = t[i];215}216}217}218219if (param >= 0 && param <= 1)220return SignedDistance(minDistance, 0);221if (param < .5)222return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize())));223else224return SignedDistance(minDistance, fabs(dotProduct(direction(1).normalize(), (p[2]-origin).normalize())));225}226227SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const {228Vector2 qa = p[0]-origin;229Vector2 ab = p[1]-p[0];230Vector2 br = p[2]-p[1]-ab;231Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br;232233Vector2 epDir = direction(0);234double minDistance = nonZeroSign(crossProduct(epDir, qa))*qa.length(); // distance from A235param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir);236{237double distance = (p[3]-origin).length(); // distance from B238if (distance < fabs(minDistance)) {239epDir = direction(1);240minDistance = nonZeroSign(crossProduct(epDir, p[3]-origin))*distance;241param = dotProduct(epDir-(p[3]-origin), epDir)/dotProduct(epDir, epDir);242}243}244// Iterative minimum distance search245for (int i = 0; i <= MSDFGEN_CUBIC_SEARCH_STARTS; ++i) {246double t = 1./MSDFGEN_CUBIC_SEARCH_STARTS*i;247Vector2 qe = qa+3*t*ab+3*t*t*br+t*t*t*as;248Vector2 d1 = 3*ab+6*t*br+3*t*t*as;249Vector2 d2 = 6*br+6*t*as;250double improvedT = t-dotProduct(qe, d1)/(dotProduct(d1, d1)+dotProduct(qe, d2));251if (improvedT > 0 && improvedT < 1) {252int remainingSteps = MSDFGEN_CUBIC_SEARCH_STEPS;253do {254t = improvedT;255qe = qa+3*t*ab+3*t*t*br+t*t*t*as;256d1 = 3*ab+6*t*br+3*t*t*as;257if (!--remainingSteps)258break;259d2 = 6*br+6*t*as;260improvedT = t-dotProduct(qe, d1)/(dotProduct(d1, d1)+dotProduct(qe, d2));261} while (improvedT > 0 && improvedT < 1);262double distance = qe.length();263if (distance < fabs(minDistance)) {264minDistance = nonZeroSign(crossProduct(d1, qe))*distance;265param = t;266}267}268}269270if (param >= 0 && param <= 1)271return SignedDistance(minDistance, 0);272if (param < .5)273return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize())));274else275return SignedDistance(minDistance, fabs(dotProduct(direction(1).normalize(), (p[3]-origin).normalize())));276}277278int LinearSegment::scanlineIntersections(double x[3], int dy[3], double y) const {279if ((y >= p[0].y && y < p[1].y) || (y >= p[1].y && y < p[0].y)) {280double param = (y-p[0].y)/(p[1].y-p[0].y);281x[0] = mix(p[0].x, p[1].x, param);282dy[0] = sign(p[1].y-p[0].y);283return 1;284}285return 0;286}287288int QuadraticSegment::scanlineIntersections(double x[3], int dy[3], double y) const {289int total = 0;290int nextDY = y > p[0].y ? 1 : -1;291x[total] = p[0].x;292if (p[0].y == y) {293if (p[0].y < p[1].y || (p[0].y == p[1].y && p[0].y < p[2].y))294dy[total++] = 1;295else296nextDY = 1;297}298{299Vector2 ab = p[1]-p[0];300Vector2 br = p[2]-p[1]-ab;301double t[2];302int solutions = solveQuadratic(t, br.y, 2*ab.y, p[0].y-y);303// Sort solutions304double tmp;305if (solutions >= 2 && t[0] > t[1])306tmp = t[0], t[0] = t[1], t[1] = tmp;307for (int i = 0; i < solutions && total < 2; ++i) {308if (t[i] >= 0 && t[i] <= 1) {309x[total] = p[0].x+2*t[i]*ab.x+t[i]*t[i]*br.x;310if (nextDY*(ab.y+t[i]*br.y) >= 0) {311dy[total++] = nextDY;312nextDY = -nextDY;313}314}315}316}317if (p[2].y == y) {318if (nextDY > 0 && total > 0) {319--total;320nextDY = -1;321}322if ((p[2].y < p[1].y || (p[2].y == p[1].y && p[2].y < p[0].y)) && total < 2) {323x[total] = p[2].x;324if (nextDY < 0) {325dy[total++] = -1;326nextDY = 1;327}328}329}330if (nextDY != (y >= p[2].y ? 1 : -1)) {331if (total > 0)332--total;333else {334if (fabs(p[2].y-y) < fabs(p[0].y-y))335x[total] = p[2].x;336dy[total++] = nextDY;337}338}339return total;340}341342int CubicSegment::scanlineIntersections(double x[3], int dy[3], double y) const {343int total = 0;344int nextDY = y > p[0].y ? 1 : -1;345x[total] = p[0].x;346if (p[0].y == y) {347if (p[0].y < p[1].y || (p[0].y == p[1].y && (p[0].y < p[2].y || (p[0].y == p[2].y && p[0].y < p[3].y))))348dy[total++] = 1;349else350nextDY = 1;351}352{353Vector2 ab = p[1]-p[0];354Vector2 br = p[2]-p[1]-ab;355Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br;356double t[3];357int solutions = solveCubic(t, as.y, 3*br.y, 3*ab.y, p[0].y-y);358// Sort solutions359double tmp;360if (solutions >= 2) {361if (t[0] > t[1])362tmp = t[0], t[0] = t[1], t[1] = tmp;363if (solutions >= 3 && t[1] > t[2]) {364tmp = t[1], t[1] = t[2], t[2] = tmp;365if (t[0] > t[1])366tmp = t[0], t[0] = t[1], t[1] = tmp;367}368}369for (int i = 0; i < solutions && total < 3; ++i) {370if (t[i] >= 0 && t[i] <= 1) {371x[total] = p[0].x+3*t[i]*ab.x+3*t[i]*t[i]*br.x+t[i]*t[i]*t[i]*as.x;372if (nextDY*(ab.y+2*t[i]*br.y+t[i]*t[i]*as.y) >= 0) {373dy[total++] = nextDY;374nextDY = -nextDY;375}376}377}378}379if (p[3].y == y) {380if (nextDY > 0 && total > 0) {381--total;382nextDY = -1;383}384if ((p[3].y < p[2].y || (p[3].y == p[2].y && (p[3].y < p[1].y || (p[3].y == p[1].y && p[3].y < p[0].y)))) && total < 3) {385x[total] = p[3].x;386if (nextDY < 0) {387dy[total++] = -1;388nextDY = 1;389}390}391}392if (nextDY != (y >= p[3].y ? 1 : -1)) {393if (total > 0)394--total;395else {396if (fabs(p[3].y-y) < fabs(p[0].y-y))397x[total] = p[3].x;398dy[total++] = nextDY;399}400}401return total;402}403404static void pointBounds(Point2 p, double &xMin, double &yMin, double &xMax, double &yMax) {405if (p.x < xMin) xMin = p.x;406if (p.y < yMin) yMin = p.y;407if (p.x > xMax) xMax = p.x;408if (p.y > yMax) yMax = p.y;409}410411void LinearSegment::bound(double &xMin, double &yMin, double &xMax, double &yMax) const {412pointBounds(p[0], xMin, yMin, xMax, yMax);413pointBounds(p[1], xMin, yMin, xMax, yMax);414}415416void QuadraticSegment::bound(double &xMin, double &yMin, double &xMax, double &yMax) const {417pointBounds(p[0], xMin, yMin, xMax, yMax);418pointBounds(p[2], xMin, yMin, xMax, yMax);419Vector2 bot = (p[1]-p[0])-(p[2]-p[1]);420if (bot.x) {421double param = (p[1].x-p[0].x)/bot.x;422if (param > 0 && param < 1)423pointBounds(point(param), xMin, yMin, xMax, yMax);424}425if (bot.y) {426double param = (p[1].y-p[0].y)/bot.y;427if (param > 0 && param < 1)428pointBounds(point(param), xMin, yMin, xMax, yMax);429}430}431432void CubicSegment::bound(double &xMin, double &yMin, double &xMax, double &yMax) const {433pointBounds(p[0], xMin, yMin, xMax, yMax);434pointBounds(p[3], xMin, yMin, xMax, yMax);435Vector2 a0 = p[1]-p[0];436Vector2 a1 = 2*(p[2]-p[1]-a0);437Vector2 a2 = p[3]-3*p[2]+3*p[1]-p[0];438double params[2];439int solutions;440solutions = solveQuadratic(params, a2.x, a1.x, a0.x);441for (int i = 0; i < solutions; ++i)442if (params[i] > 0 && params[i] < 1)443pointBounds(point(params[i]), xMin, yMin, xMax, yMax);444solutions = solveQuadratic(params, a2.y, a1.y, a0.y);445for (int i = 0; i < solutions; ++i)446if (params[i] > 0 && params[i] < 1)447pointBounds(point(params[i]), xMin, yMin, xMax, yMax);448}449450void LinearSegment::reverse() {451Point2 tmp = p[0];452p[0] = p[1];453p[1] = tmp;454}455456void QuadraticSegment::reverse() {457Point2 tmp = p[0];458p[0] = p[2];459p[2] = tmp;460}461462void CubicSegment::reverse() {463Point2 tmp = p[0];464p[0] = p[3];465p[3] = tmp;466tmp = p[1];467p[1] = p[2];468p[2] = tmp;469}470471void LinearSegment::moveStartPoint(Point2 to) {472p[0] = to;473}474475void QuadraticSegment::moveStartPoint(Point2 to) {476Vector2 origSDir = p[0]-p[1];477Point2 origP1 = p[1];478p[1] += crossProduct(p[0]-p[1], to-p[0])/crossProduct(p[0]-p[1], p[2]-p[1])*(p[2]-p[1]);479p[0] = to;480if (dotProduct(origSDir, p[0]-p[1]) < 0)481p[1] = origP1;482}483484void CubicSegment::moveStartPoint(Point2 to) {485p[1] += to-p[0];486p[0] = to;487}488489void LinearSegment::moveEndPoint(Point2 to) {490p[1] = to;491}492493void QuadraticSegment::moveEndPoint(Point2 to) {494Vector2 origEDir = p[2]-p[1];495Point2 origP1 = p[1];496p[1] += crossProduct(p[2]-p[1], to-p[2])/crossProduct(p[2]-p[1], p[0]-p[1])*(p[0]-p[1]);497p[2] = to;498if (dotProduct(origEDir, p[2]-p[1]) < 0)499p[1] = origP1;500}501502void CubicSegment::moveEndPoint(Point2 to) {503p[2] += to-p[3];504p[3] = to;505}506507void LinearSegment::splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const {508part0 = new LinearSegment(p[0], point(1/3.), color);509part1 = new LinearSegment(point(1/3.), point(2/3.), color);510part2 = new LinearSegment(point(2/3.), p[1], color);511}512513void QuadraticSegment::splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const {514part0 = new QuadraticSegment(p[0], mix(p[0], p[1], 1/3.), point(1/3.), color);515part1 = new QuadraticSegment(point(1/3.), mix(mix(p[0], p[1], 5/9.), mix(p[1], p[2], 4/9.), .5), point(2/3.), color);516part2 = new QuadraticSegment(point(2/3.), mix(p[1], p[2], 2/3.), p[2], color);517}518519void CubicSegment::splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const {520part0 = new CubicSegment(p[0], p[0] == p[1] ? p[0] : mix(p[0], p[1], 1/3.), mix(mix(p[0], p[1], 1/3.), mix(p[1], p[2], 1/3.), 1/3.), point(1/3.), color);521part1 = new CubicSegment(point(1/3.),522mix(mix(mix(p[0], p[1], 1/3.), mix(p[1], p[2], 1/3.), 1/3.), mix(mix(p[1], p[2], 1/3.), mix(p[2], p[3], 1/3.), 1/3.), 2/3.),523mix(mix(mix(p[0], p[1], 2/3.), mix(p[1], p[2], 2/3.), 2/3.), mix(mix(p[1], p[2], 2/3.), mix(p[2], p[3], 2/3.), 2/3.), 1/3.),524point(2/3.), color);525part2 = 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);526}527528EdgeSegment *QuadraticSegment::convertToCubic() const {529return new CubicSegment(p[0], mix(p[0], p[1], 2/3.), mix(p[1], p[2], 1/3.), p[2], color);530}531532}533534535