/*******************************************************************************
*                                                                              *
* Author    :  Angus Johnson                                                   *
* Version   :  0.5 pre-alpha                                                   *
* Date      :  26 October 2013                                                 *
* Website   :  http://www.angusj.com                                           *
* Copyright :  Angus Johnson 2010-2013                                         *
*                                                                              *
* License:                                                                     *
* Use, modification & distribution is subject to Boost Software License Ver 1. *
* http://www.boost.org/LICENSE_1_0.txt                                         *
*                                                                              *
*******************************************************************************/

/*******************************************************************************
MultiPathSegment: 
A MultiPathSegment represents a single curve defined by control points. All
(subclassed) MultiPathSegment have a set number of control points except for 
the MultiPathSegment representing a series of polylines (MpsLine) which accepts 
a variable number of 'control' points. The MultiPathSegment class representing 
a cubic bezier - MpsCBezier - accepts 4 control points. An MpsElipticalArc 
MultiPathSegment accepts 5 control points. Multiple curves of the same type in
a series (eg a series of cubic beziers) are represented by multiple 
MultiPathSegments. A MultiPathSegment of CurveType.Line is the only 'curve' 
type that accepts a variable number of control points.

MultiPath:
MultiPath represents a list of connected MultiPathSegment. A single MultiPath
can contain up to 32,000 MultiPathSegment, and these MultiPathSegments may be
of any type or mixture of types.

MultiPaths:
A MultiPaths object contains a list of MultiPath objects.
*******************************************************************************/


using System;
using System.Collections;
using System.Collections.Generic;
using ClipperLib;
//using System.Diagnostics;   //for debugging only
//using System.Windows.Forms; //for debugging only

namespace ClipperMultiPathsLib
{

  using Path = List<IntPoint>;
  using Paths = List<List<IntPoint>>;
  using IntList = List<int>;

  //nb: The Arc implementation will probably be merged into the more generic EllipitcalArc. 
  public enum CurveType { Unknown, Line, CubicBezier, QuadBezier, Arc, EllipticalArc};
  
  //------------------------------------------------------------------------------
  // MultiPaths
  //------------------------------------------------------------------------------

  public class MultiPaths
  {

    public const double DefaultPrecision = 0.5;
    public double Precision { get { return precision; } }
    public int Count { get { return MultiPathList.Count; } }

    private double precision;
    private List<MultiPath> MultiPathList = new List<MultiPath>();

    //---------------------------------------------------------------------------
    //Format of IntPoint Z's 64bit value (in flattened paths)
    //given that each vertex belongs to a MultiPathSegment within a MultiPath
    //within a MultiPaths container object ...
    //---------------------------------------------------------------------------
    //mpRef   16 (bits 48..63): User-defined MultiPath reference ID (eg for filtering)
    //ovrLp    1 (bit      47): Overlap flag - for vertices shared by adjacent segments
    //mpIdx   16 (bits 32..46): Index of a MultiPath (within a MultiPaths container)
    //mpsIdx  15 (bits 16..30): Index of a MultiPathSegment within a MultiPath
    //mpSort  16 (bits  0..15): Sort value (based on position in flattened path)

    internal static Int64 MakeZ(UInt16 mpRef, UInt16 mpIdx, UInt16 mpsIdx, UInt16 sort)
    {
      return (Int64)((UInt64)mpRef << 48 | (UInt64)mpIdx << 32 | (UInt64)mpsIdx << 16 | sort);
    }
    //---------------------------------------------------------------------------

    public static void ClipCallback(IntPoint vert1, IntPoint vert2, ref IntPoint intersectPt) 
    {
      //This function is called when a Clipper object encounters an intersection.
      //The 'Z' values generated by MultiPaths are always even (see MakeZ) so that 
      //generating odd values here flags to Reconstruct() that the vertex is at an 
      //intersection while also preserving sort order ...

      if (Math.Abs(vert1.Z - vert2.Z) == 2) 
      {
        //adjacent vertices in same segment
        intersectPt.Z = vert1.Z;
        intersectPt.Z = (Int64)((UInt64)vert1.Z & 0xFFFFFFFF00000000 | 
          ((UInt32)vert1.Z + (UInt32)vert2.Z)) / 2;
      }
      else if (Math.Abs((vert1.Z & 0xFFFF) - (vert2.Z & 0xFFFF)) == 2 &&
        Math.Abs(((vert1.Z >> 16) & 0xFFFF7FFFFFFF) - ((vert2.Z >> 16) & 0xFFFF7FFFFFFF)) < 2)
      {
        //adjacent vertices, one being an overlap vertex from the same or adjacent segment
        intersectPt.Z = (((UInt64)vert1.Z & 0xFFFF7FFFFFFF0000) != 0 ? vert2.Z : vert1.Z) +
          ((vert1.Z & 0xFFFF) + (vert2.Z & 0xFFFF)) / 2;
      }
      else
      {
        //we've hit the end of a segment (nb: stripping of any overlap flag too)
        if ((UInt32)vert1.Z == 0)
          intersectPt.Z = (Int64)((UInt64)vert2.Z & 0xFFFF7FFFFFFFFFFF) + 1;
        else if ((UInt32)vert2.Z == 0)
          intersectPt.Z = (Int64)((UInt64)vert1.Z & 0xFFFF7FFFFFFFFFFF) + 1;
        else if (vert1.Z > vert2.Z)
          intersectPt.Z = (Int64)((UInt64)vert2.Z & 0xFFFF7FFFFFFFFFFF) + 1;
        else
          intersectPt.Z = (Int64)((UInt64)vert1.Z & 0xFFFF7FFFFFFFFFFF) + 1;
      }
    }
    //---------------------------------------------------------------------------
    
    public MultiPaths(double precision = 0)
    {
      this.precision = (precision <= 0 ? DefaultPrecision : precision);
    }
    //---------------------------------------------------------------------------

    public MultiPath this[int i]
    {
      get { return MultiPathList[i]; }
    }
    //---------------------------------------------------------------------------

    public IEnumerator GetEnumerator() 
    { 
      foreach (MultiPath mp in MultiPathList) yield return mp;
    }
    //---------------------------------------------------------------------------

    public void Clear() { MultiPathList.Clear(); }
    //---------------------------------------------------------------------------

    public bool RemoveAt(int i)
    {
      if (i < 0 || i >= Count) return false;
      MultiPathList.RemoveAt(i);
      return true;
    }
    //---------------------------------------------------------------------------

    public MultiPath NewMultiPath(UInt16 refID, bool isClosedPath = false)
    {
      //nb: a MultiPath can be closed later too
      if (Count > 0)
      {
        //if there's an empty MultiPath at the end then just use that ...
        MultiPath mp = MultiPathList[Count - 1];
        if (mp.Count == 0)
        {
          mp.RefID = refID;
          mp.IsClosed = isClosedPath;
          return mp;
        }
      }
      MultiPath newPath = new MultiPath(this, refID, isClosedPath);
      MultiPathList.Add(newPath);
      return newPath;
    }
    //---------------------------------------------------------------------------

    internal int GetMultiIndex(Int64 Z)
    {
      return (int)((Z >> 32) & 0x7FFF); //nb: ignore the top (overlap) bit 
    }
    //---------------------------------------------------------------------------

    internal MultiPath GetMultiPath(Int64 Z)
    {
      int idx = GetMultiIndex(Z);
      if (idx >= 0 && idx < Count) return MultiPathList[idx];
      else return null;
    }
    //---------------------------------------------------------------------------

    internal static bool IsAdjacent(Int64 Z1, Int64 Z2)
    {
      //nb: Generally, uninterupted edges have the Z values of adjacent vertices 
      //differing by exactly 2 units. However, since Clipper removes collinear
      //vertices, we need to accommodate differences of up to a tolerance.
      const int tolerance = 6;
      return Math.Abs((Z1 & 0xFFFFFFFFFFFF) - (Z2 & 0xFFFFFFFFFFFF)) <= tolerance;
    }
    //---------------------------------------------------------------------------

    internal static bool IsOverlap(Int64 Z)
    {
      return Globals.Odd(Z >> 47);
    }
    //---------------------------------------------------------------------------

