/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using Lucene.Net.Spatial.Utils;
namespace Lucene.Net.Spatial.Geometry.Shape
{
public class Ellipse : IGeometry2D
{
private readonly Point2D _center;
// Half length of major axis
private readonly double _a;
// Half length of minor axis
private readonly double _b;
private readonly double _k1;
private readonly double _k2;
private readonly double _k3;
// sin of rotation angle
private readonly double _s;
// cos of rotation angle
private readonly double _c;
public Ellipse()
{
_center = new Point2D(0, 0);
}
///
/// Constructor given bounding rectangle and a rotation.
///
/// The point 1.
/// The point 2.
/// The angle.
public Ellipse(Point2D p1, Point2D p2, double angle)
{
// Set the center
_center = new Point2D
{
X = (p1.X + p2.X)*0.5f,
Y = (p1.Y + p2.Y)*0.5f
};
// Find sin and cos of the angle
double angleRad = MathHelper.ToRadians(angle);
_c = Math.Cos(angleRad);
_s = Math.Sin(angleRad);
// Find the half lengths of the semi-major and semi-minor axes
double dx = Math.Abs(p2.X - p1.X) * 0.5;
double dy = Math.Abs(p2.Y - p1.Y) * 0.5;
if (dx >= dy)
{
_a = dx;
_b = dy;
}
else
{
_a = dy;
_b = dx;
}
// Find _k1, _k2, _k3 - define when a point x,y is on the ellipse
_k1 = Sqr(_c / _a) + Sqr(_s / _b);
_k2 = 2 * _s * _c * ((1 / Sqr(_a)) - (1 / Sqr(_b)));
_k3 = Sqr(_s / _a) + Sqr(_c / _b);
}
private static double Sqr(double d)
{
return d * d;
}
public int Intersect(LineSegment seg, Point2D pt0, Point2D pt1)
{
if (pt0 == null)
pt0 = new Point2D();
if (pt1 == null)
pt1 = new Point2D();
// Solution is found by parameterizing the line segment and
// substituting those values into the ellipse equation.
// Results in a quadratic equation.
double x1 = _center.X;
double y1 = _center.Y;
double u1 = seg.A.X;
double v1 = seg.A.Y;
double u2 = seg.B.X;
double v2 = seg.B.Y;
double dx = u2 - u1;
double dy = v2 - v1;
double q0 = _k1 * Sqr(u1 - x1) + _k2 * (u1 - x1) * (v1 - y1) + _k3
* Sqr(v1 - y1) - 1;
double q1 = (2 * _k1 * dx * (u1 - x1)) + (_k2 * dx * (v1 - y1))
+ (_k2 * dy * (u1 - x1)) + (2 * _k3 * dy * (v1 - y1));
double q2 = (_k1 * Sqr(dx)) + (_k2 * dx * dy) + (_k3 * Sqr(dy));
// Compare q1^2 to 4*q0*q2 to see how quadratic solves
double d = Sqr(q1) - (4 * q0 * q2);
if (d < 0)
{
// Roots are complex valued. Line containing the segment does
// not intersect the ellipse
return 0;
}
if (d == 0)
{
// One real-valued root - line is tangent to the ellipse
double t = -q1 / (2 * q2);
if (0 <= t && t <= 1)
{
// Intersection occurs along line segment
pt0.X = u1 + t * dx;
pt0.Y = v1 + t * dy;
return 1;
}
return 0;
}
else
{
// Two distinct real-valued roots. Solve for the roots and see if
// they fall along the line segment
int n = 0;
double q = Math.Sqrt(d);
double t = (-q1 - q) / (2 * q2);
if (0 <= t && t <= 1)
{
// Intersection occurs along line segment
pt0.X = u1 + t * dx;
pt0.Y = v1 + t * dy;
n++;
}
// 2nd root
t = (-q1 + q) / (2 * q2);
if (0 <= t && t <= 1)
{
if (n == 0)
{
pt0.X = u1 + t * dx;
pt0.Y = v1 + t * dy;
n++;
}
else
{
pt1.X = u1 + t * dx;
pt1.Y = v1 + t * dy;
n++;
}
}
return n;
}
}
public void Translate(Vector2D vector)
{
throw new NotSupportedException();
}
public bool Contains(Point2D point)
{
// Plug in equation for ellipse, If evaluates to <= 0 then the
// point is in or on the ellipse.
double dx = point.X - _center.X;
double dy = point.Y - _center.Y;
double eq = (((_k1 * Sqr(dx)) + (_k2 * dx * dy) + (_k3 * Sqr(dy)) - 1));
return eq <= 0;
}
public double Area()
{
throw new NotSupportedException();
}
public Point2D Centroid()
{
throw new NotSupportedException();
}
public IntersectCase Intersect(Rectangle rectangle)
{
// Test if all 4 corners of the rectangle are inside the ellipse
var ul = new Point2D(rectangle.MinPt.X, rectangle.MaxPt.Y);
var ur = new Point2D(rectangle.MaxPt.X, rectangle.MaxPt.Y);
var ll = new Point2D(rectangle.MinPt.X, rectangle.MinPt.Y);
var lr = new Point2D(rectangle.MaxPt.X, rectangle.MinPt.Y);
if (Contains(ul) && Contains(ur) && Contains(ll) && Contains(lr)) return IntersectCase.CONTAINS;
// Test if any of the rectangle edges intersect
Point2D pt0 = new Point2D(), pt1 = new Point2D();
var bottom = new LineSegment(ll, lr);
if (Intersect(bottom, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
var top = new LineSegment(ul, ur);
if (Intersect(top, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
var left = new LineSegment(ll, ul);
if (Intersect(left, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
var right = new LineSegment(lr, ur);
if (Intersect(right, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
// Ellipse does not intersect any edge : since the case for the ellipse
// containing the rectangle was considered above then if the center
// is inside the ellipse is fully inside and if center is outside
// the ellipse is fully outside
return (rectangle.Contains(_center)) ? IntersectCase.WITHIN : IntersectCase.OUTSIDE;
}
}
}