87 lines
3.6 KiB
C#
87 lines
3.6 KiB
C#
using System;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Shapes;
|
|
|
|
namespace XP.ImageProcessing.RoiControl.Models
|
|
{
|
|
/// <summary>一次角度测量的所有视觉元素(顶点V + 射线端点A/B + 两条射线 + 弧线 + 标签)</summary>
|
|
public class AngleGroup
|
|
{
|
|
public Ellipse DotV { get; set; } // 顶点
|
|
public Ellipse DotA { get; set; } // 射线端点A
|
|
public Ellipse DotB { get; set; } // 射线端点B
|
|
public Line LineA { get; set; } // 射线VA
|
|
public Line LineB { get; set; } // 射线VB
|
|
public Path Arc { get; set; } // 角度弧线
|
|
public TextBlock Label { get; set; }
|
|
public Point V { get; set; }
|
|
public Point A { get; set; }
|
|
public Point B { get; set; }
|
|
|
|
public double AngleDeg
|
|
{
|
|
get
|
|
{
|
|
double vax = A.X - V.X, vay = A.Y - V.Y;
|
|
double vbx = B.X - V.X, vby = B.Y - V.Y;
|
|
double lenA = Math.Sqrt(vax * vax + vay * vay);
|
|
double lenB = Math.Sqrt(vbx * vbx + vby * vby);
|
|
if (lenA < 0.001 || lenB < 0.001) return 0;
|
|
double dot = vax * vbx + vay * vby;
|
|
double cos = Math.Clamp(dot / (lenA * lenB), -1.0, 1.0);
|
|
return Math.Acos(cos) * 180.0 / Math.PI;
|
|
}
|
|
}
|
|
|
|
public void UpdateVisuals()
|
|
{
|
|
// 射线
|
|
LineA.X1 = V.X; LineA.Y1 = V.Y; LineA.X2 = A.X; LineA.Y2 = A.Y;
|
|
LineA.Visibility = Visibility.Visible;
|
|
LineB.X1 = V.X; LineB.Y1 = V.Y; LineB.X2 = B.X; LineB.Y2 = B.Y;
|
|
LineB.Visibility = Visibility.Visible;
|
|
|
|
// 弧线
|
|
double vax = A.X - V.X, vay = A.Y - V.Y;
|
|
double vbx = B.X - V.X, vby = B.Y - V.Y;
|
|
double lenA = Math.Sqrt(vax * vax + vay * vay);
|
|
double lenB = Math.Sqrt(vbx * vbx + vby * vby);
|
|
|
|
if (lenA < 1 || lenB < 1) { Arc.Visibility = Visibility.Collapsed; return; }
|
|
|
|
double arcRadius = Math.Min(30, Math.Min(lenA, lenB) * 0.3);
|
|
if (arcRadius < 8) arcRadius = 8;
|
|
|
|
double angleARad = Math.Atan2(vay, vax);
|
|
double angleBRad = Math.Atan2(vby, vbx);
|
|
double cross = vax * vby - vay * vbx;
|
|
double angleDeg = AngleDeg;
|
|
bool isLargeArc = angleDeg > 180;
|
|
var sweepDir = cross >= 0 ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
|
|
|
|
var startPt = new Point(V.X + arcRadius * Math.Cos(angleARad), V.Y + arcRadius * Math.Sin(angleARad));
|
|
var endPt = new Point(V.X + arcRadius * Math.Cos(angleBRad), V.Y + arcRadius * Math.Sin(angleBRad));
|
|
|
|
var arcSeg = new ArcSegment(endPt, new Size(arcRadius, arcRadius), 0, isLargeArc, sweepDir, true);
|
|
var fig = new PathFigure(startPt, new[] { arcSeg }, false);
|
|
Arc.Data = new PathGeometry(new[] { fig });
|
|
Arc.Visibility = Visibility.Visible;
|
|
|
|
// 标签位置
|
|
double midAngle = (angleARad + angleBRad) / 2.0;
|
|
double testX = Math.Cos(midAngle), testY = Math.Sin(midAngle);
|
|
double testCross = vax * testY - vay * testX;
|
|
if ((cross >= 0 && testCross < 0) || (cross < 0 && testCross >= 0))
|
|
midAngle += Math.PI;
|
|
|
|
double labelDist = arcRadius + 16;
|
|
Label.Text = $"{angleDeg:F1}°";
|
|
Canvas.SetLeft(Label, V.X + labelDist * Math.Cos(midAngle) - 15);
|
|
Canvas.SetTop(Label, V.Y + labelDist * Math.Sin(midAngle) - 8);
|
|
Label.Visibility = Visibility.Visible;
|
|
}
|
|
}
|
|
}
|