    internal static bool IsOverlap(Int64 adjacentZ, Int64 currentZ)
    {
      //nb: returns false if currentZ is also an overlap ...
      const int tolerance = 6;
      return Math.Abs((adjacentZ & 0xFFFF) - (currentZ & 0xFFFF)) <= tolerance && 
        Globals.Even(currentZ >> 47) && Globals.Odd(adjacentZ >> 47) &&
        Math.Abs(((adjacentZ >> 16) & 0x7FFFFFFF) - ((currentZ >> 16) & 0x7FFFFFFF)) < 2;
    }
    //---------------------------------------------------------------------------

    public MultiPath Reconstruct(Path flatPath)
    {
      int highI = flatPath.Count -1;
      //nb: the MultiPath result has NO owner
      MultiPath result = new MultiPath(null, 0, false); 
      MultiPath mp1 = GetMultiPath(flatPath[0].Z);

      if (highI < 0) return result;
      else if (highI < 6)
      {
        //it's inefficient reconstructing very short curved segments, so
        //manage these simply by creating a polyline segment ...
        result.NewMultiPathSegment(CurveType.Line, flatPath);
        result.IsClosed = (mp1 != null && mp1.IsClosed);
        return result;
      }
      MultiPath mp2 = GetMultiPath(flatPath[highI].Z);

      if (mp1 == null || mp2 == null)
        throw new MultiPathException("Reconstruct error: a vertex doesn't belong to this MultiPaths object");
      if (mp1.IsClosed != mp2.IsClosed)
        throw new MultiPathException("Reconstruct error: can't combine open and closed paths");
 
      Path p = new Path();
      result.IsClosed = mp1.IsClosed;

      //start by stripping off non-adjacent vertices, 
      //returning then as a (still flat) polyline ...
      int i = 0;
      while (i < highI && !IsAdjacent(flatPath[i].Z, flatPath[i + 1].Z) &&
        !IsOverlap(flatPath[i].Z, flatPath[i + 1].Z)) i++;
      if (i > 0)
      {
        for (int j = 0; j <= i; j++) p.Add(flatPath[j]);
        result.NewMultiPathSegment(CurveType.Line, p);
        p.Clear();
      }

      if (mp1.IsClosed && i == 0)
      {
        //nb: with closed paths, flatPath[0] may start in the middle of a multipathsegment,
        //so first find the start of a multipath segment ...
        if (IsOverlap(flatPath[highI].Z, flatPath[0].Z))
          p.Add(flatPath[highI]);
        else if (IsAdjacent(flatPath[0].Z, flatPath[highI].Z))
        {
          //avoid starting in the middle of a multipathsegment ...
          int j = 1;
          while (j < highI && IsAdjacent(flatPath[highI - j].Z, flatPath[highI - j + 1].Z)) j++;
          if (IsOverlap(flatPath[highI - j].Z, flatPath[highI - j + 1].Z)) p.Add(flatPath[highI - j]); //nb: don't j++ this one!
          for (int k = 1; k <= j; k++) p.Add(flatPath[highI - j + k]);
          highI -= j;
        }
      }

      //OK, beyond here at least flatPath[i] & flatPath[i+1] should be adjacent vertices
      while (i <= highI)
      {
        p.Add(flatPath[i++]);
        //now complete then next series of vertices ...
        while (i <= highI && IsAdjacent(flatPath[i - 1].Z, flatPath[i].Z))
          p.Add(flatPath[i++]);
        //and if we've just hit an overlap, then include it too (and increment i) ...
        if (i <= highI && IsOverlap(flatPath[i].Z, flatPath[i - 1].Z)) p.Add(flatPath[i++]);

        //p now contains a copy of adjacent vertices on a multipath
        //so we can now perform the reconstruction ...

        mp1 = GetMultiPath(p[0].Z);
   
        //finally, something to reconstruct //////////////////////////
        mp2 = mp1.Reconstruct(p); //note new use for mp2 
        //////////////////////////////////////////////////////////////
   
        //and append each segment in mp2 to result ...
        for (int j = 0; j < mp2.Count; j++)
        {
          MultiPathSegment mps;
          if (j > 0 && mp2[j].curvetype == CurveType.Line &&
              result[result.Count - 1].curvetype == CurveType.Line)
            mps = result[result.Count - 1];
          else
            mps = result.NewMultiPathSegment(mp2[j].curvetype, null);
          mp2[j].AppendTo(mps);
        }

        if (i > highI) break;
        p.Clear();
        mp1 = null;
        //regardless of fp[i-1] 's overlap state, we need to add it to the new segment ...
        p.Add(flatPath[i - 1]);

        //if at an overlap then continue ...
        if (IsOverlap(flatPath[i - 1].Z, flatPath[i].Z)) continue;

        //otherwise, while there are mismatches, just make a polyline ...
        while (i < highI && !IsAdjacent(flatPath[i - 1].Z, flatPath[i].Z) &&
          !IsOverlap(flatPath[i - 1].Z, flatPath[i].Z)) p.Add(flatPath[i++]);
        if (i == highI) p.Add(flatPath[i++]);
        result.NewMultiPathSegment(CurveType.Line, p);
        p.Clear();
        p.Add(flatPath[i - 1]);
      }
      return result;
    }
    //---------------------------------------------------------------------------

    //ToSvgString: a crude (temporary) function that stores MultiPaths data.
    //Still need to merge consecutive CurveType.Line segments, and needs 
    //CurveType.Arc replaced with CurveType.EllipticalArc etc ...

    public string ToSvgString()
    {
      
      string result = "";

      foreach (MultiPath mp in MultiPathList)
      {
        if (!mp.IsValid()) continue;
        IntPoint startPt = mp[0][0];
        result += string.Format("<path refID=\"{0}\" d=\"M {1},{2}", mp.RefID, startPt.X, startPt.Y);
        CurveType lastCurveType = CurveType.Unknown;
        foreach (MultiPathSegment mps in mp)
        {
          if (lastCurveType != mps.curvetype)
          {
            switch (mps.curvetype)
            {
              case CurveType.Line:
                result += " L";
                break;
              case CurveType.Arc:
                result += " A";
                break;
              case CurveType.CubicBezier:
                result += " C";
                break;
              case CurveType.QuadBezier:
                result += " Q";
                break;
            }
            lastCurveType = mps.curvetype;
          }
          for (int i = 1; i < mps.Count; i++) //nb: skips the first vertex
            result += string.Format(" {0},{1}", mps[i].X, mps[i].Y);
        }
        result += (mp.IsClosed ? " Z\"/>\r\n" : "\"/>\r\n"); 
      }
      return result;
    }
    //---------------------------------------------------------------------------

    private int NextAlpha(string text, int startingAt = 0)
    {
      int len = text.Length;
      //very crude parsing for the time being (only accepts integers) ...
      while (startingAt < len && (text[startingAt] < 'A' || text[startingAt] > 'Z')) startingAt++;
      return startingAt;
    }
    //---------------------------------------------------------------------------

    private void AddStringToPath(string text, Path path)
    {
      string[] vals = text.Split(new char[] { ' ', ','}, StringSplitOptions.RemoveEmptyEntries);
      int cnt = vals.Length / 2;
      for (int i = 0; i < cnt; i++)
      {
        int x, y;
        if (!int.TryParse(vals[i *2], out x) ||
          !int.TryParse(vals[i *2 +1], out y)) break;
        path.Add(new IntPoint(x, y));
      }
    }
    //---------------------------------------------------------------------------

