Quantcast
Channel: 午後わてんのブログ
Viewing all articles
Browse latest Browse all 420

ベジェ曲線の長さ測定できた、C#とWPF

$
0
0
4日前からの続きで今度は曲線Pathの長さを測る
GetFlattenedPathGeometryとGetPointAtFractionLengthを使って測る

半径50の円周で確認
イメージ 1
円周は2*パイ*半径なので
2*3.14*50=341になればいい

イメージ 2
結果は314.2、小数点2桁目で四捨五入しているからこれであっている




ベジェ曲線の長さ測定
イメージ 8



イメージ 4
水色の線の長さは

イメージ 3
966.3と測定された
だいたいあっているはず




公差と分割数

GetFlattenedPathGeometryに渡すTolerance(公差)が102の場合
イメージ 5
得られる近似直線は雑になる(細い青線)
なのでこれを細かく測定しても

イメージ 6
いい結果は得られない


公差0で丁寧な近似直線でも分割数が少ないと
イメージ 7
これもかなり不正確
測定値は赤の直線の長さの合計


より正確に測るには
GetFlattenedPathGeometryに渡す引数Tolerance(公差)は0を指定して
分割数を増やしてGetPointAtFractionLengthで細かく測定する


分割数をより増やしてみたら
イメージ 9
972まで増やしたら966.7になった
200のときでも966.3だったので、あんまり変わらない
もっと長く複雑な曲線なら増やしたほうが良さそうだけど
この程度の曲線なら200で十分かな

デザイン画面
イメージ 10

C#コード

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
//曲線のPathをGetFlattenedPathGeometryで近似直線Pathに変換して
//それをGetPointAtFractionLengthで分割した頂点同士の距離を合計する

namespace _20180614_曲線の長さを測る
{
public partial class MainWindow : Window
{
PathGeometry FlattenedPathGeometry;//曲線の近似直線に変換したPathGeometry
List<Path> StepEllipseList = new List<Path>();//分割したところの○印
List<Path> MyListLine = new List<Path>();//○印を結ぶ直線Path

public MainWindow()
{
InitializeComponent();

FlattenedPathGeometry = MyPath.Data.GetFlattenedPathGeometry();
FlattendeLine.Data = FlattenedPathGeometry;

SliderStep.ValueChanged += SliderStep_ValueChanged;
SliderTolerance.ValueChanged += SliderTolerance_ValueChanged;
SliderStep.MouseWheel += Slider_MouseWheel;
SliderTolerance.MouseWheel += Slider_MouseWheel;

}

//マウスホイール回したとき
private void Slider_MouseWheel(object sender, MouseWheelEventArgs e)
{
Slider slider = (Slider)sender;
if (e.Delta > 0) { slider.Value += slider.SmallChange; }
else { slider.Value -= slider.SmallChange; }
}

//公差変更時
private void SliderTolerance_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
ChangeFlattendePath();
Measure();
}

//分割数変更時
private void SliderStep_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Measure();
}

//長さ測定
private void Measure()
{
ClearStepEllipse();
var pg = (PathGeometry)MyPath.Data;
Point p1 = pg.Figures[0].StartPoint;
Point p2;
double pathLength = 0;//長さ測定用
double step = SliderStep.Value;
for (int i = 0; i < step + 1; i++)
{
//○印の作成と追加
//第1引数は0から1を指定、0がPathの始点になり1は終点、0.5なら中間位置を取得できる
FlattenedPathGeometry.GetPointAtFractionLength(i / step, out Point p, out Point pt);
var ellipseGeo = new EllipseGeometry(p, 4, 4);
var path = new Path();
path.Stroke = Brushes.Blue;
path.Data = ellipseGeo;
MyGrid.Children.Add(path);
StepEllipseList.Add(path);

//長さ測定
p2 = p;
pathLength += Distance(p1, p2);
//測定対象の2頂点を結ぶ直線Pathの作成と追加
var lineG = new LineGeometry(p1, p2);
p1 = p;
path = new Path();
path.Stroke = Brushes.Red;
path.Data = lineG;
MyGrid.Children.Add(path);
MyListLine.Add(path);
}
//表示更新
TextBlockMeasure.Text =
"測定値 = " + Math.Round(pathLength, 1, MidpointRounding.AwayFromZero).ToString();
}

//2点間の距離、ユークリッド距離
private double Distance(Point p1, Point p2)
{
return Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
}

//表示をクリア
private void ClearStepEllipse()
{
foreach (var item in StepEllipseList)
{
MyGrid.Children.Remove(item);
}
StepEllipseList.Clear();

foreach (var item in MyListLine)
{
MyGrid.Children.Remove(item);
}
MyListLine.Clear();
}

//近似直線の更新
private void ChangeFlattendePath()
{
//第1引数Toleranceは0以上を指定、0が一番曲線に近く正確だけど計算量が増える
FlattenedPathGeometry =
MyPath.Data.GetFlattenedPathGeometry(SliderTolerance.Value, ToleranceType.Absolute);
FlattendeLine.Data = FlattenedPathGeometry;
}

}
}



もっと正確に測りたいときはGetFlattenedPathGeometryで得た近似直線のPathGeometryから直接測ればいいけど、めんどくさそう
イメージ 13
ベジェ曲線をGetFlattenedPathGeometryで
公差=0を指定で得られた近似直線のPathGeometryの頂点座標数は4096!
これを計算すればかなり正確になるんだろうけど多すぎるw
だったら公差の値を大きくすればいいけどそれなら分割数で指定しても同じかなと
それに今回はPolyLineSegmentで得られたけど、元のPathによってはLineSegmentとかになることもあったり、同じ座標が何回も出てきたりすることもあったので、近似直線のPathGeometryから直接測定するのはめんどくさそうだったので、やっぱりGetPointAtFractionLengthで分割がラクでいいかなあ

イメージ 14
円をGetFlattenedPathGeometryで公差0
Segment数が9でこの中にPolyLineSegmentとLineSegmentが交互に混じっていたはず



パスマークアップ構文で円を描く
半径50の円の場合は
PathGeometry Figures="M100,100 A50,50 0 1 1 100,200 A50,50 0 1 1 100,100"

パスマークアップ構文に円はないので、円弧を2つ組み合わせて書く
イメージ 11
"M100,100 A50,50 0 1 1 100,200"
Mは始点、Aからが円弧で
50,50が半径のx,y
0回転角度
1よくわからんけど1か0を指定する
1よくわからん
100,200終点のx,y
これで右半分描けたので
左半分を足して
"M100,100 A50,50 0 1 1 100,200 A50,50 0 1 1 100,100"
左半分は終点が100,100になるだけで右半分と同じでいいみたい
これで円になる
イメージ 12
なった

円を描くだけなら円専用のEllipseGeometryが便利だけど、これだとGetFlattenedPathGeometryメソッドがないのでPathGeometryで書く必要があった




参照したところ
C#のWPFでWaitingCircleコントロールを作る - Ararami Atudio
https://araramistudio.jimdo.com/2016/11/24/wpf%E3%81%A7waitingcircle%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%9C%E3%82%8B/
パスマークアップ構文で円を描くのはここがわかりやすかった


コード




関連記事
2018/6/14
曲線Pathを近似の直線PathにするGetFlattenedPathGeometry使ってみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15549065.html

2018/6/15
Pathを等分したところに印と角度を表示してみた、GetPointAtFractionLength ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15550562.html

2018/6/16
GetPointAtFractionLengthで分割した座標からのPathの長さ測定の確認 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15551929.html




Viewing all articles
Browse latest Browse all 420

Trending Articles