今回のアプリのダウンロード先
指定した点を通る違和感のない滑らかな
ベジェ曲線を引きたい
指定するのは主にアンカー点(アンカーポイント)座標
滑らかにするために確定しているのは、始点側方向線と終点側方向線は同じ角度にすることだけ
決まっていないのは方向線の角度と長さ(距離)
今回は角度を決め打ちして長さ(距離)だけを考えてみた
決め打ちした角度は
短い
長い
短いと直線と変わらないし、長いと不自然になるので、どれくらいの長さがいいのかなあと、長さを決める方法はどうしたらいいかなあと
長さを決める方法
A0:距離0(直線)
A1:前後アンカー点距離に比例(0.3倍)
A2:前後アンカー点距離それぞれに比例(0.3倍)、これだけ長さが非対称になる
A3:前後アンカー点距離の短いほうに合わせる(0.3倍)
B1:指定距離25
B2:指定距離、すべてのアンカー間距離の平均*0.3に指定
A1:前後アンカー点距離に比例(0.3倍)
0と2の距離が前後アンカー点距離、これの0.3倍の距離を2つの制御点までの距離にしてみた
A2:前後アンカー点距離それぞれに比例(0.3倍)、これだけ長さが非対称になる
A3:前後アンカー点距離の短いほうに合わせる(0.3倍)
A2と同じように前後のアンカー点の距離を測って、短い方を使う
B1:指定距離25
B2:指定距離、すべてのアンカー間距離の平均*0.3に指定
ここまで見るとどれも大差ない感じだけど、アンカー点の配置によっては差が出てくる
A1
本の直線と重ねて見ると
大きく外れてしまっている、これは前後のアンカー点までの距離を別々に考えればいいかなあと考えたのがA2だった
A2
違和感は少なくなったけど、滑らかさも減ってしまった
ここで方向線の長さは前後同じ長さのほうがいいのかなあと、長いと大きく外れるから短い方に合わせたらどうかなと、できたのがA3
A3
ここまでの結果
こんな感じで長さをいろいろ変えてみたけど、どれもいまいちなので角度を変えるしかなさそう
デザイン画面
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace _20181026_Bezier
{
<summary>
</summary>
public partial class MainWindow : Window
{
private PointCollection AnchorPoints;
private Path MyBezierPath;
private Path VertexPath;
private Path ControlLinePath;
public MainWindow()
{
InitializeComponent();
this.Title = this.ToString();
AnchorPoints = new PointCollection() { new Point(150, 250), new Point(100, 250), new Point(400, 350), new Point(200, 250) };
AnchorPoints = new PointCollection() { new Point(150, 200), new Point(100, 250), new Point(400, 350) };
AddBezierPath();
AddVertexPath();
AddLinePath();
ButtonRandomPoint.Click += ButtonRandomPoint_Click;
ButtonA0.Click += ButtonA0_Click;
ButtonA1.Click += ButtonA1_Click;
ButtonA2.Click += ButtonA2_Click;
ButtonA3.Click += ButtonA3_Click;
ButtonB1.Click += ButtonB1_Click;
ButtonB2.Click += ButtonB2_Click;
}
private void ButtonRandomPoint_Click(object sender, RoutedEventArgs e)
{
AnchorPoints = RandomPoint(5);
MyCanvas.Children.Remove(MyBezierPath);
AddBezierPath();
MyCanvas.Children.Remove(VertexPath);
AddVertexPath();
MyCanvas.Children.Remove(ControlLinePath);
AddLinePath();
}
private void ButtonB2_Click(object sender, RoutedEventArgs e)
{
double distance = GetTotalDistance(AnchorPoints);
distance /= AnchorPoints.Count - 1;
distance *= 0.3;
ToCurve(ControlPointsB1, MyBezierPath, AnchorPoints, distance);
MyCanvas.Children.Remove(VertexPath);
AddVertexPath();
MyCanvas.Children.Remove(ControlLinePath);
AddLinePath();
}
private void ButtonB1_Click(object sender, RoutedEventArgs e)
{
ToCurve(ControlPointsB1, MyBezierPath, AnchorPoints, 25.0);
MyCanvas.Children.Remove(VertexPath);
AddVertexPath();
MyCanvas.Children.Remove(ControlLinePath);
AddLinePath();
}
private void ButtonA3_Click(object sender, RoutedEventArgs e)
{
ToCurve(ControlPointsA3, MyBezierPath, AnchorPoints, 0.3);
MyCanvas.Children.Remove(VertexPath);
AddVertexPath();
MyCanvas.Children.Remove(ControlLinePath);
AddLinePath();
}
private void ButtonA2_Click(object sender, RoutedEventArgs e)
{
ToCurve(ControlPointsA2, MyBezierPath, AnchorPoints, 0.3);
MyCanvas.Children.Remove(VertexPath);
AddVertexPath();
MyCanvas.Children.Remove(ControlLinePath);
AddLinePath();
}
private void ButtonA1_Click(object sender, RoutedEventArgs e)
{
ToCurve(ControlPointsA1, MyBezierPath, AnchorPoints, 0.3);
MyCanvas.Children.Remove(VertexPath);
AddVertexPath();
MyCanvas.Children.Remove(ControlLinePath);
AddLinePath();
}
private void ButtonA0_Click(object sender, RoutedEventArgs e)
{
ToCurve(ControlPointsA0, MyBezierPath, AnchorPoints, 0);
MyCanvas.Children.Remove(VertexPath);
AddVertexPath();
MyCanvas.Children.Remove(ControlLinePath);
}
<summary>
</summary>
<param name="func"></param>
<param name="bezier"></param>
<param name="anchor"></param>
<param name="curve"></param>
private void ToCurve(Func<Point, Point, Point, double, (Point, Point)> func, Path bezier, PointCollection anchor, double curve)
{
PointCollection segPoints = GetPolyBezierSegmentPoints(bezier);
segPoints[0] = anchor[0];
for (int i = 1; i < anchor.Count - 1; i++)
{
(Point begin, Point end) = func(anchor[i - 1], anchor[i], anchor[i + 1], curve);
segPoints[i * 3 - 2] = begin;
segPoints[i * 3] = end;
}
segPoints[segPoints.Count - 1] = anchor[anchor.Count - 1];
}
#region 制御点座標を求める関数
private (Point begin, Point end) ControlPointsA0(Point beginSide, Point current, Point endSide, double curve)
{
return (current, current);
}
<summary>
</summary>
<param name="beginP"></param>
<param name="currentP"></param>
<param name="endP"></param>
<param name="curve"></param>
<returns></returns>
private (Point begin, Point end) ControlPointsA1(Point beginSide, Point current, Point endSide, double curve)
{
double xDiff = (endSide.X - beginSide.X) * curve;
double yDiff = (endSide.Y - beginSide.Y) * curve;
Point bPoint = new Point(current.X - xDiff, current.Y - yDiff);
Point ePoint = new Point(current.X + xDiff, current.Y + yDiff);
return (bPoint, ePoint);
}
private (Point begin, Point end) ControlPointsA2(Point beginSide, Point current, Point endSide, double curve)
{
double bDistance = GetDistance(beginSide, current);
double eDistance = GetDistance(endSide, current);
double distance = GetDistance(beginSide, endSide);
double bRatio = bDistance / distance;
double eRatio = eDistance / distance;
double xDiff = (endSide.X - beginSide.X) * curve;
double yDiff = (endSide.Y - beginSide.Y) * curve;
Point bPoint = new Point(current.X - (xDiff * bRatio), current.Y - (yDiff * bRatio));
Point ePoint = new Point(current.X + (xDiff * eRatio), current.Y + (yDiff * eRatio));
return (bPoint, ePoint);
}
private (Point begin, Point end) ControlPointsA3(Point beginSide, Point current, Point endSide, double curve)
{
double bDistance = GetDistance(beginSide, current);
double eDistance = GetDistance(endSide, current);
double distance = GetDistance(beginSide, endSide);
double bRatio = bDistance / distance;
double eRatio = eDistance / distance;
double ratio = (bRatio > eRatio) ? eRatio : bRatio;
double xDiff = (endSide.X - beginSide.X) * curve * ratio;
double yDiff = (endSide.Y - beginSide.Y) * curve * ratio;
Point bPoint = new Point(current.X - xDiff, current.Y - yDiff);
Point ePoint = new Point(current.X + xDiff, current.Y + yDiff);
return (bPoint, ePoint);
}
private (Point begin, Point end) ControlPointsB1(Point beginSide, Point current, Point endSide, double distance)
{
double xDiff = (endSide.X - beginSide.X);
double yDiff = (endSide.Y - beginSide.Y);
double radian = GetRadianFrom2Points(new Point(), new Point(xDiff, yDiff));
double cos = Math.Cos(radian) * distance;
double sin = Math.Sin(radian) * distance;
Point bPoint = new Point(current.X - cos, current.Y - sin);
Point ePoint = new Point(current.X + cos, current.Y + sin);
return (bPoint, ePoint);
}
#endregion
private PointCollection RandomPoint(int count)
{
var rand = new Random();
var points = new PointCollection();
for (int i = 0; i < count; i++)
{
points.Add(new Point(rand.Next(50, 450), rand.Next(50, 450)));
}
return points;
}
private void AddBezierPath()
{
MyBezierPath = MakeBezierPath(AnchorPoints);
ToCurve(ControlPointsA1, MyBezierPath, AnchorPoints, 0.3);
MyCanvas.Children.Add(MyBezierPath);
MyBezierPath.Stroke = Brushes.Khaki;
MyBezierPath.StrokeThickness = 10;
MyBezierPath.StrokeLineJoin = PenLineJoin.Bevel;
}
private void AddLinePath()
{
PointCollection segmentPoint = GetPolyBezierSegmentPoints(MyBezierPath);
var pg = new PathGeometry();
for (int i = 2; i < segmentPoint.Count - 1; i += 3)
{
pg.AddGeometry(new LineGeometry(segmentPoint[i], segmentPoint[i - 1]));
pg.AddGeometry(new LineGeometry(segmentPoint[i], segmentPoint[i + 1]));
}
ControlLinePath = new Path();
ControlLinePath.Data = pg;
ControlLinePath.Stroke = Brushes.DeepPink;
MyCanvas.Children.Add(ControlLinePath);
}
private void AddVertexPath()
{
var ps = GetPolyBezierSegmentPoints(MyBezierPath);
var pg = new PathGeometry();
for (int i = 0; i < ps.Count; i++)
{
var ep = new EllipseGeometry(ps[i], 4, 4);
pg.AddGeometry(ep);
}
VertexPath = new Path();
VertexPath.Data = pg;
VertexPath.Stroke = Brushes.DeepPink;
MyCanvas.Children.Add(VertexPath);
}
private double GetTotalDistance(PointCollection anchorPoints)
{
double distance = 0;
for (int i = 0; i < anchorPoints.Count - 1; i++)
{
distance += GetDistance(anchorPoints[i], anchorPoints[i + 1]);
}
return distance;
}
private PointCollection GetPolyBezierSegmentPoints(Path bezierPath)
{
var pg = (PathGeometry)bezierPath.Data;
PathFigure fig = pg.Figures[0];
var seg = (PolyBezierSegment)fig.Segments[0];
return seg.Points;
}
private PolyBezierSegment MakePolyBezierSegment(PointCollection anchorPoints)
{
var seg = new PolyBezierSegment();
seg.Points.Add(anchorPoints[0]);
for (int i = 1; i < anchorPoints.Count - 1; i++)
{
seg.Points.Add(anchorPoints[i]);
seg.Points.Add(anchorPoints[i]);
seg.Points.Add(anchorPoints[i]);
}
seg.Points.Add(anchorPoints[anchorPoints.Count - 1]);
seg.Points.Add(anchorPoints[anchorPoints.Count - 1]);
return seg;
}
private Path MakeBezierPath(PointCollection points)
{
var fig = new PathFigure();
fig.StartPoint = points[0];
fig.Segments.Add(MakePolyBezierSegment(points));
var pg = new PathGeometry();
pg.Figures.Add(fig);
var path = new Path();
path.Data = pg;
return path;
}
private double GetRadianFrom2Points(Point begin, Point end)
{
return Math.Atan2(end.Y - begin.Y, end.X - begin.X);
}
private double GetDistance(Point p1, Point p2)
{
return Math.Sqrt(Math.Pow(p2.X - p1.X, 2.0) + Math.Pow(p2.Y - p1.Y, 2.0));
}
}
}
主なのは制御点座標を求めるところ
タプル便利
普通の関数の戻り値は1つだけどタプルを使えば複数にできる
制御点は1つのアンカー点につき2つあるので、計算した結果の座標2つを返したい
つまり戻り値を2つにしたい、こんなときにタプルってのを使えばできる
//直線化、制御点距離は0なので現在アンカー点と同じ座標
private (Point begin, Point end) ControlPointsA0(Point beginSide, Point current, Point endSide, double curve)
{
return (current, current);
}
初めて使ってみたけど、いいねえ
コード
関連記事
次、2018/10/31
2018/6/23
2018/6/12
2018/6/11
2015/1/27