    //FromSvgString: a crude (temporary) function that loads MultiPaths data from storage
    public void FromSvgString(string svgText)
    {
      string[] lines = svgText.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
      //1. find path statement ...
      foreach (string line in lines)
      {
        int i = line.IndexOf("<path refID=\"");
        if (i < 0) continue;
        int j = line.IndexOf("\"", i + 13);
        if (j != i + 14) continue;
        string RefString = line.Substring(i + 13, j - i - 13);
        int refId;
        if (!int.TryParse(RefString, out refId)) continue;

        i = line.IndexOf("d=\"", i + 14);
        if (i < 0) continue;
        j = line.IndexOf("\"", i + 15);
        if (j < 0) continue;
        string pathStr = line.Substring(i + 3, j - i - 3); //everything between quotes
        i = pathStr.IndexOf("M");
        if (i < 0) continue;
        int k = pathStr.Length;
        j = i + 1;
        //very crude parsing for the time being (as only accepts integers) ...
        j = NextAlpha(pathStr, i +1);

        Path path = new Path();
        AddStringToPath(pathStr.Substring(i + 1, j - i - 1), path);
        if (path.Count != 1) continue;
        pathStr = pathStr.Substring(j);
        MultiPath currMp = NewMultiPath(0, false);
        currMp.RefID = (UInt16)refId;

        IntPoint lastIp = path[0];
        while (pathStr.Length > 0)
        {
          path.Clear();
          path.Add(lastIp);
          char c = pathStr[0];
          pathStr = pathStr.Substring(1);
          AddStringToPath(pathStr, path);
          int cnt = path.Count;
          lastIp = path[path.Count - 1];
          switch (c)
          {
            case 'Z':
              currMp.IsClosed = true;
              currMp = null;
              break;
            case 'L':
              if (currMp == null) currMp = NewMultiPath(0, false);
              currMp.NewMultiPathSegment(CurveType.Line, path);
              break;
            case 'A':
              if (currMp == null) currMp = NewMultiPath(0, false);
              for (int m = 0; m < cnt / 2; m++)
              {
                currMp.NewMultiPathSegment(CurveType.Arc, path);
                path.RemoveRange(0, 2);
              }
              break;
            case 'C':
              if (currMp == null) currMp = NewMultiPath(0, false);
              for (int m = 0; m < cnt / 3; m++)
              {
                currMp.NewMultiPathSegment(CurveType.CubicBezier, path);
                path.RemoveRange(0, 3);
              }
              break;
            case 'Q':
              if (currMp == null) currMp = NewMultiPath(0, false);
              for (int m = 0; m < cnt / 2; m++)
              {
                currMp.NewMultiPathSegment(CurveType.QuadBezier, path);
                path.RemoveRange(0, 2);
              }
              break;
            default: return;
          }
          pathStr = pathStr.Substring(NextAlpha(pathStr));       
        }

      }
    }

  } //end MultiPaths

  //---------------------------------------------------------------------------
  // MultiPath
  //---------------------------------------------------------------------------

  public class MultiPath
  {

    public MultiPaths owner;
    public UInt16 index;
    public UInt16 RefID;

    private double precision;
    private List<MultiPathSegment> segments = new List<MultiPathSegment>();
    private int currFlatIdx;
    private MultiPathSegment currMps;
    private Path flatPath = new Path();

    //---------------------------------------------------------------------------

    public MultiPath(MultiPaths owner, UInt16 refID, bool isClosed)
    {
      this.owner = owner;
      this.index = (owner == null ? (UInt16)0 : (UInt16)owner.Count);
      this.RefID = refID;
      this.precision = (owner != null ? owner.Precision : MultiPaths.DefaultPrecision);
      this.IsClosed = isClosed;
    }
    //---------------------------------------------------------------------------

    public MultiPathSegment this[int i]
    {
      get { return segments[i]; }
    }
    //---------------------------------------------------------------------------

    public IEnumerator GetEnumerator()
    {
      foreach (MultiPathSegment mps in segments) yield return mps;
    }
    //---------------------------------------------------------------------------

    public int Count { get { return segments.Count; } }
    //---------------------------------------------------------------------------

    public MultiPathSegment NewMultiPathSegment(CurveType pt, Path path)
    {
      if (Count > 0 && !segments[Count - 1].IsValid())
        throw new MultiPathException("MultiPath Error: Appending to an invalid MultiPath.");

      MultiPathSegment mps;
      UInt16 idx = (UInt16)segments.Count;
      switch (pt)
      {
        case CurveType.Arc: mps = new MpsArc(this, idx); break;
        case CurveType.Line: mps = new MpsLine(this, idx); break;
        case CurveType.CubicBezier: mps = new MpsCBezier(this, idx); break;
        case CurveType.QuadBezier: mps = new MpsQBezier(this, idx); break;
        default: return null;
      }
      if (path != null)
      {
        int highI = Math.Min(path.Count, mps.maxCtrlPts) - 1;
        for (int i = 0; i <= highI; i++) mps.Add(path[i]);
        //foreach (IntPoint ip in path) mps.Add(ip);
      }
      segments.Add(mps);
      return mps;
    }
    //---------------------------------------------------------------------------

    public void Clear() { segments.Clear(); }
    //---------------------------------------------------------------------------

    //removals are not allowed except from the path end (or the whole path) 
    //otherwise the logic is broken where adjacent segments share a common vertex.
    //In other words, for segment removals ... use either Clear() or RemoveLast()
    public void RemoveLast()
    {
      if (Count > 0) segments.RemoveAt(Count - 1);
    }
    //---------------------------------------------------------------------------

    public bool IsValid()
    {
      int i = Count;
      if (i == 0) return false;
      return segments[i - 1].IsValid();
    }
    //---------------------------------------------------------------------------

    public bool IsClosed { get; set; }
    //---------------------------------------------------------------------------

    private static IntPoint StripTop48Bits(IntPoint ip)
    {
      IntPoint result = ip;
      result.Z = ip.Z & 0xFFFF;
      return result;
    }
    //---------------------------------------------------------------------------

    public MultiPath Reconstruct(Path flatPath) 
    {
      MultiPath result = new MultiPath(null, 0, false);
      int highI = flatPath.Count - 1;

      //avoid reconstructing very short curves ...
      if (highI < 4) 
      {
        if (highI > 0) //ie don't bother if just a single vertex 
          result.NewMultiPathSegment(CurveType.Line, flatPath);
        return result;
      }

      if (IsClosed) AddClosingLineSeg();

      int seg1 = (UInt16)(flatPath[1].Z >> 16) & 0xFFFF;          //nb: list indices are offset 
      int seg2 = (UInt16)(flatPath[highI - 1].Z >> 16) & 0xFFFF;  //to avoid 'overlap' vertices

      if (seg1 != seg2)
        throw new MultiPathException("Reconstruct error: mixing segments");

      MultiPathSegment mps = segments[seg1];
      if (mps.curvetype == CurveType.Line)
      {
        result.NewMultiPathSegment(CurveType.Line, flatPath);
        return result;
      }

      //avoid sorting based on either end as they may be 'overlap' vertices ...
      bool reversed = ((Int16)flatPath[1].Z > (Int16)flatPath[2].Z);
      if (reversed) flatPath.Reverse();

      flatPath[0] = StripTop48Bits(flatPath[0]);
      flatPath[highI] = StripTop48Bits(flatPath[highI]);

      if (!mps.Reconstruct(flatPath[0], flatPath[highI], result))
      {
        if (reversed) flatPath.Reverse();
        result.NewMultiPathSegment(CurveType.Line, flatPath);
        return result;
      }
      
      int cnt = 0;
      foreach (MultiPathSegment mpsr in result)
      {
        cnt += mpsr.Count;
        if (reversed) mpsr.Reverse();
      }
      if (reversed) result.segments.Reverse();

      if (IsClosed) RemoveClosingLineSeg();


      //finally, if the reconstructed path has more controls than the flattened path has 
      //vertices (typically with some beziers), then return the path as a simple polyline ...
      if (cnt > flatPath.Count)
      {
        result.Clear();
        if (reversed) flatPath.Reverse();
        result.NewMultiPathSegment(CurveType.Line, flatPath);
      }

      return result;
    }
    //---------------------------------------------------------------------------

    internal void FlattenAdd(MultiPathSegment mps, IntPoint ip)
    {
      if (currFlatIdx < 0)
        currMps = mps;
      else if (mps != currMps)
      {
        //starting a new MultiPathSegment ...
        if (currMps != null)
        {
          //set a flag on the overlap vertex ...
          IntPoint prevIp = flatPath[currFlatIdx];
          prevIp.Z = prevIp.Z | 0x800000000000;
          flatPath[currFlatIdx] = prevIp;
        }
        currMps = mps;
        if (ip == flatPath[currFlatIdx])
        {
          mps.flattenOffset = flatPath.Count * 2 - 2;
          return;
        }
        mps.flattenOffset = flatPath.Count * 2;
      }
      else if (ip == flatPath[currFlatIdx]) 
        return; //ignore duplicate vertices

      ip.Z = MultiPaths.MakeZ(RefID, index, (UInt16)mps.index, (UInt16)(flatPath.Count * 2));
      flatPath.Add(ip);
	    currFlatIdx++;
    }
    //---------------------------------------------------------------------------

