ベジエ曲線の始点の位置がおかしい
Q&A
Closed
お世話になっております。
解決したいこと
C# + WPFでベクターグラフィックスドローイングツールを開発しています。
※下にソースコードへの案内を記載しております。よろしければそちらを参照ください。
発生している問題・エラー
現在、ベジエ曲線の描画ツールを実装中なのですが、ほぼ実装できたと思ったのですが、始点の方の制御点をぐるぐる回してみたところ、ベジエ曲線の始点の位置がずれて動いてしまうという問題が発覚しました。
詳細は以下のGIF画像を参照ください。
GIF画像では終点の方の制御点もぐるぐる回していますが、終点は正しく固定されているようです。
このバグについて原因がわかる方はいらっしゃいますでしょうか。よろしければ教えて下さい。
public class BezierCurveViewModel : ConnectorBaseViewModel
{
public ReactiveProperty<Point> ControlPoint1 { get; set; } = new ReactiveProperty<Point>();
public ReactiveProperty<Point> ControlPoint2 { get; set; } = new ReactiveProperty<Point>();
public ReactiveProperty<Point> ControlLine1LeftTop { get; set; } = new ReactiveProperty<Point>();
public ReactiveProperty<Point> ControlLine2LeftTop { get; set; } = new ReactiveProperty<Point>();
public ReactiveProperty<Point> LeftTop { get; set; } = new ReactiveProperty<Point>();
public BezierCurveViewModel(int id, IDiagramViewModel parent)
: base(id, parent)
{
Init();
}
public BezierCurveViewModel()
: base()
{
Init();
}
public BezierCurveViewModel(Point p1, Point p2, Point c1, Point c2)
: base()
{
Init();
Points.Add(p1);
Points.Add(p2);
ControlPoint1.Value = c1;
ControlPoint2.Value = c2;
}
private void Init()
{
Points.CollectionChanged += Points_CollectionChanged;
ControlPoint1.Subscribe(x =>
{
if (Points.Count > 0)
{
var point = new Point();
point.X = Math.Min(Points[0].X, ControlPoint1.Value.X);
point.Y = Math.Min(Points[0].Y, ControlPoint1.Value.Y);
ControlLine1LeftTop.Value = point;
}
})
.AddTo(_CompositeDisposable);
ControlPoint2.Subscribe(x =>
{
if (Points.Count > 1)
{
var point = new Point();
point.X = Math.Min(Points[1].X, ControlPoint2.Value.X);
point.Y = Math.Min(Points[1].Y, ControlPoint2.Value.Y);
ControlLine2LeftTop.Value = point;
}
})
.AddTo(_CompositeDisposable);
}
private void Points_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (Points.Count >= 2)
{
var point = new Point();
point.X = Math.Min(Points[0].X, Points[1].X);
point.Y = Math.Min(Points[0].Y, Points[1].Y);
LeftTop.Value = point;
}
}
public override object Clone()
{
var clone = new BezierCurveViewModel(Points[0], Points[1], ControlPoint1.Value, ControlPoint2.Value);
clone.Owner = Owner;
clone.EdgeColor = EdgeColor;
clone.EdgeThickness = EdgeThickness;
return clone;
}
}
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:convereter="clr-namespace:boilersGraphics.Converters"
xmlns:local="clr-namespace:boilersGraphics.Resources.DesignerItems"
xmlns:control="clr-namespace:boilersGraphics.Controls"
xmlns:viewModel="clr-namespace:boilersGraphics.ViewModels">
<convereter:ToSolidColorBrushConverter x:Key="solidColorBrushConverter" />
<DataTemplate DataType="{x:Type viewModel:BezierCurveViewModel}">
<Canvas>
<Path Canvas.Left="{Binding LeftTop.Value.X}"
Canvas.Top="{Binding LeftTop.Value.Y}"
IsHitTestVisible="False"
Stretch="Fill"
Stroke="{Binding EdgeColor, Converter={StaticResource solidColorBrushConverter}}"
StrokeThickness="{Binding EdgeThickness}">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="{Binding Points[0]}">
<BezierSegment Point1="{Binding ControlPoint1.Value}"
Point2="{Binding ControlPoint2.Value}"
Point3="{Binding Points[1]}" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Path Canvas.Left="{Binding ControlLine1LeftTop.Value.X}"
Canvas.Top="{Binding ControlLine1LeftTop.Value.Y}"
IsHitTestVisible="False"
Stretch="Fill"
Stroke="SkyBlue"
StrokeThickness="2"
StrokeDashArray="2 2">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="{Binding Points[0]}">
<LineSegment Point="{Binding ControlPoint1.Value}" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Path Canvas.Left="{Binding ControlLine2LeftTop.Value.X}"
Canvas.Top="{Binding ControlLine2LeftTop.Value.Y}"
IsHitTestVisible="False"
Stretch="Fill"
Stroke="SkyBlue"
StrokeThickness="2"
StrokeDashArray="2 2">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="{Binding ControlPoint2.Value}">
<LineSegment Point="{Binding Points[1]}" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<control:ControlPoint x:Name="ControlPoint1"
Background="Red"
Canvas.Left="{Binding ControlPoint1.Value.X}"
Canvas.Top="{Binding ControlPoint1.Value.Y}"
Point="{Binding ControlPoint1.Value, Mode=TwoWay}" />
<control:ControlPoint x:Name="ControlPoint2"
Background="Red"
Canvas.Left="{Binding ControlPoint2.Value.X}"
Canvas.Top="{Binding ControlPoint2.Value.Y}"
Point="{Binding ControlPoint2.Value, Mode=TwoWay}" />
</Canvas>
</DataTemplate>
</ResourceDictionary>
ソースコード
boiler's Graphics
https://github.com/dhq-boiler/boiler-s-Graphics
gitリポジトリ
https://github.com/dhq-boiler/boiler-s-Graphics.git
ブランチ:feature/BezierCurve
コミット:d04bd4b
自分で試したこと
LeftTopプロパティを実装して、Points[0]とPoint[1]のX, Yについて最小値をとり、それを設定するようなメソッドを作りました。
そして、それをPathのCanvas.Left, Canvas.Topに設定したのですが、上記のGIFアニメのようになりました。
private void Points_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (Points.Count >= 2)
{
var point = new Point();
point.X = Math.Min(Points[0].X, Points[1].X);
point.Y = Math.Min(Points[0].Y, Points[1].Y);
LeftTop.Value = point;
}
}
何か私の見落とし、致命的な勘違いなど気づいたところがあれば、回答していただけると助かります。よろしくお願いいたします。