mirror of https://github.com/Chlumsky/msdfgen.git
Merge commit '46a7c09aea05110c951507891caed178d869a672'
* commit '46a7c09aea05110c951507891caed178d869a672': extracted rad and degree conversion added origin rotation to vector fixed arc rotation problems added arc handling to SVG importer added rotation to vector
This commit is contained in:
commit
cba7d65285
|
|
@ -49,6 +49,23 @@ Vector2 Vector2::project(const Vector2 &vector, bool positive) const {
|
|||
return t*n;
|
||||
}
|
||||
|
||||
Vector2 Vector2::rotateAround(const Vector2 ¢er, double angleDegree) const {
|
||||
Vector2 result = *this - center;
|
||||
double s = std::sin(angleDegree * M_PI / 180);
|
||||
double c = std::cos(angleDegree * M_PI / 180);
|
||||
result.x = result.x * c - result.y * s;
|
||||
result.y = result.x * s + result.y * c;
|
||||
return result + center;
|
||||
}
|
||||
|
||||
Vector2 Vector2::rotate(double angleDegree) const {
|
||||
double s = std::sin(angleDegree * M_PI / 180);
|
||||
double c = std::cos(angleDegree * M_PI / 180);
|
||||
double newX = x * c - y * s;
|
||||
double newY = x * s + y * c;
|
||||
return Vector2(newX, newY);
|
||||
}
|
||||
|
||||
Vector2::operator const void*() const {
|
||||
return x || y ? this : NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ struct Vector2 {
|
|||
Vector2 getOrthonormal(bool polarity = true, bool allowZero = false) const;
|
||||
/// Returns a vector projected along this one.
|
||||
Vector2 project(const Vector2 &vector, bool positive = false) const;
|
||||
/// Returns a vector that is rotated by the angle around a center
|
||||
Vector2 rotateAround(const Vector2 ¢er, double angleDegree) const;
|
||||
/// Returns a vector that is rotated by the angle around the origin
|
||||
Vector2 rotate(double angleDegree) const;
|
||||
operator const void *() const;
|
||||
bool operator!() const;
|
||||
bool operator==(const Vector2 &other) const;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,17 @@ static bool readDouble(double &output, const char *&pathDef) {
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool readBool(bool &output, const char *&pathDef) {
|
||||
int shift;
|
||||
int v;
|
||||
if (sscanf(pathDef, "%d%n", &v, &shift) == 1) {
|
||||
pathDef += shift;
|
||||
output = static_cast<bool>(v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void consumeOptionalComma(const char *&pathDef) {
|
||||
while (*pathDef == ' ')
|
||||
++pathDef;
|
||||
|
|
@ -59,6 +70,148 @@ static void consumeOptionalComma(const char *&pathDef) {
|
|||
++pathDef;
|
||||
}
|
||||
|
||||
static double toDegrees(double rad) {
|
||||
return rad * 180.0 / M_PI;
|
||||
}
|
||||
|
||||
static double toRad(double degrees) {
|
||||
return degrees * M_PI / 180.0;
|
||||
}
|
||||
|
||||
static Point2 handleArc(const char *&pathDef, char nodeType, Point2 prevNode, Contour &contour) {
|
||||
Point2 arcEnd;
|
||||
double radiusX = 0;
|
||||
double radiusY = 0;
|
||||
double xAxisRotation = 0;
|
||||
bool largeArc = false;
|
||||
bool sweep = false;
|
||||
|
||||
// arc syntax
|
||||
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
|
||||
// a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy
|
||||
REQUIRE(readDouble(radiusX, pathDef));
|
||||
radiusX = std::abs(radiusX);
|
||||
consumeOptionalComma(pathDef);
|
||||
REQUIRE(readDouble(radiusY, pathDef));
|
||||
radiusY = std::abs(radiusY);
|
||||
consumeOptionalComma(pathDef);
|
||||
REQUIRE(readDouble(xAxisRotation, pathDef));
|
||||
consumeOptionalComma(pathDef);
|
||||
REQUIRE(readBool(largeArc, pathDef));
|
||||
consumeOptionalComma(pathDef);
|
||||
REQUIRE(readBool(sweep, pathDef));
|
||||
consumeOptionalComma(pathDef);
|
||||
REQUIRE(readCoord(arcEnd, pathDef));
|
||||
if (nodeType == 'a') {
|
||||
arcEnd += prevNode;
|
||||
}
|
||||
|
||||
// Zero arc radius results in a straight line
|
||||
if (radiusX == 0 || radiusY == 0) {
|
||||
contour.addEdge(new LinearSegment(prevNode, arcEnd));
|
||||
return arcEnd;
|
||||
}
|
||||
|
||||
if (prevNode.x == arcEnd.x && prevNode.y == arcEnd.y) {
|
||||
// End point matches previous node,
|
||||
// which means it has a length of zero and can be ignored
|
||||
return prevNode;
|
||||
}
|
||||
|
||||
double xAxisRotationRad = toRad(std::fmod(xAxisRotation, 360.0));
|
||||
double cosRotation = std::cos(xAxisRotationRad);
|
||||
double sinRotation = std::sin(xAxisRotationRad);
|
||||
Point2 mid = (prevNode - arcEnd) / 2.0;
|
||||
|
||||
// Compute transformed start point
|
||||
double x1 = (cosRotation * mid.x + sinRotation * mid.y);
|
||||
double y1 = (-sinRotation * mid.x + cosRotation * mid.y);
|
||||
double radXsq = std::pow(radiusX, 2);
|
||||
double radYsq = std::pow(radiusY, 2);
|
||||
double x1sq = std::pow(x1, 2);
|
||||
double y1sq = std::pow(y1, 2);
|
||||
|
||||
// Scale the radius if it is not big enough
|
||||
double radiiCheck = x1sq / radXsq + y1sq / radYsq;
|
||||
if (radiiCheck > 1) {
|
||||
radiusX = std::sqrt(radiiCheck) * radiusX;
|
||||
radiusY = std::sqrt(radiiCheck) * radiusY;
|
||||
radXsq = std::pow(radiusX, 2);
|
||||
radYsq = std::pow(radiusY, 2);
|
||||
}
|
||||
|
||||
// Compute centre point
|
||||
double sign = (largeArc == sweep) ? -1 : 1;
|
||||
double sq = ((radXsq * radYsq) - (radXsq * y1sq) - (radYsq * x1sq)) / ((radXsq * y1sq) + (radYsq * x1sq));
|
||||
sq = (sq < 0) ? 0 : sq;
|
||||
double coefficient = sign * std::sqrt(sq);
|
||||
double cx1 = coefficient * ((radiusX * y1) / radiusY);
|
||||
double cy1 = coefficient * -((radiusY * x1) / radiusX);
|
||||
Point2 sx = (prevNode + arcEnd) / 2.0;
|
||||
double cx = sx.x + (cosRotation * cx1 - sinRotation * cy1);
|
||||
double cy = sx.y + (sinRotation * cx1 + cosRotation * cy1);
|
||||
Point2 center(cx, cy);
|
||||
|
||||
// Compute the angle start
|
||||
double ux = (x1 - cx1) / radiusX;
|
||||
double uy = (y1 - cy1) / radiusY;
|
||||
double n = std::sqrt((ux * ux) + (uy * uy));
|
||||
double p = ux;
|
||||
sign = (uy < 0) ? -1.0 : 1.0;
|
||||
double angleStart = toDegrees(sign * std::acos(p / n));
|
||||
|
||||
// Compute the angle extent
|
||||
double vx = (-x1 - cx1) / radiusX;
|
||||
double vy = (-y1 - cy1) / radiusY;
|
||||
n = std::sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
|
||||
p = ux * vx + uy * vy;
|
||||
sign = (ux * vy - uy * vx < 0) ? -1.0 : 1.0;
|
||||
double angleExtent = toDegrees(sign * std::acos(p / n));
|
||||
if (!sweep && angleExtent > 0) {
|
||||
angleExtent -= 360;
|
||||
} else if (sweep && angleExtent < 0) {
|
||||
angleExtent += 360;
|
||||
}
|
||||
|
||||
// generate bezier curves for a unit circle that covers the given arc angles
|
||||
int segmentCount = static_cast<int>(std::ceil(std::abs(angleExtent) / 90.0));
|
||||
angleStart = toRad(std::fmod(angleStart, 360.0));
|
||||
angleExtent = toRad(std::fmod(angleExtent, 360.0));
|
||||
double angleIncrement = angleExtent / segmentCount;
|
||||
double controlLength = 4.0 / 3.0 * std::sin(angleIncrement / 2.0) / (1.0 + std::cos(angleIncrement / 2.0));
|
||||
|
||||
Point2 startPoint = prevNode;
|
||||
Point2 bezEndpoint;
|
||||
Point2 controlPoint[2];
|
||||
for (int i = 0; i < segmentCount; i++) {
|
||||
double angle = angleStart + i * angleIncrement;
|
||||
double dx = std::cos(angle);
|
||||
double dy = std::sin(angle);
|
||||
controlPoint[0].x = (dx - controlLength * dy) * radiusX;
|
||||
controlPoint[0].y = (dy + controlLength * dx) * radiusY;
|
||||
controlPoint[0] = controlPoint[0].rotate(xAxisRotation) + center;
|
||||
angle += angleIncrement;
|
||||
dx = std::cos(angle);
|
||||
dy = std::sin(angle);
|
||||
controlPoint[1].x = (dx + controlLength * dy) * radiusX;
|
||||
controlPoint[1].y = (dy - controlLength * dx) * radiusY;
|
||||
controlPoint[1] = controlPoint[1].rotate(xAxisRotation) + center;
|
||||
bezEndpoint.x = dx * radiusX;
|
||||
bezEndpoint.y = dy * radiusY;
|
||||
bezEndpoint = bezEndpoint.rotate(xAxisRotation);
|
||||
bezEndpoint = bezEndpoint + center;
|
||||
if (i == segmentCount - 1) {
|
||||
// to prevent rounding errors
|
||||
bezEndpoint = arcEnd;
|
||||
}
|
||||
|
||||
contour.addEdge(new CubicSegment(startPoint, controlPoint[0], controlPoint[1], bezEndpoint));
|
||||
startPoint = bezEndpoint;
|
||||
}
|
||||
|
||||
return arcEnd;
|
||||
}
|
||||
|
||||
static bool buildFromPath(Shape &shape, const char *pathDef) {
|
||||
char nodeType;
|
||||
Point2 prevNode(0, 0);
|
||||
|
|
@ -138,7 +291,9 @@ static bool buildFromPath(Shape &shape, const char *pathDef) {
|
|||
}
|
||||
contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
|
||||
break;
|
||||
// TODO A, a
|
||||
case 'A': case 'a':
|
||||
node = handleArc(pathDef, nodeType, prevNode, contour);
|
||||
break;
|
||||
default:
|
||||
REQUIRE(false);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue