|
|
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 |
|