GestureDrawer.cs

随便聊聊 · 61 次浏览
H-D-G 创建于 1天8小时前

如果看补全不懂可以看看源码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Media;

namespace GestureDesigner.Design;

/// <summary>
/// 角度单位(弧度或角度)
/// </summary>
public enum AngleUnit
{
    /// <summary>
    /// 弧度制
    /// </summary>
    Radians,
    /// <summary>
    /// 角度制
    /// </summary>
    Degrees
}

/// <summary>
/// 水平方向(左或右)
/// </summary>
public enum HorizontalDirection
{
    Right,
    Left
}

/// <summary>
/// 垂直方向(上或下)
/// </summary>
public enum VerticalDirection
{
    Up,
    Down
}

/// <summary>
/// 手势绘制的配置选项
/// </summary>
public record Options
{
    /// <summary>默认配置(角度制,向左,向上)</summary>
    public static readonly Options Default = new();

    /// <summary>Windows 坐标系配置(向下为正方向)</summary>
    public static readonly Options Windows = new()
    {
        VerticalDirection = VerticalDirection.Down
    };

    /// <summary>弧度制配置</summary>
    public static readonly Options Radians = new()
    {
        AngleUnit = AngleUnit.Radians
    };

    /// <summary>角度单位(默认角度制)</summary>
    public AngleUnit AngleUnit { get; set; } = AngleUnit.Degrees;
    /// <summary>X轴正方向(默认向左)</summary>
    public HorizontalDirection HorizontalDirection { get; set; } = HorizontalDirection.Left;
    /// <summary>Y轴正方向(默认向上)</summary>
    public VerticalDirection VerticalDirection { get; set; } = VerticalDirection.Up;
    /// <summary>计算精度(小数位数,默认3位)</summary>
    public int Precision { get; set; } = 3;
}

/// <summary>
/// 手势绘制器,用于生成基于数学坐标的几何路径(支持直线、圆弧等)
/// </summary>
public class GestureDrawer
{
    private readonly List<MathPoint> _internalPath = [];
    private readonly Options _options;

     
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="options">绘制配置(默认使用 <see cref="Options.Default"/>)</param>
    public GestureDrawer(Options? options = null)
    {
        _options = options ?? Options.Default;
        CurrentInternalLocation = new MathPoint(0, 0);
        _internalPath.Add(CurrentInternalLocation);
    }

    private MathPoint CurrentInternalLocation { get; set; }

    private float CurrentInternalDirection { get; set; }

    internal IReadOnlyList<Point> DrawingPath => ConvertToPath(_internalPath);

    /// <summary>
    /// 设置当前方向角度(根据配置自动转换单位)
    /// </summary>
    /// <param name="angle">角度或弧度(取决于 <see cref="Options.AngleUnit"/>)</param>
    public GestureDrawer SetDirection(float angle)
    {
        CurrentInternalDirection = ToInternalAngle(angle);
        return this;
    }

    /// <summary>
    /// 向前绘制指定长度的直线(沿当前方向)
    /// </summary>
    /// <param name="length">长度</param>
    public GestureDrawer Forward(float length)
    {
        var angleInRadians = CurrentInternalDirection * (Math.PI / 180.0);
        var endPoint = new MathPoint(
            CurrentInternalLocation.X + length * Math.Round(Math.Cos(angleInRadians), _options.Precision),
            CurrentInternalLocation.Y + length * Math.Round(Math.Sin(angleInRadians), _options.Precision)
        );
        DrawLine(CurrentInternalLocation, endPoint);
        CurrentInternalLocation = endPoint;
        return this;
    }

    /// <summary>
    /// 绘制指定长度和角度的直线
    /// </summary>
    /// <param name="length">长度</param>
    /// <param name="angle">角度或弧度</param>
    public GestureDrawer DrawLine(float length, float angle)
    {
        SetDirection(angle);
        return Forward(length);
    }

    /// <summary>
    /// 旋转当前方向(叠加角度)
    /// </summary>
    /// <param name="angle">旋转角度或弧度</param>
    public GestureDrawer Rotate(float angle)
    {
        SetDirection(CurrentInternalDirection + angle);
        return this;
    }

    /// <summary>
    /// 绘制圆弧(相对于当前坐标)
    /// </summary>
    /// <param name="relativeX">圆心X偏移</param>
    /// <param name="relativeY">圆心Y偏移</param>
    /// <param name="radius">半径</param>
    /// <param name="startAngle">起始角度</param>
    /// <param name="sweepAngle">扫过角度(可为负,表示反方向旋转)</param>
    public GestureDrawer DrawArc(float relativeX, float relativeY, float radius, float startAngle,
        float sweepAngle)
    {
        return Arc((float)CurrentInternalLocation.X + relativeX,
            (float)CurrentInternalLocation.Y + relativeY,
            radius, startAngle, sweepAngle);
    }

