/*
* This file is part of the GreasePad distribution (https://github.com/FraunhoferIOSB/GreasePad).
* Copyright (c) 2022-2023 Jochen Meidow, Fraunhofer IOSB
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "conics.h"
#include "upoint.h"
#include "ustraightline.h"
#include
namespace Conic {
using Uncertain::uPoint;
using Uncertain::uStraightLine;
using Eigen::Vector2d;
using Eigen::Vector3d;
using Eigen::Matrix2d;
using Eigen::Matrix3d;
ConicBase::ConicBase( const Matrix3d& other) : Matrix3d(other)
{
// Q_ASSERT( isSymmetric()==true);
assert( isSymmetric()==true );
}
Matrix3d ConicBase::skew( const Vector3d &x) const
{
return (Matrix3d() << 0.,-x(2),x(1), x(2),0.,-x(0), -x(1),x(0),0.).finished();
}
bool ConicBase::isSymmetric() const
{
constexpr double T_sym = 1e-6;
return (*this -this->adjoint()).norm() < T_sym ;
}
ConicBase & ConicBase::operator=( const Matrix3d & other )
{
// qDebug() << Q_FUNC_INFO;
this->Matrix3d::operator= (other);
Q_ASSERT( isSymmetric()==true );
assert ( isSymmetric()==true );
return *this;
}
Matrix3d ConicBase::cof3( const Matrix3d &MM ) const
{
Matrix3d Cof;
Cof(0,0) = MM(1,1)*MM(2,2) -MM(2,1)*MM(1,2);
Cof(0,1) = -( MM(1,0)*MM(2,2) -MM(2,0)*MM(1,2) );
Cof(0,2) = MM(1,0)*MM(2,1) -MM(2,0)*MM(1,1);
Cof(1,0) = -( MM(0,1)*MM(2,2) -MM(2,1)*MM(0,2) );
Cof(1,1) = MM(0,0)*MM(2,2) -MM(2,0)*MM(0,2);
Cof(1,2) = -(MM(0,0)*MM(2,1) -MM(2,0)*MM(0,1));
Cof(2,0) = MM(0,1)*MM(1,2) -MM(1,1)*MM(0,2);
Cof(2,1) = -(MM(0,0)*MM(1,2) -MM(1,0)*MM(0,2));
Cof(2,2) = MM(0,0)*MM(1,1) -MM(1,0)*MM(0,1);
return Cof;
}
Ellipse::Ellipse( const uPoint & ux, const double k2 )
{
uPoint uy = ux.euclidean();
// cofactor matrix is adjugate due to symmetry
*this = cof3( k2*uy.Cov() -uy.v()*uy.v().adjoint() );
}
Hyperbola::Hyperbola( const uStraightLine & ul, const double k2 )
{
uStraightLine um = ul.euclidean();
*this = k2*um.Cov() -um.v()*um.v().adjoint();
}
Vector3d Hyperbola::centerline() const
{
const Vector3d x0 = center();
const double phi_ = angle_rad();
const double nx = -std::sin(phi_);
const double ny = std::cos(phi_);
return { nx, ny, -nx*x0(0)/x0(2) -ny*x0(1)/x0(2) };
}
double Hyperbola::angle_rad() const
{
return 0.5*atan2( 2.0*coeff(0,1), coeff(0,0)-coeff(1,1));
}
double Hyperbola::angle_deg() const
{
constexpr double rho = 180./3.14159;
return rho * 0.5 * atan2( 2.0*coeff(0,1), coeff(0,0)-coeff(1,1));
}
std::pair
Hyperbola::lengthsSemiAxes() const
{
auto ev = eigenvalues();
double Delta = determinant();
double D = topLeftCorner(2,2).determinant();
assert( -Delta/( ev.first*D ) >= 0. );
assert( +Delta/( ev.second*D) >= 0. );
double a = std::sqrt( -Delta/( ev.first*D) );
double b = std::sqrt( +Delta/( ev.second*D) );
return {a,b};
}
Vector3d ConicBase::polar( const Vector3d &x ) const
{
return (*this*x); // i.e., l = C*x
}
bool ConicBase::isCentral() const
{
return topLeftCorner(2,2).determinant() !=0.;
}
bool ConicBase::isProper() const
{
return determinant() !=0.;
}
bool ConicBase::isEllipse() const
{
return topLeftCorner(2,2).determinant()>0.;
}
bool ConicBase::isHyperbola() const
{
return topLeftCorner(2,2).determinant()<0.;
}
bool ConicBase::isParabola() const
{
return topLeftCorner(2,2).determinant()==0.;
}
std::pair Hyperbola::eigenvalues() const
{
double p = -topLeftCorner(2,2).trace();
double q = topLeftCorner(2,2).determinant();
Q_ASSERT_X( q<0.0, Q_FUNC_INFO, "no hyperbola");
assert( q<0.0 );
double ev0 = -p/2 -std::sqrt( p*p/4 -q);
double ev1 = -p/2 +std::sqrt( p*p/4 -q);
return {ev0,ev1};
}
Vector3d ConicBase::center() const
{
Vector3d xh;
if ( isCentral() ) {
Matrix2d C33 = topLeftCorner(2,2);
Vector2d ch0 = topRightCorner(2,1);
Vector2d x0 = -C33.ldlt().solve(ch0);
xh << x0(0), x0(1), 1.0;
}
else {
qDebug() << "! no central conic"; // TODO(meijoc)
xh << 0,0,0;
}
return xh;
}
std::pair
ConicBase::intersect( const Vector3d &l) const
{
Matrix3d MM = skew(l);
Matrix3d BB = MM.adjoint()*(*this)*MM;
int idx; // [den,idx] = max( abs(l) );
double den = l.array().abs().maxCoeff(&idx);
// minors ...............................................
double alpha=0;
switch (idx) {
case 0:
alpha = BB(1,1)*BB(2,2) -BB(2,1)*BB(1,2);
break;
case 1:
alpha = BB(0,0)*BB(2,2) -BB(2,0)*BB(0,2);
break;
case 2:
alpha = BB(0,0)*BB(1,1) -BB(1,0)*BB(0,1);
break;
default:
Q_ASSERT_X( false, "ConicBase::intersect",
"intersection of conic and straight line: index out of range");
}
// intersection points .......................................
Q_ASSERT( alpha <= 0 );
Q_ASSERT( den > 0 );
Matrix3d DD = BB +std::sqrt(-alpha)/den*MM;
int r;
int c;
DD.array().abs().maxCoeff( &r, &c);
Vector3d p = DD.row(r);
Vector3d q = DD.col(c);
return {p,q};
}
void ConicBase::transform( const Matrix3d &HH )
{
Matrix3d TT = cof3(HH); // cf. PCV (6.57)
*this = TT*(*this)*TT.transpose();
}
void Ellipse::scale( const double s )
{
Q_ASSERT( s>=0.0 );
Vector3d c = center();
c /= c(2);
const Matrix3d HH = ( Matrix3d() <<
s, 0.0, c(0)-s*c(0),
0.0, s, c(1)-s*c(1),
0.0, 0.0, 1.0 ).finished();
this->transform(HH);
}
std::pair
Ellipse::poly( const int N) const
{
Matrix2d Chh = topLeftCorner(2,2);
Vector2d ch0 = topRightCorner(2,1);
Vector2d x0 = -Chh.ldlt().solve(ch0); // centre point
double c00q = coeff(2,2) -ch0.dot( Chh.ldlt().solve(ch0));
assert( std::fabs(c00q)>0. );
Eigen::EigenSolver eig( -Chh/c00q, true);
Vector2d ev = eig.eigenvalues().real();
Matrix2d RR = eig.eigenvectors().real();
if ( ev(0)<0.0 ) {
ev *= -1;
}
const double two_pi = 2*3.14159;
Eigen::VectorXd t = Eigen::VectorXd::LinSpaced( N, 0, two_pi);
Eigen::MatrixXd x(2,N);
x.row(0) = t.array().sin()/std::sqrt(ev(0));
x.row(1) = t.array().cos()/std::sqrt(ev(1));
x = RR*x; // rotation
x.row(0).array() += x0(0); // translation
x.row(1).array() += x0(1);
return { x.row(0), x.row(1)};
}
} // namespace Conic