Blob Blame Raw
using System;
using System.Collections.Generic;

namespace EllipseTruncate {
	public class Ellipse {
		public Matrix matrix;
		public Matrix matrixInv;
	
		public Ellipse(Point p0, Point p1, Point p2) {
			Point d = p1 - p0;
			double r1 = d.len();
			double r2 = Math.Abs( (d.rotate90()*(p2 - p0))/r1 );
			matrix = Matrix.identity()
					.translate(p0)
			        .rotate(d.atan())
			        .scale(r1, r2);
			matrixInv = matrix.invert();
		}

		public Ellipse(Matrix matrix) {
			this.matrix = matrix;
			matrixInv = matrix.invert();
		}
				
		public Ellipse(Matrix matrix, Matrix matrixInv) {
			this.matrix = matrix;
			this.matrixInv = matrixInv;
		}

		public Ellipse(Ellipse ellipse):
			this(ellipse.matrix, ellipse.matrixInv) { }
		
		public void drawFull(Cairo.Context context, bool grid = false) {
    		int segments = 100;
    		double da = 2.0*Math.PI/segments;
    		double s = Math.Sin(da);
    		double c = Math.Cos(da);
    		Point r = new Point(1.0, 0.0);

			if (!grid) {
				context.Save();
				context.Matrix = (new Matrix(context.Matrix)*matrix).toCairo();
				context.SetSourceRGBA(1.0, 0.0, 0.0, 0.1);
				context.Arc(0.0, 0.0, 1.0, 0.0, 2.0*Math.PI);
				context.ClosePath();
				context.Fill();
				context.Restore();
			}

    		context.Save();
			context.LineWidth = 0.5;
			context.SetSourceRGBA(1.0, 0.0, 0.0, 0.5);
			if (grid) context.SetSourceRGBA(0.0, 0.0, 0.0, 0.2);
			Point p = matrix*r;
			for(int i = 0; i < segments; ++i) {
				r = new Point(r.x*c - r.y*s, r.y*c + r.x*s);
  				p = matrix*r;
				context.LineTo(p.x, p.y);
			}
			context.ClosePath();
			context.Stroke();
    		context.Restore();
		}
		
		private static void swap(ref double a, ref double b)
			{ double c = a; a = b; b = c; }
		private static void swap(ref int a, ref int b)
			{ int c = a; a = b; b = c; }

		private bool cutRange(AngleRange range, uint da, double h) {
			if (h <= Geometry.precision - 1.0)
				return true;
			if (h >= 1.0 - Geometry.precision)
				return false;
			uint a = AngleRange.toUInt(Math.Asin(h));
			range.subtract(new AngleRange.Entry(
				da - a, (da + a)^AngleRange.half ));
			return !range.isEmpty();
		}
		
		public void putSegment(Cairo.Context context, double da, double s, double c, double a0, double a1) {
			int cnt = (int)Math.Floor((a1 - a0)/da);
			Point r = new Point(1.0, 0.0).rotate(a0);
			Point p = matrix*r;
			context.MoveTo(p.x, p.y);
			for(int j = 0; j < cnt; ++j) {
				r = new Point(r.x*c - r.y*s, r.y*c + r.x*s);
  				p = matrix*r;
				context.LineTo(p.x, p.y);
			}
			r = new Point(1.0, 0.0).rotate(a1);
			p = matrix*r;
			context.LineTo(p.x, p.y);
		}

		public void drawTruncated(Cairo.Context context, Point b0, Point b1, Point b2, bool grid = false) {
			Point dx = matrixInv.turn(b1 - b0);
			Point dy = matrixInv.turn(b2 - b0);
			Point nx = dx.rotate90().normalize();
			Point ny = dy.rotate90().normalize();
			Point o = matrixInv*b0;
			
			uint ax = AngleRange.toUInt(dx.atan());
			uint ay = AngleRange.toUInt(dy.atan());

			double sign = nx*dy;
			if (Math.Abs(sign) <= Geometry.precision) return;
			if (sign < 0.0) {
				nx *= -1.0; ny *= -1.0;
				ax ^= AngleRange.half; ay ^= AngleRange.half;
			}

			if (!grid) {
				// draw bounds
				context.Save();
				context.Matrix = (new Matrix(context.Matrix)*matrix).toCairo();
				context.SetSourceRGBA(1.0, 0.0, 0.0, 0.1);
				context.MoveTo(o.x, o.y);
				context.RelLineTo(dx.x, dx.y);
				context.RelLineTo(dy.x, dy.y);
				context.RelLineTo(-dx.x, -dx.y);
				context.ClosePath();
				context.Fill();
				context.Restore();
			}
			
			// build ranges
			AngleRange range = new AngleRange(true);
			if ( !cutRange(range, ax, o*nx) ) return;
			if ( !cutRange(range, ax^AngleRange.half, -((o+dx+dy)*nx)) ) return;
			if ( !cutRange(range, ay, (o+dx)*ny) ) return;
			if ( !cutRange(range, ay^AngleRange.half, -((o+dy)*ny)) ) return;
			
			// draw
    		int segments = 100;
    		double da = 2.0*Math.PI/segments;
    		double s = Math.Sin(da);
    		double c = Math.Cos(da);

    		context.Save();
			context.LineWidth = 2.0;
			context.SetSourceRGBA(0.0, 0.0, 1.0, 1.0);
			if (grid) context.LineWidth = 1.0;
			if (grid) context.SetSourceRGBA(0.0, 0.0, 0.0, 1.0);
			if (range.isFull()) {
				putSegment(context, da, s, c, 0.0, AngleRange.period);
			} else {
				bool f = range.flip;
				uint prev = range.angles[range.angles.Count - 1];
				foreach(uint a in range.angles) {
					if (f) {
						double a0 = AngleRange.toDouble(prev);
						double a1 = AngleRange.toDouble(a);
						if (a < prev) a1 += AngleRange.period;
						putSegment(context, da, s, c, a0, a1);
					}
					prev = a; f = !f;
				}
			}
			context.Stroke();
			context.Restore();
		}
	}
}