    private void AddClosingLineSeg()
    {
      //nb: adds a temporary line segment since further segments may be added
      if (IsValid())
      {
        IntPoint ip1, ip2;
        ip1 = segments[0][0];
        MultiPathSegment mps = segments[Count - 1];
        
        ip2 = mps[mps.Count - 1];
        if (ip1 != ip2)
        {
          Path path = new Path(2);
          path.Add(ip2);
          path.Add(ip1);
          mps = NewMultiPathSegment(CurveType.Line, path);
        }
      }
    }
    //---------------------------------------------------------------------------

    private void RemoveClosingLineSeg()
    {
      if (Count > 1 & IsValid()) RemoveLast(); //nb: > 1 (NOT > 0)
    }
    //---------------------------------------------------------------------------

    public Path Flatten() 
    { 
      currFlatIdx = -1;
      flatPath.Clear();

      if (IsClosed) AddClosingLineSeg();
      foreach (MultiPathSegment mps in segments) mps.Flatten();
      if (IsClosed) RemoveClosingLineSeg();
      
      return flatPath;
    }
    //---------------------------------------------------------------------------

  } //end MultiPath


  //---------------------------------------------------------------------------
  // MultiPathSegment
  //---------------------------------------------------------------------------

  public abstract class MultiPathSegment
  {

    public CurveType curvetype;
    public MultiPath owner;
    public UInt16 index;
    
    internal int flattenOffset;
    internal int maxCtrlPts;
    protected Path ctrls = new Path();

    public MultiPathSegment(MultiPath owner, UInt16 index)
    {
      if (owner == null)
        throw new MultiPathException("MultiPathSegments must have an owner.");
      this.owner = owner;
      this.index = index;
    }
    //------------------------------------------------------------------------------

    public int Count { get { return ctrls.Count; } }
    //------------------------------------------------------------------------------

    public IntPoint this[int i]
    {
      get { return ctrls[i]; }
    }
    //---------------------------------------------------------------------------

    public IEnumerator GetEnumerator()
    {
      foreach (IntPoint ip in ctrls) yield return ip;
    }
    //---------------------------------------------------------------------------

    internal virtual void Flatten()
    {
      if (index < owner.Count - 1)
        throw new MultiPathException("Flatten Error: undefined segment");
    }
    //---------------------------------------------------------------------------

    internal virtual bool IsValid()
    {
      return Count == maxCtrlPts; //maxCtrlPts is set in subclassed constructor
    }
    //---------------------------------------------------------------------------

    internal void Reverse()
    {
      ctrls.Reverse();
    }
    //---------------------------------------------------------------------------

    protected double GetPrecision()
    {
      return (owner.owner != null ? owner.owner.Precision : MultiPaths.DefaultPrecision);
    }
    //---------------------------------------------------------------------------

    public bool RemoveLast()
    {
      int i = Count;
      if (i == 0) return false;
      ctrls.RemoveAt(i - 1);
      return true;
    }
    //------------------------------------------------------------------------------

    public bool Add(IntPoint ip)
    {
      //copy the last point of the previous segment to the new segment ...
      if (Count == 0)
      {
        if (index > 0)
        {
          IntPoint prev = GetPrevPt();
          if (prev != ip) ctrls.Add(prev);
        }
      }
      else if (Count == maxCtrlPts)
        return false;
      else if (ip == ctrls[Count -1])
        return false;
      ctrls.Add(ip);
      return true;
    }
    //------------------------------------------------------------------------------

    public void Move(int index, IntPoint newPt)
    {
      //if this is an overlap vertex then adjust the adjacent segment too ...
      if (index == 0 && this.index > 0)
      {
        MultiPathSegment mpsPrev = owner[this.index - 1];
        mpsPrev.ctrls[mpsPrev.Count - 1] = newPt;
      }
      else if (IsValid() && index == Count -1 && this.index < owner.Count -1)
      {
        MultiPathSegment mpsNext = owner[this.index + 1];
        mpsNext.ctrls[0] = newPt;
      }
      ctrls[index] = newPt;
    }
    //------------------------------------------------------------------------------

    public void AppendTo(MultiPathSegment mps)
    {
      foreach (IntPoint ip in ctrls) mps.ctrls.Add(ip);
    }
    //------------------------------------------------------------------------------

    protected IntPoint GetPrevPt()
    {
      if (index == 0 || owner == null)
        throw new MultiPathException("Error: no previous control point found");
      MultiPathSegment mps = owner[index - 1];
      return mps[mps.Count -1];
    }
    //------------------------------------------------------------------------------

    internal virtual bool Reconstruct(IntPoint startPt, IntPoint endPt, MultiPath mp)
    {
      return false;
    }
    //------------------------------------------------------------------------------

  }

  //---------------------------------------------------------------------------
  // MpsLine class
  //---------------------------------------------------------------------------

  public class MpsLine : MultiPathSegment
  {
    public MpsLine(MultiPath owner, UInt16 index)
      : base(owner, index) 
    {
      curvetype = CurveType.Line;
      maxCtrlPts = 0x7FFF; //~32000
    }
    //---------------------------------------------------------------------------

    internal override bool IsValid() 
    {
      return Count > 0;
    }
    //---------------------------------------------------------------------------

    internal override void Flatten()
    {
      if (!IsValid()) { base.Flatten(); return; }
      for (int i = 0; i < Count; i++)
          owner.FlattenAdd(this, ctrls[i]);
    }
    //---------------------------------------------------------------------------

  } //end MpsLine class

  //---------------------------------------------------------------------------
  // MpsArc class
  //---------------------------------------------------------------------------

  public class MpsArc : MultiPathSegment
  {
    public DoublePoint origin;
    public double radius = 0;
    public bool isClockwise;

    public MpsArc(MultiPath owner, UInt16 index)
      : base(owner, index)
    {
      curvetype = CurveType.Arc;
      maxCtrlPts = 3;
    } //constructor
    //---------------------------------------------------------------------------

    internal override bool IsValid()
    {
      return Count == 3;
    }
    //---------------------------------------------------------------------------

    internal override void Flatten()
    {
      if (!IsValid()) { base.Flatten(); return; }
      if (ctrls[0] == ctrls[2]) return; //empty arc
      
      DoublePoint p1 = new DoublePoint(ctrls[0]);
      DoublePoint p2 = new DoublePoint(ctrls[1]);
      DoublePoint p3 = new DoublePoint(ctrls[2]);

      if (!Globals.CircleFrom3Points(p1, p2, p3, out origin, out radius))
      {
        //p1, p2 & p3 must be collinear, so ...
        owner.FlattenAdd(this, ctrls[0]);
        owner.FlattenAdd(this, ctrls[2]);
        return;
      }

      //store arc data ready for reconstruction ...
      isClockwise = Globals.RightTurning(p1, p2, p3);
      double a1 = Globals.GetAngle(origin, p1);
      double a2 = Globals.GetAngle(origin, p3);

      double frac = Math.Abs(a2 - a1) / Globals.TwoPI;
      if (isClockwise == (a2 >= a1)) frac = 1 - frac;
      int steps = (int)Globals.Round(Globals.rad180 / Math.Acos(1 - GetPrecision() / radius)) + 1;
      if (steps < 2) steps = 2;
      double asin, acos, angle = frac * Globals.TwoPI / steps;
      if (!isClockwise) angle = -angle;
      Globals.SinCos(angle, out asin, out acos);
      double x = ((double)p1.X - origin.X);
      double y = ((double)p1.Y - origin.Y);
      int j = 0;
      for (; j < steps; j++)
      {
        owner.FlattenAdd(this, new IntPoint(Globals.Round(origin.X + x), Globals.Round(origin.Y + y)));
        double x2 = x;
        x = x * acos - asin * y;
        y = x2 * asin + y * acos;
      }
      j++;
      owner.FlattenAdd(this, ctrls[2]);
    }
    //---------------------------------------------------------------------------

