You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
384 lines
10 KiB
384 lines
10 KiB
/*************************************************************************************
|
|
|
|
Extended WPF Toolkit
|
|
|
|
Copyright (C) 2007-2013 Xceed Software Inc.
|
|
|
|
This program is provided to you under the terms of the Microsoft Public
|
|
License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license
|
|
|
|
For more features, controls, and fast professional support,
|
|
pick up the Plus Edition at http://xceed.com/wpf_toolkit
|
|
|
|
Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids
|
|
|
|
***********************************************************************************/
|
|
|
|
using System;
|
|
using System.Windows;
|
|
|
|
namespace Xceed.Wpf.Toolkit.Core.Utilities
|
|
{
|
|
internal struct Segment
|
|
{
|
|
#region Constructors
|
|
|
|
public Segment( Point point )
|
|
{
|
|
_p1 = point;
|
|
_p2 = point;
|
|
_isP1Excluded = false;
|
|
_isP2Excluded = false;
|
|
}
|
|
|
|
public Segment( Point p1, Point p2 )
|
|
{
|
|
_p1 = p1;
|
|
_p2 = p2;
|
|
_isP1Excluded = false;
|
|
_isP2Excluded = false;
|
|
}
|
|
|
|
public Segment( Point p1, Point p2, bool excludeP1, bool excludeP2 )
|
|
{
|
|
_p1 = p1;
|
|
_p2 = p2;
|
|
_isP1Excluded = excludeP1;
|
|
_isP2Excluded = excludeP2;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Empty Static Properties
|
|
|
|
public static Segment Empty
|
|
{
|
|
get
|
|
{
|
|
Segment result = new Segment( new Point( 0, 0 ) );
|
|
result._isP1Excluded = true;
|
|
result._isP2Excluded = true;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region P1 Property
|
|
|
|
public Point P1
|
|
{
|
|
get
|
|
{
|
|
return _p1;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region P2 Property
|
|
|
|
public Point P2
|
|
{
|
|
get
|
|
{
|
|
return _p2;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsP1Excluded Property
|
|
|
|
public bool IsP1Excluded
|
|
{
|
|
get
|
|
{
|
|
return _isP1Excluded;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsP2Excluded Property
|
|
|
|
public bool IsP2Excluded
|
|
{
|
|
get
|
|
{
|
|
return _isP2Excluded;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsEmpty Property
|
|
|
|
public bool IsEmpty
|
|
{
|
|
get
|
|
{
|
|
return DoubleHelper.AreVirtuallyEqual( _p1, _p2 ) && ( _isP1Excluded || _isP2Excluded );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsPoint Property
|
|
|
|
public bool IsPoint
|
|
{
|
|
get
|
|
{
|
|
return DoubleHelper.AreVirtuallyEqual( _p1, _p2 );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Length Property
|
|
|
|
public double Length
|
|
{
|
|
get
|
|
{
|
|
return ( this.P2 - this.P1 ).Length;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Slope Property
|
|
|
|
public double Slope
|
|
{
|
|
get
|
|
{
|
|
return ( this.P2.X == this.P1.X ) ? double.NaN : ( this.P2.Y - this.P1.Y ) / ( this.P2.X - this.P1.X );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public bool Contains( Point point )
|
|
{
|
|
if( IsEmpty )
|
|
return false;
|
|
|
|
// if the point is an endpoint, ensure that it is not excluded
|
|
if( DoubleHelper.AreVirtuallyEqual( _p1, point ) )
|
|
return _isP1Excluded;
|
|
|
|
if( DoubleHelper.AreVirtuallyEqual( _p2, point ) )
|
|
return _isP2Excluded;
|
|
|
|
bool result = false;
|
|
|
|
// ensure that a line through P1 and the point is parallel to the current segment
|
|
if( DoubleHelper.AreVirtuallyEqual( Slope, new Segment( _p1, point ).Slope ) )
|
|
{
|
|
// finally, ensure that the point is between the segment's endpoints
|
|
result = ( point.X >= Math.Min( _p1.X, _p2.X ) )
|
|
&& ( point.X <= Math.Max( _p1.X, _p2.X ) )
|
|
&& ( point.Y >= Math.Min( _p1.Y, _p2.Y ) )
|
|
&& ( point.Y <= Math.Max( _p1.Y, _p2.Y ) );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public bool Contains( Segment segment )
|
|
{
|
|
return ( segment == this.Intersection( segment ) );
|
|
}
|
|
|
|
public override bool Equals( object o )
|
|
{
|
|
if( !( o is Segment ) )
|
|
return false;
|
|
|
|
Segment other = ( Segment )o;
|
|
|
|
// empty segments are always considered equal
|
|
if( this.IsEmpty )
|
|
return other.IsEmpty;
|
|
|
|
// segments are considered equal if
|
|
// 1) the endpoints are equal and equally excluded
|
|
// 2) the opposing endpoints are equal and equally excluded
|
|
if( DoubleHelper.AreVirtuallyEqual( _p1, other._p1 ) )
|
|
{
|
|
return ( DoubleHelper.AreVirtuallyEqual( _p2, other._p2 )
|
|
&& _isP1Excluded == other._isP1Excluded
|
|
&& _isP2Excluded == other._isP2Excluded );
|
|
}
|
|
else
|
|
{
|
|
return ( DoubleHelper.AreVirtuallyEqual( _p1, other._p2 )
|
|
&& DoubleHelper.AreVirtuallyEqual( _p2, other._p1 )
|
|
&& _isP1Excluded == other._isP2Excluded
|
|
&& _isP2Excluded == other._isP1Excluded );
|
|
}
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return _p1.GetHashCode() ^ _p2.GetHashCode() ^ _isP1Excluded.GetHashCode() ^ _isP2Excluded.GetHashCode();
|
|
}
|
|
|
|
public Segment Intersection( Segment segment )
|
|
{
|
|
// if either segment is empty, the intersection is also empty
|
|
if( this.IsEmpty || segment.IsEmpty )
|
|
return Segment.Empty;
|
|
|
|
// if the segments are equal, just return a new equal segment
|
|
if( this == segment )
|
|
return new Segment( this._p1, this._p2, this._isP1Excluded, this._isP2Excluded );
|
|
|
|
// if either segment is a Point, just see if the point is contained in the other segment
|
|
if( this.IsPoint )
|
|
return segment.Contains( this._p1 ) ? new Segment( this._p1 ) : Segment.Empty;
|
|
|
|
if( segment.IsPoint )
|
|
return this.Contains( segment._p1 ) ? new Segment( segment._p1 ) : Segment.Empty;
|
|
|
|
// okay, no easy answer, so let's do the math...
|
|
Point p1 = this._p1;
|
|
Vector v1 = this._p2 - this._p1;
|
|
Point p2 = segment._p1;
|
|
Vector v2 = segment._p2 - segment._p1;
|
|
Vector endpointVector = p2 - p1;
|
|
|
|
double xProd = Vector.CrossProduct( v1, v2 );
|
|
|
|
// if segments are not parallel, then look for intersection on each segment
|
|
if( !DoubleHelper.AreVirtuallyEqual( Slope, segment.Slope ) )
|
|
{
|
|
// check for intersection on other segment
|
|
double s = ( Vector.CrossProduct( endpointVector, v1 ) ) / xProd;
|
|
if( s < 0 || s > 1 )
|
|
return Segment.Empty;
|
|
|
|
// check for intersection on this segment
|
|
s = ( Vector.CrossProduct( endpointVector, v2 ) ) / xProd;
|
|
if( s < 0 || s > 1 )
|
|
return Segment.Empty;
|
|
|
|
// intersection of segments is a point
|
|
return new Segment( p1 + s * v1 );
|
|
}
|
|
|
|
// segments are parallel
|
|
xProd = Vector.CrossProduct( endpointVector, v1 );
|
|
if( xProd * xProd > 1.0e-06 * v1.LengthSquared * endpointVector.LengthSquared )
|
|
{
|
|
// segments do not intersect
|
|
return Segment.Empty;
|
|
}
|
|
|
|
// intersection is overlapping segment
|
|
Segment result = new Segment();
|
|
|
|
// to determine the overlapping segment, create reference segments where the endpoints are *not* excluded
|
|
Segment refThis = new Segment( this._p1, this._p2 );
|
|
Segment refSegment = new Segment( segment._p1, segment._p2 );
|
|
|
|
// check whether this segment is contained in the other segment
|
|
bool includeThisP1 = refSegment.Contains( refThis._p1 );
|
|
bool includeThisP2 = refSegment.Contains( refThis._p2 );
|
|
if( includeThisP1 && includeThisP2 )
|
|
{
|
|
result._p1 = this._p1;
|
|
result._p2 = this._p2;
|
|
result._isP1Excluded = this._isP1Excluded || !segment.Contains( this._p1 );
|
|
result._isP2Excluded = this._isP2Excluded || !segment.Contains( this._p2 );
|
|
return result;
|
|
}
|
|
|
|
// check whether the other segment is contained in this segment
|
|
bool includeSegmentP1 = refThis.Contains( refSegment._p1 );
|
|
bool includeSegmentP2 = refThis.Contains( refSegment._p2 );
|
|
if( includeSegmentP1 && includeSegmentP2 )
|
|
{
|
|
result._p1 = segment._p1;
|
|
result._p2 = segment._p2;
|
|
result._isP1Excluded = segment._isP1Excluded || !this.Contains( segment._p1 );
|
|
result._isP2Excluded = segment._isP2Excluded || !this.Contains( segment._p2 );
|
|
return result;
|
|
}
|
|
|
|
// the intersection must include one endpoint from this segment and one endpoint from the other segment
|
|
if( includeThisP1 )
|
|
{
|
|
result._p1 = this._p1;
|
|
result._isP1Excluded = this._isP1Excluded || !segment.Contains( this._p1 );
|
|
}
|
|
else
|
|
{
|
|
result._p1 = this._p2;
|
|
result._isP1Excluded = this._isP2Excluded || !segment.Contains( this._p2 );
|
|
}
|
|
if( includeSegmentP1 )
|
|
{
|
|
result._p2 = segment._p1;
|
|
result._isP2Excluded = segment._isP1Excluded || !this.Contains( segment._p1 );
|
|
}
|
|
else
|
|
{
|
|
result._p2 = segment._p2;
|
|
result._isP2Excluded = segment._isP2Excluded || !this.Contains( segment._p2 );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
string s = base.ToString();
|
|
|
|
if( this.IsEmpty )
|
|
{
|
|
s = s + ": {Empty}";
|
|
}
|
|
else if( this.IsPoint )
|
|
{
|
|
s = s + ", Point: " + _p1.ToString();
|
|
}
|
|
else
|
|
{
|
|
s = s + ": " + _p1.ToString() + ( _isP1Excluded ? " (excl)" : " (incl)" )
|
|
+ " to " + _p2.ToString() + ( _isP2Excluded ? " (excl)" : " (incl)" );
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
#region Operators Methods
|
|
|
|
public static bool operator ==( Segment s1, Segment s2 )
|
|
{
|
|
if( ( object )s1 == null )
|
|
return ( object )s2 == null;
|
|
|
|
if( ( object )s2 == null )
|
|
return ( object )s1 == null;
|
|
|
|
return s1.Equals( s2 );
|
|
}
|
|
|
|
public static bool operator !=( Segment s1, Segment s2 )
|
|
{
|
|
return !( s1 == s2 );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
|
|
private bool _isP1Excluded;
|
|
private bool _isP2Excluded;
|
|
private Point _p1;
|
|
private Point _p2;
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
|