    /// <summary>
    /// 绘制圆弧(绝对圆心坐标)
    /// </summary>
    /// <param name="centerX">圆心X坐标</param>
    /// <param name="centerY">圆心Y坐标</param>
    /// <param name="radius">半径</param>
    /// <param name="startAngle">起始角度</param>
    /// <param name="sweepAngle">扫过角度(可为负,表示反方向旋转)</param>
    public GestureDrawer Arc(float centerX, float centerY, float radius, float startAngle,
        float sweepAngle)
    {
        var internalCenter = new MathPoint(centerX, centerY);
        var internalStartAngle = ToInternalAngle(startAngle);
        var sweepDegrees = ToDegrees(sweepAngle);
        var internalEndAngle = internalStartAngle + sweepDegrees;

        var steps = Math.Max(10, (int)Math.Abs(sweepDegrees));

        for (var i = 0; i <= steps; i++)
        {
            var currentInternalAngle = internalStartAngle + i * (sweepDegrees / steps);
            var angleRad = currentInternalAngle * (Math.PI / 180.0);
            var pointOnArc = new MathPoint(
                internalCenter.X + radius * Math.Round(Math.Cos(angleRad), _options.Precision),
                internalCenter.Y + radius * Math.Round(Math.Sin(angleRad), _options.Precision)
            );
            _internalPath.Add(pointOnArc);
        }

        var endAngleRad = internalEndAngle * (Math.PI / 180.0);
        CurrentInternalLocation = new MathPoint(
            internalCenter.X + radius * Math.Round(Math.Cos(endAngleRad), _options.Precision),
            internalCenter.Y + radius * Math.Round(Math.Sin(endAngleRad), _options.Precision)
        );
        CurrentInternalDirection = (internalEndAngle + (sweepDegrees >= 0 ? 90 : -90) + 360) % 360;

        return this;
    }

    internal PathGeometry ToPathGeometry()
    {
        var path = DrawingPath;
        if (path.Count == 0)
            return new PathGeometry();

        var pathGeometry = new PathGeometry();
        var pathFigure = new PathFigure { StartPoint = path[0] };

        for (var i = 1; i < path.Count; i++)
            pathFigure.Segments.Add(new LineSegment(path[i], true));

        pathGeometry.Figures.Add(pathFigure);
        return pathGeometry;
    }

    private float ToDegrees(float angleInUnit)
    {
        //角度制填角度,弧度制填pi的系数
        return _options.AngleUnit == AngleUnit.Radians
            ? (float)(angleInUnit * 180.0)
            : angleInUnit;
    }

    private float ToInternalAngle(float angle)
    {
        var degrees = ToDegrees(angle); //转角度制
        Debug.WriteLine(degrees, "ToInternalAngle_degrees");
        var effectiveAngle = degrees;
        if (_options.HorizontalDirection == HorizontalDirection.Right) effectiveAngle += 180;
        Debug.WriteLine(effectiveAngle, "ToInternalAngle_effectiveAngle");
        return (effectiveAngle % 360 + 360) % 360; //去除周期,转为0~359的正角度
    }

    private IReadOnlyList<Point> ConvertToPath(List<MathPoint> internalPath)
    {
        var xSign = _options.HorizontalDirection == HorizontalDirection.Left ? 1 : -1;
        var ySign = _options.VerticalDirection == VerticalDirection.Down ? 1 : -1;

        return internalPath.Select(p => new Point(p.X * xSign, p.Y * ySign)).ToList().AsReadOnly();
    }

    private void DrawLine(MathPoint start, MathPoint end)
    {
        // Simple line interpolation in internal coordinate space
        var distance = Math.Sqrt(Math.Pow(end.X - start.X, 2) + Math.Pow(end.Y - start.Y, 2));
        const float stepSize = 1.0f;
        var steps = (int)(distance / stepSize);

        if (steps <= 0)
        {
            _internalPath.Add(end);
            return;
        }

        for (var i = 1; i <= steps; i++)
        {
            var t = i / (double)steps;
            var point = new MathPoint(
                start.X + (end.X - start.X) * t,
                start.Y + (end.Y - start.Y) * t
            );
            _internalPath.Add(point);
        }
    }
    
    private readonly struct MathPoint(double x, double y)
    {
        public double X { get; } = x;
        public double Y { get; } = y;
    }
}


回复内容
Naked_Snake_M 9小时32分钟前
#1

打算让AI学习代码,然后我提出需求,让AI自己画图形,可惜不成功

H-D-G 回复 Naked_Snake_M 7小时38分钟前 :

发这个配合另一个帖子的示例,理论上是可以的

Naked_Snake_M 回复 H-D-G 7小时35分钟前 :

回头有空我再试试吧,主要是大佬之前的画手势的动作已经十分强大了,基本想要的图形都完美画出来了

回复主贴