diff --git a/mono/EllipseTruncate/Ellipse.cs b/mono/EllipseTruncate/Ellipse.cs
new file mode 100644
index 0000000..33a8f6d
--- /dev/null
+++ b/mono/EllipseTruncate/Ellipse.cs
@@ -0,0 +1,215 @@
+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();
+ }
+ }
+}
+
diff --git a/mono/EllipseTruncate/EllipseTruncate.csproj b/mono/EllipseTruncate/EllipseTruncate.csproj
new file mode 100644
index 0000000..3f7e0a9
--- /dev/null
+++ b/mono/EllipseTruncate/EllipseTruncate.csproj
@@ -0,0 +1,66 @@
+
+
+
+ Debug
+ x86
+ 10.0.0
+ 2.0
+ {A7CD6953-34F0-4125-9DF5-957ECF0E23F0}
+ Exe
+ EllipseTruncate
+ EllipseTruncate
+ v4.5
+
+
+ true
+ full
+ false
+ bin\Debug
+ DEBUG;
+ prompt
+ 4
+ x86
+ false
+
+
+ none
+ true
+ bin\Release
+ prompt
+ 4
+ x86
+ false
+
+
+
+
+
+
+
+ Rectangle %28copy%29.cs
+
+
+
+
+
+
+
+
+ False
+ gtk-sharp-2.0
+
+
+ False
+ gtk-sharp-2.0
+
+
+ False
+ glib-sharp-2.0
+
+
+ False
+ gtk-sharp-2.0
+
+
+
+
\ No newline at end of file
diff --git a/mono/EllipseTruncate/EllipseTruncate.sln b/mono/EllipseTruncate/EllipseTruncate.sln
new file mode 100644
index 0000000..ff1a262
--- /dev/null
+++ b/mono/EllipseTruncate/EllipseTruncate.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{AAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EllipseTruncate", "EllipseTruncate.csproj", "{A7CD6953-34F0-4125-9DF5-957ECF0E23F0}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A7CD6953-34F0-4125-9DF5-957ECF0E23F0}.Debug|x86.ActiveCfg = Debug|x86
+ {A7CD6953-34F0-4125-9DF5-957ECF0E23F0}.Debug|x86.Build.0 = Debug|x86
+ {A7CD6953-34F0-4125-9DF5-957ECF0E23F0}.Release|x86.ActiveCfg = Release|x86
+ {A7CD6953-34F0-4125-9DF5-957ECF0E23F0}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ StartupItem = EllipseTruncate.csproj
+ EndGlobalSection
+EndGlobal
diff --git a/mono/EllipseTruncate/Geometry.cs b/mono/EllipseTruncate/Geometry.cs
new file mode 100644
index 0000000..b04234a
--- /dev/null
+++ b/mono/EllipseTruncate/Geometry.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace EllipseTruncate {
+ public static class Geometry {
+ public static readonly double precision = 1e-8;
+ public static readonly double subPrecision = precision*0.1;
+ public static readonly double precisionSqr = precision*precision;
+ public static readonly double sqrt2Pi = Math.Sqrt(2.0*Math.PI);
+
+ public static bool isEqual(double a, double b)
+ { return Math.Abs(b - a) <= precision; }
+ public static bool isLess(double a, double b)
+ { return a - precision < b; }
+ public static bool isGreater(double a, double b)
+ { return b - precision < a; }
+ public static bool isLessOrEqual(double a, double b)
+ { return a + precision <= b; }
+ public static bool isGreaterOrEqual(double a, double b)
+ { return b + precision <= a; }
+
+ public static double logNormalDistribuitionUnscaled(double x, double x0, double w) {
+ return Math.Exp(-0.5*Math.Pow(Math.Log(x/x0)/w, 2.0))/x;
+ }
+
+ public static double logNormalDistribuition(double x, double x0, double w) {
+ return logNormalDistribuition(x, x0, w)/(w*sqrt2Pi);
+ }
+
+ public static void truncateInfiniteLine(Rectangle bounds, ref Point p0, ref Point p1) {
+ if (p0.isEqual(p1)) return;
+ Point d = p0 - p1;
+ if (Math.Abs(d.x)*bounds.height > bounds.width*Math.Abs(d.y)) {
+ // horizontal
+ double k = d.y/d.x;
+ p1 = new Point(bounds.x1, p0.y + k*(bounds.x1 - p0.x));
+ p0 = new Point(bounds.x0, p0.y + k*(bounds.x0 - p0.x));
+ } else {
+ // vertical
+ double k = d.x/d.y;
+ p1 = new Point(p0.x + k*(bounds.y1 - p0.y), bounds.y1);
+ p0 = new Point(p0.x + k*(bounds.y0 - p0.y), bounds.y0);
+ }
+ }
+
+ public static class Interpolation {
+ public delegate T HalfFunc(T a, double b);
+ public delegate T FullFunc(T a, T b);
+
+ public static FullFunc add;
+ public static FullFunc sub;
+ public static HalfFunc mul;
+ public static HalfFunc div;
+
+ static Interpolation() {
+ { // add
+ ParameterExpression a = Expression.Parameter(typeof(T));
+ ParameterExpression b = Expression.Parameter(typeof(T));
+ add = Expression.Lambda(Expression.Add(a, b), a, b).Compile();
+ }
+ { // sub
+ ParameterExpression a = Expression.Parameter(typeof(T));
+ ParameterExpression b = Expression.Parameter(typeof(T));
+ sub = Expression.Lambda(Expression.Subtract(a, b), a, b).Compile();
+ }
+ { // mul
+ ParameterExpression a = Expression.Parameter(typeof(T));
+ ParameterExpression b = Expression.Parameter(typeof(double));
+ mul = Expression.Lambda(Expression.Multiply(a, b), a, b).Compile();
+ }
+ { // div
+ ParameterExpression a = Expression.Parameter(typeof(T));
+ ParameterExpression b = Expression.Parameter(typeof(double));
+ div = Expression.Lambda(Expression.Divide(a, b), a, b).Compile();
+ }
+ }
+
+ public static T linear(T p0, T p1, double l)
+ { return add(mul(p0, 1.0-l), mul(p1, l)); }
+
+ public static T spline(T p0, T p1, T t0, T t1, double l) {
+ double ll = l*l;
+ double lll = ll*l;
+ return add( add( mul(p0, ( 2.0*lll - 3.0*ll + 1.0)),
+ mul(p1, (-2.0*lll + 3.0*ll )) ),
+ add( mul(t0, ( lll - 2.0*ll + l )),
+ mul(t1, ( lll - 1.0*ll )) ));
+ }
+
+ public static T splineTangent(T p0, T p1, T t0, T t1, double l) {
+ double ll = l*l;
+ return add( mul(sub(p0, p1), 6.0*(ll - l)),
+ add( mul(t0, ( 3.0*ll - 4.0*l + 1.0)),
+ mul(t1, ( 3.0*ll - 2.0*l )) ));
+ }
+ }
+
+ public static double interpolationLinear(double p0, double p1, double l)
+ { return p0*(1.0 - l) + p1*l; }
+
+ public static double interpolationSpline(double p0, double p1, double t0, double t1, double l) {
+ double ll = l*l;
+ double lll = ll*l;
+ return p0*( 2.0*lll - 3.0*ll + 1.0)
+ + p1*(-2.0*lll + 3.0*ll )
+ + t0*( lll - 2.0*ll + l )
+ + t1*( lll - 1.0*ll );
+ }
+
+ public static double interpolationSplineTangent(double p0, double p1, double t0, double t1, double l) {
+ double ll = l*l;
+ return (p0 - p1)*6.0*(ll - l)
+ + t0*( 3.0*ll - 4.0*l + 1.0)
+ + t1*( 3.0*ll - 2.0*l );
+ }
+
+ public static Point interpolationLinear(Point p0, Point p1, double l)
+ { return p0*(1.0 - l) + p1*l; }
+
+ public static Point interpolationSpline(Point p0, Point p1, Point t0, Point t1, double l) {
+ double ll = l*l;
+ double lll = ll*l;
+ return p0*( 2.0*lll - 3.0*ll + 1.0)
+ + p1*(-2.0*lll + 3.0*ll )
+ + t0*( lll - 2.0*ll + l )
+ + t1*( lll - 1.0*ll );
+ }
+
+ public static Point interpolationSplineTangent(Point p0, Point p1, Point t0, Point t1, double l) {
+ double ll = l*l;
+ return (p0 - p1)*6.0*(ll - l)
+ + t0*( 3.0*ll - 4.0*l + 1.0)
+ + t1*( 3.0*ll - 2.0*l );
+ }
+ }
+}
diff --git a/mono/EllipseTruncate/MainWindow.cs b/mono/EllipseTruncate/MainWindow.cs
new file mode 100644
index 0000000..520eee1
--- /dev/null
+++ b/mono/EllipseTruncate/MainWindow.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+
+namespace EllipseTruncate {
+ public class ActivePoint {
+ public Point point;
+ public ActivePoint(double x, double y) {
+ point = new Point(x, y);
+ }
+ }
+
+ public class MainWindow : Gtk.Window {
+ static public void Main() {
+ Gtk.Application.Init();
+ MainWindow win = new MainWindow();
+ win.Show();
+ win.Maximize();
+ Gtk.Application.Run();
+ }
+
+ Point offset;
+ ActivePoint point;
+ List points = new List();
+
+ ActivePoint ellipse0 = new ActivePoint(500.0, 500.0),
+ ellipse1 = new ActivePoint(600.0, 500.0),
+ ellipse2 = new ActivePoint(500.0, 450.0);
+ ActivePoint bounds0 = new ActivePoint(450.0, 500.0),
+ bounds1 = new ActivePoint(500.0, 700.0),
+ bounds2 = new ActivePoint(600.0, 550.0);
+
+ public MainWindow(): base(Gtk.WindowType.Toplevel) {
+ Events = Gdk.EventMask.KeyPressMask
+ | Gdk.EventMask.KeyReleaseMask
+ | Gdk.EventMask.ButtonPressMask
+ | Gdk.EventMask.ButtonReleaseMask
+ | Gdk.EventMask.ButtonMotionMask
+ | Gdk.EventMask.PointerMotionMask;
+ ExtensionEvents = Gdk.ExtensionMode.All;
+ points.AddRange(new ActivePoint[] {ellipse0, ellipse1, ellipse2, bounds0, bounds1, bounds2});
+ }
+
+ private bool refreshOnIdle()
+ { QueueDraw(); return false; }
+ private void Refresh() {
+ QueueDraw(); //GLib.Idle.Add(refreshOnIdle);
+ }
+
+ protected override bool OnDeleteEvent(Gdk.Event e) {
+ Gtk.Application.Quit();
+ return false;
+ }
+
+ protected override bool OnExposeEvent(Gdk.EventExpose e) {
+ Cairo.Context context = Gdk.CairoHelper.Create(e.Window);
+
+ context.Save();
+ context.SetSourceRGBA(1.0, 1.0, 1.0, 1.0);
+ context.Rectangle(0, 0, Allocation.Width, Allocation.Height);
+ context.Fill();
+ context.Restore();
+ context.Save();
+ context.Antialias = Cairo.Antialias.Gray;
+
+ foreach(ActivePoint p in points) {
+ context.Save();
+ context.SetSourceRGBA(0.0, point == p ? 1.0 : 0.0, 1.0, 1.0);
+ context.Arc(p.point.x, p.point.y, 5.0, 0, 2.0*Math.PI);
+ context.Fill();
+ context.Restore();
+ }
+
+ { // draw bounds
+ Point p0 = bounds0.point, p1 = bounds1.point, p2 = bounds2.point;
+ context.Save();
+ context.SetSourceRGBA(0.5, 0.5, 0.5, 0.2);
+ context.LineWidth = 2.0;
+ context.MoveTo(p0.x, p0.y);
+ context.LineTo(p1.x, p1.y);
+ context.LineTo(p1.x + p2.x - p0.x, p1.y + p2.y - p0.y);
+ context.LineTo(p2.x, p2.y);
+ context.ClosePath();
+ context.Stroke();
+ context.Restore();
+ }
+
+ // draw ellipse
+ Ellipse ellipse = new Ellipse(ellipse0.point, ellipse1.point, ellipse2.point);
+ ellipse.drawFull(context);
+ ellipse.drawTruncated(context, bounds0.point, bounds1.point, bounds2.point);
+
+ context.Restore();
+ context.Dispose();
+ return false;
+ }
+
+ protected override bool OnButtonPressEvent(Gdk.EventButton e) {
+ if (e.Button == 1) {
+ Point cursor = new Point(e.X, e.Y);
+ point = null;
+ foreach(ActivePoint p in points)
+ if ((p.point - cursor).len() <= 5.0)
+ { offset = p.point - cursor; point = p; }
+ }
+ Refresh();
+ return false;
+ }
+
+ protected override bool OnButtonReleaseEvent(Gdk.EventButton e) {
+ if (e.Button == 1) point = null;
+ Refresh();
+ return false;
+ }
+
+ protected override bool OnMotionNotifyEvent(Gdk.EventMotion e) {
+ if (point != null) {
+ Point cursor = new Point(e.X, e.Y);
+ point.point = cursor + offset;
+ }
+ Refresh();
+ return false;
+ }
+ }
+}
diff --git a/mono/EllipseTruncate/Matrix.cs b/mono/EllipseTruncate/Matrix.cs
new file mode 100644
index 0000000..272404f
--- /dev/null
+++ b/mono/EllipseTruncate/Matrix.cs
@@ -0,0 +1,162 @@
+using System;
+
+namespace EllipseTruncate {
+ public struct Matrix {
+ public double m00, m01, m02,
+ m10, m11, m12,
+ m20, m21, m22;
+
+ public Matrix(Point p0, Point p1, Point p2 = new Point()) {
+ m00 = p0.x; m01 = p0.y; m02 = 0.0;
+ m10 = p1.x; m11 = p1.y; m12 = 0.0;
+ m20 = p2.x; m21 = p2.y; m22 = 1.0;
+ }
+
+ public Matrix(Cairo.Matrix m) {
+ m00 = m.Xx; m01 = m.Yx; m02 = 0.0;
+ m10 = m.Xy; m11 = m.Yy; m12 = 0.0;
+ m20 = m.X0; m21 = m.Y0; m22 = 1.0;
+ }
+
+ public Point row0()
+ { return new Point(m00, m01); }
+ public Point row1()
+ { return new Point(m10, m11); }
+ public Point row2()
+ { return new Point(m20, m21); }
+
+ public Point row(int index) {
+ switch(index) {
+ case 0: return row0();
+ case 1: return row1();
+ case 2: return row2();
+ }
+ return new Point();
+ }
+
+ public static Matrix zero()
+ { return new Matrix(); }
+
+ public static Matrix identity() {
+ Matrix m = new Matrix();
+ m.m00 = m.m11 = m.m22 = 1.0;
+ return m;
+ }
+
+ public static Matrix rotation(double angle) {
+ double s = Math.Sin(angle);
+ double c = Math.Cos(angle);
+ Matrix m = identity();
+ m.m00 = c; m.m01 = s;
+ m.m10 =-s; m.m11 = c;
+ return m;
+ }
+
+ public static Matrix translation(double x, double y) {
+ Matrix m = identity();
+ m.m20 = x; m.m21 = y;
+ return m;
+ }
+
+ public static Matrix scaling(double x, double y) {
+ Matrix m = identity();
+ m.m00 = x; m.m11 = y;
+ return m;
+ }
+
+ public static Matrix translation(Point t)
+ { return translation(t.x, t.y); }
+ public static Matrix scaling(Point s)
+ { return scaling(s.x, s.y); }
+ public static Matrix scaling(double s)
+ { return scaling(s, s); }
+
+ public Matrix rotate(double angle)
+ { return this*rotation(angle); }
+ public Matrix translate(double x, double y)
+ { return this*translation(x, y); }
+ public Matrix translate(Point t)
+ { return this*translation(t); }
+ public Matrix scale(double x, double y)
+ { return this*scaling(x, y); }
+ public Matrix scale(Point s)
+ { return this*scaling(s); }
+
+ public Point transform(Point p) {
+ return new Point( m00*p.x + m10*p.y + m20,
+ m01*p.x + m11*p.y + m21 );
+ }
+
+ public Point turn(Point p) {
+ return new Point( m00*p.x + m10*p.y,
+ m01*p.x + m11*p.y );
+ }
+
+ public static Point operator* (Matrix m, Point p)
+ { return m.transform(p); }
+
+ public static Matrix operator* (Matrix a, Matrix b) {
+ Matrix m = new Matrix();
+
+ m.m00 = a.m00*b.m00 + a.m10*b.m01 + a.m20*b.m02;
+ m.m01 = a.m01*b.m00 + a.m11*b.m01 + a.m21*b.m02;
+ m.m02 = a.m02*b.m00 + a.m12*b.m01 + a.m22*b.m02;
+
+ m.m10 = a.m00*b.m10 + a.m10*b.m11 + a.m20*b.m12;
+ m.m11 = a.m01*b.m10 + a.m11*b.m11 + a.m21*b.m12;
+ m.m12 = a.m02*b.m10 + a.m12*b.m11 + a.m22*b.m12;
+
+ m.m20 = a.m00*b.m20 + a.m10*b.m21 + a.m20*b.m22;
+ m.m21 = a.m01*b.m20 + a.m11*b.m21 + a.m21*b.m22;
+ m.m22 = a.m02*b.m20 + a.m12*b.m21 + a.m22*b.m22;
+
+ return m;
+ }
+
+ public bool isEqual(Matrix other) {
+ return Math.Abs(m00 - other.m00) <= Geometry.precision
+ && Math.Abs(m01 - other.m01) <= Geometry.precision
+ && Math.Abs(m02 - other.m02) <= Geometry.precision
+ && Math.Abs(m10 - other.m10) <= Geometry.precision
+ && Math.Abs(m11 - other.m11) <= Geometry.precision
+ && Math.Abs(m12 - other.m12) <= Geometry.precision
+ && Math.Abs(m20 - other.m20) <= Geometry.precision
+ && Math.Abs(m21 - other.m21) <= Geometry.precision
+ && Math.Abs(m22 - other.m22) <= Geometry.precision;
+ }
+
+ static double det2x2(double m00, double m01, double m10, double m11)
+ { return m00*m11 - m10*m01; }
+
+ public double det() {
+ return m00*m11*m22 - m00*m12*m21
+ + m01*m12*m20 - m01*m10*m22
+ + m02*m10*m21 - m02*m11*m20;
+ }
+
+ public Matrix invert() {
+ double d = det();
+ Matrix m = new Matrix();
+ if (Math.Abs(d) > Geometry.precision) {
+ d = 1.0/d;
+ double e = -d;
+ m.m00 = det2x2(m11, m12, m21, m22)*d;
+ m.m10 = det2x2(m10, m12, m20, m22)*e;
+ m.m20 = det2x2(m10, m11, m20, m21)*d;
+
+ m.m01 = det2x2(m01, m02, m21, m22)*e;
+ m.m11 = det2x2(m00, m02, m20, m22)*d;
+ m.m21 = det2x2(m00, m01, m20, m21)*e;
+
+ m.m02 = det2x2(m01, m02, m11, m12)*d;
+ m.m12 = det2x2(m00, m02, m10, m12)*e;
+ m.m22 = det2x2(m00, m01, m10, m11)*d;
+ }
+ return m;
+ }
+
+ public Cairo.Matrix toCairo()
+ { return new Cairo.Matrix(m00, m01, m10, m11, m20, m21); }
+ }
+}
+
diff --git a/mono/EllipseTruncate/Point.cs b/mono/EllipseTruncate/Point.cs
new file mode 100644
index 0000000..9adb0e2
--- /dev/null
+++ b/mono/EllipseTruncate/Point.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace EllipseTruncate {
+ public struct Point {
+ public double x;
+ public double y;
+
+ public Point(double x, double y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public static Point operator+ (Point a, Point b)
+ { return new Point(a.x + b.x, a.y + b.y); }
+ public static Point operator- (Point a, Point b)
+ { return new Point(a.x - b.x, a.y - b.y); }
+ public static double operator* (Point b, Point a)
+ { return a.x*b.x + a.y*b.y; }
+ public static Point operator* (Point a, double b)
+ { return new Point(a.x*b, a.y*b); }
+ public static Point operator* (double b, Point a)
+ { return a*b; }
+ public static Point operator/ (Point a, double b)
+ { return new Point(a.x/b, a.y/b); }
+
+ public bool isEqual(Point other)
+ { return (this - other).lenSqr() <= Geometry.precisionSqr; }
+ public double lenSqr()
+ { return x*x + y*y; }
+ public double len()
+ { return Math.Sqrt(lenSqr()); }
+
+ public Point normalize() {
+ double l = len();
+ return l > Geometry.precision ? this/l : this;
+ }
+
+ public Point rotate90()
+ { return new Point(-y, x); }
+
+ public Point rotate180()
+ { return new Point(-x, -y); }
+
+ public Point rotate270()
+ { return new Point(y, -x); }
+
+ public Point rotate(double angle) {
+ double s = Math.Sin(angle);
+ double c = Math.Cos(angle);
+ return new Point(c*x - s*y, s*x + c*y);
+ }
+
+ public double atan()
+ { return Math.Atan2(y, x); }
+ }
+}
+
diff --git a/mono/EllipseTruncate/Rectangle.cs b/mono/EllipseTruncate/Rectangle.cs
new file mode 100644
index 0000000..372eaf4
--- /dev/null
+++ b/mono/EllipseTruncate/Rectangle.cs
@@ -0,0 +1,85 @@
+using System;
+
+namespace EllipseTruncate {
+ public struct Rectangle {
+ public double x0, y0, x1, y1;
+
+ public Point p0 {
+ get { return new Point(x0, y0); }
+ set { x0 = value.x; y0 = value.y; }
+ }
+
+ public Point p1 {
+ get { return new Point(x1, y1); }
+ set { x1 = value.x; y1 = value.y; }
+ }
+
+ public double width {
+ get { return x1 - x0; }
+ set { x1 = x0 + value; }
+ }
+
+ public double height {
+ get { return y1 - y0; }
+ set { y1 = y0 + value; }
+ }
+
+ public bool empty {
+ get { return width <= Geometry.precision || height <= Geometry.precision; }
+ }
+
+ public Rectangle(double x0, double y0, double x1, double y1) {
+ this.x0 = x0;
+ this.y0 = y0;
+ this.x1 = x1;
+ this.y1 = y1;
+ }
+ public Rectangle(double x, double y):
+ this(x, y, x, y) { }
+ public Rectangle(Point p):
+ this(p.x, p.y) { }
+ public Rectangle(Point p0, Point p1):
+ this(p0.x, p0.y, p1.x, p1.y) { }
+
+ public Rectangle expand(Point p, double radius = 0.0) {
+ return new Rectangle(
+ Math.Min(x0, p.x),
+ Math.Min(y0, p.y),
+ Math.Max(x1, p.x),
+ Math.Max(y1, p.y) );
+ }
+
+ public Rectangle inflate(double x, double y) {
+ return new Rectangle(
+ x0 - x,
+ y0 - y,
+ x1 + x,
+ y1 + y );
+ }
+
+ public Rectangle inflate(double size) {
+ return inflate(size, size);
+ }
+
+ public static Rectangle operator| (Rectangle a, Rectangle b) {
+ Rectangle rect = a.empty ? b
+ : b.empty ? a
+ : new Rectangle( Math.Min(a.x0, b.x0),
+ Math.Min(a.y0, b.y0),
+ Math.Max(a.x1, b.x1),
+ Math.Max(a.y1, b.y1) );
+ return rect.empty ? new Rectangle() : rect;
+ }
+
+ public static Rectangle operator& (Rectangle a, Rectangle b) {
+ Rectangle rect = a.empty ? b
+ : b.empty ? a
+ : new Rectangle( Math.Max(a.x0, b.x0),
+ Math.Max(a.y0, b.y0),
+ Math.Min(a.x1, b.x1),
+ Math.Min(a.y1, b.y1) );
+ return rect.empty ? new Rectangle() : rect;
+ }
+ }
+}
+