Geometry.cpp
#include "Geometry.h"
using namespace Robotics;
using namespace System;
using namespace System::Diagnostics;
/// グラハムスキャン (凸包を求める)
Points^ Geometry::GrahamScan( Points^ vertices )
{
int vertexSum = vertices->Length; // 頂点数
Debug::Assert( 3 <= vertexSum, "頂点数は3以上である (さもなければ多角形となり得ない)" );
Geometry^ geometry = gcnew Geometry();
// 演算用に 点の配列を生成する
Points^ result = gcnew Points( vertexSum + 1 );
// ... 配列の前を空けるようにコピーする
Array::Copy(
vertices, // コピー元
0, // コピーを開始するインデックス
result, // コピー先
1, // 格納を開始するインデックス
vertexSum // コピーする要素の数
);
// Y方向に最小、X方向に最大となる点のインデックスを取得する
int pointInCorner = geometry->GetIndexOfPointInCorner( result );
// ... その点と 最初の点を交換する
Point::Swap( result[ pointInCorner ], result[ 1 ] );
// 角度係数を格納する配列を生成する
array< double >^ comparisonOfAngle = gcnew array< double >( vertexSum + 1 );
for( int i=1; i<=vertexSum; i++ )
{
// 角度の間の大小関係の判定を行う係数を取得する
comparisonOfAngle[ i ] = geometry->GetCoefficientComparisonOfAngle( result[ 1 ], result[ i ] );
}
// ... 求めた角度係数に従い 頂点の配列をソートする
geometry->QuickSort(
1, // 区間の左端
vertexSum, // 区間の右端
comparisonOfAngle, // ソートの基準となるもの
result // ソートするもの
);
Debug::Assert( result[ 0 ] == Point( 0.0, 0.0 ), "要素ゼロの頂点は処理されていない" );
// 最初の頂点に 最後の頂点を代入する
result[ 0 ] = result[ vertexSum ];
int count = 2; // 凸包をなしている(処理が終わっている)頂点数
/// @note 基準となる頂点は、最初から処理が終わっているとみなす
// 凸包を形成する
for( int i=3; i<=vertexSum; i++ )
{
// 3点のなす角度が負になるまでくり返して、処理すべき頂点を探索する
while( -1 != geometry->WhetherAngleIsPlusOrMinus(
result[ count + 0 ],
result[ count - 1 ],
result[ i ]
) )
{
count--;
if( count == 0 )
{
// 探索を中断する
break;
/// @note
/// エラー回避のために考案した方法であり、
/// この処理には問題があると思われる。
}
}
count++;
// 2点を交換する
Point::Swap( result[ count ], result[ i ] );
}
// 配列の大きさを、頂点数と同じにする
Array::Resize( result, count );
return result;
// [ Reference ]「Cで書くアルゴリズム」疋田輝雄(サイエンス社) P135
}
/// 隅にある点のインデックスを取得する (Y方向に最小、X方向に最大の位置の点)
int Geometry::GetIndexOfPointInCorner( Points^ point )
{
/// @note 要素0は特別な意味を持つため、比較の対象としない
int result = 1;
// Y方向に最小となる点を探索する (最初の点同士を比較する必要はないので、その次の要素から始めている)
for( int i=result + 1; i < point->Length; i++ )
{
if( point[ i ].Y < point[ result ].Y )
{
result = i;
}
}
// ... その点とY座標が一致する点において X方向に最大となる点を探索する
for( int i=1; i < point->Length; i++ )
{
if( ( Math::Abs( point[ i ].Y - point[ result ].Y ) < Double::Epsilon )
&& ( point[ result ].X < point[ i ].X ) )
{
result = i;
}
}
return result;
// [ Reference ]「Cで書くアルゴリズム」疋田輝雄(サイエンス社) P135
}
/// 角度の間の大小関係の判定を行う 係数を取得する
double Geometry::GetCoefficientComparisonOfAngle( Point point1, Point point2 )
{
// 2点間の距離を求める
Point distance = point2 - point1;
double result = 0.0;
if( Double::Epsilon <= Math::Abs( distance.X )
|| Double::Epsilon <= Math::Abs( distance.Y ) )
{
// X方向の距離またはY方向の距離が ゼロではない
result = distance.Y / ( Math::Abs( distance.X ) + Math::Abs( distance.Y ) );
}
if( distance.X < 0.0 )
{
// X方向の距離が負数となっている (ここの定数の意味は不明)
result = 2.0 - result;
}
else if( distance.Y < 0.0 )
{
// Y方向の距離が負数となっている (ここの定数の意味は不明)
result = result + 4.0;
}
result *= 90.0;
/// @note この計算の意味は不明
// 係数の範囲を確認する (テキストで示されている範囲。根拠は不明)
Debug::Assert( 0.0 <= result && result <= 360.0, "結果は範囲内にある" );
return result;
// [ Reference ]「Cで書くアルゴリズム」疋田輝雄(サイエンス社) P132
}
/// 3点のなす角度は正か負か?
int Geometry::WhetherAngleIsPlusOrMinus( Point middle, Point left, Point right )
{
// 両端の点の距離を求める
Point d1 = right - left;
// 左端と中央の点との距離を求める
Point d2 = middle - left;
double area1 = d1.X * d2.Y;
double area2 = d1.Y * d2.X;
if( area1 > area2 )
{
return +1;
}
if( area1 < area2 )
{
return -1;
}
if( ( d1.X * d2.X < 0.0 ) || ( d1.Y * d2.Y < 0.0 ) )
{
return -1;
}
if( ( ( d1.X * d1.X ) + ( d1.Y * d1.Y ) ) < ( ( d2.X * d2.X ) + ( d2.Y * d2.Y ) ) )
{
return +1;
}
// 基本となる点は 2点の直線上にある
return 0;
// [ Reference ]「Cで書くアルゴリズム」疋田輝雄(サイエンス社) P127
}
/// 点と線分の距離を求める
double Geometry::CalculateDistanceOfPointAndSegment( Point point, Segment segment )
{
if( segment.IsSamePoint() )
{
// 線分が点(両端が同一点)ならば それと点との距離を返す
return Point::CalculateDistance( segment.Point1, point );
}
Point dPoint = segment.Point2 - segment.Point1;
Point origin = segment.Point1 - point;
double a = ( dPoint.X * dPoint.X ) + ( dPoint.Y * dPoint.Y );
double b = ( dPoint.X * origin.X ) + ( dPoint.Y * origin.Y );
// 媒介変数を求める
double parameter = -( b / a );
/// @note
/// 媒介変数を0から1の範囲に押さえ込むことで、垂線の足が線分の範囲外になるときに
/// それを線分の端に合わせることができる。
if( parameter < 0.0 ) parameter = 0.0;
if( parameter > 1.0 ) parameter = 1.0;
// 垂線の足を求める
Point perpendicular = segment.Point1 + ( parameter * dPoint );
// ... それと点との距離を求める
return Point::CalculateDistance( perpendicular, point );
// [ Reference ] ToyWiki - 線分と点の距離 ( http://toycode.com/toywiki/pages/_E7B79AE58886E381A8E782B9E381AEE8B79DE99BA2_.html )
}
/// 多角形の内部か?
bool Geometry::IsInsideOfPolygon( Points^ vertices, Point target )
{
int vertexSum = vertices->Length; // 頂点数
Debug::Assert( 3 <= vertexSum, "頂点数は3以上である (さもなければ多角形となり得ない)" );
// 演算用に 点の配列を生成する
Points^ points = gcnew Points( vertexSum + 2 );
// ... 配列の前後を空けるようにコピーする
Array::Copy(
vertices, // コピー元
0, // コピーを開始するインデックス
points, // コピー先
1, // 格納を開始するインデックス
vertexSum // コピーする要素の数
);
// 最初に 最後の頂点を格納する
points[ 0 ] = vertices[ vertexSum - 1 ];
// 最後に 最初の頂点を格納する
points[ vertexSum + 1 ] = vertices[ 0 ];
int crossingCount = 0; // 交差数
Point point = points[ 0 ]; // 点
// 幾何学クラスを生成する
Geometry^ geometry = gcnew Geometry();
// 対象とする点から X軸と平行に正の方向へ向かう 半直線を定義する
Segment halfLine( target, Point( Double::MaxValue, target.Y ) );
for( int i=1; i<=vertexSum; i++ )
{
// 多角形の頂点上に 大きさゼロの線分を定義する
Segment segment( points[ i ], points[ i ] );
if( !geometry->IsSegmentIntersect( segment, halfLine ) )
{
// それが半直線と交差しないならば、線分の一端を定義しなおす
segment.Point2 = point;
// ... 点を 対象としている頂点に定義しなおす
point = points[ i ];
if( geometry->IsSegmentIntersect( segment, halfLine ) )
{
// 半直線と交差するならば、交差数をカウントする
crossingCount++;
}
}
}
// 奇数個の点が交差するならば 点が内部にあると判断する
return( crossingCount & 1 );
// [ Reference ]「Cで書くアルゴリズム」疋田輝雄(サイエンス社) P129
}
/// 線分は交差するか?
bool Geometry::IsSegmentIntersect( Segment segment1, Segment segment2 )
{
int case1 = WhetherAngleIsPlusOrMinus( segment1.Point1, segment1.Point2, segment2.Point1 )
* WhetherAngleIsPlusOrMinus( segment1.Point1, segment1.Point2, segment2.Point2 );
int case2 = WhetherAngleIsPlusOrMinus( segment2.Point1, segment2.Point2, segment1.Point1 )
* WhetherAngleIsPlusOrMinus( segment2.Point1, segment2.Point2, segment1.Point2 );
return ( case1 <= 0 ) && ( case2 <= 0 );
// [ Reference ]「Cで書くアルゴリズム」疋田輝雄(サイエンス社) P128
}
/// クイックソートをする
void Geometry::QuickSort( int leftEdge, int rightEdge, array< double >^ reference, Points^% points )
{
Debug::Assert( reference->Length == points->Length, "参照値と点の数は一致している" );
// 基準値(区間の中央の値) を求める
const double standardValue = reference[ ( leftEdge + rightEdge ) / 2 ];
int left = leftEdge;
int right = rightEdge;
while( left <= right )
{
// 区間の左端から 基準値より大きなものを見つける
while( reference[ left ] < standardValue )
{
left++;
}
// 区間の右端から 基準値より小さなものを見つける
while( reference[ right ] > standardValue )
{
right--;
}
if( left <= right )
{
// 2点を交換する
Point::Swap( points[ left ], points[ right ] );
// ... ソートの参照値も交換する
Swap( reference[ left ], reference[ right ] );
left++;
right--;
}
}
// 区間の左端に達するまで 処理をくり返す
if( leftEdge < right )
{
QuickSort( leftEdge, right, reference, points );
}
// 区間の右端に達するまで 処理をくり返
if( left < rightEdge )
{
QuickSort( left, rightEdge, reference, points );
}
// [ Reference ]「Cで書くアルゴリズム」疋田輝雄(サイエンス社) P135
}
/// 2つの値を交換する
void Geometry::Swap( double% left, double% right )
{
double swap = left;
left = right;
right = swap;
}