我有4个点... 我可以使用这段代码绘制多边形
var p = new Polygon();
p.Points.Add(new Point(0, 0));
p.Points.Add(new Point(70, 0));
p.Points.Add(new Point(90, 100));
p.Points.Add(new Point(0, 80));
如何使用Silverlight绘制适合此多边形的“椭圆”?
我有4个点... 我可以使用这段代码绘制多边形
var p = new Polygon();
p.Points.Add(new Point(0, 0));
p.Points.Add(new Point(70, 0));
p.Points.Add(new Point(90, 100));
p.Points.Add(new Point(0, 80));
如何使用Silverlight绘制适合此多边形的“椭圆”?
一种方法是使用 QuadraticBezierSegment
或者 BezierSegment
。
例如,像这样:
<Path Stroke="Red" StrokeThickness="2" >
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0,40">
<PathFigure.Segments>
<PathSegmentCollection>
<BezierSegment Point1="0,93"
Point2="90,117"
Point3="80,50"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="2" >
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0,40">
<PathFigure.Segments>
<PathSegmentCollection>
<BezierSegment Point1="0,-13"
Point2="70,-17"
Point3="80,50"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Polygon Points="0,0 70,0 90,100 0,80"></Polygon>
这不是完美的解决方案,要想得到精确的结果,您必须计算曲线上的精确点并使用4个QuadraticBezierSegment
编辑:
QuadraticBezierSegment
的示例
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0,40">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="6,79"
Point2="45,90"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="45,90">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="80,91"
Point2="80,50"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0,40">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="2,3"
Point2="35,0"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="35,0">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="72,8"
Point2="80,50"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Polygon Name="pol" Points="0,0 70,0 90,100 0,80" Stroke="Red" StrokeThickness="1"</Polygon>
这还是一个实验性的算法,不过它非常准确。
编辑2: 您可以使用此图像和我的注释计算曲线的点:
曲线有起始点、中间点和终点。在此图像中,起点和终点为L、M、N、O
;中间点为W、X、Y、Z
。
例如,我们如何计算点L
:
通过直线方程y = k * x + b
找到直线AB、DC、AC、DB、AD
的方程。交叉点AC
和DB
得到R
。交叉点AB
和DC
得到E
。然后找到直线ER
的方程,交叉点ER
和AD
得到L
。
我们如何计算点W
:
通过长度公式l = sqrt(sqr(x2 - x1) + sqr(y2 - y1))
找到AR
的长度。使用公式AW = AR/(4*pi)
和此系数,以及直线方程和长度公式,解方程组后得到W
。
其他点的计算方式类似。
这个算法不仅适用于具有平行线的多边形,而且在这种情况下,算法更加简单。长度系数也是相同的。
通过这个算法,我找到了您示例中3条曲线的点:
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0,36">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="4.7,74.6"
Point2="39.9,88.9"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="39.9,88.9">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="83.43,92.7"
Point2="78.8,43.9"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0,36">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment Point1="3.55,3.94"
Point2="31.8,0"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
这个曲线和椭圆线完全相同。如下图:
您可以将此算法转化为“公式”,从而找到您的解决方案。
您需要4个函数来完成这个过程:
1)从2个点的坐标中查找直线系数
2)从它的系数中找到与2条线交叉的点的坐标
3)从2个点的坐标中找到线段的长度
4)从该起始点的系数和坐标以及您在前一个函数中找到的长度(除以(4*pi))中找到位于该直线上的点的坐标
Edit3: 您可以优化这个解决方案,它有一些缺陷,比如平行线等。但它很快,如果您优化它可能满足您的要求。
Xaml:
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathleftdown" StartPoint="0,0">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment x:Name="bezleftdown" Point1="0,0"
Point2="0,0"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathrigthdown" StartPoint="0,0">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment x:Name="bezrigthdown" Point1="0,0"
Point2="0,0"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathleftup" StartPoint="0,0">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment x:Name="bezleftup" Point1="0,0"
Point2="0,0"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathrigthup" StartPoint="0,0">
<PathFigure.Segments>
<PathSegmentCollection>
<QuadraticBezierSegment x:Name="bezrigthup" Point1="0,0"
Point2="0,0"
/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Polygon Name="pol" Points="0,0 250,0 251,340 0,341" Stroke="Red" StrokeThickness="1"></Polygon>
<Button Content="Generate" Width ="80" Height="30" HorizontalAlignment="Right" VerticalAlignment="Top" Click="Button_Click"></Button>
和代码:
private class pointXY
{
public double x;
public double y;
}
private class lineKB
{
public double k;
public double b;
public bool flagXconst = false;
public double xConst = 0;
}
private lineKB GetLineFromPonts(pointXY A, pointXY B)
{
lineKB line = new lineKB();
if ((B.x - A.x) != 0)
{
line.k = (B.y - A.y) / (B.x - A.x);
line.b = A.y - A.x * line.k;
}
else
{
line.xConst = A.x;
line.flagXconst = true;
}
return line;
}
private pointXY GetPointFromLines(lineKB a, lineKB b)
{
pointXY point = new pointXY();
if (a.flagXconst)
{
point.x = a.xConst;
point.y = a.xConst * b.k + b.b;
}else
if (b.flagXconst)
{
point.x = b.xConst;
point.y = b.xConst * a.k + a.b;
}
else
{
point.x = (a.b - b.b) / (b.k - a.k);
point.y = a.k * point.x + a.b;
}
return point;
}
private double LengthOfLine(pointXY A, pointXY B)
{
return Math.Sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y));
}
private pointXY GetMidlePoint(pointXY S, double l, lineKB line, bool leftright)
{
double b = -2 * S.x - 2 * line.k * (-line.b + S.y);
double a = (1 + line.k * line.k);
double c = (S.x * S.x - l * l + (-line.b + S.y) * (-line.b + S.y));
double d = b*b - 4 * a * c;
double x1 = (-b + Math.Sqrt(d)) / (2 * a);
double x2 = (-b - Math.Sqrt(d)) / (2 * a);
pointXY ret = new pointXY();
if (leftright)
if (x1 > S.x) ret.x = x1;
else ret.x = x2;
else
if (x1 < S.x) ret.x = x1;
else ret.x = x2;
ret.y = line.k * ret.x + line.b;
return ret;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
pointXY A = new pointXY();
A.x = pol.Points[0].X;
A.y = pol.Points[0].Y;
pointXY B = new pointXY();
B.x = pol.Points[1].X;
B.y = pol.Points[1].Y;
pointXY C = new pointXY();
C.x = pol.Points[2].X;
C.y = pol.Points[2].Y;
pointXY D = new pointXY();
D.x = pol.Points[3].X;
D.y = pol.Points[3].Y;
lineKB AC = GetLineFromPonts(A, C);
lineKB BD = GetLineFromPonts(B, D);
pointXY R = GetPointFromLines(AC, BD);
lineKB AB = GetLineFromPonts(A, B);
lineKB BC = GetLineFromPonts(B, C);
lineKB CD = GetLineFromPonts(C, D);
lineKB DA = GetLineFromPonts(D, A);
pointXY E = GetPointFromLines(AB, CD);
lineKB ER = GetLineFromPonts(E, R);
pointXY L = GetPointFromLines(ER, DA);
pointXY N = GetPointFromLines(ER, BC);
pointXY F = GetPointFromLines(BC, DA);
lineKB FR = GetLineFromPonts(F, R);
pointXY M = GetPointFromLines(FR, AB);
pointXY O = GetPointFromLines(FR, CD);
pointXY W = GetMidlePoint(A, (LengthOfLine(A, R) / (4 * Math.PI)), AC, true);
pointXY X = GetMidlePoint(B, (LengthOfLine(B, R) / (4 * Math.PI)), BD, false);
pointXY Y = GetMidlePoint(C, (LengthOfLine(C, R) / (4 * Math.PI)), AC, false);
pointXY Z = GetMidlePoint(D, (LengthOfLine(D, R) / (4 * Math.PI)), BD, true);
pathleftup.StartPoint = new Point(L.x, L.y);
bezleftup.Point1 = new Point(W.x, W.y);
bezleftup.Point2 = new Point(M.x, M.y);
pathleftdown.StartPoint = new Point(L.x, L.y);
bezleftdown.Point1 = new Point(Z.x, Z.y);
bezleftdown.Point2 = new Point(O.x, O.y);
pathrigthdown.StartPoint = new Point(O.x, O.y);
bezrigthdown.Point1 = new Point(Y.x, Y.y);
bezrigthdown.Point2 = new Point(N.x, N.y);
pathrigthup.StartPoint = new Point(M.x, M.y);
bezrigthup.Point1 = new Point(X.x, X.y);
bezrigthup.Point2 = new Point(N.x, N.y);
}
根据Jeff M提供的信息,我创建了一个函数,该函数返回适合于多边形内的椭圆:
Ellipse FitEllipse(Polygon poly)
{
double W0 = poly.Points[0].X;
double W1 = poly.Points[0].Y;
double X0 = poly.Points[1].X;
double X1 = poly.Points[1].Y;
double Y0 = poly.Points[2].X;
double Y1 = poly.Points[2].Y;
double Z0 = poly.Points[3].X;
double Z1 = poly.Points[3].Y;
double A = X0 * Y0 * Z1 - W0 * Y0 * Z1 - X0 * Y1 * Z0 + W0 * Y1 * Z0 - W0 * X1 * Z0 + W1 * X0 * Z0 + W0 * X1 * Y0 - W1 * X0 * Y0;
double B = W0 * Y0 * Z1 - W0 * X0 * Z1 - X0 * Y1 * Z0 + X1 * Y0 * Z0 - W1 * Y0 * Z0 + W1 * X0 * Z0 + W0 * X0 * Y1 - W0 * X1 * Y0;
double C = X0 * Y0 * Z1 - W0 * X0 * Z1 - W0 * Y1 * Z0 - X1 * Y0 * Z0 + W1 * Y0 * Z0 + W0 * X1 * Z0 + W0 * X0 * Y1 - W1 * X0 * Y0;
double D = X1 * Y0 * Z1 - W1 * Y0 * Z1 - W0 * X1 * Z1 + W1 * X0 * Z1 - X1 * Y1 * Z0 + W1 * Y1 * Z0 + W0 * X1 * Y1 - W1 * X0 * Y1;
double E = -X0 * Y1 * Z1 + W0 * Y1 * Z1 + X1 * Y0 * Z1 - W0 * X1 * Z1 - W1 * Y1 * Z0 + W1 * X1 * Z0 + W1 * X0 * Y1 - W1 * X1 * Y0;
double F = X0 * Y1 * Z1 - W0 * Y1 * Z1 + W1 * Y0 * Z1 - W1 * X0 * Z1 - X1 * Y1 * Z0 + W1 * X1 * Z0 + W0 * X1 * Y1 - W1 * X1 * Y0;
double G = X0 * Z1 - W0 * Z1 - X1 * Z0 + W1 * Z0 - X0 * Y1 + W0 * Y1 + X1 * Y0 - W1 * Y0;
double H = Y0 * Z1 - X0 * Z1 - Y1 * Z0 + X1 * Z0 + W0 * Y1 - W1 * Y0 - W0 * X1 + W1 * X0;
double I = Y0 * Z1 - W0 * Z1 - Y1 * Z0 + W1 * Z0 + X0 * Y1 - X1 * Y0 + W0 * X1 - W1 * X0;
double detT = A * E * I + B * F * G + C * D * H - A * F * H - B * D * I - C * E * G;
double J = (E * I - F * H) / detT;
double K = (C * H - B * I) / detT;
double L = (B * F - C * E) / detT;
double M = (F * G - D * I) / detT;
double N = (A * I - C * G) / detT;
double O = (C * D - A * F) / detT;
double P = (D * H - E * G) / detT;
double Q = (B * G - A * H) / detT;
double R = (A * E - B * D) / detT;
double a = J * J + M * M + P * P;
double b = J * K + M * N - P * Q;
double c = K * K + N * N - Q * Q;
double d = J * L + M * O - P * R;
double f = K * L + N * O - Q * R;
double g = L * L + O * O - R * R;
double Ex = (c * d - b * f) / (b * b - a * c);
double Ey = (a * f - b * d) / (b * b - a * c);
double Ea = Math.Sqrt(2.0 * (a * f * f + c * d * d + g * b * b - 2.0 * b * d * f - a * c * g) / ((b * b - a * c) * (Math.Sqrt((a - c) * (a - c) + 4.0 * b * b) - (a + c))));
double Eb = Math.Sqrt(2.0 * (a * f * f + c * d * d + g * b * b - 2.0 * b * d * f - a * c * g) / ((a * c - b * b) * (Math.Sqrt((a - c) * (a - c) + 4.0 * b * b) + (a + c))));
double phi = 0;
if (b == 0 && a < c) {
phi = 0;
} else if (b == 0 && a > c) {
phi = Math.PI / 2;
} else if (b != 0 && a < c) {
phi = (Math.PI / 2 - Math.Atan((a - c) / (2 * b))) / 2;
} else if (b != 0 && a > c) {
phi = (Math.PI / 2 - Math.Atan((a - c) / (2 * b))) / 2 + Math.PI / 2;
}
Ellipse el = new Ellipse();
el.Height = Ea * 2;
el.Width = Eb * 2;
el.RenderTransform = new RotateTransform(phi * 180 / Math.PI);
return el;
}
虽然使用四条Bezier曲线就可以很好地解决这个问题(而且可能是最简单的解决方案),但我在这里提出了一种不同的方法,仅仅是为了好玩。 :-)
这样考虑你的问题:给定一个常规的矩形,在内部画一个椭圆,然后将矩形“变形”为最终形状。
我认为你的变形转换不是线性的,因此你可能不能简单地找到一个矩阵来表示它(注意:我可能是错的,并且很想被证明是错的;这里有数学专家吗?)。 请参见下面的编辑。
其中一种方法是:首先画出椭圆,然后逐个拉伸/压缩四个角落之一。通过仅变形形状的一个角落,您始终可以保持左右两个角之间的对角线不变地插值椭圆曲线上的任何点到其新位置。
编辑:变换
从正方形开始(并且有一个内部的圆)。
请注意,所有四个变换都是纯线性或仿射(即线性+平移),因此它们均可用变换矩阵来表示。最终结果是另一种仿射变换,也可以由矩阵表示。
因此,这个矩阵将圆形形状转换为新形状。
希望我的数学没有错误...
卡斯滕的代码中有一个小错误
} else if (b != 0 && a < c) {
phi = (Math.PI / 2 - Math.Atan((a - c) / (2 * b))) / 2;
} else if (b != 0 && a > c) {
当a==c时,它返回phi = 0,这是不正确的。相反,上述行应该是
} else if (b != 0 && a < c) {
phi = (Math.PI / 2 - Math.Atan((a - c) / (2 * b))) / 2;
} else if (b != 0 && a >= c) {
或者
} else if (b != 0 && a <= c) {
phi = (Math.PI / 2 - Math.Atan((a - c) / (2 * b))) / 2;
} else if (b != 0 && a > c) {