    internal override bool Reconstruct(IntPoint startPt, IntPoint endPt, MultiPath mp)
    {
      Path arcPath = new Path();

      if (!IsValid() || radius == 0)
      {
        arcPath.Add(startPt);
        arcPath.Add(endPt);
        mp.NewMultiPathSegment(CurveType.Line, arcPath);
        return false;
      }

      if (startPt.Z == 0) startPt = ctrls[0];
      if (endPt.Z == 0) endPt = ctrls[2];

      arcPath.Add(startPt);
      double a1 = Globals.GetAngle(origin, new DoublePoint(startPt));
      double a2 = Globals.GetAngle(origin, new DoublePoint(endPt));
      double midA = Globals.GetMidAngle(a1, a2, isClockwise);
      DoublePoint midPt = Globals.GetPointFromOrigin(origin, radius, midA);
      arcPath.Add(new IntPoint(midPt));
      arcPath.Add(endPt);
      mp.NewMultiPathSegment(CurveType.Arc, arcPath);
      return true;
    }
    //---------------------------------------------------------------------------

  } //end MpsArc class


  //---------------------------------------------------------------------------
  // MpsEllipticalArc class
  //---------------------------------------------------------------------------

  public class MpsEllipticalArc : MultiPathSegment
  {
    public DoublePoint origin;
    public DoublePoint radii;
    public bool isClockwise;
    public double ellipticalAngle;

    public MpsEllipticalArc(MultiPath owner, UInt16 index)
      : base(owner, index)
    {
      curvetype = CurveType.EllipticalArc;
      maxCtrlPts = 5; //startPt, Axis1Pt, OriginPt (Z:orientation), Axis2Pt, endPt
    } //constructor
    //---------------------------------------------------------------------------

    internal override void Flatten()
    {
      if (!IsValid()) { base.Flatten(); return; }
      if (ctrls[0] == ctrls[4]) return; //empty arc (full ellipses are ignored too)

      DoublePoint sp = new DoublePoint(ctrls[0]);
      DoublePoint ep = new DoublePoint(ctrls[4]);
      DoublePoint axis1 = new DoublePoint(ctrls[1]);
      DoublePoint axis2 = new DoublePoint(ctrls[3]);
      this.origin = new DoublePoint(ctrls[2]);
      this.isClockwise = ctrls[2].Z >= 0;
      this.radii = new DoublePoint(Globals.Distance(origin, axis1), Globals.Distance(origin, axis2));
      this.ellipticalAngle = Globals.GetAngle(origin, axis1);
      if (ellipticalAngle > Globals.rad180) ellipticalAngle -= Globals.rad180;
      List<DoublePoint> flat = 
        Globals.GetFlattenedEllipticalArc(origin, radii, 
        sp, ep, true, isClockwise, ellipticalAngle, GetPrecision());
      foreach (DoublePoint pt in flat)
        owner.FlattenAdd(this, new IntPoint(pt));
    }
    //---------------------------------------------------------------------------

    internal override bool Reconstruct(IntPoint startPt, IntPoint endPt, MultiPath mp)
    {
      Path arcPath = new Path();

      if (!IsValid() || radii.X == 0 || radii.Y == 0)
      {
        arcPath.Add(startPt);
        arcPath.Add(endPt);
        mp.NewMultiPathSegment(CurveType.Line, arcPath);
        return false;
      }

      if (startPt.Z == 0) startPt = ctrls[0];
      if (endPt.Z == 0) endPt = ctrls[4];

      arcPath.Add(startPt);
      arcPath.Add(ctrls[1]);
      arcPath.Add(ctrls[2]);
      arcPath.Add(ctrls[3]);
      arcPath.Add(endPt);
      mp.NewMultiPathSegment(CurveType.EllipticalArc, arcPath);
      return true;
    }
    //---------------------------------------------------------------------------

  } //end MpsEllipticalArc class

  //---------------------------------------------------------------------------
  // MpsBezier class
  //---------------------------------------------------------------------------
  public abstract class MpsBezier : MultiPathSegment
  {


    internal List<BezSeg> BezSegList = new List<BezSeg>();
    internal BezSeg bezSegTree = null;

    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    internal class BezSeg
    {
      internal UInt16 RefID;
      internal UInt16 SegID;
      internal UInt32 Index;
      internal DoublePoint[] Ctrls;
      internal BezSeg[] Childs = new BezSeg[2];
      internal CurveType curvetype;

      internal BezSeg(UInt16 refID, UInt16 segID, UInt32 idx)
      {
        this.RefID = refID;
        this.SegID = segID;
        this.Index = idx;
      }

      internal void GetFlattenedSeg(List<BezSeg> bezSegs)
      {
        if (Childs[0] == null)
        {
          bezSegs.Add(this);
          return;
        }
        Childs[0].GetFlattenedSeg(bezSegs);
        Childs[1].GetFlattenedSeg(bezSegs);
      }

    }
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------

    internal class CBezSeg : BezSeg
    {
      internal CBezSeg(DoublePoint pt1, DoublePoint pt2, DoublePoint pt3, DoublePoint pt4,
        UInt16 refID, UInt16 segID, UInt32 idx, double precision)
        : base(refID, segID, idx)
      {
        curvetype = CurveType.CubicBezier;
        Ctrls = new DoublePoint[4];
        Ctrls[0] = pt1; Ctrls[1] = pt2; Ctrls[2] = pt3; Ctrls[3] = pt4;
        //assess curve flatness:
        //http://groups.google.com/group/comp.graphics.algorithms  ree/browse_frm  hread/d85ca902fdbd746e
        if (Math.Abs(pt1.X + pt3.X - 2 * pt2.X) + Math.Abs(pt2.X + pt4.X - 2 * pt3.X) +
          Math.Abs(pt1.Y + pt3.Y - 2 * pt2.Y) + Math.Abs(pt2.Y + pt4.Y - 2 * pt3.Y) < precision)
          return;

        //if not at maximum precision then (recursively) create sub-segments ...
        //, p23, p34, p123, p234, p1234;
        const double half = 0.5;
        DoublePoint p12 = new DoublePoint((pt1.X + pt2.X) * half, (pt1.Y + pt2.Y) * half);
        DoublePoint p23 = new DoublePoint((pt2.X + pt3.X) * half, (pt2.Y + pt3.Y) * half);
        DoublePoint p34 = new DoublePoint((pt3.X + pt4.X) * half, (pt3.Y + pt4.Y) * half);
        DoublePoint p123 = new DoublePoint((p12.X + p23.X) * half, (p12.Y + p23.Y) * half);
        DoublePoint p234 = new DoublePoint((p23.X + p34.X) * half, (p23.Y + p34.Y) * half);
        DoublePoint p1234 = new DoublePoint((p123.X + p234.X) * half, (p123.Y + p234.Y) * half);
        idx = idx << 1;
        Childs[0] = new CBezSeg(pt1, p12, p123, p1234, refID, segID, idx, precision);
        Childs[1] = new CBezSeg(p1234, p234, p34, pt4, refID, segID, idx + 1, precision);
      }
    } //end CBezSeg

    internal class QBezSeg : BezSeg
    {
      internal QBezSeg(DoublePoint pt1, DoublePoint pt2, DoublePoint pt3,
        UInt16 refID, UInt16 segID, UInt32 idx, double precision)
        : base(refID, segID, idx)
      {
        curvetype = CurveType.QuadBezier;
        Ctrls = new DoublePoint[3];
        Ctrls[0] = pt1; Ctrls[1] = pt2; Ctrls[2] = pt3;
        //assess curve flatness:
        if (Math.Abs(pt1.X + pt3.X - 2 * pt2.X) + Math.Abs(pt1.Y + pt3.Y - 2 * pt2.Y) < precision) return;

        //if not at maximum precision then (recursively) create sub-segments ...
        //DoublePoint p12, p23, p123;
        const double half = 0.5;
        DoublePoint p12 = new DoublePoint((pt1.X + pt2.X) * half, (pt1.Y + pt2.Y) * half);
        DoublePoint p23 = new DoublePoint((pt2.X + pt3.X) * half, (pt2.Y + pt3.Y) * half);
        DoublePoint p123 = new DoublePoint((p12.X + p23.X) * half, (p12.Y + p23.Y) * half);
        idx = idx << 1;
        Childs[0] = new QBezSeg(pt1, p12, p123, refID, segID, idx, precision);
        Childs[1] = new QBezSeg(p123, p23, pt3, refID, segID, idx + 1, precision);
      }
    } //end QBezSeg
    //------------------------------------------------------------------------------


    public MpsBezier(MultiPath owner, UInt16 index) : base(owner, index) { }
    //---------------------------------------------------------------------------

