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(); } } }