/************************************************************************************* 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 } }