    protected UInt32 GetMostSignificantBit(UInt32 v) //index is zero based
    {
      UInt32[] b = { 0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000 };
      Int32[] s = { 0x1, 0x2, 0x4, 0x8, 0x10 };
      Int32 result = 0;
      for (int i = 4; i >= 0; --i)
        if ((v & b[i]) != 0)
        {
          v = v >> s[i];
          result = result | s[i];
        }
      return (UInt32)result;
    }
    //------------------------------------------------------------------------------

    protected static bool IsBitSet(UInt32 val, Int32 index)
    {
      return (val & (1 << (int)index)) != 0;
    }
    //------------------------------------------------------------------------------

    internal IntList BezierReconstruct(UInt32 startIdx, UInt32 endIdx)
    {
      IntList ilist = new IntList();

      //get the maximum level ...
      UInt32 L1 = GetMostSignificantBit(startIdx);
      UInt32 L2 = GetMostSignificantBit(endIdx);
      UInt32 Level = Math.Max(L1, L2);

      if (Level == 0)
      {
        ilist.Add(1);
        return ilist;
      }

      int L, R;
      //Right marker (R): EndIdx projected onto the bottom level ...
      if (endIdx == 1)
      {
        R = (1 << (int)((Level + 1))) - 1;
      }
      else
      {
        int k = (int)(Level - L2);
        R = ((int)endIdx << k) + (1 << k) - 1;
      }

      if (startIdx == 1) //special case
      {
        //Left marker (L) is bottom left of the binary tree ...
        L = (1 << (int)Level);
        L1 = Level;
      }
      else
      {
        L = (int)startIdx + 1;
        if (L == (1 << (int)(Level + 1))) return ilist; //loops around tree so already at the end
      }

      //now get blocks of nodes from the LEFT ...
      int j = (int)(Level - L1);
      do
      {
        //while next level up then down-right doesn't exceed L2 do ...
        while (Globals.Even(L) && ((L << j) + (1 << (j + 1)) - 1 <= R))
        {
          L = (L >> 1); //go up a level
          j++;
        }
        ilist.Add(L);
        L++;
      } while (L != (3 << (int)(Level - j - 1)) && //ie crosses the ditch in the middle
        (L << j) + (1 << j) < R);                  //or L is now over or to the right of R

      L = (L << j);

      //now get blocks of nodes from the RIGHT ...
      List<int> tmpList = new List<int>();
      j = 0;
      if (R >= L)
        do
        {
          while (Globals.Odd(R) && ((R - 1) << j >= L))
          {
            R = R >> 1; //go up a level
            j++;
          }
          tmpList.Add(R);
          R--;
        } while (R != (3 << (int)(Level - j)) - 1 && ((R << j) > L));
     
      for (j = tmpList.Count - 1; j >= 0; j--)
        ilist.Add(tmpList[j]);
      return ilist;
    }
    //------------------------------------------------------------------------------

    internal override bool Reconstruct(IntPoint startPt, IntPoint endPt, MultiPath mp)
    {

      if (!IsValid() || BezSegList.Count == 0) return false;

      uint startIdx, endIdx;
      if (startPt.Z == 0 || startPt.Z == this.flattenOffset)
      {
        startIdx = 1;
      }
      else
      {
        startIdx = (uint)((Int16)startPt.Z - this.flattenOffset) / 2;
        startIdx = BezSegList[(int)startIdx].Index;
      }

      if (endPt.Z == 0)
      {
        endIdx = BezSegList[BezSegList.Count - 1].Index;
      }
      else
      {
        endIdx = (uint)((Int16)endPt.Z - this.flattenOffset) / 2;
        if (endIdx > 0) endIdx--;
        endIdx = BezSegList[(int)endIdx].Index;
      }

      IntList intlist = BezierReconstruct(startIdx, endIdx);
      //IntList now contains the indexes of one or a series of sub-segments
      //that together define part of or the whole of the original bezier segment.
      //We now append these sub-segments to the new list of control points ...

      for (int i = 0; i < intlist.Count; i++)
      {
        uint ii = (uint)intlist[i];
        BezSeg bs = this.bezSegTree;
        Int32 k = (Int32)GetMostSignificantBit(ii) - 1;
        while (k >= 0)
        {
          if (bs.Childs[0] == null) break;
          if (IsBitSet(ii, k--))
            bs = bs.Childs[1];
          else
            bs = bs.Childs[0];
        }

        if (i == 0 && startPt.Z != 0)
        {
          IntPoint pt = new IntPoint(bs.Ctrls[0]);
          if (pt != startPt)
          {
            Path line = new Path(2);
            line.Add(startPt);
            line.Add(pt);
            mp.NewMultiPathSegment(CurveType.Line, line);
          }
        }
        Path bezCtrls = new Path(bs.Ctrls.Length);
        foreach (DoublePoint dp in bs.Ctrls)
          bezCtrls.Add(new IntPoint(dp));
        mp.NewMultiPathSegment(curvetype, bezCtrls);

        if (i == intlist.Count - 1 && endPt.Z != 0)
        {
          IntPoint pt = new IntPoint(bs.Ctrls[bs.Ctrls.Length - 1]);
          if (pt != endPt)
          {
            Path line = new Path(2);
            line.Add(pt);
            line.Add(endPt);
            mp.NewMultiPathSegment(CurveType.Line, line);
          }
        }
      }
      return true;
    }
    //---------------------------------------------------------------------------

  }; //end MpsBezier

  public class MpsCBezier : MpsBezier
  {

    public MpsCBezier(MultiPath owner, UInt16 index)
      : base(owner, index) 
    {
      curvetype = CurveType.CubicBezier;
      maxCtrlPts = 4;
    }
    //---------------------------------------------------------------------------

    internal override void Flatten()
    {
      if (!IsValid())
      {
        if (index < owner.Count - 1) base.Flatten();
        return;
      }

      DoublePoint [] c = new DoublePoint [4];
      //construct (recursively) the BezSeg tree structure ...
      c[0] = new DoublePoint(ctrls[0]);
      c[1] = new DoublePoint(ctrls[1]);
      c[2] = new DoublePoint(ctrls[2]);
      c[3] = new DoublePoint(ctrls[3]);
      bezSegTree = new CBezSeg(c[0], c[1], c[2], c[3], this.index, 0, 1, GetPrecision());

      BezSegList.Clear();
      bezSegTree.GetFlattenedSeg(BezSegList);
      owner.FlattenAdd(this, new IntPoint(BezSegList[0].Ctrls[0]));
      foreach (BezSeg bs in BezSegList)
        owner.FlattenAdd(this, new IntPoint(bs.Ctrls[3]));
    }
    //---------------------------------------------------------------------------

  } //end MpsCBezier class
  //---------------------------------------------------------------------------
  //---------------------------------------------------------------------------

  public class MpsQBezier : MpsBezier
  {

    public MpsQBezier(MultiPath owner, UInt16 index)
      : base(owner, index)
    {
      curvetype = CurveType.QuadBezier;
      maxCtrlPts = 3;
    }
    //---------------------------------------------------------------------------

    internal override void Flatten()
    {
      if (!IsValid())
      {
        if (index < owner.Count - 1) base.Flatten();
        return;
      }

      DoublePoint[] c = new DoublePoint[3];
      //construct (recursively) the BezSeg tree structure ...
      c[0] = new DoublePoint(ctrls[0]);
      c[1] = new DoublePoint(ctrls[1]);
      c[2] = new DoublePoint(ctrls[2]);
      bezSegTree = new QBezSeg(c[0], c[1], c[2], this.index, 0, 1, GetPrecision());

      BezSegList.Clear();
      bezSegTree.GetFlattenedSeg(BezSegList);
      owner.FlattenAdd(this, new IntPoint(BezSegList[0].Ctrls[0]));
      foreach (BezSeg bs in BezSegList)
        owner.FlattenAdd(this, new IntPoint(bs.Ctrls[2]));
    }
    //---------------------------------------------------------------------------

  } //end MpsQBezier class


  public class Globals
  {
    public const double rad180 = Math.PI;
    public const double rad90 = rad180 / 2;
    public const double rad270 = rad90 * 3;
    public const double rad360 = rad180 * 2;
    public const double TwoPI = rad360;

