mirror of
https://github.com/hneemann/Digital.git
synced 2025-09-15 07:48:29 -04:00
added the arc path element
This commit is contained in:
parent
418d77dac5
commit
36acbb7d96
@ -196,11 +196,11 @@ public class PolygonParser {
|
||||
addQuadraticWithReflect(p, getCurrent(), nextVector());
|
||||
break;
|
||||
case 'a':
|
||||
addArc(p, nextVectorInc(), nextValue(), nextValue() != 0, nextValue() != 0, nextVectorInc());
|
||||
addArc(p, getCurrent(), nextValue(), nextValue(), nextValue(), nextValue() != 0, nextValue() != 0, nextVectorInc());
|
||||
clearControl();
|
||||
break;
|
||||
case 'A':
|
||||
addArc(p, nextVector(), nextValue(), nextValue() != 0, nextValue() != 0, nextVector());
|
||||
addArc(p, getCurrent(), nextValue(), nextValue(), nextValue(), nextValue() != 0, nextValue() != 0, nextVector());
|
||||
clearControl();
|
||||
break;
|
||||
case 'Z':
|
||||
@ -250,8 +250,102 @@ public class PolygonParser {
|
||||
return lastCubicControlPoint;
|
||||
}
|
||||
|
||||
private void addArc(Polygon p, VectorFloat rad, float rot, boolean large, boolean sweep, VectorFloat pos) {
|
||||
p.add(pos);
|
||||
/*
|
||||
* Substitutes the arc by a number of quadratic bezier curves
|
||||
*/
|
||||
//CHECKSTYLE.OFF: ParameterNumberCheck
|
||||
private void addArc(Polygon p, VectorInterface current, float rx, float ry, float rot, boolean large, boolean sweep, VectorFloat pos) {
|
||||
Transform tr = Transform.IDENTITY;
|
||||
if (rx != ry)
|
||||
tr = TransformMatrix.scale(1, rx / ry);
|
||||
|
||||
if (rot != 0)
|
||||
tr = Transform.mul(TransformMatrix.rotate(-rot), tr);
|
||||
|
||||
Transform invert = tr.invert();
|
||||
|
||||
VectorInterface p1 = current.transform(tr);
|
||||
VectorInterface p2 = pos.transform(tr);
|
||||
|
||||
// ellipse is transformed to a circle with radius r
|
||||
float r = rx;
|
||||
|
||||
double x1 = p1.getXFloat();
|
||||
double y1 = p1.getYFloat();
|
||||
double x2 = p2.getXFloat();
|
||||
double y2 = p2.getYFloat();
|
||||
|
||||
double x1q = x1 * x1;
|
||||
double y1q = y1 * y1;
|
||||
double x2q = x2 * x2;
|
||||
double y2q = y2 * y2;
|
||||
double rq = r * r;
|
||||
|
||||
double x0A = (r * (y1 - y2) * sqrt(rq * (4 * rq - y1q + y2 * (2 * y1 - y2)) - rq * (x1q - 2 * x1 * x2 + x2q)) * sign(x1 - x2) + r * (x1 + x2) * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q))) / (2 * r * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)));
|
||||
double y0A = (r * (y1 + y2) * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)) - r * sqrt(rq * (4 * rq - y1q + y2 * (2 * y1 - y2)) - rq * (x1q - 2 * x1 * x2 + x2q)) * abs(x1 - x2)) / (2 * r * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)));
|
||||
double x0B = (r * (x1 + x2) * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)) - r * (y1 - y2) * sqrt(rq * (4 * rq - y1q + y2 * (2 * y1 - y2)) - rq * (x1q - 2 * x1 * x2 + x2q)) * sign(x1 - x2)) / (2 * r * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)));
|
||||
double y0B = (r * sqrt(rq * (4 * rq - y1q + y2 * (2 * y1 - y2)) - rq * (x1q - 2 * x1 * x2 + x2q)) * abs(x1 - x2) + r * (y1 + y2) * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q))) / (2 * r * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)));
|
||||
|
||||
double startA = Math.atan2(y1 - y0A, x1 - x0A);
|
||||
double endA = Math.atan2(y2 - y0A, x2 - x0A);
|
||||
|
||||
double startB = Math.atan2(y1 - y0B, x1 - x0B);
|
||||
double endB = Math.atan2(y2 - y0B, x2 - x0B);
|
||||
|
||||
double delta = 2 * Math.PI / 12;
|
||||
if (!sweep) delta = -delta;
|
||||
|
||||
if (delta > 0) {
|
||||
if (endA < startA) endA += 2 * Math.PI;
|
||||
if (endB < startB) endB += 2 * Math.PI;
|
||||
} else {
|
||||
if (endA > startA) endA -= 2 * Math.PI;
|
||||
if (endB > startB) endB -= 2 * Math.PI;
|
||||
}
|
||||
|
||||
double sizeA = Math.abs(startA - endA);
|
||||
double sizeB = Math.abs(startB - endB);
|
||||
|
||||
double start = startA;
|
||||
double end = endA;
|
||||
double x0 = x0A;
|
||||
double y0 = y0A;
|
||||
if (large ^ (sizeA > sizeB)) {
|
||||
start = startB;
|
||||
end = endB;
|
||||
x0 = x0B;
|
||||
y0 = y0B;
|
||||
}
|
||||
|
||||
double lastStart = start;
|
||||
start += delta;
|
||||
while (delta < 0 ^ start < end) {
|
||||
addArcPoint(p, lastStart, start, x0, y0, r, invert);
|
||||
lastStart = start;
|
||||
start += delta;
|
||||
}
|
||||
addArcPoint(p, lastStart, end, x0, y0, r, invert);
|
||||
}
|
||||
//CHECKSTYLE.ON: ParameterNumberCheck
|
||||
|
||||
private void addArcPoint(Polygon p, double alpha0, double alpha1, double x0, double y0, float r, Transform tr) {
|
||||
final double mean = (alpha0 + alpha1) / 2;
|
||||
double rLong = r / Math.cos(Math.abs(alpha0 - alpha1) / 2);
|
||||
final VectorInterface c = new VectorFloat((float) (x0 + rLong * Math.cos(mean)), (float) (y0 + rLong * Math.sin(mean)));
|
||||
final VectorInterface p1 = new VectorFloat((float) (x0 + r * Math.cos(alpha1)), (float) (y0 + r * Math.sin(alpha1)));
|
||||
p.add(c.transform(tr), p1.transform(tr));
|
||||
}
|
||||
|
||||
private static double sqrt(double x) {
|
||||
return Math.sqrt(x);
|
||||
}
|
||||
|
||||
private static double sign(double x) {
|
||||
return Math.signum(x);
|
||||
}
|
||||
|
||||
private static double abs(double x) {
|
||||
return Math.abs(x);
|
||||
}
|
||||
|
||||
private void addQuadraticWithReflect(Polygon poly, VectorInterface start, VectorInterface p) {
|
||||
|
@ -28,6 +28,11 @@ public interface Transform {
|
||||
public TransformMatrix getMatrix() {
|
||||
return new TransformMatrix(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transform invert() {
|
||||
return IDENTITY;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -72,4 +77,11 @@ public interface Transform {
|
||||
* @return the transformed Transform
|
||||
*/
|
||||
TransformMatrix getMatrix();
|
||||
|
||||
/**
|
||||
* @return the inverse transform
|
||||
*/
|
||||
default Transform invert() {
|
||||
return getMatrix().invert();
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,30 @@ public class TransformMatrix implements Transform {
|
||||
|
||||
/**
|
||||
* Creates a rotation.
|
||||
* Rotates in mathematically positive direction. Takes into account that
|
||||
* in Digital the y-axis goes downwards.
|
||||
*
|
||||
* @param w the angle in 360 grad
|
||||
* @param w the angle in 360 grad units
|
||||
* @return the transformation
|
||||
*/
|
||||
public static TransformMatrix rotate(float w) {
|
||||
public static TransformMatrix rotate(double w) {
|
||||
final double phi = w / 180 * Math.PI;
|
||||
float cos = (float) Math.cos(phi);
|
||||
float sin = (float) Math.sin(phi);
|
||||
return new TransformMatrix(cos, sin, -sin, cos, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a scaling transformation
|
||||
*
|
||||
* @param sx scaling in x direction
|
||||
* @param sy scaling in y direction
|
||||
* @return the transformation
|
||||
*/
|
||||
public static TransformMatrix scale(float sx, float sy) {
|
||||
return new TransformMatrix(sx, 0, 0, sy, 0, 0);
|
||||
}
|
||||
|
||||
final float a;
|
||||
final float b;
|
||||
final float c;
|
||||
@ -66,7 +79,8 @@ public class TransformMatrix implements Transform {
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a direction vector
|
||||
* Transforms a direction vector.
|
||||
* Ignores the translation part of the transformation.
|
||||
*
|
||||
* @param v the vector to transform
|
||||
* @return the transformed vector
|
||||
@ -82,4 +96,37 @@ public class TransformMatrix implements Transform {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the inverse transformation.
|
||||
*
|
||||
* @return the inverse transformation.
|
||||
*/
|
||||
public TransformMatrix invert() {
|
||||
float q = a * d - b * c;
|
||||
|
||||
return new TransformMatrix(d / q, -b / q, -c / q, a / q,
|
||||
(b * y - d * x) / q, (c * x - a * y) / q);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -54,4 +54,9 @@ public class TransformTranslate implements Transform {
|
||||
public TransformMatrix getMatrix() {
|
||||
return new TransformMatrix(1, 0, 0, 1, trans.getXFloat(), trans.getYFloat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transform invert() {
|
||||
return new TransformTranslate(trans.div(-1));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
package de.neemann.digital.draw.graphics;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class TransformMatrixTest extends TestCase {
|
||||
|
||||
public void testScale() {
|
||||
TransformMatrix tr = TransformMatrix.scale(2, 3);
|
||||
VectorInterface p = new VectorFloat(3, 4).transform(tr);
|
||||
assertEquals(6, p.getXFloat(), 1e-4);
|
||||
assertEquals(12, p.getYFloat(), 1e-4);
|
||||
}
|
||||
|
||||
public void testRotate() {
|
||||
TransformMatrix tr = TransformMatrix.rotate(45);
|
||||
VectorInterface p = new VectorFloat(2, 0).transform(tr);
|
||||
assertEquals(Math.sqrt(2), p.getXFloat(), 1e-4);
|
||||
assertEquals(-Math.sqrt(2), p.getYFloat(), 1e-4);
|
||||
}
|
||||
|
||||
public void testRotateInverse() {
|
||||
Transform tr = new TransformRotate(new Vector(2,3),1);
|
||||
VectorInterface p = new VectorFloat(7, 8);
|
||||
|
||||
VectorInterface t = p.transform(tr).transform(tr.invert());
|
||||
assertEquals(p.getXFloat(), t.getXFloat(), 1e-4);
|
||||
assertEquals(p.getYFloat(), t.getYFloat(), 1e-4);
|
||||
}
|
||||
|
||||
public void testInverse() {
|
||||
TransformMatrix tr = new TransformMatrix(1, 2, 3, 4, 5, 6);
|
||||
VectorInterface p = new VectorFloat(7, 8);
|
||||
|
||||
VectorInterface t = p.transform(tr).transform(tr.invert());
|
||||
assertEquals(p.getXFloat(), t.getXFloat(), 1e-4);
|
||||
assertEquals(p.getYFloat(), t.getYFloat(), 1e-4);
|
||||
}
|
||||
|
||||
}
|
@ -216,7 +216,7 @@ public class SvgImporterTest extends TestCase {
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testScale() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
public void testScale() throws IOException, SvgException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<g transform=\"scale(2,3)\">\n" +
|
||||
@ -230,6 +230,78 @@ public class SvgImporterTest extends TestCase {
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testArc() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 0,0 L 40,0 A 31,80,30,0,0,100,0 L 140,0\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 40,0 Q 37.161327,16.337067 40.99908,25.612656 Q 44.83683,34.888245 54.32269,34.616966 Q 63.80854,34.345688 76.40078,24.600235 Q 88.27738,15.408623 100,0 L 140,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testArc2() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 0,0 L 40,0 A 31,80,30,1,0,100,0 L 140,0\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 40,0 Q 27.571037,16.337067 18.5496,35.35811 Q 9.528162,54.37915 6.331539,70.987495 Q 3.1349144,87.59584 6.6196365,97.3413 Q 10.104355,107.086754 19.336689,107.358025 Q 28.569027,107.6293 41.075184,98.35372 Q 53.581345,89.078125 66.0103,72.74106 Q 78.43926,56.403996 87.46069,37.38295 Q 96.48214,18.36191 99.67876,1.7535629 Q 99.84891,0.86956406 100,0 L 140,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testArc3() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 0,0 L 40,0 A 31,80,30,0,1,100,0 L 140,0\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 40,0 Q 52.42896,-16.33707 64.93511,-25.612656 Q 77.44127,-34.88825 86.67361,-34.616966 Q 95.905945,-34.345688 99.39067,-24.600235 Q 102.67735,-15.408623 100,0 L 140,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testArc4() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 0,0 L 40,0 A 31,80,30,1,1,100,0 L 140,0\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 40,0 Q 42.83867,-16.337069 51.593147,-35.35811 Q 60.347626,-54.379158 72.67216,-70.987495 Q 84.99669,-87.59584 97.58891,-97.3413 Q 110.18115,-107.086754 119.66701,-107.35804 Q 129.15286,-107.62932 132.99062,-98.353714 Q 136.82837,-89.07814 133.98969,-72.74107 Q 131.15103,-56.403996 122.396545,-37.382957 Q 113.64207,-18.361908 101.317535,-1.7535667 Q 100.661545,-0.86956406 100,0 L 140,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testInkscape1() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"m 40,-40 h 20 c 11.08,0 20,8.92 20,20 V 20 C 80,31.08 71.08,40 60,40 H 40 C 28.92,40 20,31.08 20,20 v -40 c 0,-11.08 8.92,-20 20,-20 z\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 40,-40 L 60,-40 C 71.08,-40 80,-31.08 80,-20 L 80,20 C 80,31.08 71.08,40 60,40 L 40,40 C 28.92,40 20,31.08 20,20 L 20,-20 C 20,-31.08 28.92,-40 40,-40 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testInkscape2() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 80,0 A 40,40 0 0 1 58.083689,35.678848 40,40 0 0 1 16.350991,32.26026 L 40,0 Z\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 80,0 Q 80,10.717968 74.641014,20 Q 69.282036,29.282032 60,34.641018 Q 59.05599,35.186043 58.08369,35.67885 Q 48.52357,40.52436 37.82151,39.940636 Q 27.119452,39.35691 18.143057,33.500362 Q 17.230127,32.904728 16.35099,32.26026 L 40,0 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
|
||||
//*****************************************************************************************************
|
||||
|
||||
@ -238,12 +310,12 @@ public class SvgImporterTest extends TestCase {
|
||||
return new ByteArrayInputStream(s.getBytes());
|
||||
}
|
||||
|
||||
public static class CSDChecker {
|
||||
private static class CSDChecker {
|
||||
private final CustomShapeDescription csd;
|
||||
private final ArrayList<Checker> checker;
|
||||
private final ArrayList<TestPin> pins;
|
||||
|
||||
public CSDChecker(CustomShapeDescription csd) {
|
||||
private CSDChecker(CustomShapeDescription csd) {
|
||||
this.csd = csd;
|
||||
this.checker = new ArrayList<>();
|
||||
this.pins = new ArrayList<>();
|
||||
@ -276,26 +348,26 @@ public class SvgImporterTest extends TestCase {
|
||||
fail("not enough elements found in the csd");
|
||||
}
|
||||
|
||||
public CSDChecker checkPolygon(String s) throws PolygonParser.ParserException {
|
||||
private CSDChecker checkPolygon(String s) throws PolygonParser.ParserException {
|
||||
checker.add(new CheckPolygon(new PolygonParser(s).create()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public CSDChecker checkPolygon(String s, boolean evenOdd) throws PolygonParser.ParserException {
|
||||
private CSDChecker checkPolygon(String s, boolean evenOdd) throws PolygonParser.ParserException {
|
||||
checker.add(new CheckPolygon(new PolygonParser(s).create().setEvenOdd(evenOdd)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public CSDChecker checkPin(int x, int y, String name, boolean showLabel) {
|
||||
private CSDChecker checkPin(int x, int y, String name, boolean showLabel) {
|
||||
pins.add(new TestPin(x, y, name, showLabel));
|
||||
return this;
|
||||
}
|
||||
|
||||
public CSDChecker checkLine(int x1, int y1, int x2, int y2) {
|
||||
private CSDChecker checkLine(int x1, int y1, int x2, int y2) {
|
||||
checker.add(new Checker() {
|
||||
@Override
|
||||
public void check(Drawable d) {
|
||||
assertTrue(d instanceof CustomShapeDescription.LineHolder);
|
||||
assertTrue("element is no line", d instanceof CustomShapeDescription.LineHolder);
|
||||
CustomShapeDescription.LineHolder l = (CustomShapeDescription.LineHolder) d;
|
||||
assertEquals(x1, l.getP1().x);
|
||||
assertEquals(y1, l.getP1().y);
|
||||
@ -312,7 +384,7 @@ public class SvgImporterTest extends TestCase {
|
||||
private final String name;
|
||||
private final boolean showLabel;
|
||||
|
||||
public TestPin(int x, int y, String name, boolean showLabel) {
|
||||
private TestPin(int x, int y, String name, boolean showLabel) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.name = name;
|
||||
@ -329,7 +401,7 @@ public class SvgImporterTest extends TestCase {
|
||||
|
||||
private final Polygon should;
|
||||
|
||||
public CheckPolygon(Polygon should) {
|
||||
private CheckPolygon(Polygon should) {
|
||||
this.should = should;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user