I don't think that is it. I think I'm frustrated that I am not performing like I want to... and that there's a lot to juggle... and there's lots and lots of things to remember and check...
And I want it to be clean and just flow... like a master carpenter or somebody who has worked at something and is really good at what they do... like that Yankee Woodworker dude. It all fits and it's nice and neat.
Here is some *test* code I wrote for some ellipse code I wrote. What you have there is just a small part of a giant mess I am creating that will probably never be used.
#include "test_ellipse.h"
#define TEST2_FORMULA(ang) 10/(3+2*cos(ang))
void TestEllipse::temp_transform_curve()
{
QTransform t;
qreal ww = 5.0;
qreal hh = 2.3;
qreal max = -100000.0;
qreal max_sf ;
for ( int ii=0; ii < 10000; ii++ ) {
qreal sf = ii*0.0001;
QTransform t1 = t.shear(sf,0.0);
Ellipse e1(ww,hh,t1);
qreal ang = e1.rot_angle();
if ( ang > max ) {
max = ang ;
max_sf = sf;
}
}
qDebug() << "msf=" << max_sf;
qDebug() << "msa=" << max*180/M_PI;
qDebug() << "mang=" << (asin(hh/ww)/2.0)*(180.0/M_PI);
}
// Should TODO: test this much better
// Maybe TODO: answer is only correct to 1.0e-8
void TestEllipse::test_transform_construction()
{
qreal epsilon = 1.0e-8;
QTransform t1;
t1.translate(100,100);
t1.rotate(30*M_PI/180.0);
QBENCHMARK(Ellipse(2.0,1.0,t1));
// Testing rotation transform
for ( int ii = 0 ; ii < 12 ; ii++ ) {
qreal ang = ii*30.0;
qreal rang = ang*M_PI/180.0;
t1.reset();
t1.translate(100,100);
t1.rotate(ang);
QVERIFY( qAbs(Ellipse(2.0,1.0,t1).rot_angle()-rang) < epsilon ) ;
}
#if 0
//
// I did this by hand with grid paper!
//
t1.reset();
t1.shear(1.0,0.0);
qreal ang = Ellipse(2.0,1.0,t1).rot_angle()*180/M_PI;
qreal ww = Ellipse(2.0,1.0,t1).width();
qDebug() << "ang=" << ang;
qDebug() << "w=" << ww;
#endif
}
void TestEllipse::test_is_intersects()
{
// this is the worst case
// bboxes intersect, but ellipses don't
QBENCHMARK(Ellipse(8,6).is_intersects(Ellipse(8,6,QPointF(7,3))));
QCOMPARE(Ellipse(8,6).is_intersects(Ellipse(8,6)),true);
QCOMPARE(Ellipse(8,6).is_intersects(Ellipse(8,6,QPointF(9,0))),false);
QCOMPARE(Ellipse(8,6).is_intersects(Ellipse(8,6,QPointF(30,30))),false);
QCOMPARE(Ellipse(8,6).is_intersects(Ellipse(8,6,QPointF(30,30))),false);
QCOMPARE((Ellipse(8.0,6.0).is_intersects(
Ellipse(8.0,6.0,QPointF(12.0,0.0)))), false);
QCOMPARE((Ellipse(8.0,6.0).is_intersects(
Ellipse(8.0,6.0,QPointF(0,7.0)))),false);
QCOMPARE((Ellipse(8.0,6.0,QPointF(6,5)).is_intersects(
Ellipse(8.0,6.0,QPointF(8,12)))),false);
QCOMPARE((Ellipse(8.0,6.0,QPointF(6,5)).is_intersects(
Ellipse(20.0,6.0,QPointF(8,12)))),false);
// a big ellipse with other ellipse tucked in upper right corner
// of big ellipse' bbox
QCOMPARE((Ellipse(400,200).is_intersects(
Ellipse(2,1,QPointF(195,95)))),false);
// a big ellipse with other ellipse tucked in upper right corner
// of big ellipse' bbox and spun 45 degrees
QCOMPARE((Ellipse(400,200).is_intersects(
Ellipse(2,1,QPointF(195,95),M_PI/4.0))),false);
// two long ellipses overlapping in bbox corner
QCOMPARE((Ellipse(400,20).is_intersects(
Ellipse(600,2,QPointF(199,9.5)))),false);
}
//
// for bbox and line that splits bbox this returns
// non-intersecting poly0 and poly1
void TestEllipse::_bbox_split(const QRectF& bbox, const QLineF& line,
QPolygonF* poly0, QPolygonF* poly1)
{
QPointF p0 = bbox.topLeft();
QPointF p1 = bbox.topRight();
QPointF p2 = bbox.bottomRight();
QPointF p3 = bbox.bottomLeft();
QList pts;
pts.append(p0); pts.append(p1); pts.append(p2); pts.append(p3);
QPointF l0 = line.p1();
QPointF l1 = line.p2();
// Walk around bbox and make the polygons
int which_poly = 0;
for (int ii = 0; ii < 4; ii++) {
int jj = (ii+1)%4;
if ( which_poly == 0 ) {
poly0->append(pts.at(ii));
} else {
poly1->append(pts.at(ii));
}
if ( _is_pt_between(l0,pts.at(ii),pts.at(jj)) ) {
poly0->append(l0);
poly1->append(l0);
if ( which_poly == 0 ) {
which_poly = 1;
} else {
which_poly = 0;
}
} else if ( _is_pt_between(l1,pts.at(ii),pts.at(jj)) ) {
poly0->append(l1);
poly1->append(l1);
if ( which_poly == 0 ) {
which_poly = 1;
} else {
which_poly = 0;
}
}
}
}
// helps _bbox_split
// p1 & p2 are corners of bbox
// pt is on bbox boundary
// is pt in [p1,p2] --- includes p1 & p2
bool TestEllipse::_is_pt_between(const QPointF& pt,
const QPointF& p1, const QPointF& p2)
{
bool is_between=0;
if ( qFuzzyCompare(p1.x(),p2.x()) && qFuzzyCompare(p1.x(),pt.x())) {
// on vertical
is_between = 1;
} else if (qFuzzyCompare(p1.y(),p2.y()) && qFuzzyCompare(p1.y(),pt.y())){
// on horizontal
is_between = 1;
}
return is_between;
}
QPolygonF TestEllipse::_gen_approximate_ellipse(const Ellipse& e)
{
QPolygonF poly;
qreal ang=0.0;
for (int ii = 0; ii < 60; ii++) {
ang = ii*6*180/M_PI;
QPointF pt = e.point_at(ang);
poly.append(pt);
}
return poly;
}
bool TestEllipse::_is_divided(const Ellipse& e0,const Ellipse& e1)
{
QLineF line = e0.divider(e1);
QPolygonF e0_approx = this->_gen_approximate_ellipse(e0);
QPolygonF e1_approx = this->_gen_approximate_ellipse(e1);
QRectF bb = e0.bbox().united(e1.bbox());
QPolygonF poly0;
QPolygonF poly1;
_bbox_split(bb, line, &poly0, &poly1);
int i00 = poly0.intersected(e0_approx).size();
int i01 = poly0.intersected(e1_approx).size();
int i10 = poly1.intersected(e0_approx).size();
int i11 = poly1.intersected(e1_approx).size();
return ( i00*i01 == 0 && i10*i11 == 0 ) ;
}
void TestEllipse::test_divider()
{
QCOMPARE(_is_divided(Ellipse(8.0,6.0),
Ellipse(8.0,6.0,QPointF(12.0,0.0))),
1);
QCOMPARE(_is_divided(Ellipse(8.0,6.0),
Ellipse(8.0,6.0,QPointF(0,7.0))),
1);
QCOMPARE(_is_divided(Ellipse(8.0,6.0,QPointF(6,5)),
Ellipse(8.0,6.0,QPointF(8,12)))
,1);
QCOMPARE(_is_divided(Ellipse(8.0,6.0,QPointF(6,5)),
Ellipse(20.0,6.0,QPointF(8,12)))
,1);
// a big ellipse with other ellipse tucked in upper right corner
// of big ellipse' bbox
QCOMPARE(_is_divided(Ellipse(400,200),
Ellipse(2,1,QPointF(195,95)))
,1);
// a big ellipse with other ellipse tucked in upper right corner
// of big ellipse' bbox and spun 45 degrees
QCOMPARE(_is_divided(Ellipse(400,200),
Ellipse(2,1,QPointF(195,95),M_PI/4.0))
,1);
// two long ellipses overlapping in bbox corner
QCOMPARE(_is_divided(Ellipse(400,20),
Ellipse(600,2,QPointF(199,9.5)))
,1);
/*
// Touch at a single point
QCOMPARE(_is_divided(Ellipse(8.0,6.0),
Ellipse(8.0,6.0,QPointF(8.0,0.0)))
,1);
// Totally intersect
Ellipse e2(8.0,6.0);
Ellipse e3(8.0,6.0);
QCOMPARE(_is_divided(e2,e3),1);
*/
}
// TODO: not well tested on rotation
void TestEllipse::test_bbox()
{
// no rotation
Ellipse e0(8.0,6.0);
QRectF rect = e0.bbox();
QCOMPARE(rect,QRectF(QPointF(-4.0,3.0),QPointF(4.0,-3.0)));
// no rotation with offset
QPointF poff(35.0,55.0);
Ellipse e01(8.0,6.0,poff);
rect = e01.bbox();
QCOMPARE(rect,QRectF(-4.0+poff.x(),3.0+poff.y(),8.0,-6.0));
// Circle
Ellipse circle(12.0,12.0,poff,M_PI/3.893);
rect = circle.bbox();
QCOMPARE(rect,QRectF(-6.0+poff.x(),6.0+poff.y(),12.0,-12.0));
// zero size
Ellipse e1(0.0,0.0);
rect = e1.bbox();
QCOMPARE(rect,QRectF(QPointF(0.0,0.0),QPointF(0.0,0.0)));
// zero size with translation and rotation
QPointF p53(5.0,3.0);
Ellipse e2(0.0,0.0,p53,M_PI/23.0);
rect = e2.bbox();
QCOMPARE(rect,QRectF(p53,p53));
// y side > 0 and x side 0, no rotation or translation
Ellipse e4(0.0,2.0);
rect = e4.bbox();
QCOMPARE(rect,QRectF(QPointF(0,1),QPointF(0,-1)));
// x side > 0 and y side 0, with trans/rot
Ellipse e3(2*sqrt(2),0.0,QPointF(10.0,10.0),M_PI/4.0);
rect = e3.bbox();
QCOMPARE(rect,QRectF(9.0,11.0,2.0,-2.0));
// w h > 0, rot 90
Ellipse e5(4.0,8.0) ;
rect = e5.bbox();
QCOMPARE(rect,QRectF(-2.0,4.0,4.0,-8.0));
// w h > 0, rot 90 and trans 10,10
Ellipse e6(8.0,4.0,QPoint(10.0,10.0),M_PI/2.0) ;
rect = e6.bbox();
QCOMPARE(rect,QRectF(8.0,14.0,4.0,-8.0));
// Pretty sure bbox for ellipse rotated 45 is square
Ellipse e7(7.0,9.0,QPoint(35,45),M_PI/4.0);
rect = e7.bbox().normalized();
QCOMPARE(rect.width(),rect.height());
}
// TODO: test non-intersecting chord
void TestEllipse::test_intersect_chord()
{
Ellipse e0(10.0,8.0,QPointF(-1.0,2.0));
QLineF line0(QPointF(10.0,218.0/5.0),QPointF(-10.0,-342.0/5.0));
QLineF chord0(QPointF(2,-6.0/5.0),QPointF(3,22.0/5.0));
QCOMPARE(e0.intersect_chord(line0),chord0);
QLineF line1(QPointF(-1.0,-10),QPointF(-1.0,10));
QLineF chord1(QPointF(-1.0,-2.0),QPointF(-1.0,6.0));
QCOMPARE(e0.intersect_chord(line1),chord1);
QLineF line2(QPointF(-100.0,2.0),QPointF(100.0,2.0));
QLineF chord2(QPointF(-6.0,2.0),QPointF(4.0,2.0));
QCOMPARE(e0.intersect_chord(line2),chord2);
Ellipse e1(10.0,8.0,QPointF(-1.0,2.0),M_PI/4.0);
QLineF line3(QPointF(-1.0,2.0),QPointF(9.0,12.0));
qreal s52 = 5*sqrt(2.0)/2.0;
QLineF chord3(QPointF(-1.0-s52,2.0-s52),QPointF(-1.0+s52,2.0+s52));
QCOMPARE(e1.intersect_chord(line3),chord3);
}
void TestEllipse::test_rot_offset()
{
QPointF pt;
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
qreal rot = M_PI/4.0;
QPointF off(30,48);
Ellipse e0(w,h);
Ellipse e2(w,h,off,rot);
// General
QCOMPARE(e2.eccentricity(),2.0/3.0);
QCOMPARE(e2.focus(),_rotate_pt(QPointF(4.0,0.0),rot)+off);
// Radius
foreach (qreal ang, _angles) {
QCOMPARE(e2.radius(ang),TEST2_FORMULA(ang));
}
QCOMPARE(e2.radius(M_PI),10.0);
// point_at
QCOMPARE(e2.point_at(0.0),_rotate_pt(QPointF(6.0,0.0),rot)+off);
QCOMPARE(e2.point_at(M_PI),_rotate_pt(QPointF(-6.0,0.0),rot)+off);
foreach (qreal ang, _angles) {
pt = e0.point_at(ang);
QCOMPARE(e2.point_at(ang),_rotate_pt(pt,rot)+off);
}
// angle at
foreach (qreal ang, _angles) {
QPointF p0 = e0.point_at(ang);
pt = _rotate_pt(p0,rot)+off;
QCOMPARE(e2.angle_at(pt),e0.angle_at(p0));
}
// slope at angle
foreach (qreal ang, _angles) {
qreal m = e0.slope(ang);
QPointF pm(1,m);
if ( m == DBL_MAX ) {
QPointF p0 = e0.point_at(ang);
if ( p0.x() > 0 ) {
pm.setX(0);
pm.setY(1);
} else {
pm.setX(0);
pm.setY(-1);
}
}
pm = _rotate_pt(pm,rot);
QCOMPARE(e2.slope(ang),pm.y()/pm.x());
}
// normal
foreach (qreal ang, _angles) {
QVector3D vec = e0.normal(e0.point_at(ang));
QPointF pt(vec.x(),vec.y());
pt = _rotate_pt(pt,rot);
vec.setX(pt.x());
vec.setY(pt.y());
vec.normalize();
// TODO: the compare was choking because the xval diff was
// about 1.0e-8. It really should be closer. I dug
// into a bit but needed to keep going
// I'll live with 1.0e-8 for now :(
QVERIFY( qAbs(vec.x()-e2.normal(e2.point_at(ang)).x()) < 0.0000001 ) ;
QVERIFY( qAbs(vec.y()-e2.normal(e2.point_at(ang)).y()) < 0.0000001 ) ;
//QCOMPARE(e2.normal(e2.point_at(ang)),vec);
// Polar chord
qreal rot = M_PI/4.0;
Ellipse ec(20,10,off,rot);
QPointF p0(14.0,1.0);
qreal x0 = p0.x();
qreal y0 = p0.y();
qreal x = x0*cos(rot) - y0*sin(rot);
qreal y = x0*sin(rot) + y0*cos(rot);
p0.setX(x);
p0.setY(y);
p0 += off ;
QLineF chord = ec.polar_chord(p0);
QPointF p1(8,-3);
x0 = p1.x();
y0 = p1.y();
x = x0*cos(rot) - y0*sin(rot);
y = x0*sin(rot) + y0*cos(rot);
p1.setX(x);
p1.setY(y);
p1 += off ;
QPointF p2(6,4);
x0 = p2.x();
y0 = p2.y();
x = x0*cos(rot) - y0*sin(rot);
y = x0*sin(rot) + y0*cos(rot);
p2.setX(x);
p2.setY(y);
p2 += off ;
QCOMPARE(chord,QLineF(p1,p2));
}
// Closest point
// Take an ellipse, spin 45 and place on line y=x
// closest point to origin is w/2 along line y=x
QPointF oo(0.0,0.0);
Ellipse ej(20,10,QPointF(100,100),M_PI/4.0);
QPointF pj = ej.closest_pt(oo);
qreal x707 = sqrt(2)/2.0;
qreal diag = x707*ej.width()/2.0;
QPointF pjv(100-diag,100-diag);
QCOMPARE(pj,pjv);
}
void TestEllipse::initTestCase()
{
_angles.append(0.0*M_PI/4.0);
_angles.append(1.0*M_PI/4.0);
_angles.append(2.0*M_PI/4.0);
_angles.append(3.0*M_PI/4.0);
_angles.append(4.0*M_PI/4.0);
_angles.append(5.0*M_PI/4.0);
_angles.append(6.0*M_PI/4.0);
_angles.append(7.0*M_PI/4.0);
_angles.append(8.0*M_PI/4.0);
}
QPointF TestEllipse::_rotate_pt(const QPointF& pt, qreal angle)
{
QPointF pt_out;
qreal xx = pt.x();
qreal yy = pt.y();
qreal cc = cos(angle);
qreal ss = sin(angle);
pt_out.setX(cc*xx - ss*yy);
pt_out.setY(ss*xx + cc*yy);
return pt_out ;
}
void TestEllipse::test_rot()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse e0(w,h);
Ellipse e1(h,w);
Ellipse e2(w,h,QPointF(0,0),M_PI/2.0);
// General
QCOMPARE(e2.eccentricity(),2.0/3.0);
QCOMPARE(e2.focus(),QPointF(0.0,4.0));
// Radius
foreach (qreal ang, _angles) {
QCOMPARE(e2.radius(ang),TEST2_FORMULA(ang));
}
QCOMPARE(e2.radius(M_PI),10.0);
// point_at
QCOMPARE(e2.point_at(0.0),QPointF(0.0,6.0));
QCOMPARE(e2.point_at(M_PI),QPointF(0.0,-6.0));
foreach (qreal ang, _angles) {
QPointF pt = e1.point_at(ang);
QCOMPARE(e2.point_at(ang),pt);
}
// angle at
foreach (qreal ang, _angles) {
QPointF pt = e1.point_at(ang);
if ( ang == 2*M_PI ) {
QCOMPARE(e2.angle_at(pt),0.0);
} else {
QCOMPARE(e2.angle_at(pt),ang);
}
}
// slope at angle
foreach (qreal ang, _angles) {
QCOMPARE(e2.slope(ang),e1.slope(ang));
}
// normal
foreach (qreal ang, _angles) {
QCOMPARE(e2.normal(e2.point_at(ang)),e1.normal(e1.point_at(ang)));
}
// Polar chord
qreal rot = M_PI/4.0;
Ellipse ec(20,10,QPointF(0,0),rot);
QPointF p0(14.0,1.0);
qreal x0 = p0.x();
qreal y0 = p0.y();
qreal x = x0*cos(rot) - y0*sin(rot);
qreal y = x0*sin(rot) + y0*cos(rot);
p0.setX(x);
p0.setY(y);
QLineF chord = ec.polar_chord(p0);
QPointF p1(8,-3);
x0 = p1.x();
y0 = p1.y();
x = x0*cos(rot) - y0*sin(rot);
y = x0*sin(rot) + y0*cos(rot);
p1.setX(x);
p1.setY(y);
QPointF p2(6,4);
x0 = p2.x();
y0 = p2.y();
x = x0*cos(rot) - y0*sin(rot);
y = x0*sin(rot) + y0*cos(rot);
p2.setX(x);
p2.setY(y);
QCOMPARE(chord,QLineF(p1,p2));
// Closest point
foreach (qreal ang, _angles) {
QCOMPARE(e2.closest_pt(e2.point_at(ang)),
e1.closest_pt(e1.point_at(ang)));
}
QPointF p1c(35,40);
QCOMPARE(e2.closest_pt(p1c),
e1.closest_pt(p1c));
}
void TestEllipse::test_sanity()
{
qreal w = 200.0;
qreal h = 50.0;
Ellipse e(w,h);
QCOMPARE(e.width(),200.0);
QCOMPARE(e.height(),50.0);
QCOMPARE(e.center(),QPointF(0,0));
}
// This differs a bit because
// the center is at (0,0)
// In swokowski, the center is the focus
void TestEllipse::test_swokowski()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
QCOMPARE(ellipse.eccentricity(),2.0/3.0);
QCOMPARE(ellipse.focus(),QPointF(4.0,0.0));
QCOMPARE(ellipse.point_at(0.0),QPointF(6.0,0.0));
QCOMPARE(ellipse.point_at(M_PI),QPointF(-6.0,0));
QCOMPARE(ellipse.radius(M_PI),10.0);
foreach (qreal ang, _angles) {
QCOMPARE(ellipse.radius(ang),TEST2_FORMULA(ang));
}
}
void TestEllipse::test_slope_vh()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
QCOMPARE(ellipse.slope(ellipse.point_at(0.0)),DBL_MAX);
QCOMPARE(ellipse.slope(ellipse.point_at(M_PI)),DBL_MAX);
QCOMPARE(1.0+ellipse.slope(QPointF(0.0,sqrt(20.0))),1.0);
QCOMPARE(1.0+ellipse.slope(QPointF(0.0,-sqrt(20.0))),1.0);
}
void TestEllipse::test_slope_around()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
// Use long formula to find slope and compare it to
// the short formula used by ellipse
qreal d = ellipse.directrix();
qreal e = ellipse.eccentricity();
foreach (qreal ang, _angles) {
qreal s = sin(ang);
qreal c = cos(ang);
qreal r = d*e/(1+e*c);
qreal rdot = (d*e*e*s)/((1+e*c)*(1+e*c));
qreal m = (rdot*s + r*c)/(rdot*c - r*s);
if ( ang == 0 || ang == M_PI || ang == 2*M_PI ) {
QCOMPARE(ellipse.slope(ang),DBL_MAX);
} else {
QCOMPARE(ellipse.slope(ang),m);
}
}
}
// Page 313 of vnr math encyclopedia
void TestEllipse::test_polar_chord_vnr()
{
qreal w = 20.0;
qreal h = 10.0;
Ellipse ellipse(w,h);
// On axes
QPointF p0(14.0,1.0);
QLineF chord = ellipse.polar_chord(p0);
QCOMPARE(chord,QLineF(QPointF(8,-3),QPointF(6,4)));
}
void TestEllipse::test_polar_chord_border_pt()
{
qreal w = 20.0;
qreal h = 10.0;
Ellipse ellipse(w,h);
foreach (qreal ang, _angles) {
QPointF pt = ellipse.point_at(ang);
QLineF chord = ellipse.polar_chord(pt);
QCOMPARE(chord.p1().x(),pt.x());
}
}
void TestEllipse::test_polar_chord_inside_pt()
{
qreal w = 20.0;
qreal h = 10.0;
Ellipse ellipse(w,h);
QPointF p0(0,0);
QPointF p1(1.0,1.0);
QPointF p2(-1.0,-1.0);
QPointF p3(-1.0,0.0);
QPointF p4(0.0,-1.0);
QPointF p5(0.0,1.0);
QList points;
points.append(p0);
points.append(p1);
points.append(p2);
points.append(p3);
points.append(p4);
points.append(p5);
foreach ( QPointF pt, points ) {
QCOMPARE( ellipse.polar_chord(pt),
QLineF(QPointF(0,0),QPointF(0,0))) ;
}
}
void TestEllipse::test_normal_on_xy_axes()
{
qreal w = 100.0;
qreal h = 25.0;
Ellipse ellipse(w,h);
QVector3D vec ;
QPointF pdog(10,0);
vec = ellipse.normal(pdog);
QCOMPARE(ellipse.normal(QPointF( 10, 0)), QVector3D( 1, 0, 0));
QCOMPARE(ellipse.normal(QPointF(-10, 0)),QVector3D( -1, 0, 0));
QCOMPARE(ellipse.normal(QPointF( 0, 5)), QVector3D( 0, 1, 0));
QCOMPARE(ellipse.normal(QPointF( 0,-5)), QVector3D( 0, -1, 0));
}
// TODO: need better test on normal
void TestEllipse::test_normal_on_circle()
{
qreal w = 25.0;
qreal h = 25.0;
Ellipse ellipse(w,h);
QCOMPARE(ellipse.normal(QPointF(sqrt(5),sqrt(5))),
QVector3D(1/sqrt(2),1/sqrt(2),0));
QCOMPARE(ellipse.normal(QPointF(-sqrt(5),sqrt(5))),
QVector3D(-1/sqrt(2),1/sqrt(2),0));
QCOMPARE(ellipse.normal(QPointF(-sqrt(5),-sqrt(5))),
QVector3D(-1/sqrt(2),-1/sqrt(2),0));
QCOMPARE(ellipse.normal(QPointF(sqrt(5),-sqrt(5))),
QVector3D(1/sqrt(2),-1/sqrt(2),0));
}
// use cosine rule as a kinda separate
// way to check angle at
#define YVAL(X) sqrt(20.0)*sqrt(1 - X*X/36);
void TestEllipse::test_angle_at()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
qreal x = 5.0;
qreal y = YVAL(x);
qreal alpha = atan2(y,x);
qreal b = sqrt(x*x+y*y);
qreal c = ellipse.focus().x();
qreal a = sqrt(b*b + c*c - 2*b*c*cos(alpha));
qreal ang = asin(y/a);
QPointF p1(x,y);
QCOMPARE(ellipse.angle_at(p1)*180/M_PI,ang*180/M_PI);
x = -5.0;
y = YVAL(x);
alpha = atan2(y,x);
b = sqrt(x*x+y*y);
c = ellipse.focus().x();
a = sqrt(b*b + c*c - 2*b*c*cos(alpha));
ang = asin(y/a);
QPointF p2(x,y);
QCOMPARE(ellipse.angle_at(p2)*180/M_PI,180.0-ang*180/M_PI);
x = -3.0;
y = -YVAL(x);
c = ellipse.focus().x();
ang = atan2(-y,-x+c)+M_PI;
QPointF p3(x,y);
QCOMPARE(ellipse.angle_at(p3)*180/M_PI,ang*180/M_PI);
x = 6.0;
y = YVAL(x);
QPointF p4(x,y);
QCOMPARE(ellipse.angle_at(p4)*180/M_PI,0.0);
ang = 7*M_PI/4.0;
QPointF p5 = ellipse.point_at(ang);
QCOMPARE(ellipse.angle_at(p5),ang);
ang = 2*M_PI;
QPointF p6 = ellipse.point_at(ang);
QCOMPARE(ellipse.angle_at(p6),0.0);
ang = 5.5*M_PI;
QPointF p7 = ellipse.point_at(ang);
QCOMPARE(sin(ellipse.angle_at(p7)),sin(ang));
}
void TestEllipse::test_closest_pt()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
QCOMPARE(ellipse.closest_pt(QPointF(7.0,0.0)) ,QPointF(6.0,0.0));
QCOMPARE(ellipse.closest_pt(QPointF(-7.0,0.0)) ,QPointF(-6.0,0.0));
QLineF l1(ellipse.closest_pt(QPointF(0.0,8.0)),
QPointF(0.0,sqrt(20.0)));
QVERIFY(l1.length() < 0.0001 ) ;
QLineF l2(ellipse.closest_pt(QPointF(0.0,-10.0)),
QPointF(0.0,-sqrt(20.0)));
QVERIFY(l2.length() < 0.0001 ) ;
Ellipse circle(2.00,2.00);
QPointF p7(1000.0,1000.0);
QPointF p8(1.0/sqrt(2.0),1.0/sqrt(2.0));
QLineF l3(circle.closest_pt(p7),p8);
QVERIFY(l3.length() < 0.0000001 ) ;
QBENCHMARK(ellipse.closest_pt(QPointF(0.0,-10.0)));
}
void TestEllipse::test_offset()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h,QPointF(10,20));
QCOMPARE(ellipse.eccentricity(),2.0/3.0);
QCOMPARE(ellipse.focus(),QPointF(14.0,20.0));
QCOMPARE(ellipse.center(),QPointF(10.0,20.0));
QCOMPARE(ellipse.point_at(0.0),QPointF(16.0,20.0));
QCOMPARE(ellipse.radius(M_PI),10.0);
foreach (qreal ang, _angles) {
QCOMPARE(ellipse.radius(ang),TEST2_FORMULA(ang));
}
QList xvals;
xvals.append(16.0);
xvals.append(14.0);
xvals.append(12.0);
xvals.append(11.0);
xvals.append(10.0);
xvals.append(8.0);
foreach (qreal x, xvals) {
qreal y = sqrt(20*(1-(x-10)*(x-10)/36))+20;
QPointF pt(x,y);
qreal ang = atan2(y-ellipse.focus().y(),
x-ellipse.focus().x());
QCOMPARE(ellipse.angle_at(pt),ang);
QCOMPARE(ellipse.point_at(ang),pt);
}
// Use long formula to find slope and compare it to
// the short formula used by ellipse
qreal d = ellipse.directrix();
qreal e = ellipse.eccentricity();
foreach (qreal ang, _angles) {
qreal s = sin(ang);
qreal c = cos(ang);
qreal r = d*e/(1+e*c);
qreal rdot = (d*e*e*s)/((1+e*c)*(1+e*c));
qreal m = (rdot*s + r*c)/(rdot*c - r*s);
if ( ang == 0 || ang == M_PI || ang == 2*M_PI ) {
QCOMPARE(ellipse.slope(ang),DBL_MAX);
} else {
QCOMPARE(ellipse.slope(ang),m);
}
}
QCOMPARE(ellipse.normal(QPointF( 16,20)), QVector3D( 1, 0, 0));
QCOMPARE(ellipse.normal(QPointF( 4,20)), QVector3D(-1, 0, 0));
QCOMPARE(ellipse.normal(QPointF( 10,20+sqrt(20))), QVector3D( 0, 1, 0));
QCOMPARE(ellipse.normal(QPointF( 10,+20-sqrt(20))), QVector3D( 0, -1, 0));
}
// Page 313 of vnr math encyclopedia
void TestEllipse::test_offset_polar_chord()
{
qreal w = 20.0;
qreal h = 10.0;
Ellipse ellipse(w,h,QPointF(10,20));
// Modified off of vnr math page 313
QPointF pt(24.0,21.0);
QLineF chord = ellipse.polar_chord(pt);
QCOMPARE(chord,QLineF(QPointF(18,17),QPointF(16,24)));
// Points along border
foreach (qreal ang, _angles) {
QPointF pt = ellipse.point_at(ang);
QLineF chord = ellipse.polar_chord(pt);
QCOMPARE(chord.p1(),pt);
}
// Points inside
QPointF p0(0,0); QPointF p1(1.0,1.0); QPointF p2(-1.0,-1.0);
QPointF p3(-1.0,0.0); QPointF p4(0.0,-1.0); QPointF p5(0.0,1.0);
QList points;
points.append(p0); points.append(p1); points.append(p2);
points.append(p3); points.append(p4); points.append(p5);
foreach ( QPointF pt, points ) {
QPointF off(10,20);
QCOMPARE( ellipse.polar_chord(pt+off), QLineF(off,off));
}
}
void TestEllipse::test_offset_closest_pt()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
QPointF off(10,20);
Ellipse ellipse(w,h,off);
QPointF p1(7.0,0.0);
QPointF p2(6.0,0.0);
QCOMPARE(ellipse.closest_pt(p1+off),p2+off);
QCOMPARE(ellipse.closest_pt(-p1+off),-p2+off);
QPointF p3(0.0,8.0);
QPointF p4(0.0,sqrt(20.0));
QLineF l1(ellipse.closest_pt(p3+off),p4+off);
QVERIFY(l1.length() < 0.0001 ) ;
QPointF p5(0.0,-10.0);
QPointF p6(0.0,-sqrt(20.0));
QLineF l2(ellipse.closest_pt(p5+off,1.0e-12),p6+off);
QVERIFY(l2.length() < 0.0001 ) ;
}
QTEST_MAIN(TestEllipse)
#define TEST2_FORMULA(ang) 10/(3+2*cos(ang))
void TestEllipse::temp_transform_curve()
{
QTransform t;
qreal ww = 5.0;
qreal hh = 2.3;
qreal max = -100000.0;
qreal max_sf ;
for ( int ii=0; ii < 10000; ii++ ) {
qreal sf = ii*0.0001;
QTransform t1 = t.shear(sf,0.0);
Ellipse e1(ww,hh,t1);
qreal ang = e1.rot_angle();
if ( ang > max ) {
max = ang ;
max_sf = sf;
}
}
qDebug() << "msf=" << max_sf;
qDebug() << "msa=" << max*180/M_PI;
qDebug() << "mang=" << (asin(hh/ww)/2.0)*(180.0/M_PI);
}
// Should TODO: test this much better
// Maybe TODO: answer is only correct to 1.0e-8
void TestEllipse::test_transform_construction()
{
qreal epsilon = 1.0e-8;
QTransform t1;
t1.translate(100,100);
t1.rotate(30*M_PI/180.0);
QBENCHMARK(Ellipse(2.0,1.0,t1));
// Testing rotation transform
for ( int ii = 0 ; ii < 12 ; ii++ ) {
qreal ang = ii*30.0;
qreal rang = ang*M_PI/180.0;
t1.reset();
t1.translate(100,100);
t1.rotate(ang);
QVERIFY( qAbs(Ellipse(2.0,1.0,t1).rot_angle()-rang) < epsilon ) ;
}
#if 0
//
// I did this by hand with grid paper!
//
t1.reset();
t1.shear(1.0,0.0);
qreal ang = Ellipse(2.0,1.0,t1).rot_angle()*180/M_PI;
qreal ww = Ellipse(2.0,1.0,t1).width();
qDebug() << "ang=" << ang;
qDebug() << "w=" << ww;
#endif
}
void TestEllipse::test_is_intersects()
{
// this is the worst case
// bboxes intersect, but ellipses don't
QBENCHMARK(Ellipse(8,6).is_intersects(Ellipse(8,6,QPointF(7,3))));
QCOMPARE(Ellipse(8,6).is_intersects(Ellipse(8,6)),true);
QCOMPARE(Ellipse(8,6).is_intersects(Ellipse(8,6,QPointF(9,0))),false);
QCOMPARE(Ellipse(8,6).is_intersects(Ellipse(8,6,QPointF(30,30))),false);
QCOMPARE(Ellipse(8,6).is_intersects(Ellipse(8,6,QPointF(30,30))),false);
QCOMPARE((Ellipse(8.0,6.0).is_intersects(
Ellipse(8.0,6.0,QPointF(12.0,0.0)))), false);
QCOMPARE((Ellipse(8.0,6.0).is_intersects(
Ellipse(8.0,6.0,QPointF(0,7.0)))),false);
QCOMPARE((Ellipse(8.0,6.0,QPointF(6,5)).is_intersects(
Ellipse(8.0,6.0,QPointF(8,12)))),false);
QCOMPARE((Ellipse(8.0,6.0,QPointF(6,5)).is_intersects(
Ellipse(20.0,6.0,QPointF(8,12)))),false);
// a big ellipse with other ellipse tucked in upper right corner
// of big ellipse' bbox
QCOMPARE((Ellipse(400,200).is_intersects(
Ellipse(2,1,QPointF(195,95)))),false);
// a big ellipse with other ellipse tucked in upper right corner
// of big ellipse' bbox and spun 45 degrees
QCOMPARE((Ellipse(400,200).is_intersects(
Ellipse(2,1,QPointF(195,95),M_PI/4.0))),false);
// two long ellipses overlapping in bbox corner
QCOMPARE((Ellipse(400,20).is_intersects(
Ellipse(600,2,QPointF(199,9.5)))),false);
}
//
// for bbox and line that splits bbox this returns
// non-intersecting poly0 and poly1
void TestEllipse::_bbox_split(const QRectF& bbox, const QLineF& line,
QPolygonF* poly0, QPolygonF* poly1)
{
QPointF p0 = bbox.topLeft();
QPointF p1 = bbox.topRight();
QPointF p2 = bbox.bottomRight();
QPointF p3 = bbox.bottomLeft();
QList
pts.append(p0); pts.append(p1); pts.append(p2); pts.append(p3);
QPointF l0 = line.p1();
QPointF l1 = line.p2();
// Walk around bbox and make the polygons
int which_poly = 0;
for (int ii = 0; ii < 4; ii++) {
int jj = (ii+1)%4;
if ( which_poly == 0 ) {
poly0->append(pts.at(ii));
} else {
poly1->append(pts.at(ii));
}
if ( _is_pt_between(l0,pts.at(ii),pts.at(jj)) ) {
poly0->append(l0);
poly1->append(l0);
if ( which_poly == 0 ) {
which_poly = 1;
} else {
which_poly = 0;
}
} else if ( _is_pt_between(l1,pts.at(ii),pts.at(jj)) ) {
poly0->append(l1);
poly1->append(l1);
if ( which_poly == 0 ) {
which_poly = 1;
} else {
which_poly = 0;
}
}
}
}
// helps _bbox_split
// p1 & p2 are corners of bbox
// pt is on bbox boundary
// is pt in [p1,p2] --- includes p1 & p2
bool TestEllipse::_is_pt_between(const QPointF& pt,
const QPointF& p1, const QPointF& p2)
{
bool is_between=0;
if ( qFuzzyCompare(p1.x(),p2.x()) && qFuzzyCompare(p1.x(),pt.x())) {
// on vertical
is_between = 1;
} else if (qFuzzyCompare(p1.y(),p2.y()) && qFuzzyCompare(p1.y(),pt.y())){
// on horizontal
is_between = 1;
}
return is_between;
}
QPolygonF TestEllipse::_gen_approximate_ellipse(const Ellipse& e)
{
QPolygonF poly;
qreal ang=0.0;
for (int ii = 0; ii < 60; ii++) {
ang = ii*6*180/M_PI;
QPointF pt = e.point_at(ang);
poly.append(pt);
}
return poly;
}
bool TestEllipse::_is_divided(const Ellipse& e0,const Ellipse& e1)
{
QLineF line = e0.divider(e1);
QPolygonF e0_approx = this->_gen_approximate_ellipse(e0);
QPolygonF e1_approx = this->_gen_approximate_ellipse(e1);
QRectF bb = e0.bbox().united(e1.bbox());
QPolygonF poly0;
QPolygonF poly1;
_bbox_split(bb, line, &poly0, &poly1);
int i00 = poly0.intersected(e0_approx).size();
int i01 = poly0.intersected(e1_approx).size();
int i10 = poly1.intersected(e0_approx).size();
int i11 = poly1.intersected(e1_approx).size();
return ( i00*i01 == 0 && i10*i11 == 0 ) ;
}
void TestEllipse::test_divider()
{
QCOMPARE(_is_divided(Ellipse(8.0,6.0),
Ellipse(8.0,6.0,QPointF(12.0,0.0))),
1);
QCOMPARE(_is_divided(Ellipse(8.0,6.0),
Ellipse(8.0,6.0,QPointF(0,7.0))),
1);
QCOMPARE(_is_divided(Ellipse(8.0,6.0,QPointF(6,5)),
Ellipse(8.0,6.0,QPointF(8,12)))
,1);
QCOMPARE(_is_divided(Ellipse(8.0,6.0,QPointF(6,5)),
Ellipse(20.0,6.0,QPointF(8,12)))
,1);
// a big ellipse with other ellipse tucked in upper right corner
// of big ellipse' bbox
QCOMPARE(_is_divided(Ellipse(400,200),
Ellipse(2,1,QPointF(195,95)))
,1);
// a big ellipse with other ellipse tucked in upper right corner
// of big ellipse' bbox and spun 45 degrees
QCOMPARE(_is_divided(Ellipse(400,200),
Ellipse(2,1,QPointF(195,95),M_PI/4.0))
,1);
// two long ellipses overlapping in bbox corner
QCOMPARE(_is_divided(Ellipse(400,20),
Ellipse(600,2,QPointF(199,9.5)))
,1);
/*
// Touch at a single point
QCOMPARE(_is_divided(Ellipse(8.0,6.0),
Ellipse(8.0,6.0,QPointF(8.0,0.0)))
,1);
// Totally intersect
Ellipse e2(8.0,6.0);
Ellipse e3(8.0,6.0);
QCOMPARE(_is_divided(e2,e3),1);
*/
}
// TODO: not well tested on rotation
void TestEllipse::test_bbox()
{
// no rotation
Ellipse e0(8.0,6.0);
QRectF rect = e0.bbox();
QCOMPARE(rect,QRectF(QPointF(-4.0,3.0),QPointF(4.0,-3.0)));
// no rotation with offset
QPointF poff(35.0,55.0);
Ellipse e01(8.0,6.0,poff);
rect = e01.bbox();
QCOMPARE(rect,QRectF(-4.0+poff.x(),3.0+poff.y(),8.0,-6.0));
// Circle
Ellipse circle(12.0,12.0,poff,M_PI/3.893);
rect = circle.bbox();
QCOMPARE(rect,QRectF(-6.0+poff.x(),6.0+poff.y(),12.0,-12.0));
// zero size
Ellipse e1(0.0,0.0);
rect = e1.bbox();
QCOMPARE(rect,QRectF(QPointF(0.0,0.0),QPointF(0.0,0.0)));
// zero size with translation and rotation
QPointF p53(5.0,3.0);
Ellipse e2(0.0,0.0,p53,M_PI/23.0);
rect = e2.bbox();
QCOMPARE(rect,QRectF(p53,p53));
// y side > 0 and x side 0, no rotation or translation
Ellipse e4(0.0,2.0);
rect = e4.bbox();
QCOMPARE(rect,QRectF(QPointF(0,1),QPointF(0,-1)));
// x side > 0 and y side 0, with trans/rot
Ellipse e3(2*sqrt(2),0.0,QPointF(10.0,10.0),M_PI/4.0);
rect = e3.bbox();
QCOMPARE(rect,QRectF(9.0,11.0,2.0,-2.0));
// w h > 0, rot 90
Ellipse e5(4.0,8.0) ;
rect = e5.bbox();
QCOMPARE(rect,QRectF(-2.0,4.0,4.0,-8.0));
// w h > 0, rot 90 and trans 10,10
Ellipse e6(8.0,4.0,QPoint(10.0,10.0),M_PI/2.0) ;
rect = e6.bbox();
QCOMPARE(rect,QRectF(8.0,14.0,4.0,-8.0));
// Pretty sure bbox for ellipse rotated 45 is square
Ellipse e7(7.0,9.0,QPoint(35,45),M_PI/4.0);
rect = e7.bbox().normalized();
QCOMPARE(rect.width(),rect.height());
}
// TODO: test non-intersecting chord
void TestEllipse::test_intersect_chord()
{
Ellipse e0(10.0,8.0,QPointF(-1.0,2.0));
QLineF line0(QPointF(10.0,218.0/5.0),QPointF(-10.0,-342.0/5.0));
QLineF chord0(QPointF(2,-6.0/5.0),QPointF(3,22.0/5.0));
QCOMPARE(e0.intersect_chord(line0),chord0);
QLineF line1(QPointF(-1.0,-10),QPointF(-1.0,10));
QLineF chord1(QPointF(-1.0,-2.0),QPointF(-1.0,6.0));
QCOMPARE(e0.intersect_chord(line1),chord1);
QLineF line2(QPointF(-100.0,2.0),QPointF(100.0,2.0));
QLineF chord2(QPointF(-6.0,2.0),QPointF(4.0,2.0));
QCOMPARE(e0.intersect_chord(line2),chord2);
Ellipse e1(10.0,8.0,QPointF(-1.0,2.0),M_PI/4.0);
QLineF line3(QPointF(-1.0,2.0),QPointF(9.0,12.0));
qreal s52 = 5*sqrt(2.0)/2.0;
QLineF chord3(QPointF(-1.0-s52,2.0-s52),QPointF(-1.0+s52,2.0+s52));
QCOMPARE(e1.intersect_chord(line3),chord3);
}
void TestEllipse::test_rot_offset()
{
QPointF pt;
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
qreal rot = M_PI/4.0;
QPointF off(30,48);
Ellipse e0(w,h);
Ellipse e2(w,h,off,rot);
// General
QCOMPARE(e2.eccentricity(),2.0/3.0);
QCOMPARE(e2.focus(),_rotate_pt(QPointF(4.0,0.0),rot)+off);
// Radius
foreach (qreal ang, _angles) {
QCOMPARE(e2.radius(ang),TEST2_FORMULA(ang));
}
QCOMPARE(e2.radius(M_PI),10.0);
// point_at
QCOMPARE(e2.point_at(0.0),_rotate_pt(QPointF(6.0,0.0),rot)+off);
QCOMPARE(e2.point_at(M_PI),_rotate_pt(QPointF(-6.0,0.0),rot)+off);
foreach (qreal ang, _angles) {
pt = e0.point_at(ang);
QCOMPARE(e2.point_at(ang),_rotate_pt(pt,rot)+off);
}
// angle at
foreach (qreal ang, _angles) {
QPointF p0 = e0.point_at(ang);
pt = _rotate_pt(p0,rot)+off;
QCOMPARE(e2.angle_at(pt),e0.angle_at(p0));
}
// slope at angle
foreach (qreal ang, _angles) {
qreal m = e0.slope(ang);
QPointF pm(1,m);
if ( m == DBL_MAX ) {
QPointF p0 = e0.point_at(ang);
if ( p0.x() > 0 ) {
pm.setX(0);
pm.setY(1);
} else {
pm.setX(0);
pm.setY(-1);
}
}
pm = _rotate_pt(pm,rot);
QCOMPARE(e2.slope(ang),pm.y()/pm.x());
}
// normal
foreach (qreal ang, _angles) {
QVector3D vec = e0.normal(e0.point_at(ang));
QPointF pt(vec.x(),vec.y());
pt = _rotate_pt(pt,rot);
vec.setX(pt.x());
vec.setY(pt.y());
vec.normalize();
// TODO: the compare was choking because the xval diff was
// about 1.0e-8. It really should be closer. I dug
// into a bit but needed to keep going
// I'll live with 1.0e-8 for now :(
QVERIFY( qAbs(vec.x()-e2.normal(e2.point_at(ang)).x()) < 0.0000001 ) ;
QVERIFY( qAbs(vec.y()-e2.normal(e2.point_at(ang)).y()) < 0.0000001 ) ;
//QCOMPARE(e2.normal(e2.point_at(ang)),vec);
// Polar chord
qreal rot = M_PI/4.0;
Ellipse ec(20,10,off,rot);
QPointF p0(14.0,1.0);
qreal x0 = p0.x();
qreal y0 = p0.y();
qreal x = x0*cos(rot) - y0*sin(rot);
qreal y = x0*sin(rot) + y0*cos(rot);
p0.setX(x);
p0.setY(y);
p0 += off ;
QLineF chord = ec.polar_chord(p0);
QPointF p1(8,-3);
x0 = p1.x();
y0 = p1.y();
x = x0*cos(rot) - y0*sin(rot);
y = x0*sin(rot) + y0*cos(rot);
p1.setX(x);
p1.setY(y);
p1 += off ;
QPointF p2(6,4);
x0 = p2.x();
y0 = p2.y();
x = x0*cos(rot) - y0*sin(rot);
y = x0*sin(rot) + y0*cos(rot);
p2.setX(x);
p2.setY(y);
p2 += off ;
QCOMPARE(chord,QLineF(p1,p2));
}
// Closest point
// Take an ellipse, spin 45 and place on line y=x
// closest point to origin is w/2 along line y=x
QPointF oo(0.0,0.0);
Ellipse ej(20,10,QPointF(100,100),M_PI/4.0);
QPointF pj = ej.closest_pt(oo);
qreal x707 = sqrt(2)/2.0;
qreal diag = x707*ej.width()/2.0;
QPointF pjv(100-diag,100-diag);
QCOMPARE(pj,pjv);
}
void TestEllipse::initTestCase()
{
_angles.append(0.0*M_PI/4.0);
_angles.append(1.0*M_PI/4.0);
_angles.append(2.0*M_PI/4.0);
_angles.append(3.0*M_PI/4.0);
_angles.append(4.0*M_PI/4.0);
_angles.append(5.0*M_PI/4.0);
_angles.append(6.0*M_PI/4.0);
_angles.append(7.0*M_PI/4.0);
_angles.append(8.0*M_PI/4.0);
}
QPointF TestEllipse::_rotate_pt(const QPointF& pt, qreal angle)
{
QPointF pt_out;
qreal xx = pt.x();
qreal yy = pt.y();
qreal cc = cos(angle);
qreal ss = sin(angle);
pt_out.setX(cc*xx - ss*yy);
pt_out.setY(ss*xx + cc*yy);
return pt_out ;
}
void TestEllipse::test_rot()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse e0(w,h);
Ellipse e1(h,w);
Ellipse e2(w,h,QPointF(0,0),M_PI/2.0);
// General
QCOMPARE(e2.eccentricity(),2.0/3.0);
QCOMPARE(e2.focus(),QPointF(0.0,4.0));
// Radius
foreach (qreal ang, _angles) {
QCOMPARE(e2.radius(ang),TEST2_FORMULA(ang));
}
QCOMPARE(e2.radius(M_PI),10.0);
// point_at
QCOMPARE(e2.point_at(0.0),QPointF(0.0,6.0));
QCOMPARE(e2.point_at(M_PI),QPointF(0.0,-6.0));
foreach (qreal ang, _angles) {
QPointF pt = e1.point_at(ang);
QCOMPARE(e2.point_at(ang),pt);
}
// angle at
foreach (qreal ang, _angles) {
QPointF pt = e1.point_at(ang);
if ( ang == 2*M_PI ) {
QCOMPARE(e2.angle_at(pt),0.0);
} else {
QCOMPARE(e2.angle_at(pt),ang);
}
}
// slope at angle
foreach (qreal ang, _angles) {
QCOMPARE(e2.slope(ang),e1.slope(ang));
}
// normal
foreach (qreal ang, _angles) {
QCOMPARE(e2.normal(e2.point_at(ang)),e1.normal(e1.point_at(ang)));
}
// Polar chord
qreal rot = M_PI/4.0;
Ellipse ec(20,10,QPointF(0,0),rot);
QPointF p0(14.0,1.0);
qreal x0 = p0.x();
qreal y0 = p0.y();
qreal x = x0*cos(rot) - y0*sin(rot);
qreal y = x0*sin(rot) + y0*cos(rot);
p0.setX(x);
p0.setY(y);
QLineF chord = ec.polar_chord(p0);
QPointF p1(8,-3);
x0 = p1.x();
y0 = p1.y();
x = x0*cos(rot) - y0*sin(rot);
y = x0*sin(rot) + y0*cos(rot);
p1.setX(x);
p1.setY(y);
QPointF p2(6,4);
x0 = p2.x();
y0 = p2.y();
x = x0*cos(rot) - y0*sin(rot);
y = x0*sin(rot) + y0*cos(rot);
p2.setX(x);
p2.setY(y);
QCOMPARE(chord,QLineF(p1,p2));
// Closest point
foreach (qreal ang, _angles) {
QCOMPARE(e2.closest_pt(e2.point_at(ang)),
e1.closest_pt(e1.point_at(ang)));
}
QPointF p1c(35,40);
QCOMPARE(e2.closest_pt(p1c),
e1.closest_pt(p1c));
}
void TestEllipse::test_sanity()
{
qreal w = 200.0;
qreal h = 50.0;
Ellipse e(w,h);
QCOMPARE(e.width(),200.0);
QCOMPARE(e.height(),50.0);
QCOMPARE(e.center(),QPointF(0,0));
}
// This differs a bit because
// the center is at (0,0)
// In swokowski, the center is the focus
void TestEllipse::test_swokowski()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
QCOMPARE(ellipse.eccentricity(),2.0/3.0);
QCOMPARE(ellipse.focus(),QPointF(4.0,0.0));
QCOMPARE(ellipse.point_at(0.0),QPointF(6.0,0.0));
QCOMPARE(ellipse.point_at(M_PI),QPointF(-6.0,0));
QCOMPARE(ellipse.radius(M_PI),10.0);
foreach (qreal ang, _angles) {
QCOMPARE(ellipse.radius(ang),TEST2_FORMULA(ang));
}
}
void TestEllipse::test_slope_vh()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
QCOMPARE(ellipse.slope(ellipse.point_at(0.0)),DBL_MAX);
QCOMPARE(ellipse.slope(ellipse.point_at(M_PI)),DBL_MAX);
QCOMPARE(1.0+ellipse.slope(QPointF(0.0,sqrt(20.0))),1.0);
QCOMPARE(1.0+ellipse.slope(QPointF(0.0,-sqrt(20.0))),1.0);
}
void TestEllipse::test_slope_around()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
// Use long formula to find slope and compare it to
// the short formula used by ellipse
qreal d = ellipse.directrix();
qreal e = ellipse.eccentricity();
foreach (qreal ang, _angles) {
qreal s = sin(ang);
qreal c = cos(ang);
qreal r = d*e/(1+e*c);
qreal rdot = (d*e*e*s)/((1+e*c)*(1+e*c));
qreal m = (rdot*s + r*c)/(rdot*c - r*s);
if ( ang == 0 || ang == M_PI || ang == 2*M_PI ) {
QCOMPARE(ellipse.slope(ang),DBL_MAX);
} else {
QCOMPARE(ellipse.slope(ang),m);
}
}
}
// Page 313 of vnr math encyclopedia
void TestEllipse::test_polar_chord_vnr()
{
qreal w = 20.0;
qreal h = 10.0;
Ellipse ellipse(w,h);
// On axes
QPointF p0(14.0,1.0);
QLineF chord = ellipse.polar_chord(p0);
QCOMPARE(chord,QLineF(QPointF(8,-3),QPointF(6,4)));
}
void TestEllipse::test_polar_chord_border_pt()
{
qreal w = 20.0;
qreal h = 10.0;
Ellipse ellipse(w,h);
foreach (qreal ang, _angles) {
QPointF pt = ellipse.point_at(ang);
QLineF chord = ellipse.polar_chord(pt);
QCOMPARE(chord.p1().x(),pt.x());
}
}
void TestEllipse::test_polar_chord_inside_pt()
{
qreal w = 20.0;
qreal h = 10.0;
Ellipse ellipse(w,h);
QPointF p0(0,0);
QPointF p1(1.0,1.0);
QPointF p2(-1.0,-1.0);
QPointF p3(-1.0,0.0);
QPointF p4(0.0,-1.0);
QPointF p5(0.0,1.0);
QList
points.append(p0);
points.append(p1);
points.append(p2);
points.append(p3);
points.append(p4);
points.append(p5);
foreach ( QPointF pt, points ) {
QCOMPARE( ellipse.polar_chord(pt),
QLineF(QPointF(0,0),QPointF(0,0))) ;
}
}
void TestEllipse::test_normal_on_xy_axes()
{
qreal w = 100.0;
qreal h = 25.0;
Ellipse ellipse(w,h);
QVector3D vec ;
QPointF pdog(10,0);
vec = ellipse.normal(pdog);
QCOMPARE(ellipse.normal(QPointF( 10, 0)), QVector3D( 1, 0, 0));
QCOMPARE(ellipse.normal(QPointF(-10, 0)),QVector3D( -1, 0, 0));
QCOMPARE(ellipse.normal(QPointF( 0, 5)), QVector3D( 0, 1, 0));
QCOMPARE(ellipse.normal(QPointF( 0,-5)), QVector3D( 0, -1, 0));
}
// TODO: need better test on normal
void TestEllipse::test_normal_on_circle()
{
qreal w = 25.0;
qreal h = 25.0;
Ellipse ellipse(w,h);
QCOMPARE(ellipse.normal(QPointF(sqrt(5),sqrt(5))),
QVector3D(1/sqrt(2),1/sqrt(2),0));
QCOMPARE(ellipse.normal(QPointF(-sqrt(5),sqrt(5))),
QVector3D(-1/sqrt(2),1/sqrt(2),0));
QCOMPARE(ellipse.normal(QPointF(-sqrt(5),-sqrt(5))),
QVector3D(-1/sqrt(2),-1/sqrt(2),0));
QCOMPARE(ellipse.normal(QPointF(sqrt(5),-sqrt(5))),
QVector3D(1/sqrt(2),-1/sqrt(2),0));
}
// use cosine rule as a kinda separate
// way to check angle at
#define YVAL(X) sqrt(20.0)*sqrt(1 - X*X/36);
void TestEllipse::test_angle_at()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
qreal x = 5.0;
qreal y = YVAL(x);
qreal alpha = atan2(y,x);
qreal b = sqrt(x*x+y*y);
qreal c = ellipse.focus().x();
qreal a = sqrt(b*b + c*c - 2*b*c*cos(alpha));
qreal ang = asin(y/a);
QPointF p1(x,y);
QCOMPARE(ellipse.angle_at(p1)*180/M_PI,ang*180/M_PI);
x = -5.0;
y = YVAL(x);
alpha = atan2(y,x);
b = sqrt(x*x+y*y);
c = ellipse.focus().x();
a = sqrt(b*b + c*c - 2*b*c*cos(alpha));
ang = asin(y/a);
QPointF p2(x,y);
QCOMPARE(ellipse.angle_at(p2)*180/M_PI,180.0-ang*180/M_PI);
x = -3.0;
y = -YVAL(x);
c = ellipse.focus().x();
ang = atan2(-y,-x+c)+M_PI;
QPointF p3(x,y);
QCOMPARE(ellipse.angle_at(p3)*180/M_PI,ang*180/M_PI);
x = 6.0;
y = YVAL(x);
QPointF p4(x,y);
QCOMPARE(ellipse.angle_at(p4)*180/M_PI,0.0);
ang = 7*M_PI/4.0;
QPointF p5 = ellipse.point_at(ang);
QCOMPARE(ellipse.angle_at(p5),ang);
ang = 2*M_PI;
QPointF p6 = ellipse.point_at(ang);
QCOMPARE(ellipse.angle_at(p6),0.0);
ang = 5.5*M_PI;
QPointF p7 = ellipse.point_at(ang);
QCOMPARE(sin(ellipse.angle_at(p7)),sin(ang));
}
void TestEllipse::test_closest_pt()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h);
QCOMPARE(ellipse.closest_pt(QPointF(7.0,0.0)) ,QPointF(6.0,0.0));
QCOMPARE(ellipse.closest_pt(QPointF(-7.0,0.0)) ,QPointF(-6.0,0.0));
QLineF l1(ellipse.closest_pt(QPointF(0.0,8.0)),
QPointF(0.0,sqrt(20.0)));
QVERIFY(l1.length() < 0.0001 ) ;
QLineF l2(ellipse.closest_pt(QPointF(0.0,-10.0)),
QPointF(0.0,-sqrt(20.0)));
QVERIFY(l2.length() < 0.0001 ) ;
Ellipse circle(2.00,2.00);
QPointF p7(1000.0,1000.0);
QPointF p8(1.0/sqrt(2.0),1.0/sqrt(2.0));
QLineF l3(circle.closest_pt(p7),p8);
QVERIFY(l3.length() < 0.0000001 ) ;
QBENCHMARK(ellipse.closest_pt(QPointF(0.0,-10.0)));
}
void TestEllipse::test_offset()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
Ellipse ellipse(w,h,QPointF(10,20));
QCOMPARE(ellipse.eccentricity(),2.0/3.0);
QCOMPARE(ellipse.focus(),QPointF(14.0,20.0));
QCOMPARE(ellipse.center(),QPointF(10.0,20.0));
QCOMPARE(ellipse.point_at(0.0),QPointF(16.0,20.0));
QCOMPARE(ellipse.radius(M_PI),10.0);
foreach (qreal ang, _angles) {
QCOMPARE(ellipse.radius(ang),TEST2_FORMULA(ang));
}
QList
xvals.append(16.0);
xvals.append(14.0);
xvals.append(12.0);
xvals.append(11.0);
xvals.append(10.0);
xvals.append(8.0);
foreach (qreal x, xvals) {
qreal y = sqrt(20*(1-(x-10)*(x-10)/36))+20;
QPointF pt(x,y);
qreal ang = atan2(y-ellipse.focus().y(),
x-ellipse.focus().x());
QCOMPARE(ellipse.angle_at(pt),ang);
QCOMPARE(ellipse.point_at(ang),pt);
}
// Use long formula to find slope and compare it to
// the short formula used by ellipse
qreal d = ellipse.directrix();
qreal e = ellipse.eccentricity();
foreach (qreal ang, _angles) {
qreal s = sin(ang);
qreal c = cos(ang);
qreal r = d*e/(1+e*c);
qreal rdot = (d*e*e*s)/((1+e*c)*(1+e*c));
qreal m = (rdot*s + r*c)/(rdot*c - r*s);
if ( ang == 0 || ang == M_PI || ang == 2*M_PI ) {
QCOMPARE(ellipse.slope(ang),DBL_MAX);
} else {
QCOMPARE(ellipse.slope(ang),m);
}
}
QCOMPARE(ellipse.normal(QPointF( 16,20)), QVector3D( 1, 0, 0));
QCOMPARE(ellipse.normal(QPointF( 4,20)), QVector3D(-1, 0, 0));
QCOMPARE(ellipse.normal(QPointF( 10,20+sqrt(20))), QVector3D( 0, 1, 0));
QCOMPARE(ellipse.normal(QPointF( 10,+20-sqrt(20))), QVector3D( 0, -1, 0));
}
// Page 313 of vnr math encyclopedia
void TestEllipse::test_offset_polar_chord()
{
qreal w = 20.0;
qreal h = 10.0;
Ellipse ellipse(w,h,QPointF(10,20));
// Modified off of vnr math page 313
QPointF pt(24.0,21.0);
QLineF chord = ellipse.polar_chord(pt);
QCOMPARE(chord,QLineF(QPointF(18,17),QPointF(16,24)));
// Points along border
foreach (qreal ang, _angles) {
QPointF pt = ellipse.point_at(ang);
QLineF chord = ellipse.polar_chord(pt);
QCOMPARE(chord.p1(),pt);
}
// Points inside
QPointF p0(0,0); QPointF p1(1.0,1.0); QPointF p2(-1.0,-1.0);
QPointF p3(-1.0,0.0); QPointF p4(0.0,-1.0); QPointF p5(0.0,1.0);
QList
points.append(p0); points.append(p1); points.append(p2);
points.append(p3); points.append(p4); points.append(p5);
foreach ( QPointF pt, points ) {
QPointF off(10,20);
QCOMPARE( ellipse.polar_chord(pt+off), QLineF(off,off));
}
}
void TestEllipse::test_offset_closest_pt()
{
qreal w = 12.0;
qreal h = 2*sqrt(20.0);
QPointF off(10,20);
Ellipse ellipse(w,h,off);
QPointF p1(7.0,0.0);
QPointF p2(6.0,0.0);
QCOMPARE(ellipse.closest_pt(p1+off),p2+off);
QCOMPARE(ellipse.closest_pt(-p1+off),-p2+off);
QPointF p3(0.0,8.0);
QPointF p4(0.0,sqrt(20.0));
QLineF l1(ellipse.closest_pt(p3+off),p4+off);
QVERIFY(l1.length() < 0.0001 ) ;
QPointF p5(0.0,-10.0);
QPointF p6(0.0,-sqrt(20.0));
QLineF l2(ellipse.closest_pt(p5+off,1.0e-12),p6+off);
QVERIFY(l2.length() < 0.0001 ) ;
}
QTEST_MAIN(TestEllipse)
No comments:
Post a Comment