939 lines
29 KiB
C#
Raw Normal View History

2025-03-16 17:32:09 +08:00
using System;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using CanFly.Canvas.Helper;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Net.NetworkInformation;
using System.Drawing;
namespace CanFly.Canvas.Shape
{
[Serializable]
public class FlyShape
{
private const float DFT_VTX_EPSILON = 4f;
private float _epsilon = DFT_VTX_EPSILON;
public float LineWidth { get; set; } = 2f;
#region Shape颜色
#region drawing
public Color line_color = Color.FromArgb(128, 0, 255, 0);
public Color fill_color = Color.FromArgb(64, 0, 0, 0);
public Color vertex_fill_color = Color.FromArgb(255, 0, 255, 0);
#endregion
#region selecting / hovering
public Color select_line_color = Color.FromArgb(255, 255, 255, 255);
public Color select_fill_color = Color.FromArgb(64, 0, 255, 0);
public Color hvertex_fill_color = Color.FromArgb(255, 255, 255, 255);
#endregion
#endregion
private PointTypeEnum point_type = PointTypeEnum.ROUND;
private float point_size = 8.0f;
private float _scale = 1.0f;
private float scale
{
get
{
return _scale;
}
set
{
_scale = value;
}
}
private ShapeTypeEnum _shape_type;
private Matrix _matrix = new Matrix();
public ShapeTypeEnum ShapeType
{
get => _shape_type;
set { _shape_type = value; }
}
public string label = "";
public int? group_id = null;
private List<PointF> _points
{
get;
set;
} = new List<PointF>();
public List<PointF> Points
{
get { return _points; }
set
{
this._points = value;
}
}
private List<PointF> _pointsRaw = new List<PointF>();
/// <summary>
/// 辅助节点
/// </summary>
public List<PointF> GuidePoints = new List<PointF>();
public float _currentRotateAngle;
private bool _isRotating = false;
public List<int> point_labels = new List<int>();
private ShapeTypeEnum _shape_type_raw;
/// <summary>
2025-03-18 14:20:11 +08:00
/// 是否填充多边形 使用select_fill_color 或 fill_color 填充
2025-03-16 17:32:09 +08:00
/// </summary>
public bool fill = false;
public bool Selected { get; set; } = false;
public object? flags;
public string description = "";
private List<object> other_data = new List<object>();
private int _highlightIndex = -1;
private HighlightModeEnum _highlightMode = HighlightModeEnum.NEAR_VERTEX;
private Dictionary<HighlightModeEnum, HighlightSetting> _highlightSettings = new Dictionary<HighlightModeEnum, HighlightSetting>()
{
{ HighlightModeEnum.NEAR_VERTEX,new HighlightSetting(4,PointTypeEnum.ROUND)},
{ HighlightModeEnum.MOVE_VERTEX,new HighlightSetting(1.5f,PointTypeEnum.SQUARE)},
};
private bool _closed = false;
private Color _vertex_fill_color;
/// <summary>
/// 当图形是Line时是否绘制辅助矩形框
/// </summary>
public bool IsDrawLineVirtualRect { get; set; } = false;
/// <summary>
/// 画Line时辅助矩形的宽度
/// </summary>
public float LineVirtualRectWidth = 40;
public PointF[] LineVirtualRectPoints = new PointF[4];
public FlyShape()
{
}
private PointF ScalePoint(PointF point)
{
return point;
//return new PointF(
// (float)(point.X * scale),
// (float)(point.Y * scale));
}
public void Close()
{
this._closed = true;
}
public void AddPoint(PointF point, int label = 1)
{
if (_points != null && _points.Count > 0 && point.Equals(_points.ElementAt(0)))
{
Close();
}
else
{
if (_points.Count > 0 && this[-1].Equals(point))
{
return;
}
_points.Add(point);
point_labels.Add(label);
}
}
public bool CanAddPoint()
{
return ShapeType == ShapeTypeEnum.Polygon
|| ShapeType == ShapeTypeEnum.LineStrip;
}
public PointF? PopPoint()
{
if (_points != null && _points.Count > 0)
{
if (point_labels != null && point_labels.Count > 0)
{
point_labels.RemoveAt(point_labels.Count - 1);
}
PointF lastPoint = _points[_points.Count - 1];
_points.RemoveAt(_points.Count - 1);
return lastPoint;
}
return null;
}
public void InsertPoint(int i, PointF point, int label = 1)
{
_points.Insert(i, point);
point_labels.Insert(i, label);
}
public void RemovePoint(int i)
{
if (!CanAddPoint())
{
return;
}
if (ShapeType == ShapeTypeEnum.Polygon && _points.Count <= 3)
{
return;
}
else if (ShapeType == ShapeTypeEnum.LineStrip && _points.Count <= 2)
{
return;
}
_points.RemoveAt(_points.Count - 1);
point_labels.RemoveAt(point_labels.Count - 1);
}
public bool IsClosed() => _closed;
public void SetOpen() { _closed = false; }
#region
/// <summary>
/// 矩形模式下,选中的点索引
/// </summary>
[JsonIgnore]
private int _rectSelectedVertex = -1;
[JsonIgnore]
private PointF _rectSelectedMoveVertex;
[JsonIgnore]
private PointF _rectCenterPoint;
[JsonIgnore]
private bool isVertexMoving;
/// <summary>
/// 正在移动节点
/// </summary>
[JsonIgnore]
public bool IsVertexMoving
{
get
{
return isVertexMoving;
}
internal set
{
//float centerX = (_points[0].X + _points[2].X) / 2f;
//float centerY = (_points[0].Y + _points[2].Y) / 2f;
//_rectCenterVertex = new PointF(centerX, centerY);
isVertexMoving = value;
}
}
//private PointF[] TransformPoints(List<PointF> points, PointF center, float angle)
//{
// PointF[] ptsArray = points.ToArray();
// using (Matrix matrix = new Matrix())
// {
// matrix.RotateAt(angle, center);
// matrix.TransformPoints(ptsArray);
// }
// return ptsArray;
//}
//GraphicsPath vrtx_path = new GraphicsPath();
#endregion
public void Paint(Graphics painter)
{
if (_points == null || _points.Count == 0)
{
return;
}
Color color = Selected ? select_line_color : line_color;
using Pen pen = new Pen(color, LineWidth);
// Create paths
GraphicsPath line_path = new GraphicsPath();
GraphicsPath vrtx_path = new GraphicsPath();
GraphicsPath guide_vrtx_path = new GraphicsPath();
switch (ShapeType)
{
//case ShapeTypeEnum.Rectangle:
// {
// if (_points.Count == 2)
// {
// float centerX = (_points[0].X + _points[1].X) / 2f;
// float centerY = (_points[0].Y + _points[1].Y) / 2f;
// _rectCenterPoint = new PointF(centerX, centerY);
// line_path.StartFigure();
// if (_points[1].X < _points[0].X)
// {
// _points[1] = new PointF(_points[0].X + LineWidth / 2f, _points[1].Y);
// }
// if (_points[1].Y < _points[0].Y)
// {
// _points[1] = new PointF(_points[1].X, _points[0].Y + LineWidth / 2f);
// }
// //float x = Math.Min(_points[0].X, _points[1].X);
// //float y = Math.Min(_points[0].Y, _points[1].Y);
// float w = Math.Abs(ScalePoint(_points[1]).X - ScalePoint(_points[0]).X);
// float h = Math.Abs(ScalePoint(_points[1]).Y - ScalePoint(_points[0]).Y);
// RectangleF drawRect = new(new PointF(_points[0].X, _points[0].Y), new SizeF(w, h));
// bool bRotated = false;
// NomalizeRotateAngle();
// Matrix oMatrix = null;
// if (_currentRotateAngle > 0)
// {
// // Create rotation matrix
// oMatrix = new Matrix();
// oMatrix.RotateAt(_currentRotateAngle, _rectCenterPoint, MatrixOrder.Append);
// painter.Transform = oMatrix;
// bRotated = true;
// }
// //Store rectangle region
// //Region _drawRectRegion = new Region(drawRect);
// if (oMatrix != null)
// line_path.Transform(oMatrix);
// line_path.AddRectangle(drawRect);
// // Reset transform
// if (bRotated)
// {
// bRotated = false;
// painter.ResetTransform();
// }
// //_matrix.Reset();
// //_matrix.RotateAt(_currentRotateAngle, new PointF(
// // (_points[0].X + _points[1].X) / 2,
// // (_points[0].Y + _points[1].Y) / 2));
// //line_path.Transform(_matrix);
// //line_path.AddPolygon(_pointsRaw.ToArray());
// }
// if (_regionVertex.Length != _points.Count)
// {
// _regionVertex = new Region[_points.Count];
// }
// for (int i = 0; i < _points.Count; i++)
// {
// DrawVertex(vrtx_path, i);
// }
// vrtx_path.Transform(_matrix);
// }
// break;
case ShapeTypeEnum.Rectangle:
{
if (_points.Count == 2)
{
float centerX = (_points[0].X + _points[1].X) / 2f;
float centerY = (_points[0].Y + _points[1].Y) / 2f;
_rectCenterPoint = new PointF(centerX, centerY);
line_path.StartFigure();
if (_points[1].X < _points[0].X)
{
_points[1] = new PointF(_points[0].X + LineWidth / 2f, _points[1].Y);
}
if (_points[1].Y < _points[0].Y)
{
_points[1] = new PointF(_points[1].X, _points[0].Y + LineWidth / 2f);
}
float w = Math.Abs(ScalePoint(_points[1]).X - ScalePoint(_points[0]).X);
float h = Math.Abs(ScalePoint(_points[1]).Y - ScalePoint(_points[0]).Y);
RectangleF drawRect = new(new PointF(_points[0].X, _points[0].Y), new SizeF(w, h));
line_path.AddRectangle(drawRect);
_matrix.Reset();
_matrix.RotateAt(_currentRotateAngle, _rectCenterPoint); // 使用更新后的旋转角度
line_path.Transform(_matrix);
}
if (_regionVertex.Length != _points.Count)
{
_regionVertex = new Region[_points.Count];
}
for (int i = 0; i < _points.Count; i++)
{
DrawVertex1(vrtx_path, i);
}
vrtx_path.Transform(_matrix);
}
break;
case ShapeTypeEnum.Circle:
{
if (_points.Count == 2)
{
float radius = PointHelper.Distance(ScalePoint(_points[0]), ScalePoint(_points[1]));
line_path.AddEllipse(
ScalePoint(_points[0]).X - radius,
ScalePoint(_points[0]).Y - radius,
radius * 2,
radius * 2);
}
for (int i = 0; i < _points.Count; i++)
{
DrawVertex(vrtx_path, i);
}
}
break;
case ShapeTypeEnum.LineStrip:
{
line_path.StartFigure();
line_path.AddLine(ScalePoint(_points[0]), ScalePoint(_points[0]));
for (int i = 0; i < _points.Count; i++)
{
PointF pt = _points[i];
line_path.AddLine(ScalePoint(pt), ScalePoint(pt));
DrawVertex(vrtx_path, i);
}
}
break;
case ShapeTypeEnum.Line:
{
// 添加框线到路径
var tmpPoints = _points.Select(p => ScalePoint(p)).ToList();
line_path.AddLines(tmpPoints.ToArray());
if (IsDrawLineVirtualRect && tmpPoints.Count == 2)
{
var center = new PointF((tmpPoints[0].X + tmpPoints[1].X) / 2,
(tmpPoints[0].Y + tmpPoints[1].Y) / 2);
// 计算两点之间的角度
float dx = tmpPoints[1].X - tmpPoints[0].X;
float dy = tmpPoints[1].Y - tmpPoints[0].Y;
float distance = PointHelper.Distance(tmpPoints[0], tmpPoints[1]);
double angle = Math.Atan2(dy, dx) * (180.0 / Math.PI); // 转换为度数
float l = center.X - distance / 2;
float t = center.Y - LineVirtualRectWidth / 2;
float r = center.X + distance / 2;
float b = center.Y + LineVirtualRectWidth / 2;
PointF ptLT = new PointF(l, t);
PointF ptRT = new PointF(r, t);
PointF ptRB = new PointF(r, b);
PointF ptLB = new PointF(l, b);
#if false
RectangleF rect = new RectangleF(ptLT, new SizeF(distance, LineVirtualRectWidth));
GraphicsPath rectPath = new GraphicsPath();
rectPath.AddRectangle(rect);
//// 设置矩阵以进行旋转和位移
Matrix matrix = new Matrix();
matrix.RotateAt((float)angle, center); // 旋转
// 应用变换
rectPath.Transform(matrix);
// 画框线
painter.DrawPath(pen, rectPath);
#else
RectangleF rect = new RectangleF(ptLT, new SizeF(distance, LineVirtualRectWidth));
LineVirtualRectPoints = new PointF[4] {
ptLT,ptRT,ptRB,ptLB
};
//// 设置矩阵以进行旋转和位移
Matrix matrix = new Matrix();
matrix.RotateAt((float)angle, center); // 旋转
matrix.TransformPoints(LineVirtualRectPoints);
GraphicsPath rectPath = new GraphicsPath();
rectPath.AddPolygon(LineVirtualRectPoints);
Pen rectpen = new Pen(Color.FromArgb(60, 0, 255, 0), 1);
// 画框线
painter.DrawPath(rectpen, rectPath);
#endif
}
// 添加节点到路径
for (int i = 0; i < _points.Count; i++)
{
DrawVertex(vrtx_path, i);
}
if (IsClosed())
{
line_path.AddLine(ScalePoint(_points[0]), ScalePoint(_points[0]));
}
}
break;
case ShapeTypeEnum.Polygon:
case ShapeTypeEnum.Point:
default:
{
// 添加多边形框线到路径
line_path.AddLines(_points.Select(p => ScalePoint(p)).ToArray());
// 添加节点到路径
for (int i = 0; i < _points.Count; i++)
{
DrawVertex(vrtx_path, i);
}
if (IsClosed())
{
line_path.AddLine(ScalePoint(_points[0]), ScalePoint(_points[0]));
}
}
break;
}
#region
// 画框线
painter.DrawPath(pen, line_path);
// 填充节点
if (vrtx_path.PointCount > 0)
{
painter.DrawPath(pen, vrtx_path);
painter.FillPath(new SolidBrush(vertex_fill_color), vrtx_path);
}
if (fill) // 是否填充多边形
{
Color fillColor = Selected ? select_fill_color : fill_color;
painter.FillPath(new SolidBrush(fillColor), line_path);
}
#endregion
}
private Region[] _regionVertex = new Region[] { };
private void DrawVertex1(GraphicsPath path, int i)
{
PointF pt = _points[i];
_regionVertex[i] = new Region(new RectangleF(
pt.X - _epsilon, pt.Y - _epsilon,
_epsilon * 2, _epsilon * 2));
// 将节点变换
PointF[] transformedPoint = new PointF[] { pt };
_matrix.TransformPoints(transformedPoint); // 变换节点位置
pt = transformedPoint[0]; // 获取变换后的节点位置
// 绘制节点
float d = point_size; // Point size
PointTypeEnum shape = point_type; // Point shape
PointF point = ScalePoint(pt);
if (i == _highlightIndex)
{
var setting = _highlightSettings[_highlightMode];
var size = setting.PointSize;
shape = setting.PointType;
d *= size; // Example for highlighting
}
if (_highlightIndex >= 0)
{
_vertex_fill_color = hvertex_fill_color;
}
else
{
_vertex_fill_color = vertex_fill_color;
}
switch (shape)
{
case PointTypeEnum.SQUARE:
path.AddRectangle(new RectangleF(point.X - d / 2, point.Y - d / 2, d, d));
break;
case PointTypeEnum.ROUND:
path.AddEllipse(point.X - d / 2, point.Y - d / 2, d, d);
break;
default:
throw new InvalidOperationException("Unsupported vertex shape");
}
}
private void DrawVertex(GraphicsPath path, int i)
{
PointF pt = _points[i];
float d = point_size; // Point size
PointTypeEnum shape = point_type; // Point shape
PointF point = ScalePoint(pt);
if (i == _highlightIndex)
{
var setting = _highlightSettings[_highlightMode];
var size = setting.PointSize;
shape = setting.PointType;
d *= size; // Example for highlighting
}
if (_highlightIndex >= 0)
{
_vertex_fill_color = hvertex_fill_color;
}
else
{
_vertex_fill_color = vertex_fill_color;
}
switch (shape)
{
case PointTypeEnum.SQUARE:
path.AddRectangle(new RectangleF(point.X - d / 2, point.Y - d / 2, d, d));
break;
case PointTypeEnum.ROUND:
path.AddEllipse(point.X - d / 2, point.Y - d / 2, d, d);
break;
default:
throw new InvalidOperationException("Unsupported vertex shape");
}
}
/// <summary>
/// 查找离鼠标最近且距离小于阈值的节点
/// </summary>
/// <param name="point">鼠标位置</param>
/// <param name="epsilon">阈值</param>
/// <returns>返回节点的索引</returns>
public int NearestVertex(PointF point, float epsilon = DFT_VTX_EPSILON)
{
switch (ShapeType)
{
case ShapeTypeEnum.Rectangle:
{
for (int i = 0; i < _regionVertex.Length; i++)
{
if (_regionVertex[i] == null)
{
break;
}
if (_regionVertex[i].IsVisible(point))
{
return i;
}
}
}
break;
default:
{
_epsilon = epsilon;
float min_distance = float.MaxValue;
int min_i = -1;
PointF scaledPoint = new PointF(point.X * scale, point.Y * scale);
for (int i = 0; i < _points.Count; i++)
{
// 缩放顶点
PointF scaledVertex = new PointF(_points[i].X * scale, _points[i].Y * scale);
float dist = PointHelper.Distance(scaledVertex, scaledPoint);
// 检查距离是否在 epsilon 范围内
if (dist <= epsilon && dist < min_distance)
{
min_distance = dist;
min_i = i;
}
}
return min_i;
}
}
return -1;
}
public int NearestEdge(PointF point, float epsilon)
{
float min_distance = float.MaxValue;
int post_i = -1;
PointF scaledPoint = new PointF(point.X * scale, point.Y * scale);
for (int i = 0; i < _points.Count; i++)
{
// 计算边的两个端点
PointF start = new PointF(this[i - 1].X * scale, this[i - 1].Y * scale);
PointF end = new PointF(this[i].X * scale, this[i].Y * scale);
// 计算到线段的距离
float dist = PointHelper.DistanceToLine(scaledPoint, start, end);
// 检查距离是否在 epsilon 范围内
if (dist <= epsilon && dist < min_distance)
{
min_distance = dist;
post_i = i;
}
}
return post_i;
}
public bool ContainsPoint(PointF point)
{
return MakePath().IsVisible(point);
}
private GraphicsPath MakePath()
{
GraphicsPath path = new GraphicsPath();
if (ShapeType == ShapeTypeEnum.Rectangle)
{
if (_points.Count == 2)
{
// 创建矩形路径
RectangleF rect = new RectangleF(
Math.Min(_points[0].X, _points[1].X),
Math.Min(_points[0].Y, _points[1].Y),
Math.Abs(_points[1].X - _points[0].X),
Math.Abs(_points[1].Y - _points[0].Y));
path.AddRectangle(rect);
}
}
else if (ShapeType == ShapeTypeEnum.Circle)
{
if (_points.Count == 2)
{
// 计算半径
float radius = PointHelper.Distance(_points[0], _points[1]);
path.AddEllipse(_points[0].X - radius, _points[0].Y - radius, radius * 2, radius * 2);
}
}
else
{
// 处理多边形
path.StartFigure();
path.AddLine(_points[0], _points[1]);
for (int i = 2; i < _points.Count; i++)
{
path.AddLine(_points[i - 1], _points[i]);
}
path.CloseFigure(); // 结束图形
}
return path;
}
public RectangleF BoundingRect()
{
return MakePath().GetBounds();
}
public void MoveBy(PointF offset)
{
for (int i = 0; i < _points.Count; i++)
{
_points[i] = new PointF(_points[i].X + offset.X, _points[i].Y + offset.Y);
}
}
/// <summary>
/// 移动特定顶点
/// </summary>
/// <param name="index"></param>
/// <param name="offset"></param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public void MoveVertexBy(int index, PointF offset)
{
if (index >= 0 && index < _points.Count)
{
_rectSelectedVertex = index;
_rectSelectedMoveVertex = new PointF(_points[index].X, _points[index].Y);
_points[index] = new PointF(_points[index].X + offset.X, _points[index].Y + offset.Y);
}
else
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
}
public void HighlightVertex(int i, HighlightModeEnum action)
{
this._highlightIndex = i;
this._highlightMode = action;
}
public void HighlightClear()
{
_highlightIndex = -1;
}
public FlyShape Copy()
{
var jsonStr = JsonConvert.SerializeObject(this);
FlyShape copyShp = JsonConvert.DeserializeObject<FlyShape>(jsonStr);
return copyShp;
}
public int Length => _points.Count();
public PointF this[int index]
{
get
{
if (index == -1)
{
return _points[_points.Count - 1];
}
return _points[index];
}
set
{
if (index == -1)
{
_points[_points.Count - 1] = value;
}
else
{
_points[index] = value;
}
}
}
private void NomalizeRotateAngle()
{
if (_currentRotateAngle >= 360)
{
_currentRotateAngle %= 360;
}
else if (_currentRotateAngle < 0)
{
_currentRotateAngle = 360 - (-_currentRotateAngle % 360);
}
}
}
}