Avalonia 创建拼图块图形
Avalonia 创建拼图块图形
零、前言
因为想要做一个拼图游戏,如何自绘一个矢量的拼图成为了一个难题,但是还是手搓出来了,实现代码我们放在最后,在前面我们先介绍一下用法。
一、快速开始
你可以为你的 Path.Data 属性提供 JigsawGeometry(代码在文章最后)实例,实现拼图的显示。

示例代码如下:
// 主体的尺寸
var mainSize = 100;
// 拼图块四个方向的情况,HoleType 枚举有三个状态:In Out Flat 分别表示的是该拼图块内凹、外凸与平边缘。
var jigsawConfig = new JigsawConfig(
left: HoleType.Out,
top: HoleType.Out,
right: HoleType.In,
bottom: HoleType.Flat);
// 这样就可以创建出一个拼图图形了。
var geometry = new JigsawGeometry(mainSize, jigsawConfig);
PART_Path.Data = geometry;
二、拼图块的属性
我们封装了一些属性,包括拼图块黄色区域表示的实际尺寸、天蓝色区域表示的主要尺寸,以及拼图块的中心。


你可以通过以下代码搭建一个测试场景:
<Grid ColumnDefinitions="auto,1*">
<Button
Name="PART_Button"
Click="Button_Click"
Content="点击" />
<Canvas Grid.Column="1" ClipToBounds="True">
<Rectangle Name="PART_FullRect" Fill="Yellow" />
<Rectangle Name="PART_MainRect" Fill="Aqua" />
<Path
Name="PART_Path"
Stroke="Black"
StrokeThickness="2" />
<Ellipse
Name="PART_Point"
Width="20"
Height="20"
Fill="Red"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TranslateTransform X="-10" Y="-10" />
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>
</Grid>
private List<JigsawConfig> _jigsawConfigs;
private int i = 0;
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
_jigsawConfigs = new List<JigsawConfig>();
var values = Enum.GetValues(typeof(HoleType));
foreach (HoleType left in values)
{
foreach (HoleType top in values)
{
foreach (HoleType right in values)
{
foreach (HoleType bottom in values)
{
_jigsawConfigs.Add(new JigsawConfig(left, top, right, bottom));
}
}
}
}
}
private void Button_Click(object? sender, RoutedEventArgs e)
{
var mainSize = 100;
var jigsawConfig = _jigsawConfigs[i];
i = (i + 1) % _jigsawConfigs.Count;
var geometry = new JigsawGeometry(mainSize, jigsawConfig);
PART_Path.Data = geometry;
// 属性可视化
{
Canvas.SetLeft(PART_Point, geometry.MainCenter.X);
Canvas.SetTop(PART_Point, geometry.MainCenter.Y);
PART_FullRect.Width = geometry.FullSize.Width;
PART_FullRect.Height = geometry.FullSize.Height;
PART_MainRect.Width = geometry.MainRect.Width;
PART_MainRect.Height = geometry.MainRect.Height;
Canvas.SetLeft(PART_MainRect, geometry.MainRect.X);
Canvas.SetTop(PART_MainRect, geometry.MainRect.Y);
}
}
三、核心代码
我们的代码实现会很长,具体就会是下面这样:
using Avalonia;
using Avalonia.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AvaloniaPlayground.Controls
{
/// <summary>
/// 拼图角的方向。
/// </summary>
public enum HoleType
{
In, Out, Flat
}
/// <summary>
/// 单个拼图块的具体配置信息。
/// </summary>
public class JigsawConfig
{
public HoleType Left { get; set; }
public HoleType Top { get; set; }
public HoleType Right { get; set; }
public HoleType Bottom { get; set; }
public JigsawConfig() { }
public JigsawConfig(HoleType left, HoleType top, HoleType right, HoleType bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public JigsawConfig(JigsawConfig other)
{
Left = other.Left;
Top = other.Top;
Right = other.Right;
Bottom = other.Bottom;
}
public static JigsawConfig Parse(string s)
{
var parts = s.Split(',', StringSplitOptions.RemoveEmptyEntries);
return new JigsawConfig(Enum.Parse<HoleType>(parts[0], true), Enum.Parse<HoleType>(parts[1], true), Enum.Parse<HoleType>(parts[2], true), Enum.Parse<HoleType>(parts[3], true));
}
}
/// <summary>
/// 拼图块。
/// </summary>
public partial class JigsawGeometry : PathGeometry
{
#region props
/// <summary>
/// 主中心。
/// </summary>
public Point MainCenter { get; private set; }
/// <summary>
/// 主尺寸。
/// </summary>
public double MainSize { get; private set; }
/// <summary>
/// 全尺寸。
/// </summary>
public Size FullSize { get; private set; }
/// <summary>
/// 主体矩阵。
/// </summary>
public Rect MainRect { get; private set; }
/// <summary>
/// 图形的配置信息。
/// </summary>
public JigsawConfig JigsawConfig { get; private set; }
/// <summary>
/// 连接处的尺寸。
/// </summary>
public double Outter { get; private set; }
#endregion
#region ctors
/// <summary>
/// 拼图几何图形的构造函数。
/// </summary>
/// <param name="mainSize">拼图块主尺寸,不包括连接处的外围尺寸。</param>
/// <param name="jigsawConfig">该拼图块的外观。</param>
public JigsawGeometry(double mainSize, JigsawConfig jigsawConfig)
{
JigsawConfig = jigsawConfig;
MainSize = mainSize;
Outter = GetOutterFromMainSize(mainSize);
Build(mainSize, jigsawConfig);
CreateMainCenterAndRect(mainSize, jigsawConfig);
CreateFullSize(mainSize, jigsawConfig);
}
#endregion
#region methods
/// <summary>
/// 创建四个方向的边缘和连接处曲线,核心是 CreateSegmentFromJigsawShapeData。
/// </summary>
/// <param name="mainSize"></param>
/// <param name="jigsawConfig"></param>
/// <exception cref="NotImplementedException"></exception>
private void Build(double mainSize, JigsawConfig jigsawConfig)
{
var data = new FullConfigJigsawShapeData(mainSize, jigsawConfig);
var figure = new PathFigure();
figure.IsClosed = true;
figure.StartPoint = data.StartPoint;
for (int i = 0; i < data.JigsawPoints.Count - 1; i++)
{
var segment = CreateSegmentFromJigsawShapeData(data, i);
if (segment is null) throw new NotImplementedException();
figure!.Segments!.Add(segment);
}
if (Figures is null) Figures = new PathFigures();
Figures.Add(figure);
}
private void CreateMainCenterAndRect(double mainSize, JigsawConfig jigsawConfig)
{
var offx = 0d;
var offy = 0d;
var x = mainSize / 2;
var y = mainSize / 2;
var outter = GetOutterFromMainSize(mainSize);
if (jigsawConfig.Left == HoleType.Out) offx = outter + offx;
if (jigsawConfig.Top == HoleType.Out) offy = outter + offy;
MainCenter = new Point(x + offx, y + offy);
MainRect = new Rect(offx, offy, mainSize, mainSize);
}
private void CreateFullSize(double mainSize, JigsawConfig jigsawConfig)
{
var width = mainSize;
var height = mainSize;
var outter = GetOutterFromMainSize(mainSize);
if (jigsawConfig.Left == HoleType.Out) width = outter + width;
if (jigsawConfig.Right == HoleType.Out) width = outter + width;
if (jigsawConfig.Top == HoleType.Out) height = outter + height;
if (jigsawConfig.Bottom == HoleType.Out) height = outter + height;
FullSize = new Size(width, height);
}
private PathSegment CreateSegmentFromJigsawShapeData(IJigsawShapeData data, int index)
{
if (index < 0) return null;
if (index >= data.JigsawPoints.Count) return null;
var jigsawPoint1 = data.JigsawPoints[index];
var jigsawPoint2 = data.JigsawPoints[index + 1];
if (jigsawPoint1.Type == JigsawPointType.Line)
{
return new LineSegment() { Point = jigsawPoint2.Point };
}
else if (jigsawPoint1.Type == JigsawPointType.Arc)
{
return new ArcSegment()
{
IsLargeArc = jigsawPoint1.IsLargeArc,
Point = jigsawPoint2.Point,
Size = jigsawPoint1.Size,
SweepDirection = jigsawPoint1.SweepDirection,
RotationAngle = jigsawPoint1.ArcAngle
};
}
return null;
}
#endregion
}
public partial class JigsawGeometry
{
private enum JigsawPointType
{
Line, Arc, Point
}
private class JigsawPoint
{
public JigsawPointType Type;
public Point Point; // 实际坐标
public double ArcAngle;
public Size Size;
public SweepDirection SweepDirection;
public bool IsLargeArc = false;
public static JigsawPoint CreateLine(Point point) => new JigsawPoint() { Point = point, Type = JigsawPointType.Line };
public static JigsawPoint CreateArc(Point point, Size size, double arcAngle, SweepDirection sweepDirection, bool isLargeArc) =>
new JigsawPoint()
{
Point = point,
Type = JigsawPointType.Arc,
Size = size,
ArcAngle = arcAngle,
SweepDirection = sweepDirection,
IsLargeArc = isLargeArc
};
public static JigsawPoint CreatePoint(Point point) => new JigsawPoint() { Point = point, Type = JigsawPointType.Point };
}
private interface IJigsawShapeData
{
Size LargeSize { get; }
Size SmallSize { get; }
List<JigsawPoint> JigsawPoints { get; }
Point StartPoint { get; }
double Extend { get; }
}
private class FullConfigJigsawShapeData : IJigsawShapeData
{
#region statics
///<summary>从包含延伸的全尺寸计算单个延伸尺寸</summary>
public static double GetOutterFromFullSize(double fullSize) => fullSize / 6;
///<summary>计算</summary>
public static double GetFullSize(double mainSize) => mainSize / 2 * 3;
///<summary>从拼图体尺寸计算延伸的尺寸</summary>
public static double GetOutterFromMainSize(double mainSize) => mainSize / 4;
#endregion
#region fields
double _largeRadius;
double _theta;
double _majorDistance;
double _mainSize;
double _extend;
double _half;
Size _smallSize;
double _borderPart;
#endregion
#region props
public Size LargeSize { get; private set; }
public Size SmallSize { get; private set; }
public List<JigsawPoint> JigsawPoints { get; private set; }
public Point StartPoint { get; private set; }
public JigsawConfig JigsawConfig { get; private set; }
public double Extend { get; private set; }
#endregion
#region ctors
public FullConfigJigsawShapeData(double mainSize, JigsawConfig jigsawConfig)
{
var extend = GetOutterFromMainSize(mainSize);
_extend = extend;
_majorDistance = extend * 0.55;
_largeRadius = extend * 0.45;
_mainSize = mainSize;
_theta = 45 * Math.PI / 180;
_half = GetHalf(_majorDistance, _largeRadius, _theta);
_smallSize = GetSmallSize(_majorDistance, _largeRadius, _theta);
_borderPart = _mainSize / 2 - _half;
LargeSize = new Size(_largeRadius, _largeRadius);
SmallSize = _smallSize;
JigsawConfig = jigsawConfig;
Extend = extend;
BuildPoints();
}
#endregion
#region methods
private void BuildPoints()
{
var startPoint = new Point(JigsawConfig.Left == HoleType.Out ? _extend : 0, JigsawConfig.Top == HoleType.Out ? _extend : 0);
var pA = startPoint;
var pB = pA + new Point(_mainSize, 0);
var pC = pB + new Point(0, _mainSize);
var pD = pC + new Point(-_mainSize, 0);
var pAB = (pA + pB) / 2;
var pBC = (pB + pC) / 2;
var pCD = (pC + pD) / 2;
var pDA = (pD + pA) / 2;
var ABPoints = new List<JigsawPoint>();
var BCPoints = new List<JigsawPoint>();
var CDPoints = new List<JigsawPoint>();
var DAPoints = new List<JigsawPoint>();
#region A2B
if (JigsawConfig.Top == HoleType.Out)
{
var pAB1 = pA + new Point(_borderPart, 0);
var pAB2 = pAB + GetUpInsectionPoint1();
var pAB3 = pAB + GetUpInsectionPoint2();
var pAB4 = pB - new Point(_borderPart, 0);
ABPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pA),
JigsawPoint.CreateArc(pAB1, _smallSize, 0, SweepDirection.CounterClockwise, false),
JigsawPoint.CreateArc(pAB2, LargeSize, 0, SweepDirection.Clockwise, true),
JigsawPoint.CreateArc(pAB3, _smallSize, 0, SweepDirection.CounterClockwise, false),
JigsawPoint.CreateLine(pAB4),
};
}
else if (JigsawConfig.Top == HoleType.In)
{
var pAB1 = pA + new Point(_borderPart, 0);
var pAB2 = pAB + ReverseY(GetUpInsectionPoint1());
var pAB3 = pAB + ReverseY(GetUpInsectionPoint2());
var pAB4 = pB - new Point(_borderPart, 0);
ABPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pA),
JigsawPoint.CreateArc(pAB1, _smallSize, 0, SweepDirection.Clockwise, false),
JigsawPoint.CreateArc(pAB2, LargeSize, 0, SweepDirection.CounterClockwise, true),
JigsawPoint.CreateArc(pAB3, _smallSize, 0, SweepDirection.Clockwise, false),
JigsawPoint.CreateLine(pAB4),
};
}
else if (JigsawConfig.Top == HoleType.Flat)
{
ABPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pA),
};
}
else
{
throw new NotImplementedException();
}
#endregion
#region B2C
if (JigsawConfig.Right == HoleType.Out)
{
var pBC1 = pB + new Point(0, _borderPart);
var pBC2 = pBC + GetRightInsectionPoint1();
var pBC3 = pBC + GetRightInsectionPoint2();
var pBC4 = pC - new Point(0, _borderPart);
BCPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pB),
JigsawPoint.CreateArc(pBC1, _smallSize, 0, SweepDirection.CounterClockwise, false),
JigsawPoint.CreateArc(pBC2, LargeSize, 0, SweepDirection.Clockwise, true),
JigsawPoint.CreateArc(pBC3, _smallSize, 0, SweepDirection.CounterClockwise, false),
JigsawPoint.CreateLine(pBC4),
};
}
else if (JigsawConfig.Right == HoleType.In)
{
var pBC1 = pB + new Point(0, _borderPart);
var pBC2 = pBC + ReverseX(GetRightInsectionPoint1());
var pBC3 = pBC + ReverseX(GetRightInsectionPoint2());
var pBC4 = pC - new Point(0, _borderPart);
BCPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pB),
JigsawPoint.CreateArc(pBC1, _smallSize, 0, SweepDirection.Clockwise, false),
JigsawPoint.CreateArc(pBC2, LargeSize, 0, SweepDirection.CounterClockwise, true),
JigsawPoint.CreateArc(pBC3, _smallSize, 0, SweepDirection.Clockwise, false),
JigsawPoint.CreateLine(pBC4),
};
}
else if (JigsawConfig.Right == HoleType.Flat)
{
BCPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pB),
};
}
else
{
throw new NotImplementedException();
}
#endregion
#region C2D
if (JigsawConfig.Bottom == HoleType.Out)
{
var pCD1 = pC - new Point(_borderPart, 0);
var pCD2 = pCD + GetDownInsectionPoint2();
var pCD3 = pCD + GetDownInsectionPoint1();
var pCD4 = pD + new Point(_borderPart, 0);
CDPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pC),
JigsawPoint.CreateArc(pCD1, _smallSize, 0, SweepDirection.CounterClockwise, false),
JigsawPoint.CreateArc(pCD2, LargeSize, 0, SweepDirection.Clockwise, true),
JigsawPoint.CreateArc(pCD3, _smallSize, 0, SweepDirection.CounterClockwise, false),
JigsawPoint.CreateLine(pCD4),
};
}
else if (JigsawConfig.Bottom == HoleType.In)
{
var pCD1 = pC - new Point(_borderPart, 0);
var pCD2 = pCD + ReverseY(GetDownInsectionPoint2());
var pCD3 = pCD + ReverseY(GetDownInsectionPoint1());
var pCD4 = pD + new Point(_borderPart, 0);
CDPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pC),
JigsawPoint.CreateArc(pCD1, _smallSize, 0, SweepDirection.Clockwise, false),
JigsawPoint.CreateArc(pCD2, LargeSize, 0, SweepDirection.CounterClockwise, true),
JigsawPoint.CreateArc(pCD3, _smallSize, 0, SweepDirection.Clockwise, false),
JigsawPoint.CreateLine(pCD4),
};
}
else if (JigsawConfig.Bottom == HoleType.Flat)
{
CDPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pC),
};
}
else
{
throw new NotImplementedException();
}
#endregion
#region D2A
if (JigsawConfig.Left == HoleType.Out)
{
var pDA1 = pD - new Point(0, _borderPart);
var pDA2 = pDA + GetLeftInsectionPoint2();
var pDA3 = pDA + GetLeftInsectionPoint1();
var pDA4 = pA + new Point(0, _borderPart);
DAPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pD),
JigsawPoint.CreateArc(pDA1, _smallSize, 0, SweepDirection.CounterClockwise, false),
JigsawPoint.CreateArc(pDA2, LargeSize, 0, SweepDirection.Clockwise, true),
JigsawPoint.CreateArc(pDA3, _smallSize, 0, SweepDirection.CounterClockwise, false),
JigsawPoint.CreateLine(pDA4),
};
}
else if (JigsawConfig.Left == HoleType.In)
{
var pDA1 = pD - new Point(0, _borderPart);
var pDA2 = pDA + ReverseX(GetLeftInsectionPoint2());
var pDA3 = pDA + ReverseX(GetLeftInsectionPoint1());
var pDA4 = pA + new Point(0, _borderPart);
DAPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pD),
JigsawPoint.CreateArc(pDA1, _smallSize, 0, SweepDirection.Clockwise, false),
JigsawPoint.CreateArc(pDA2, LargeSize, 0, SweepDirection.CounterClockwise, true),
JigsawPoint.CreateArc(pDA3, _smallSize, 0, SweepDirection.Clockwise, false),
JigsawPoint.CreateLine(pDA4),
};
}
else if (JigsawConfig.Left == HoleType.Flat)
{
DAPoints = new List<JigsawPoint>()
{
JigsawPoint.CreateLine(pD),
};
}
else
{
throw new NotImplementedException();
}
#endregion
StartPoint = startPoint;
JigsawPoints = new List<JigsawPoint>();
JigsawPoints.AddRange(ABPoints);
JigsawPoints.AddRange(BCPoints);
JigsawPoints.AddRange(CDPoints);
JigsawPoints.AddRange(DAPoints);
}
#endregion
#region utils
private static Point ReverseX(Point p)
{
return new Point(-p.X, p.Y);
}
private static Point ReverseY(Point p)
{
return new Point(p.X, -p.Y);
}
private double GetSmallRadius(double majorDistance, double largeRadius, double angle)
{
var sin = Math.Sin(angle);
var cos = Math.Cos(angle);
var smallRadius = (majorDistance - largeRadius * cos) / (1 + cos);
return smallRadius;
}
private Size GetSmallSize(double majorDistance, double largeRadius, double angle)
{
var smallRadius = GetSmallRadius(majorDistance, largeRadius, angle);
return new Size(smallRadius, smallRadius);
}
private double GetHalf(double majorDistance, double largeRadius, double angle)
{
var sin = Math.Sin(angle);
var cos = Math.Cos(angle);
var smallRadius = GetSmallRadius(majorDistance, largeRadius, angle);
var x = smallRadius;
var y = (smallRadius + largeRadius) * sin;
return y;
}
#endregion
#region utils
private Point GetRightInsectionPoint1()
{
var sin = Math.Sin(_theta);
var cos = Math.Cos(_theta);
var xf = _majorDistance - cos * _largeRadius;
var y = _largeRadius * sin;
return new Point(xf, -y);
}
private Point GetRightInsectionPoint2()
{
var sin = Math.Sin(_theta);
var cos = Math.Cos(_theta);
var xf = _majorDistance - cos * _largeRadius;
var y = _largeRadius * sin;
return new Point(xf, y);
}
private Point GetLeftInsectionPoint1()
{
var sin = Math.Sin(_theta);
var cos = Math.Cos(_theta);
var xf = _majorDistance - cos * _largeRadius;
var y = _largeRadius * sin;
return new Point(-xf, -y);
}
private Point GetLeftInsectionPoint2()
{
var sin = Math.Sin(_theta);
var cos = Math.Cos(_theta);
var xf = _majorDistance - cos * _largeRadius;
var y = _largeRadius * sin;
return new Point(-xf, y);
}
private Point GetUpInsectionPoint1()
{
var sin = Math.Sin(_theta);
var cos = Math.Cos(_theta);
var xf = _majorDistance - cos * _largeRadius;
var y = _largeRadius * sin;
return new Point(-y, -xf);
}
private Point GetUpInsectionPoint2()
{
var sin = Math.Sin(_theta);
var cos = Math.Cos(_theta);
var xf = _majorDistance - cos * _largeRadius;
var y = _largeRadius * sin;
return new Point(y, -xf);
}
private Point GetDownInsectionPoint1()
{
var sin = Math.Sin(_theta);
var cos = Math.Cos(_theta);
var xf = _majorDistance - cos * _largeRadius;
var y = _largeRadius * sin;
return new Point(-y, xf);
}
private Point GetDownInsectionPoint2()
{
var sin = Math.Sin(_theta);
var cos = Math.Cos(_theta);
var xf = _majorDistance - cos * _largeRadius;
var y = _largeRadius * sin;
return new Point(y, xf);
}
private Size GetSmallSize()
{
var smallRadius = GetSmallRadius(_majorDistance, _largeRadius, _theta);
return new Size(smallRadius * 2, smallRadius * 2);
}
private double GetHalf()
{
var sin = Math.Sin(_theta);
var cos = Math.Cos(_theta);
var smallRadius = GetSmallRadius(_majorDistance, _largeRadius, _theta);
var x = smallRadius;
var y = (smallRadius + _largeRadius) * sin;
return y;
}
#endregion
}
}
public partial class JigsawGeometry
{
///<summary>从包含延伸的全尺寸计算单个延伸尺寸</summary>
public static double GetOutterFromFullSize(double fullSize) => FullConfigJigsawShapeData.GetOutterFromFullSize(fullSize);
///<summary>计算</summary>
public static double GetFullSize(double mainSize) => FullConfigJigsawShapeData.GetFullSize(mainSize);
///<summary>从拼图体尺寸计算延伸的尺寸</summary>
public static double GetOutterFromMainSize(double mainSize) => FullConfigJigsawShapeData.GetOutterFromMainSize(mainSize);
}
}
四、补充
第三节是核心代码,如果你只抄了这里是会有问题的,因为这里是补充的内容!
在这里另外贴了一个封装了 JigsawGeometry 的 Path,旨在更方便的创建拼图块。
public class JigsawShape : Path
{
#region props
/// <summary>
/// 主中心。
/// </summary>
public Point MainCenter => _jigsawGeometry.MainCenter;
/// <summary>
/// 主尺寸。
/// </summary>
public double MainSize => _jigsawGeometry.MainSize;
/// <summary>
/// 全尺寸。
/// </summary>
public Size FullSize => _jigsawGeometry.FullSize;
/// <summary>
/// 主体矩阵。
/// </summary>
public Rect MainRect => _jigsawGeometry.MainRect;
/// <summary>
/// 图形的配置信息。
/// </summary>
public JigsawConfig JigsawConfig => _jigsawGeometry.JigsawConfig;
/// <summary>
/// 连接处的尺寸。
/// </summary>
public double Outter => _jigsawGeometry.Outter;
#endregion
#region fields
private JigsawGeometry _jigsawGeometry;
#endregion
#region ctors
/// <summary>
/// 拼图图形的构造函数。
/// </summary>
/// <param name="mainSize">拼图块主尺寸,不包括连接处的外围尺寸。</param>
/// <param name="jigsawConfig">该拼图块的外观。</param>
public JigsawShape(double mainSize, JigsawConfig jigsawConfig)
{
_jigsawGeometry = new JigsawGeometry(mainSize, jigsawConfig);
Data = _jigsawGeometry;
}
#endregion
}

浙公网安备 33010602011771号