Blame mono/EllipseTruncate/Ellipse.cs

4ef489
using System;
4ef489
4ef489
namespace EllipseTruncate {
4ef489
	public struct AngleRange {
4ef489
		public double angle, size;
4ef489
		
4ef489
		public AngleRange(double angle, double size)
4ef489
			{ this.angle = angle; this.size = size; }
4ef489
		public bool isEmpty()
4ef489
			{ return size <= Geometry.precision; }
4ef489
		public bool isFull()
4ef489
			{ return size >= 2.0*Math.PI - Geometry.precision; }
4ef489
		public double end()
4ef489
			{ return wrap(angle + size); }
4ef489
		public bool contains(double a)
4ef489
			{ return diff(a, angle) < size; }
4ef489
		public bool intersects(AngleRange r)
4ef489
			{ return contains(r.angle) || r.contains(angle); }
4ef489
		public AngleRange union(AngleRange r) {
4ef489
			double da = diff(r.angle, angle);
4ef489
			if (da <= size + Geometry.precision)
4ef489
				return new AngleRange(angle, Math.Max(size, da + r.size));
4ef489
			da = 2.0*Math.PI - da;
4ef489
			if (da <= r.size + Geometry.precision)
4ef489
				return new AngleRange(r.angle, Math.Max(r.size, da + size));
4ef489
			da = Math.Min(angle, r.angle);
4ef489
			return new AngleRange(da, Math.Max(angle + size, r.angle + r.size) - da);
4ef489
		}
4ef489
4ef489
		public static AngleRange byAngles(double a0, double a1)
4ef489
			{ return new AngleRange(a0, diff(a1, a0)); }
4ef489
		public static double diff(double a1, double a0)
4ef489
			{ return a0 < a1 ? a1 - a0 : a1 - a0 + 2.0*Math.PI; }
4ef489
		public static double wrap(double angle) {
4ef489
			return angle >  Math.PI ? angle - 2.0*Math.PI
4ef489
			     : angle < -Math.PI ? angle + 2.0*Math.PI
4ef489
			     : angle;
4ef489
		}
4ef489
	}
4ef489
4ef489
	public class Ellipse {
4ef489
		public Point center;
4ef489
		public Point p1;
4ef489
		public Point p2;
4ef489
		public double r1;
4ef489
		public double r2;
4ef489
		public double angle;
4ef489
		
4ef489
		public Matrix matrix;
4ef489
		public Matrix matrixInv;
4ef489
	
4ef489
		public Ellipse(Point p0, Point p1, Point p2) {
4ef489
			center = p0;
4ef489
			this.p1 = p1;
4ef489
			Point d = p1 - p0;
4ef489
			r1 = d.len();
4ef489
			angle = d.atan();
4ef489
			Point dp = d.rotate90()/r1;
4ef489
			r2 = Math.Abs(dp*(p2 - p0));
4ef489
			this.p2 = p0 + dp*r2;
4ef489
			
4ef489
			matrix = Matrix.identity()
4ef489
					.translate(center)
4ef489
			        .rotate(angle)
4ef489
			        .scale(r1, r2);
4ef489
			matrixInv = matrix.invert();
4ef489
		}
4ef489
		
4ef489
		public void drawFull(Cairo.Context context) {
4ef489
    		int segments = 100;
4ef489
    		double da = 2.0*Math.PI/segments;
4ef489
    		double s = Math.Sin(da);
4ef489
    		double c = Math.Cos(da);
4ef489
    		Point r = new Point(1.0, 0.0);
4ef489
4ef489
			context.Save();
4ef489
			context.Matrix = (new Matrix(context.Matrix)*matrix).toCairo();
4ef489
			context.SetSourceRGBA(1.0, 0.0, 0.0, 0.1);
4ef489
			context.Arc(0.0, 0.0, 1.0, 0.0, 2.0*Math.PI);
4ef489
			context.ClosePath();
4ef489
			context.Fill();
4ef489
			context.Restore();
4ef489
4ef489
    		context.Save();
4ef489
			context.LineWidth = 0.5;
4ef489
			context.SetSourceRGBA(1.0, 0.0, 0.0, 0.5);
4ef489
			Point p = matrix*r;
4ef489
			for(int i = 0; i < segments; ++i) {
4ef489
				r = new Point(r.x*c - r.y*s, r.y*c + r.x*s);
4ef489
  				p = matrix*r;
4ef489
				context.LineTo(p.x, p.y);
4ef489
			}
4ef489
			context.ClosePath();
4ef489
			context.Stroke();
4ef489
    		context.Restore();
4ef489
		}
4ef489
		
4ef489
		private static void swap(ref double a, ref double b)
4ef489
			{ double c = a; a = b; b = c; }
4ef489
		private static void swap(ref int a, ref int b)
4ef489
			{ int c = a; a = b; b = c; }
4ef489
4ef489
		private bool cutRange(ref int index, AngleRange[] ranges, double da, double h) {
4ef489
			if (h <= Geometry.precision - 1.0) {
4ef489
				return true;
4ef489
			} else
4ef489
			if (h >= 1.0 - Geometry.precision) {
4ef489
				return false;
4ef489
			} else {
4ef489
				double a = Math.Asin(h);
4ef489
				ranges[index].angle = AngleRange.wrap( a < 0.0
4ef489
				                                     ? da - a - Math.PI
4ef489
				                                     : da - a + Math.PI );
4ef489
				ranges[index].size = Math.PI + a + a;
4ef489
				int j = index;
4ef489
				for(int i = 0, r = 0; i < index;) {
4ef489
					if (r > 0) ranges[i] = ranges[i + r];
4ef489
					if (ranges[i].intersects(ranges[j])) {
4ef489
						if (j < i) { ranges[j] = ranges[i].union(ranges[j]); ++r; --index; }
4ef489
						      else { ranges[i] = ranges[i].union(ranges[j]); j = i; ++i; }
4ef489
						if (ranges[j].isFull())
4ef489
							return false;
4ef489
					} else ++i;
4ef489
				}
4ef489
				if (j == index) ++index;
4ef489
			}
4ef489
			return true;
4ef489
		}
4ef489
		
4ef489
		public void drawTruncated(Cairo.Context context, Point b0, Point b1, Point b2) {
4ef489
			Point dx = matrixInv.turn(b1 - b0);
4ef489
			Point dy = matrixInv.turn(b2 - b0);
4ef489
			Point nx = dx.rotate90().normalize();
4ef489
			Point ny = dy.rotate90().normalize();
4ef489
			Point o = matrixInv*b0;
4ef489
			
4ef489
			double ax = dx.atan();
4ef489
			double ay = dy.atan();
4ef489
			double aax = AngleRange.wrap(ax + Math.PI);
4ef489
			double aay = AngleRange.wrap(ay + Math.PI);
4ef489
4ef489
			double sign = nx*dy;
4ef489
			if (Math.Abs(sign) <= Geometry.precision) return;
4ef489
			if (sign < 0.0) {
4ef489
				double a;
4ef489
				nx *= -1.0; ny *= -1.0;
4ef489
				a = ax; ax = aax; aax = a;
4ef489
				a = ay; ay = aay; aay = a;
4ef489
			}
4ef489
4ef489
			context.Save();
4ef489
			context.Matrix = (new Matrix(context.Matrix)*matrix).toCairo();
4ef489
			context.SetSourceRGBA(1.0, 0.0, 0.0, 0.1);
4ef489
			context.MoveTo(o.x, o.y);
4ef489
			context.RelLineTo(dx.x, dx.y);
4ef489
			context.RelLineTo(dy.x, dy.y);
4ef489
			context.RelLineTo(-dx.x, -dx.y);
4ef489
			context.ClosePath();
4ef489
			context.Fill();
4ef489
			context.Restore();
4ef489
			
4ef489
			// gather invisible ranges
4ef489
			AngleRange[] cutRanges = new AngleRange[4];
4ef489
			int count = 0;
4ef489
			if ( !cutRange(ref count, cutRanges, ax , (o*nx))          ) return;
4ef489
			if ( !cutRange(ref count, cutRanges, aax, -((o+dx+dy)*nx)) ) return;
4ef489
			if ( !cutRange(ref count, cutRanges, ay , (o+dx)*ny)       ) return;
4ef489
			if ( !cutRange(ref count, cutRanges, aay, -((o+dy)*ny))    ) return;
4ef489
			
4ef489
			// sort bounds
4ef489
			for(int i = 0; i < count; ++i)
4ef489
				for(int j = i+1; j < count; ++j)
4ef489
					if (cutRanges[j].angle < cutRanges[i].angle)
4ef489
						{ AngleRange r = cutRanges[i]; cutRanges[i] = cutRanges[j]; cutRanges[j] = r; }
4ef489
						
4ef489
			// invert bounds
4ef489
			AngleRange[] ranges = new AngleRange[4];
4ef489
			for(int i = 0; i < count; ++i)
4ef489
				ranges[i] = AngleRange.byAngles(cutRanges[(i > 0 ? i : count) - 1].end(), cutRanges[i].angle);
4ef489
			
4ef489
			// dummy 
4ef489
			if (count == 0)
4ef489
				ranges[count++] = new AngleRange(0.0, 2.0*Math.PI);
4ef489
			
4ef489
			// draw
4ef489
    		int segments = 100;
4ef489
    		double da = 2.0*Math.PI/segments;
4ef489
    		double s = Math.Sin(da);
4ef489
    		double c = Math.Cos(da);
4ef489
4ef489
    		context.Save();
4ef489
			context.LineWidth = 2.0;
4ef489
			context.SetSourceRGBA(0.0, 0.0, 1.0, 1.0);
4ef489
			for(int i = 0; i < count; ++i) {
4ef489
				double angle = ranges[i].angle;
4ef489
				double size = ranges[i].size;
4ef489
    			int cnt = (int)Math.Floor(size/da);
4ef489
    			Point r = new Point(1.0, 0.0).rotate(angle);
4ef489
    			Point p = matrix*r;
4ef489
    			context.MoveTo(p.x, p.y);
4ef489
				for(int j = 0; j < cnt; ++j) {
4ef489
					r = new Point(r.x*c - r.y*s, r.y*c + r.x*s);
4ef489
	  				p = matrix*r;
4ef489
					context.LineTo(p.x, p.y);
4ef489
				}
4ef489
    			r = new Point(1.0, 0.0).rotate(angle + size);
4ef489
    			p = matrix*r;
4ef489
    			context.LineTo(p.x, p.y);
4ef489
			}
4ef489
			context.Stroke();
4ef489
			context.Restore();
4ef489
		}
4ef489
	}
4ef489
}
4ef489