    public static bool Odd(Int64 val)
    {
      return (val % 2) == 1;
    }
    //------------------------------------------------------------------------------

    public static bool Even(Int64 val)
    {
      return (val % 2) == 0;
    }
    //---------------------------------------------------------------------------

    public static double Distance(IntPoint pt1, IntPoint pt2)
    {
      double dx = pt1.X - pt2.X;
      double dy = pt1.Y - pt2.Y;
      return Math.Sqrt(dx * dx + dy * dy);
    }
    //---------------------------------------------------------------------------

    public static double Distance(DoublePoint pt1, DoublePoint pt2)
    {
      double dx = pt1.X - pt2.X;
      double dy = pt1.Y - pt2.Y;
      return Math.Sqrt(dx * dx + dy * dy);
    }
    //---------------------------------------------------------------------------

    public static double DistanceSqrd(DoublePoint pt1, DoublePoint pt2)
    {
      double dx = pt1.X - pt2.X;
      double dy = pt1.Y - pt2.Y;
      return dx * dx + dy * dy;
    }
    //---------------------------------------------------------------------------

    public static void SinCos(double angle, out double sinval, out double cosval)
    {
      sinval = Math.Sin(angle);
      cosval = Math.Cos(angle);
    }
    //------------------------------------------------------------------------------

    public static Int64 Round(double value)
    {
      return value < 0 ? (Int64)(value - 0.5) : (Int64)(value + 0.5);
    }
    //------------------------------------------------------------------------------

    public static double NormalizeAngle(double angle)
    {
      if (angle > rad360) return angle - rad360;
      else if (angle < 0) return angle + rad360;
      else return angle;
    }
    //---------------------------------------------------------------------------

    public static double GetAngle(DoublePoint startPt, DoublePoint endPt)
    {
      return NormalizeAngle(Math.Atan2(startPt.Y - endPt.Y, endPt.X - startPt.X));
    }
    //------------------------------------------------------------------------------

    public static double GetEllipticalAngle(DoublePoint origin, DoublePoint pt, 
      DoublePoint radii, double ellipseAngle = 0.0)
    {
      //returns the angle of 'pt' relative to the ellipse's axis
      double dx = pt.X - origin.X, dy = pt.Y - origin.Y;
      if (ellipseAngle != 0.0)
      {
        double dx2 = dx;
        double asin = Math.Sin(-ellipseAngle), acos = Math.Cos(-ellipseAngle);
        dx = dx2 * acos + dy * asin; 
        dy = dy * acos - dx2 * asin;
      }
      return NormalizeAngle(Math.Atan2(-dy * radii.X / radii.Y, dx));
    }
    //------------------------------------------------------------------------------

    public static DoublePoint GetPointFromOrigin(DoublePoint origin, double radius, double angle)
    {
      double asin, acos;
      SinCos(angle, out asin, out acos);
      return new DoublePoint(radius * acos + origin.X, -radius * asin + origin.Y);
    }
    //------------------------------------------------------------------------------

    public static double GetMidAngle(double angle1, double angle3, bool isClockwise)
    {
      double a2 = NormalizeAngle((angle1 + angle3) / 2);
      if (Math.Abs(angle1 - angle3) < 0.025) return a2;
      else if ((a2 < angle1) != isClockwise) return NormalizeAngle(a2 + rad180);
      else return a2;
    }
    //------------------------------------------------------------------------------

    public static bool CircleFrom3Points(DoublePoint dp1, DoublePoint dp2, DoublePoint dp3,
      out DoublePoint origin, out double radius)
    {
      //logic: A line perpendicular to a chord that passes through the chord's midpoint 
      //must also pass through the origin. Therefore given 2 chords we can find the origin.
      origin = new DoublePoint();
      radius = 0.0;
      double m1, m2, mp1x, mp1y, mp2x, mp2y;
      if (dp1.Y == dp2.Y)
      {
        //test for collinear points ...
        if ((dp3.Y - dp1.Y) * (dp2.X - dp3.X) == (dp3.X - dp1.X) * (dp3.Y - dp2.Y)) return false;
        m1 = (dp3.X - dp1.X) / (dp1.Y - dp3.Y); //nb: inverse slopes
        m2 = (dp2.X - dp3.X) / (dp3.Y - dp2.Y);
        mp1x = (dp1.X + dp3.X) / 2;
        mp1y = (dp1.Y + dp3.Y) / 2;
        mp2x = (dp3.X + dp2.X) / 2;
        mp2y = (dp3.Y + dp2.Y) / 2;
      }
      else if (dp2.Y == dp3.Y)
      {
        if ((dp1.Y - dp2.Y) * (dp1.X - dp3.X) == (dp2.X - dp1.X) * (dp3.Y - dp1.Y)) return false;
        m1 = (dp2.X - dp1.X) / (dp1.Y - dp2.Y); //nb: inverse slopes
        m2 = (dp1.X - dp3.X) / (dp3.Y - dp1.Y);
        mp1x = (dp1.X + dp2.X) / 2;
        mp1y = (dp1.Y + dp2.Y) / 2;
        mp2x = (dp3.X + dp1.X) / 2;
        mp2y = (dp3.Y + dp1.Y) / 2;
      }
      else //use 1-2, 2-3
      {
        if ((dp1.Y - dp2.Y) * (dp2.X - dp3.X) == (dp2.X - dp1.X) * (dp3.Y - dp2.Y)) return false;
        m1 = (dp2.X - dp1.X) / (dp1.Y - dp2.Y); //nb: inverse slopes
        m2 = (dp2.X - dp3.X) / (dp3.Y - dp2.Y);
        mp1x = (dp1.X + dp2.X) / 2;
        mp1y = (dp1.Y + dp2.Y) / 2;
        mp2x = (dp3.X + dp2.X) / 2;
        mp2y = (dp3.Y + dp2.Y) / 2;
      }
      double b1 = mp1y - mp1x * m1;
      double b2 = mp2y - mp2x * m2;
      double x = (b2 - b1) / (m1 - m2);
      origin = new DoublePoint(x, m1 * x + b1);
      radius = Math.Sqrt((dp1.X - origin.X) * (dp1.X - origin.X) +
        (dp1.Y - origin.Y) * (dp1.Y - origin.Y));
      return true;
    }
    //---------------------------------------------------------------------------

    public static DoublePoint CircleOriginFrom2Points(DoublePoint dp1, DoublePoint dp2, 
      double radius, bool IsClockwise)
    {
      DoublePoint origin = new DoublePoint();
      if (dp1.X == dp2.X && dp1.Y == dp2.Y) return origin;
      DoublePoint mp = new DoublePoint((dp1.X + dp2.X)/2.0,(dp1.Y + dp2.Y)/2.0);
      double opp = Math.Sqrt(radius * radius - DistanceSqrd(dp1, mp));
      double adjMul2 = Distance(dp1, dp2);
      double sign = (IsClockwise ? 1.0 : -1.0);
      origin = new DoublePoint(mp.X + sign * opp * (dp1.Y - dp2.Y) / adjMul2,
        mp.Y + sign * opp * (dp2.X - dp1.X) / adjMul2);
      return origin;
    }
    //---------------------------------------------------------------------------

    public static double GetEllipticalRadiusY(double radiusX, DoublePoint origin, DoublePoint dp)
    {
      dp = new DoublePoint(dp.X - origin.X, dp.Y - origin.Y);
      if (Math.Abs(dp.X) > radiusX) return 0.0;
      return Math.Sqrt(dp.Y * dp.Y / (1 - (dp.X * dp.X / (radiusX * radiusX))));
    }
    //------------------------------------------------------------------------------

    public static DoublePoint RotatePoint(DoublePoint origin, DoublePoint pt, double radians)
    {
      if (radians == 0.0) return pt;
      double asin = Math.Sin(radians);
      double acos = Math.Cos(radians);
      pt = new DoublePoint(pt.X - origin.X, pt.Y - origin.Y);
      return new DoublePoint(pt.X * acos + pt.Y * asin + origin.X, pt.Y * acos - pt.X * asin + origin.Y);
    }
    //---------------------------------------------------------------------------

