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