#include "GrainsAsEllipsoids.h" /// qCC_db #include #include #include #include #include #include #include #include #include // from GeometricTools/GTE // #include // #include // #include GrainsAsEllipsoids::GrainsAsEllipsoids(ccMainAppInterface *app) : m_app(app) { assert(m_app); this->setMetaData("class_name", "GrainsAsEllipsoids"); this->setMetaData("plugin_name", "G3Point"); setShaderPath(); } GrainsAsEllipsoids::~GrainsAsEllipsoids(){} GrainsAsEllipsoids::GrainsAsEllipsoids(ccPointCloud *cloud, ccMainAppInterface *app, const std::vector >& stacks, const RGBAColorsTableType& colors) : m_cloud(cloud) , m_app(app) , m_stacks(stacks) { this->setMetaData("class_name", "GrainsAsEllipsoids"); this->setMetaData("plugin_name", "G3Point"); setShaderPath(); setGrainColorsTable(colors); m_center.resize(m_stacks.size()); m_radii.resize(m_stacks.size()); m_rotationMatrix.resize(m_stacks.size()); // fit all ellipsoids std::cout << "[GrainsAsEllipsoids::GrainsAsEllipsoids] fit " << stacks.size() << " ellipsoids" << std::endl; lockVisibility(false); setVisible(true); m_ccBBoxAll.setValidity(false); m_ccBBoxAll.clear(); for (int idx = 0; idx < m_stacks.size(); idx++) { if (!fitEllipsoidToGrain(idx, m_center[idx], m_radii[idx], m_rotationMatrix[idx])) { m_fitNotOK.insert(idx); ccLog::Warning("[GrainsAsEllipsoids::GrainsAsEllipsoids] fit not possible for grain " + QString::number(idx) + " of size " + QString::number(m_stacks[idx].size())); } else { // update the bounding box float maxRadius = m_radii[idx].maxCoeff(); CCVector3 center(m_center[idx](0), m_center[idx](1), m_center[idx](2)); m_ccBBoxAll.add(CCVector3(center.x + maxRadius, center.y + maxRadius, center.z + maxRadius)); m_ccBBoxAll.add(CCVector3(center.x - maxRadius, center.y - maxRadius, center.z - maxRadius)); } } // remove data corresponding to stacks were the fit was not successful for (auto el : m_fitNotOK) { // if the fit is not OK, we use the centroid as a center int nPoints = static_cast(m_stacks[el].size()); Eigen::MatrixX3d points(nPoints, 3); for (int index = 0; index < nPoints; index++) { const CCVector3* point = m_cloud->getPoint(m_stacks[el][index]); points(index, 0) = point->x; points(index, 1) = point->y; points(index, 2) = point->z; } // compute the centroid of the label Eigen::RowVector3d centroid = points.colwise().mean(); m_center[el] << centroid.x(), centroid.y(), centroid.z(); m_radii[el].fill(0); m_rotationMatrix[el].fill(NAN); } m_ccBBoxAll.setValidity(true); } void GrainsAsEllipsoids::setShaderPath() { QDir appDir = QCoreApplication::applicationDirPath(); m_shaderPath = (appDir.absolutePath() + "/shaders/G3Point"); QCoreApplication::instance(); } void GrainsAsEllipsoids::setGrainColorsTable(const RGBAColorsTableType& colorTable) { m_grainColors.resize(colorTable.size()); for (int k = 0; k < colorTable.size(); k++) { ccColor::Rgba color = colorTable[k]; m_grainColors[k] = CCVector3f(static_cast(color.r) / ccColor::MAX, static_cast(color.g) / ccColor::MAX, static_cast(color.b) / ccColor::MAX); } } bool GrainsAsEllipsoids::exportResultsAsCloud() { // create cloud QString cloudName = "g3point_results"; ccPointCloud *cloud = new ccPointCloud(cloudName); for (int idx = 0; idx < m_center.size(); idx++) { // if (m_fitNotOK.count(idx)) // { // continue; // } Eigen::Vector3f center {m_center[idx].x(), m_center[idx].y(), m_center[idx].z()}; Eigen::Vector3f point = center; CCVector3 ccPoint(point(0), point(1), point(2)); cloud->addPoint(ccPoint); } //allocate colors if necessary if (cloud->resizeTheRGBTable()) { for (unsigned int index = 0; index < cloud->size(); index++) { ccColor::Rgb color(m_grainColors[index].x * ccColor::MAX * 0.8, m_grainColors[index].y * ccColor::MAX * 0.8, m_grainColors[index].z * ccColor::MAX * 0.8); cloud->setPointColor(index, color); } } int sfIdx; CCCoreLib::ScalarField* sf; // EXPORT g3point_index sfIdx = cloud->addScalarField("g3point_index"); if (sfIdx == -1) { ccLog::Error("[GrainsAsEllipsoids::exportResultsAsCloud] impossible to allocate g3point_index scalar field"); return false; } sf = cloud->getScalarField(sfIdx); int indexInResults = 0; for (int index = 0; index < m_center.size(); index++) { // if (m_fitNotOK.count(index)) // when the fit was not successful, the point is not exported // { // continue; // } sf->setValue(indexInResults, index); indexInResults++; } sf->computeMinAndMax(); // int sfIdxRadiusX = cloud->addScalarField("g3point_radius_x"); int sfIdxRadiusY = cloud->addScalarField("g3point_radius_y"); int sfIdxRadiusZ = cloud->addScalarField("g3point_radius_z"); if (sfIdxRadiusX == -1 || sfIdxRadiusY == -1 || sfIdxRadiusZ == -1) { ccLog::Error("[GrainsAsEllipsoids::exportResultsAsCloud] impossible to allocate scalar fields to export the radii"); return false; } CCCoreLib::ScalarField* sfRadiusX = cloud->getScalarField(sfIdxRadiusX); CCCoreLib::ScalarField* sfRadiusY = cloud->getScalarField(sfIdxRadiusY); CCCoreLib::ScalarField* sfRadiusZ = cloud->getScalarField(sfIdxRadiusZ); for (unsigned int index = 0; index < cloud->size(); index++) { // if (m_fitNotOK.count(index)) // { // continue; // } sfRadiusX->setValue(index, m_radii[index].x()); sfRadiusY->setValue(index, m_radii[index].y()); sfRadiusZ->setValue(index, m_radii[index].z()); } sfRadiusX->computeMinAndMax(); sfRadiusY->computeMinAndMax(); sfRadiusZ->computeMinAndMax(); // // int sfIdxR00 = cloud->addScalarField("g3point_r00"); int sfIdxR01 = cloud->addScalarField("g3point_r01"); int sfIdxR02 = cloud->addScalarField("g3point_r02"); int sfIdxR10 = cloud->addScalarField("g3point_r10"); int sfIdxR11 = cloud->addScalarField("g3point_r11"); int sfIdxR21 = cloud->addScalarField("g3point_r12"); int sfIdxR20 = cloud->addScalarField("g3point_r20"); int sfIdxR12 = cloud->addScalarField("g3point_r21"); int sfIdxR22 = cloud->addScalarField("g3point_r22"); if (sfIdxR00 == -1 || sfIdxR01 == -1 || sfIdxR02 == -1 || sfIdxR10 == -1 || sfIdxR11 == -1 || sfIdxR12 == -1 || sfIdxR20 == -1 || sfIdxR21 == -1 || sfIdxR22 == -1) { ccLog::Error("[GrainsAsEllipsoids::exportResultsAsCloud] impossible to allocate scalar fields to export the rotation"); return false; } CCCoreLib::ScalarField* sfR00 = cloud->getScalarField(sfIdxR00); CCCoreLib::ScalarField* sfR01 = cloud->getScalarField(sfIdxR01); CCCoreLib::ScalarField* sfR02 = cloud->getScalarField(sfIdxR02); CCCoreLib::ScalarField* sfR10 = cloud->getScalarField(sfIdxR10); CCCoreLib::ScalarField* sfR11 = cloud->getScalarField(sfIdxR11); CCCoreLib::ScalarField* sfR12 = cloud->getScalarField(sfIdxR12); CCCoreLib::ScalarField* sfR20 = cloud->getScalarField(sfIdxR20); CCCoreLib::ScalarField* sfR21 = cloud->getScalarField(sfIdxR21); CCCoreLib::ScalarField* sfR22 = cloud->getScalarField(sfIdxR22); for (unsigned int index = 0; index < cloud->size(); index++) { // if (m_fitNotOK.count(index)) // { // continue; // } sfR00->setValue(index, m_rotationMatrix[index](0, 0)); sfR01->setValue(index, m_rotationMatrix[index](0, 1)); sfR02->setValue(index, m_rotationMatrix[index](0, 2)); sfR10->setValue(index, m_rotationMatrix[index](1, 0)); sfR11->setValue(index, m_rotationMatrix[index](1, 1)); sfR12->setValue(index, m_rotationMatrix[index](1, 2)); sfR20->setValue(index, m_rotationMatrix[index](2, 0)); sfR21->setValue(index, m_rotationMatrix[index](2, 1)); sfR22->setValue(index, m_rotationMatrix[index](2, 2)); } sfR00->computeMinAndMax(); sfR01->computeMinAndMax(); sfR02->computeMinAndMax(); sfR10->computeMinAndMax(); sfR11->computeMinAndMax(); sfR12->computeMinAndMax(); sfR20->computeMinAndMax(); sfR21->computeMinAndMax(); sfR22->computeMinAndMax(); // cloud->showColors(true); cloud->setPointSize(9); m_cloud->addChild(cloud); m_app->addToDB(cloud); return true; } // INIT ORIGINAL SPHERE void GrainsAsEllipsoids::initSphereVertices() { // clear memory of prev arrays std::vector().swap(vertices); std::vector().swap(normals); std::vector().swap(texCoords); float x, y, z, xy; // vertex position float nx, ny, nz; // vertex normal float s, t; // vertex texCoord float sectorStep = 2 * M_PI / sectorCount; float stackStep = M_PI / stackCount; float sectorAngle, stackAngle; for(int i = 0; i <= stackCount; ++i) { stackAngle = M_PI / 2 - i * stackStep; // starting from pi/2 to -pi/2 xy = cosf(stackAngle); // r * cos(u) z = sinf(stackAngle); // r * sin(u) // add (sectorCount+1) vertices per stack // first and last vertices have same position and normal, but different tex coords for(int j = 0; j <= sectorCount; ++j) { sectorAngle = j * sectorStep; // starting from 0 to 2pi // vertex position (x, y, z) x = xy * cosf(sectorAngle); // r * cos(u) * cos(v) y = xy * sinf(sectorAngle); // r * cos(u) * sin(v) vertices.push_back(x); vertices.push_back(y); vertices.push_back(z); // normalized vertex normal (nx, ny, nz) nx = x; ny = y; nz = z; normals.push_back(nx); normals.push_back(ny); normals.push_back(nz); // vertex tex coord (s, t) range between [0, 1] s = (float)j / sectorCount; t = (float)i / stackCount; texCoords.push_back(s); texCoords.push_back(t); } } } void GrainsAsEllipsoids::initSphereIndexes() { // generate CCW index list of sphere triangles // k1--k1+1 // | / | // | / | // k2--k2+1 int k1, k2; for(int i = 0; i < stackCount; ++i) { k1 = i * (sectorCount + 1); // beginning of current stack k2 = k1 + sectorCount + 1; // beginning of next stack for(int j = 0; j < sectorCount; ++j, ++k1, ++k2) { // 2 triangles per sector excluding first and last stacks // k1 => k2 => k1+1 if(i != 0) { indices.push_back(k1); indices.push_back(k2); indices.push_back(k1 + 1); } // k1+1 => k2 => k2+1 if(i != (stackCount-1)) { indices.push_back(k1 + 1); indices.push_back(k2); indices.push_back(k2 + 1); } // store indices for lines // vertical lines for all stacks, k1 => k2 lineIndices.push_back(k1); lineIndices.push_back(k2); if(i != 0) // horizontal lines except 1st stack, k1 => k+1 { lineIndices.push_back(k1); lineIndices.push_back(k1 + 1); } } } } // ELLIPSOID FITTING double GrainsAsEllipsoids::ellipsoidDistance(const Eigen::ArrayXd& p, int idx) { // // Compute the mean distance between the points of the grain and the ellipsoid // // GTE Geometric Tools Engine // // center // gte::Vector3 center = {m_center[idx](0), m_center[idx](1), m_center[idx](2)}; // // axis // std::array, 3> axis; // axis[0] = {m_rotationMatrix[idx](0, 0), m_rotationMatrix[idx](1, 0), m_rotationMatrix[idx](2, 0)}; // axis[1] = {m_rotationMatrix[idx](0, 1), m_rotationMatrix[idx](1, 1), m_rotationMatrix[idx](2, 1)}; // axis[2] = {m_rotationMatrix[idx](0, 2), m_rotationMatrix[idx](1, 2), m_rotationMatrix[idx](2, 2)}; // // extent // gte::Vector3 extent = {m_radii[idx](0), m_radii[idx](1), m_radii[idx](2)}; // // create the ellipsoid // gte::Ellipsoid3 ellipsoid(center, axis, extent); // gte::DCPQuery, gte::Ellipsoid3> query; // std::vector stack = m_stacks[idx]; // double sum_a = 0; // distances with respect to the ellipsoid // double sum_b = 0; // distances with respect to the mean // //compute gravity center // size_t count = m_cloud->size(); // CCVector3 mean(0, 0, 0); // for (int index : m_stacks[idx]) // { // const CCVector3* P = m_cloud->getPoint(index); // mean.x += P->x; // mean.y += P->y; // mean.z += P->z; // } // mean.x = mean.x / count; // mean.y = mean.y / count; // mean.z = mean.z / count; // for (int index : stack) // { // const CCVector3 *P = m_cloud->getPoint(index); // gte::Vector3 P_gte = {P->x, P->y, P->z}; // auto result = query(P_gte, ellipsoid); // sum_a = sum_a + pow(result.distance, 2); // CCVector3 P_minus_min = *P - CCVector3(mean.x, mean.y, mean.z); // sum_b = sum_b + P_minus_min.norm2d(); // } // double r2 = 1 - sum_a / sum_b; // // in Matlab // // d = (x-xp).^2 + (y-yp).^2 + (z-zp).^2; // // r2 = 1 - sum((x-xp).^2 + (y-yp).^2 + (z-zp).^2)./sum((x-mean(x)).^2 + (y-mean(y)).^2 + (z-mean(z)).^2); // return r2; return 0.; } void GrainsAsEllipsoids::updateBBoxOnlyOne(int index) { m_ccBBoxOnlyOne.setValidity(false); m_ccBBoxOnlyOne.clear(); if (index < m_stacks.size()) { if (m_fitNotOK.count(index) == 0) { float maxRadius = m_radii[index].maxCoeff(); CCVector3 center(m_center[index](0), m_center[index](1), m_center[index](2)); m_ccBBoxOnlyOne.add(CCVector3(center.x + maxRadius, center.y + maxRadius, center.z + maxRadius)); m_ccBBoxOnlyOne.add(CCVector3(center.x - maxRadius, center.y - maxRadius, center.z - maxRadius)); m_ccBBoxOnlyOne.setValidity(true); } } else { ccLog::Error("[GrainsAsEllipsoids::updateBBox] asking for the bounding of index " + QString::number(index) + " out of range"); } } bool GrainsAsEllipsoids::explicitToImplicit(const Eigen::Array3f& center, const Eigen::Array3f& radii, const Eigen::Matrix3f& rotationMatrix, Eigen::ArrayXd& parameters) { // INSPIRED BY MATLAB CODE // Cast ellipsoid defined with explicit parameters to implicit vector form. // // Examples: // p = ellipse_ex2im([xc,yc,zc],[xr,yr,zr],eye(3,3)); // Matlab code => Copyright 2011 Levente Hunyadi float xrr = 1 / radii(0); float yrr = 1 / radii(1); float zrr = 1 / radii(2); float r11 = rotationMatrix.data()[0]; float r21 = rotationMatrix.data()[1]; float r31 = rotationMatrix.data()[2]; float r12 = rotationMatrix.data()[3]; float r22 = rotationMatrix.data()[4]; float r32 = rotationMatrix.data()[5]; float r13 = rotationMatrix.data()[6]; float r23 = rotationMatrix.data()[7]; float r33 = rotationMatrix.data()[8]; float xc = center(0); float yc = center(1); float zc = center(2); // terms collected from symbolic expression parameters << pow(r11, 2) * pow(xrr, 2) + pow(r21, 2) * pow(yrr, 2) + pow(r31, 2) * pow(zrr, 2), pow(r12, 2) * pow(xrr, 2) + pow(r22, 2) * pow(yrr, 2) + pow(r32, 2) * pow(zrr, 2), pow(r13, 2) * pow(xrr, 2) + pow(r23, 2) * pow(yrr, 2) + pow(r33, 2) * pow(zrr, 2), 2 * r11 * r12 * pow(xrr, 2) + 2 * r21 * r22 * pow(yrr, 2) + 2 * r31 * r32 * pow(zrr, 2), 2 * r11 * r13 * pow(xrr, 2) + 2 * r21 * r23 * pow(yrr, 2) + 2 * r31 * r33 * pow(zrr, 2), 2 * r12 * r13 * pow(xrr, 2) + 2 * r22 * r23 * pow(yrr, 2) + 2 * r32 * r33 * pow(zrr, 2), (-2) * (pow(r11, 2) * xc * pow(xrr, 2) + pow(r21, 2) * xc * pow(yrr, 2) + pow(r31, 2) * xc * pow(zrr, 2) + r11 * r12 * pow(xrr, 2) * yc + r11 * r13 * pow(xrr, 2) * zc + r21 * r22 * yc * pow(yrr, 2) + r21 * r23 * pow(yrr, 2) * zc + r31 * r32 * yc * pow(zrr, 2) + r31 * r33 * zc * pow(zrr, 2)), (-2) * (pow(r12, 2) * pow(xrr, 2) * yc + pow(r22, 2) * yc * pow(yrr, 2) + pow(r32, 2) * yc * pow(zrr, 2) + r11 * r12 * xc * pow(xrr, 2) + r21 * r22 * xc * pow(yrr, 2) + r12 * r13 * pow(xrr, 2) * zc + r31 * r32 * xc * pow(zrr, 2) + r22 * r23 * pow(yrr, 2) * zc + r32 * r33 * zc * pow(zrr, 2)), (-2) * (pow(r13, 2)*pow(xrr, 2) * zc + pow(r23, 2) * pow(yrr, 2) * zc + pow(r33, 2) * zc * pow(zrr, 2) + r11 * r13 * xc * pow(xrr, 2) + r12 * r13 * pow(xrr, 2) * yc + r21 * r23 * xc * pow(yrr, 2) + r22 * r23 * yc * pow(yrr, 2) + r31 * r33 * xc * pow(zrr, 2) + r32 * r33 * yc * pow(zrr, 2)), pow(r11, 2) * pow(xc, 2) * pow(xrr, 2) + 2 * r11 * r12 * xc * pow(xrr, 2) * yc + 2 * r11 * r13 * xc * pow(xrr, 2) * zc + pow(r12, 2) * pow(xrr, 2) * pow(yc, 2) + 2 * r12 * r13 * pow(xrr, 2) * yc * zc + pow(r13, 2) * pow(xrr, 2) * pow(zc, 2) + pow(r21, 2) *pow(xc, 2) * pow(yrr, 2) + 2 * r21 * r22 * xc * yc * pow(yrr, 2) + 2 * r21 * r23 * xc * pow(yrr, 2) * zc + pow(r22, 2) * pow(yc, 2) * pow(yrr, 2) + 2 * r22 * r23 * yc * pow(yrr, 2) * zc + pow(r23, 2) * pow(yrr, 2) * pow(zc, 2) + pow(r31, 2) * pow(xc, 2) * pow(zrr, 2) + 2 * r31 * r32 * xc * yc * pow(zrr, 2) + 2 * r31 * r33 * xc * zc * pow(zrr, 2) + pow(r32, 2) * pow(yc, 2) * pow(zrr, 2) + 2 * r32 * r33 * yc * zc * pow(zrr, 2) + pow(r33, 2) * pow(zc, 2) * pow(zrr, 2) - 1; return true; } bool GrainsAsEllipsoids::implicitToExplicit(const Eigen::ArrayXd& parameters, Eigen::Array3f& center, Eigen::Array3f& radii, Eigen::Matrix3f& rotationMatrix) { // INSPIRED BY MATLAB CODE // Cast ellipsoid defined with implicit parameter vector to explicit form. // The implicit equation of a general ellipse is // F(x,y,z) = Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + 2Iz - 1 = 0 // // Input arguments: // v: // the 10 parameters describing the ellipsoid algebraically // Output arguments: // center: // ellispoid center coordinates [cx; cy; cz] // ax: // ellipsoid semi-axes (radii) [a; b; c] // quat: NOT IN THIS CPP VERSION, ONLY MATLAB VERSION // ellipsoid rotation in quaternion representation // R: // ellipsoid rotation (radii directions as rows of the 3x3 matrix) // // See also: ellipse_im2ex // Matlab code => Copyright 2011 Levente Hunyadi Eigen::ArrayXd p = parameters; p(3) = 0.5 * p(3); p(4) = 0.5 * p(4); p(5) = 0.5 * p(5); p(6) = 0.5 * p(6); p(7) = 0.5 * p(7); p(8) = 0.5 * p(8); Eigen::MatrixXd q(4, 4); q << p(0), p(3), p(4), p(6) , p(3), p(1), p(5), p(7) , p(4), p(5), p(2), p(8) , p(6), p(7), p(8), p(9); center = q.block(0, 0, 3, 3).colPivHouseholderQr().solve(-p(Eigen::seq(6, 8)).matrix()).cast(); Eigen::MatrixXd t(4, 4); t = Eigen::MatrixXd::Identity(4, 4); t(3, 0) = center(0); t(3, 1) = center(1); t(3, 2) = center(2); Eigen::MatrixXd s(4, 4); s = t * q * t.transpose(); // check for positive definiteness Eigen::LLT lltOfA((-s(3, 3) * s.block(0, 0, 3, 3).array())); if (lltOfA.info() != Eigen::Success) { return false; } Eigen::EigenSolver eigensolver(s.block(0, 0, 3, 3)); if (eigensolver.info() != Eigen::Success) { return false; } radii = (-s(3, 3) / eigensolver.eigenvalues().array().real()).sqrt().cast(); rotationMatrix = eigensolver.eigenvectors().transpose().real().cast(); return true; } bool GrainsAsEllipsoids::directFit(const Eigen::ArrayX3d& xyz, Eigen::ArrayXd& parameters) { // INSPIRED BY MATLAB CODE // Direct least squares fitting of ellipsoids under the constraint 4J - I^2 > 0. // The constraint confines the class of ellipsoids to fit to those whose smallest radius // is at least half of the largest radius. // // Input arguments: // x,y,z; // x, y and z coodinates of 3D points // // Output arguments: // p: // a 10-parameter vector of the algebraic ellipsoid fit // // References: // Qingde Li and John G. Griffiths, "Least Squares Ellipsoid Specific Fitting", // Proceedings of the Geometric Modeling and Processing, 2004. // Matlab code reference => Copyright 2011 Levente Hunyadi Eigen::MatrixXd d(xyz.rows(), 10); d << xyz(Eigen::placeholders::all, 0).pow(2).matrix() , xyz(Eigen::placeholders::all, 1).pow(2).matrix() , xyz(Eigen::placeholders::all, 2).pow(2).matrix() , (2 * xyz(Eigen::placeholders::all, 1) * xyz(Eigen::placeholders::all, 2)).matrix() , (2 * xyz(Eigen::placeholders::all, 0) * xyz(Eigen::placeholders::all, 2)).matrix() , (2 * xyz(Eigen::placeholders::all, 0) * xyz(Eigen::placeholders::all, 1)).matrix() , (2 * xyz(Eigen::placeholders::all, 0)).matrix() , (2 * xyz(Eigen::placeholders::all, 1)).matrix() , (2 * xyz(Eigen::placeholders::all, 2)).matrix() , Eigen::MatrixXd::Ones(xyz.rows(), 1); Eigen::MatrixXd s = d.transpose() * d; int k = 4; Eigen::Matrix3d c1; Eigen::Matrix3d c2; Eigen::MatrixXd c; c = Eigen::MatrixXd::Zero(10, 10); c1 << 0 , k , k , k, 0, k , k , k , 0; c1 = c1.array() / 2 - 1; c2 = - k * Eigen::Matrix3d::Identity(); c.block(0, 0, 3, 3) = c1; c.block(3, 3, 3, 3) = c2; Eigen::GeneralizedEigenSolver eigensolver(s, c); if (eigensolver.info() != Eigen::Success) { return false; } Eigen::ArrayXd eigenValues(10); Eigen::VectorXd eigenValuesAsAMatrix(10); eigenValues = eigensolver.eigenvalues().real(); eigenValuesAsAMatrix = eigensolver.eigenvalues().real().matrix(); Xb condition = (eigenValues > 0) && (!eigenValues.isInf()); int flt = condition.count(); // std::cout << "flt " << flt << std::endl; Eigen::ArrayXd finiteValues(flt); finiteValues = Eigen::ArrayXd::Zero(flt); int finiteValuesCounter = 0; for (int k = 0; k < eigenValues.size(); k++) { if (condition(k)) { finiteValues(finiteValuesCounter++) = eigenValues(k); } } double eigenValue; Eigen::MatrixXd v; switch (flt) { case 1: // regular case eigenValue = finiteValues(0); // there is only one positive finite value for (k = 0; k < 10; k++) { if (eigenValues(k) == eigenValue) { v = eigensolver.eigenvectors()(Eigen::placeholders::all, k).real(); break; } } break; case 0: // degenerate case // # single positive eigenvalue becomes near-zero negative eigenvalue due to round-off error eigenValue = eigenValues.abs().minCoeff(); for (k = 0; k < 10; k++) { if (abs(eigenValues(k)) == eigenValue) { v = eigensolver.eigenvectors()(Eigen::placeholders::all, k).real(); break; } } break; default: // degenerate case // several positive eigenvalues appear eigenValue = finiteValues.abs().minCoeff(); for (k = 0; k < 10; k++) { if (eigenValues(k) == eigenValue) { v = eigensolver.eigenvectors()(Eigen::placeholders::all, k).real(); break; } } break; } parameters.resize(10); if (v.rows() != 10 || v.cols() != 1) { return false; // Invalid eigenvector size } parameters << v(0), v(1), v(2) , 2 * v(5), 2 * v(4), 2* v(3) , 2 * v(6), 2 * v(7), 2 * v(8) , v(9); return true; } bool GrainsAsEllipsoids::fitEllipsoidToGrain(const int grainIndex, Eigen::Array3f& center, Eigen::Array3f& radii, Eigen::Matrix3f& rotationMatrix, const Method& method) { // Shift point cloud to have only positive coordinates // (problem with quadfit if the point cloud is far from the coordinates of the origin (0,0,0)) bool ret = true; // extract the point cloud related to the current index CCCoreLib::ReferenceCloud referenceCloud(m_cloud); for (int index : m_stacks[grainIndex]) { referenceCloud.addPointIndex(index); } ccPointCloud* grainCloud = m_cloud->partialClone(&referenceCloud); Eigen::Map> grainPoints(static_cast(grainCloud->getPoint(0)->u), grainCloud->size(), 3); CCVector3 bbMin; CCVector3 bbMax; grainCloud->getBoundingBox(bbMin, bbMax); CCVector3 bb(bbMax - bbMin); Eigen::Vector3d scales(bb.x, bb.y, bb.z); double scale = 1 / scales.maxCoeff(); Eigen::RowVector3d means = grainPoints.cast().colwise().mean(); Eigen::ArrayXd p(10); switch (method) { case DIRECT: // Direct least squares fitting of ellipsoids under the constraint 4J - I**2 > 0. // The constraint confines the class of ellipsoids to fit to those whose smallest radius is at least half of the // largest radius. if(!directFit(scale * (grainPoints.cast().rowwise() - means), p)) // Ellipsoid fit { return false; } if (!implicitToExplicit(p, center, radii, rotationMatrix)) // Get the explicit parameters { return false; } break; default: break; } // Rescale the explicit parameters (the rotation matrix is unchanged by the scaling) center = center / scale + Eigen::Array3f(means.cast()); radii = radii / scale; // re-order the radii std::vector sortedRadii{radii(0), radii(1), radii(2)}; std::sort(sortedRadii.begin(), sortedRadii.end()); Eigen::Array3f updatedRadii = {sortedRadii[0], sortedRadii[1], sortedRadii[2]}; // from the smallest to the largest Eigen::Matrix3f updatedRotationMatrix; for (int k = 0; k < 3; k++) { float radius = updatedRadii(k); int col = 0; for (int idx = 0; idx < 3; idx++) { if (radii[idx] == radius) { break; } col++; } updatedRotationMatrix(k, 0) = rotationMatrix(col, 0); updatedRotationMatrix(k, 1) = rotationMatrix(col, 1); updatedRotationMatrix(k, 2) = rotationMatrix(col, 2); } radii = updatedRadii; rotationMatrix = updatedRotationMatrix; ret = explicitToImplicit(center, radii, rotationMatrix, p); return ret; } // DRAW void GrainsAsEllipsoids::releaseShaders() { m_program.clear(); } void GrainsAsEllipsoids::setUniformValueColor(const ccColor::Rgba &color) { m_program->setUniformValue("color", color.r, color.g, color.b, color.a); } bool GrainsAsEllipsoids::initProgram(QOpenGLContext* context) { if (m_program.isNull()) { QString error; if (!context) { assert(false); return false; } m_program.reset(new QOpenGLShaderProgram(context)); // create vertex shader QString vertexShaderFile(m_shaderPath + "/DrawGrains.vs"); if (!m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, vertexShaderFile)) { error = m_program->log(); ccLog::Error(error); return false; } // create fragment shader QString fragmentShaderFile(m_shaderPath + "/DrawGrains.fs"); if (!m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, fragmentShaderFile)) { error = m_program->log(); ccLog::Error(error); return false; } if (!m_program->link()) { error = m_program->log(); ccLog::Error(error); return false; } initSphereVertices(); initSphereIndexes(); } return true; } void GrainsAsEllipsoids::drawEllipsoid(CC_DRAW_CONTEXT& context, int idx) { QOpenGLFunctions_2_1* glFunc = context.glFunctions(); assert(glFunc != nullptr); if (!m_fitNotOK.count(idx)) { QMatrix4x4 projection; QMatrix4x4 modelView; QMatrix4x4 model; QMatrix4x4 matrixNormal; Eigen::Matrix3f rotation(m_rotationMatrix[idx].transpose()); QMatrix4x4 matrixFromFit(rotation(0, 0), rotation(0, 1), rotation(0, 2), m_center[idx](0), rotation(1, 0), rotation(1, 1), rotation(1, 2), m_center[idx](1), rotation(2, 0), rotation(2, 1), rotation(2, 2), m_center[idx](2), 0, 0, 0, 1); CCVector3f color; color = m_grainColors[idx]; glFunc->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glFunc->glEnable(GL_BLEND); m_program->setUniformValue("materialAmbient", color.x, color.y, color.z, 1.); m_program->setUniformValue("materialDiffuse", color.x, color.y, color.z, 1.); m_program->setUniformValue("objectColor", color.x, color.y, color.z); // prepare translation, rotation and scaling glFunc->glPushMatrix(); // save the current matrix // rotation and translation from the ellipsoid fitting glFunc->glMultMatrixf(matrixFromFit.data()); // scale from the ellipsoid fitting glFunc->glScalef(m_radii[idx](0), m_radii[idx](1), m_radii[idx](2)); // get matrices glFunc->glGetFloatv(GL_PROJECTION_MATRIX, projection.data()); glFunc->glGetFloatv(GL_MODELVIEW_MATRIX, modelView.data()); matrixNormal = modelView; matrixNormal.setColumn(3, QVector4D(0,0,0,1)); m_program->setUniformValue("modelViewMatrix", modelView); m_program->setUniformValue("normalMatrix", matrixNormal); m_program->setUniformValue("modelViewProjectionMatrix", projection * modelView); // draw triangles if (m_drawSurfaces) { m_program->setUniformValue("drawLines", 0); m_program->setUniformValue("drawPoints", 0); glFunc->glEnable(GL_POLYGON_OFFSET_FILL); glFunc->glPolygonOffset(1.0, 1.0f); // move polygon backward glFunc->glDrawElements(GL_TRIANGLES, (unsigned int) indices.size(), GL_UNSIGNED_INT, indices.data()); glFunc->glDisable(GL_POLYGON_OFFSET_FILL); } // draw lines if (m_drawLines) { m_program->setUniformValue("drawLines", 1); m_program->setUniformValue("drawPoints", 0); glFunc->glDrawElements(GL_LINES, (unsigned int)lineIndices.size(), GL_UNSIGNED_INT, lineIndices.data()); } glFunc->glPopMatrix(); } } bool GrainsAsEllipsoids::drawEllipsoids(CC_DRAW_CONTEXT& context) { QOpenGLFunctions_2_1* glFunc = context.glFunctions(); assert(glFunc != nullptr); CCVector3f color; // set uniforms QVector4D lightPosition(2 * m_ccBBoxAll.maxCorner().x, 2 * m_ccBBoxAll.maxCorner().y, 10 * m_ccBBoxAll.maxCorner().z, 0); QVector4D lightAmbient(0.8f, 0.8f, 0.8f, 1); // grey QVector4D lightDiffuse(0.8f, 0.8f, 0.8f, 1); // light grey QVector4D lightSpecular(1.0f, 1.0f, 1.0f, 1); // white QVector4D materialDiffuse(0.7f, 0.7f, 0.7f, m_transparency); QVector4D materialSpecular(0.4f, 0.4f, 0.4f, 1); float materialShininess = 16; m_program->setUniformValue("lightPosition", lightPosition); m_program->setUniformValue("lightAmbient", lightAmbient); m_program->setUniformValue("lightDiffuse", lightDiffuse); m_program->setUniformValue("lightSpecular", lightSpecular); m_program->setUniformValue("materialSpecular", materialSpecular); m_program->setUniformValue("materialShininess", materialShininess); m_program->setAttributeArray("vertexPosition", static_cast(vertices.data()), 3); m_program->setAttributeArray("vertexNormal", static_cast(normals.data()), 3); m_program->setAttributeArray("vertexTexCoord", static_cast(texCoords.data()), 2); m_program->enableAttributeArray("vertexPosition"); m_program->enableAttributeArray("vertexNormal"); m_program->enableAttributeArray("vertexTexCoord"); QMatrix4x4 projection; QMatrix4x4 modelView; if (m_showAll) { for (int idx = 0; idx < m_center.size(); idx++) { drawEllipsoid(context, idx); } } else { drawEllipsoid(context, m_onlyOne); // draw points if (m_drawPoints) { // get matrices glFunc->glGetFloatv(GL_PROJECTION_MATRIX, projection.data()); glFunc->glGetFloatv(GL_MODELVIEW_MATRIX, modelView.data()); m_program->setUniformValue("modelViewProjectionMatrix", projection * modelView); m_program->setUniformValue("drawLines", 0); m_program->setUniformValue("drawPoints", 1); m_program->setUniformValue("materialAmbient", 1, 1, 1, 1.); std::vector stack = m_stacks[m_onlyOne]; std::vector> points(stack.size()); std::vector indices; for (int k = 0; k < stack.size(); k++) { const CCVector3* P = m_cloud->getPoint(stack[k]); points[k] = {P->x, P->y, P->z}; } // change the vertex positions to the points of the current grain m_program->setAttributeArray("vertexPosition", static_cast(points[0].data()), 3); m_program->setUniformValue("pointSize", m_glPointSize); glFunc->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); glFunc->glDisable(GL_LIGHTING); glFunc->glDisable(GL_TEXTURE_2D); glFunc->glDrawArrays(GL_POINTS, 0, static_cast(stack.size())); // reset the vertex positions to the template sphere m_program->setAttributeArray("vertexPosition", static_cast(vertices.data()), 3); } } m_program->disableAttributeArray("vertexPosition"); m_program->disableAttributeArray("vertexNormal"); m_program->disableAttributeArray("vertexTexCoord"); return true; } void GrainsAsEllipsoids::drawGrains(CC_DRAW_CONTEXT& context) { if (!initProgram(context.qGLContext)) { ccLog::Warning("[GrainsAsEllipsoids::drawGrains] impossible to init shader program"); return; } else { m_programInitialized = true; } if (m_programInitialized) { m_program->bind(); drawEllipsoids(context); m_program->release(); m_app->redrawAll(); } } void GrainsAsEllipsoids::setOnlyOne(int i) { m_onlyOne = i; updateBBoxOnlyOne(i); redrawDisplay(); } void GrainsAsEllipsoids::showOnlyOne(bool state) { m_showAll =!state; m_ccBBox = m_ccBBoxOnlyOne; redrawDisplay(); } void GrainsAsEllipsoids::showAll(bool state) { m_showAll = state; m_ccBBox = m_ccBBoxAll; redrawDisplay(); } void GrainsAsEllipsoids::draw(CC_DRAW_CONTEXT& context) { if (m_radii.empty()) // nothing to draw, probably due to a bad initialization return; if (isVisible() && isEnabled()) { if (MACRO_Draw3D(context)) { drawGrains(context); //if the entity is currently selected, we draw its bounding-box if (m_selected && !MACRO_EntityPicking(context) && context.currentLODLevel == 0) { drawBB(context, context.bbDefaultCol); } } } } ccBBox GrainsAsEllipsoids::getOwnBB(bool withGLFeatures) { return m_ccBBox; } /// template static /// bool genericArrayToFile(const std::vector& data, QFile& out) { assert(out.isOpen() && (out.openMode() & QIODevice::WriteOnly)); //removed to allow saving empty clouds //if (data.empty()) //{ // return ccSerializableObject::MemoryError(); //} int N = 1; //component count (dataVersion>=20) ::uint8_t componentCount = static_cast<::uint8_t>(N); if (out.write((const char*)&componentCount, 1) < 0) return ccSerializableObject::WriteError(); //element count = array size (dataVersion>=20) ::uint32_t elementCount = static_cast<::uint32_t>(data.size()); if (out.write((const char*)&elementCount, 4) < 0) return ccSerializableObject::WriteError(); //array data (dataVersion>=20) { //DGM: do it by chunks, in case it's too big to be processed by the system const char* _data = (const char*)data.data(); qint64 byteCount = static_cast(elementCount); byteCount *= sizeof(Eigen::Array3f); while (byteCount != 0) { static const qint64 s_maxByteSaveCount = (1 << 26); //64 Mb each time qint64 saveCount = std::min(byteCount, s_maxByteSaveCount); if (out.write(_data, saveCount) < 0) return ccSerializableObject::WriteError(); _data += saveCount; byteCount -= saveCount; } } return true; } bool readArrayHeader(QFile& in, short dataVersion, ::uint8_t &componentCount, ::uint32_t &elementCount) { assert(in.isOpen() && (in.openMode() & QIODevice::ReadOnly)); if (dataVersion < 20) return ccSerializableObject::CorruptError(); //component count (dataVersion>=20) if (in.read((char*)&componentCount, 1) < 0) return ccSerializableObject::ReadError(); //element count = array size (dataVersion>=20) if (in.read((char*)&elementCount, 4) < 0) return ccSerializableObject::ReadError(); return true; } template bool stdVectorToFile(QString name, std::vector vector) { std::ofstream file(name.toLatin1()); int elementSize = vector[0].size(); for (int i = 0; i < vector.size(); i++) { for (int j = 0; j < elementSize; j++) { file << vector[i][j] << ", "; } file << std::endl; } return true; } bool rotationMatrixToFile(QString name, std::vector rotationMatrix) { std::ofstream file(name.toLatin1()); int elementSize = rotationMatrix[0].size(); for (int i = 0; i < rotationMatrix.size(); i++) { for (int j = 0; j < elementSize; j++) { file << rotationMatrix[i](j) << ", "; } file << std::endl; } return true; } bool GrainsAsEllipsoids::toFile_MeOnly(QFile& out, short dataVersion) const { ccLog::Print("[G3Point] write GrainsAsEllipsoids object in .bin"); if (!ccHObject::toFile_MeOnly(out, dataVersion)) { return false; } if (!ccSerializationHelper::GenericArrayToFile(m_center, out)) return WriteError(); if (!ccSerializationHelper::GenericArrayToFile(m_radii, out)) return WriteError(); if (!ccSerializationHelper::GenericArrayToFile(m_rotationMatrix, out)) return WriteError(); if (!ccSerializationHelper::GenericArrayToFile(m_grainColors, out)) return WriteError(); return true; } bool GrainsAsEllipsoids::fromFile_MeOnly(QFile& in, short dataVersion, int flags, LoadedIDMap& oldToNewIDMap) { ccLog::Print("[G3Point] read GrainsAsEllipsoids object from .bin"); if (!ccHObject::fromFile_MeOnly(in, dataVersion, flags, oldToNewIDMap)) return false; if (!ccSerializationHelper::GenericArrayFromFile(m_center, in, dataVersion, "G3Point m_center")) { ccLog::Warning("[G3Point] error reading m_center"); return ReadError(); } if (!ccSerializationHelper::GenericArrayFromFile(m_radii, in, dataVersion, "G3Point m_radii")) { ccLog::Warning("[G3Point] error reading m_radii"); return ReadError(); } if (!ccSerializationHelper::GenericArrayFromFile(m_rotationMatrix, in, dataVersion, "G3Point m_rotationMatrix")) { ccLog::Warning("[G3Point] error reading m_rorationMatrix"); return ReadError(); } if (!ccSerializationHelper::GenericArrayFromFile(m_grainColors, in, dataVersion, "G3Point m_grainColors")) { ccLog::Warning("[G3Point] error reading m_rorationMatrix"); return ReadError(); } lockVisibility(false); setVisible(true); m_ccBBoxAll.setValidity(false); m_ccBBoxAll.clear(); for (int idx = 0; idx < m_center.size(); idx++) { float maxRadius = m_radii[idx].maxCoeff(); CCVector3 center(m_center[idx](0), m_center[idx](1), m_center[idx](2)); if (m_radii[idx].x() != -1) // all radii are equal to zero when the fit was not successful { m_fitNotOK.insert(idx); continue; } m_ccBBoxAll.add(CCVector3(center.x + maxRadius, center.y + maxRadius, center.z + maxRadius)); m_ccBBoxAll.add(CCVector3(center.x - maxRadius, center.y - maxRadius, center.z - maxRadius)); } m_ccBBoxAll.setValidity(true); m_ccBBox = m_ccBBoxAll; redrawDisplay(); return true; }