    public static List<DoublePoint> RotatePoints(DoublePoint origin, List<DoublePoint> pts, double radians)
    {
      if (radians == 0.0) return pts;
      double asin = Math.Sin(radians);
      double acos = Math.Cos(radians);
      List<DoublePoint> result = new List<DoublePoint>(pts.Count);
      foreach (DoublePoint dp in pts)
      {
        double x = ((dp.X - origin.X) * acos + (dp.Y - origin.Y) * asin) + origin.X;
        double y = ((dp.Y - origin.Y) * acos - (dp.X - origin.X) * asin) + origin.Y;
        result.Add(new DoublePoint(x, y));
      }
      return result;
    }
    //---------------------------------------------------------------------------

    public static bool GetEllipseFrom3Points(
      DoublePoint axis1a, DoublePoint axis1b, DoublePoint pt,
      out DoublePoint origin, out DoublePoint radii, out double angle_radians)
    {
      //axis1a & axis1b define the extent of one axis and origin is their midpoint ...
      //the remaining point can be anywhere on the ellipse except on the defined axis.
      origin = new DoublePoint((axis1a.X + axis1b.X) / 2, (axis1a.Y + axis1b.Y) / 2);
      angle_radians = GetAngle(origin, axis1b);
      if (angle_radians > rad90) angle_radians -= rad180;
      pt = RotatePoint(origin, pt, -angle_radians); //ie rotate pt so it's axis aligned
      double x = Distance(axis1a, origin);
      double y = GetEllipticalRadiusY(x, origin, pt);
      radii = new DoublePoint(x, y);
      return y > 0;
    }
    //------------------------------------------------------------------------------

    public static DoublePoint GetSvgEllipseOrigin(DoublePoint startPt, DoublePoint endPt,
      DoublePoint radii, double angle_radians, bool largeArc, bool sweep)
    {
      //Code adapted from ...
      //http://stackoverflow.com/questions/1805101/svg-elliptical-arcs-with-java

      // Ensure radii are valid ...
      if (radii.X < 0.0 || radii.Y < 0.0) 
        radii = new DoublePoint(Math.Abs(radii.X),Math.Abs(radii.Y));
      if (radii.X == 0 || radii.Y == 0) return startPt;

      // Compute the half distance between the current and the final point
      double dx2 = (startPt.X - endPt.X) / 2.0;
      double dy2 = (startPt.Y - endPt.Y) / 2.0;

      //if the start and end points are the same then the result is indeterminate ...
      if (dx2 == 0 && dy2 == 0) return startPt;

      double asin, acos;
      SinCos(-angle_radians, out asin, out acos);
      // Step 1 : Compute (x1, y1)
      double x1 = acos * dx2 + asin * dy2;
      double y1 = -asin * dx2 + acos * dy2;

      // Ensure radii are large enough
      double rxSqr = radii.X * radii.X;
      double rySqr = radii.Y * radii.Y;
      double rx2Sqr = x1 * x1;
      double ry2Sqr = y1 * y1;
      double d = rx2Sqr / rxSqr + ry2Sqr / rySqr;
      if (d > 1)
      {
        double sqrtD = Math.Sqrt(d);
        radii = new DoublePoint(sqrtD * radii.X, sqrtD * radii.Y);
        rxSqr = radii.X * radii.X;
        rySqr = radii.Y * radii.Y;
      }

      // Step 2 : Compute (cx1, cy1)
      double sign = (largeArc == sweep ? -1 : 1);
      double cx1, cy1;
      double coef = sign * Math.Sqrt(Math.Abs(((rxSqr * rySqr) -
        (rxSqr * ry2Sqr) - (rySqr * rx2Sqr)) / ((rxSqr * ry2Sqr) + (rySqr * rx2Sqr))));
      cx1 = coef * radii.X * y1 / radii.Y;
      cy1 = coef * -radii.Y * x1 / radii.X;

      // Step 3 : Compute (cx, cy) from (cx1, cy1)
      double cx = (startPt.X + endPt.X) / 2 + acos * cx1 - asin * cy1;
      double cy = (startPt.Y + endPt.Y) / 2 + asin * cx1 + acos * cy1;
      return new DoublePoint(cx, cy);
    }
    //---------------------------------------------------------------------------

    public static void Swap<T>(ref T val1, ref T val2)
    {
      T tmp = val1; val1 = val2; val2 = tmp;
    }
    //------------------------------------------------------------------------------

    public static List<DoublePoint> GetFlattenedCircle(DoublePoint origin,
      double radius, double precision = 0)
    {
      List<DoublePoint> result = new List<DoublePoint>();
      if (precision <= 0.0) precision = MultiPaths.DefaultPrecision;
      int steps = (int)Globals.Round(Globals.rad180 / Math.Acos(1 - precision / radius)) + 1;
      if (steps < 2) steps = 2;
      double angle_delta = rad360 / steps;

      double asin, acos;
      Globals.SinCos(angle_delta, out asin, out acos);
      double x = radius;
      double y = 0;
      for (int j = 0; j <= steps; j++)
      {
        result.Add(new DoublePoint(origin.X + x, origin.Y + y));
        double x2 = x;
        x = x2 * acos - asin * y;
        y = x2 * asin + y * acos;
      }
      return result;
    }
    //------------------------------------------------------------------------------

    public static List<DoublePoint> GetFlattenedEllipse(DoublePoint origin,
      DoublePoint radii, double angleOfEllipse = 0.0, double precision = 0)
    {
      if (radii.X == radii.Y) return GetFlattenedCircle(origin, radii.X, precision);

      List<DoublePoint> result = new List<DoublePoint>();
      if (precision <= 0.0) precision = MultiPaths.DefaultPrecision;
      int steps = (int)Globals.Round(Globals.rad180 / 
        Math.Acos(1 - precision / ((radii.X + radii.Y) / 2))) + 1;
      if (steps < 2) steps = 2;
      double angle = 0, angle_delta = rad360 / steps;

      for (int j = 0; j <= steps; j++)
      {
        double x = origin.X + radii.X * Math.Cos(angle);
        double y = origin.Y - radii.Y * Math.Sin(angle);
        result.Add(new DoublePoint(x, y));
        angle += angle_delta;
      }
      if (angleOfEllipse != 0.0)
        result = RotatePoints(origin, result, angleOfEllipse);
      return result;
    }
    //------------------------------------------------------------------------------

    public static List<DoublePoint> GetFlattenedEllipticalArc(DoublePoint origin,
      DoublePoint radii, DoublePoint startPt, DoublePoint endPt, bool LargeArc, bool IsClockwise,
      double angleOfEllipse = 0.0, double precision = 0)
    {
      double a1 = GetEllipticalAngle(origin, startPt, radii, angleOfEllipse);
      double a2 = GetEllipticalAngle(origin, endPt, radii, angleOfEllipse);
      if (IsClockwise) Swap(ref a1, ref a2);
      double aDiff = NormalizeAngle(a2 - a1);
      if (aDiff > rad180 != LargeArc) Swap(ref a1, ref a2);

      double frac = aDiff / rad360;
      List<DoublePoint> result = new List<DoublePoint>();
      if (precision <= 0.0) precision = MultiPaths.DefaultPrecision;
      int steps = (int)Globals.Round(Globals.rad180 * frac / Math.Acos(1 - precision / ((radii.X + radii.Y) / 2))) + 1;
      if (steps < 2) steps = 2;
      double angle_delta = rad360 * frac / steps;

      for (int j = 0; j <= steps; j++)
      {
        double x = origin.X + radii.X * Math.Cos(a1);
        double y = origin.Y - radii.Y * Math.Sin(a1);
        result.Add(new DoublePoint(x, y));
        a1 += angle_delta;
      }
      if (angleOfEllipse != 0.0)
        result = RotatePoints(origin, result, angleOfEllipse);
      return result;
    }
    //------------------------------------------------------------------------------

    public static bool RightTurning(DoublePoint dp1, DoublePoint dp2, DoublePoint dp3)
    {
      //cross product ...
      double dx1 = dp2.X - dp1.X;
      double dy1 = dp2.Y - dp1.Y;
      double dx2 = dp3.X - dp2.X;
      double dy2 = dp3.Y - dp2.Y;
      return ((dx1 * dy2) - (dx2 * dy1)) > 0;
    }
    //------------------------------------------------------------------------------

  } //end Globals class
  //---------------------------------------------------------------------------
  //---------------------------------------------------------------------------

  class MultiPathException : Exception
  {
    public MultiPathException(string description) : base(description) { }
  }

}

