//helper: computes a facet horizontal and vertical extensions void ComputeFacetExtensions(CCVector3& N, ccPolyline* facetContour, double& horizExt, double& vertExt) { //horizontal and vertical extensions horizExt = vertExt = 0; CCLib::GenericIndexedCloudPersist* vertCloud = facetContour->getAssociatedCloud(); if (vertCloud) { //oriRotMat.applyRotation(N); //DGM: oriRotMat is only for display! //we assume that at this point the "up" direction is always (0,0,1) CCVector3 Xf(1,0,0), Yf(0,1,0); //we get the horizontal vector on the plane CCVector3 D = CCVector3(0,0,1).cross(N); if (D.norm2() > ZERO_TOLERANCE) //otherwise the facet is horizontal! { Yf = D; Yf.normalize(); Xf = N.cross(Yf); } const CCVector3* G = CCLib::Neighbourhood(vertCloud).getGravityCenter(); ccBBox box; for (unsigned i=0; i<vertCloud->size(); ++i) { const CCVector3 P = *(vertCloud->getPoint(i)) - *G; CCVector3 p( P.dot(Xf), P.dot(Yf), 0 ); box.add(p); } vertExt = box.getDiagVec().x; horizExt = box.getDiagVec().y; } }
void TestShpFilter::readPolygonFile(const QString &filePath) const { const unsigned expectedNumPoints = 14; // File has 15 points but as its a polygon, CC will keep the 14 first pts CCVector3d bbMin(-626146.0444521683, 5219675.646154184, 0); CCVector3d shift = ccGlobalShiftManager::BestShift(bbMin); bool shiftEnabled = true; ccHObject container; FileIOFilter::LoadParameters params; params.alwaysDisplayLoadDialog = false; params.shiftHandlingMode = ccGlobalShiftManager::Mode::NO_DIALOG; params.coordinatesShiftEnabled = &shiftEnabled; params.coordinatesShift = &shift; params.preserveShiftOnSave = true; ShpFilter filter; CC_FILE_ERROR error = filter.loadFile(filePath, container, params); QVERIFY(error == CC_FERR_NO_ERROR); QVERIFY(container.getChildrenNumber() == 1); ccHObject *item = container.getFirstChild(); QVERIFY(item->getClassID() == CC_TYPES::POLY_LINE); auto *poly = static_cast<ccPolyline *>(item); QVERIFY(poly->size() == expectedNumPoints); QVERIFY(!poly->isScalarFieldEnabled()); QVERIFY(!poly->is2DMode()); QVERIFY(poly->isClosed()); CCLib::GenericIndexedCloudPersist *vertices = poly->getAssociatedCloud(); QVERIFY(vertices->size() == expectedNumPoints); std::array<double, 14> expectedXs{-626146.0444521683, -187004.53123683017, -59884.61951660062, 169316.43343351, 180872.78904444003, 300288.4636907161, 914701.3703384919, 752912.3917854726, 880032.303505702, 749060.273248496, 473633.79785466543, 375404.77516176086, -212043.3017271784, -187004.53123683017}; std::array<double, 14> expectedYs{6182705.280398346, 6409980.274079968, 6383015.444321131, 6488948.704087989, 6606438.319465778, 6650737.682641009, 6236634.939916019, 5878387.91597719, 5391094.921049644, 5271679.246403368, 5352573.735679878, 5219675.646154184, 5348721.617142901, 5789789.189626727}; for (unsigned i(0); i < expectedNumPoints; ++i) { const CCVector3 *p = vertices->getPoint(i); QCOMPARE(p->x, static_cast<ScalarType >(expectedXs[i] + shift.x)); QCOMPARE(p->y, static_cast<ScalarType >(expectedYs[i] + shift.y)); QCOMPARE(p->z, 0.0); } }
bool ccRegistrationTools::ICP( ccHObject* data, ccHObject* model, ccGLMatrix& transMat, double &finalScale, double& finalRMS, unsigned& finalPointCount, double minRMSDecrease, unsigned maxIterationCount, unsigned randomSamplingLimit, bool removeFarthestPoints, CCLib::ICPRegistrationTools::CONVERGENCE_TYPE method, bool adjustScale, double finalOverlapRatio/*=1.0*/, bool useDataSFAsWeights/*=false*/, bool useModelSFAsWeights/*=false*/, int filters/*=CCLib::ICPRegistrationTools::SKIP_NONE*/, int maxThreadCount/*=0*/, QWidget* parent/*=0*/) { //progress bar QScopedPointer<ccProgressDialog> progressDlg; if (parent) { progressDlg.reset(new ccProgressDialog(false, parent)); } Garbage<CCLib::GenericIndexedCloudPersist> cloudGarbage; //if the 'model' entity is a mesh, we need to sample points on it CCLib::GenericIndexedCloudPersist* modelCloud = nullptr; ccGenericMesh* modelMesh = nullptr; if (model->isKindOf(CC_TYPES::MESH)) { modelMesh = ccHObjectCaster::ToGenericMesh(model); modelCloud = modelMesh->getAssociatedCloud(); } else { modelCloud = ccHObjectCaster::ToGenericPointCloud(model); } //if the 'data' entity is a mesh, we need to sample points on it CCLib::GenericIndexedCloudPersist* dataCloud = nullptr; if (data->isKindOf(CC_TYPES::MESH)) { dataCloud = CCLib::MeshSamplingTools::samplePointsOnMesh(ccHObjectCaster::ToGenericMesh(data), s_defaultSampledPointsOnDataMesh, progressDlg.data()); if (!dataCloud) { ccLog::Error("[ICP] Failed to sample points on 'data' mesh!"); return false; } cloudGarbage.add(dataCloud); } else { dataCloud = ccHObjectCaster::ToGenericPointCloud(data); } //we activate a temporary scalar field for registration distances computation CCLib::ScalarField* dataDisplayedSF = nullptr; int oldDataSfIdx = -1, dataSfIdx = -1; //if the 'data' entity is a real ccPointCloud, we can even create a proper temporary SF for registration distances if (data->isA(CC_TYPES::POINT_CLOUD)) { ccPointCloud* pc = static_cast<ccPointCloud*>(data); dataDisplayedSF = pc->getCurrentDisplayedScalarField(); oldDataSfIdx = pc->getCurrentInScalarFieldIndex(); dataSfIdx = pc->getScalarFieldIndexByName(REGISTRATION_DISTS_SF); if (dataSfIdx < 0) dataSfIdx = pc->addScalarField(REGISTRATION_DISTS_SF); if (dataSfIdx >= 0) pc->setCurrentScalarField(dataSfIdx); else { ccLog::Error("[ICP] Couldn't create temporary scalar field! Not enough memory?"); return false; } } else { if (!dataCloud->enableScalarField()) { ccLog::Error("[ICP] Couldn't create temporary scalar field! Not enough memory?"); return false; } } //add a 'safety' margin to input ratio static double s_overlapMarginRatio = 0.2; finalOverlapRatio = std::max(finalOverlapRatio, 0.01); //1% minimum //do we need to reduce the input point cloud (so as to be close //to the theoretical number of overlapping points - but not too //low so as we are not registered yet ;) if (finalOverlapRatio < 1.0 - s_overlapMarginRatio) { //DGM we can now use 'approximate' distances as SAITO algorithm is exact (but with a coarse resolution) //level = 7 if < 1.000.000 //level = 8 if < 10.000.000 //level = 9 if > 10.000.000 int gridLevel = static_cast<int>(floor(log10(static_cast<double>(std::max(dataCloud->size(), modelCloud->size()))))) + 2; gridLevel = std::min(std::max(gridLevel, 7), 9); int result = -1; if (modelMesh) { CCLib::DistanceComputationTools::Cloud2MeshDistanceComputationParams c2mParams; c2mParams.octreeLevel = gridLevel; c2mParams.maxSearchDist = 0; c2mParams.useDistanceMap = true; c2mParams.signedDistances = false; c2mParams.flipNormals = false; c2mParams.multiThread = false; result = CCLib::DistanceComputationTools::computeCloud2MeshDistance(dataCloud, modelMesh, c2mParams, progressDlg.data()); } else { result = CCLib::DistanceComputationTools::computeApproxCloud2CloudDistance( dataCloud, modelCloud, gridLevel, -1, progressDlg.data()); } if (result < 0) { ccLog::Error("Failed to determine the max (overlap) distance (not enough memory?)"); return false; } //determine the max distance that (roughly) corresponds to the input overlap ratio ScalarType maxSearchDist = 0; { unsigned count = dataCloud->size(); std::vector<ScalarType> distances; try { distances.resize(count); } catch (const std::bad_alloc&) { ccLog::Error("Not enough memory!"); return false; } for (unsigned i=0; i<count; ++i) { distances[i] = dataCloud->getPointScalarValue(i); } ParallelSort(distances.begin(), distances.end()); //now look for the max value at 'finalOverlapRatio+margin' percent maxSearchDist = distances[static_cast<unsigned>(std::max(1.0,count*(finalOverlapRatio+s_overlapMarginRatio)))-1]; } //evntually select the points with distance below 'maxSearchDist' //(should roughly correspond to 'finalOverlapRatio + margin' percent) { CCLib::ReferenceCloud* refCloud = new CCLib::ReferenceCloud(dataCloud); cloudGarbage.add(refCloud); unsigned countBefore = dataCloud->size(); unsigned baseIncrement = static_cast<unsigned>(std::max(100.0,countBefore*finalOverlapRatio*0.05)); for (unsigned i=0; i<countBefore; ++i) { if (dataCloud->getPointScalarValue(i) <= maxSearchDist) { if ( refCloud->size() == refCloud->capacity() && !refCloud->reserve(refCloud->size() + baseIncrement) ) { ccLog::Error("Not enough memory!"); return false; } refCloud->addPointIndex(i); } } refCloud->resize(refCloud->size()); dataCloud = refCloud; unsigned countAfter = dataCloud->size(); double keptRatio = static_cast<double>(countAfter)/countBefore; ccLog::Print(QString("[ICP][Partial overlap] Selecting %1 points out of %2 (%3%) for registration").arg(countAfter).arg(countBefore).arg(static_cast<int>(100*keptRatio))); //update the relative 'final overlap' ratio finalOverlapRatio /= keptRatio; } } //weights CCLib::ScalarField* modelWeights = nullptr; CCLib::ScalarField* dataWeights = nullptr; { if (!modelMesh && useModelSFAsWeights) { if (modelCloud == dynamic_cast<CCLib::GenericIndexedCloudPersist*>(model) && model->isA(CC_TYPES::POINT_CLOUD)) { ccPointCloud* pc = static_cast<ccPointCloud*>(model); modelWeights = pc->getCurrentDisplayedScalarField(); if (!modelWeights) ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but model has no displayed scalar field!"); } else { ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but only point cloud scalar fields can be used as weights!"); } } if (useDataSFAsWeights) { if (!dataDisplayedSF) { if (dataCloud == ccHObjectCaster::ToPointCloud(data)) ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but data has no displayed scalar field!"); else ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but only point cloud scalar fields can be used as weights!"); } else { dataWeights = dataDisplayedSF; } } } CCLib::ICPRegistrationTools::RESULT_TYPE result; CCLib::PointProjectionTools::Transformation transform; CCLib::ICPRegistrationTools::Parameters params; { params.convType = method; params.minRMSDecrease = minRMSDecrease; params.nbMaxIterations = maxIterationCount; params.adjustScale = adjustScale; params.filterOutFarthestPoints = removeFarthestPoints; params.samplingLimit = randomSamplingLimit; params.finalOverlapRatio = finalOverlapRatio; params.modelWeights = modelWeights; params.dataWeights = dataWeights; params.transformationFilters = filters; params.maxThreadCount = maxThreadCount; } result = CCLib::ICPRegistrationTools::Register( modelCloud, modelMesh, dataCloud, params, transform, finalRMS, finalPointCount, static_cast<CCLib::GenericProgressCallback*>(progressDlg.data())); if (result >= CCLib::ICPRegistrationTools::ICP_ERROR) { ccLog::Error("Registration failed: an error occurred (code %i)",result); } else if (result == CCLib::ICPRegistrationTools::ICP_APPLY_TRANSFO) { transMat = FromCCLibMatrix<PointCoordinateType, float>(transform.R, transform.T, transform.s); finalScale = transform.s; } //remove temporary SF (if any) if (dataSfIdx >= 0) { assert(data->isA(CC_TYPES::POINT_CLOUD)); ccPointCloud* pc = static_cast<ccPointCloud*>(data); pc->setCurrentScalarField(oldDataSfIdx); pc->deleteScalarField(dataSfIdx); } return (result < CCLib::ICPRegistrationTools::ICP_ERROR); }
bool DistanceMapGenerationTool::ComputeSurfacesAndVolumes( const QSharedPointer<Map>& map, ccPolyline* profile, Measures& surface, Measures& volume) { if (!map || !profile) //invalid input! return false; CCLib::GenericIndexedCloudPersist* vertices = profile->getAssociatedCloud(); unsigned vertexCount = vertices ? vertices->size() : 0; if (vertexCount < 2) { //invalid profile! return false; } const ccPointCloud* pcVertices = dynamic_cast<ccPointCloud*>(profile->getAssociatedCloud()); if (!pcVertices) return false; //surface measures surface = Measures(); //volume measures volume = Measures(); //theoretical surface and volumes { double surfaceProd = 0.0; double volumeProd = 0.0; const double yMax = map->yMin + map->yStep * (double)map->ySteps; for (unsigned i=1; i<pcVertices->size(); ++i) { const CCVector3* P0 = pcVertices->getPoint(i-1); const CCVector3* P1 = pcVertices->getPoint(i); //polyline: X = radius, Y = height double r0 = P0->x; double y0 = P0->y; double r1 = P1->x; double y1 = P1->y; //without loss of generality ;) if (y0 > y1) { std::swap(y0,y1); std::swap(r0,r1); } //segment is totally outside the map? if (y1 < map->yMin || y0 > yMax) { //we skip it continue; } if (y0 < map->yMin) { //interpolate r0 @ map->yMin double alpha = (map->yMin - y0)/(y1 - y0); assert(alpha >= 0.0 && alpha <= 1.0); r0 = r0 + alpha * (r1 - r0); y0 = map->yMin; } else if (y1 > yMax) { //interpolate r1 @ map->yMax double alpha = (yMax - y0)/(y1 - y0); assert(alpha >= 0.0 && alpha <= 1.0); r1 = r0 + alpha * (r1 - r0); y1 = yMax; } //product for truncated cone surface (see http://en.wikipedia.org/wiki/Frustum) double segmentLength = sqrt((r1-r0)*(r1-r0) + (y1-y0)*(y1-y0)); surfaceProd += (r0 + r1) * segmentLength; //product for truncated cone volume (see http://en.wikipedia.org/wiki/Frustum) volumeProd += (y1 - y0) * (r0*r0 + r1*r1 + r0*r1); } surface.theoretical = M_PI * surfaceProd; volume.theoretical = M_PI / 3.0 * volumeProd; } int revolDim = GetPoylineRevolDim(profile); if (revolDim < 0) return false; //constant factors const double surfPart = map->xStep / 2.0; //perimeter of a portion of circle of angle alpha = alpha * r (* height to get the external surface) const double volPart = map->yStep * map->xStep / 6.0; //area of a portion of circle of angle alpha = alpha/2 * r^2 (* height to get the volume) const MapCell* cell = &map->at(0); //for each row for (unsigned j=0; j<map->ySteps; ++j) { //corresponding heights double height1 = map->yMin + (double)j * map->yStep; double height2 = height1 + map->yStep; double r_th1 = -1.0; double r_th2 = -1.0; //search nearest "segment" in polyline double height_middle = (height1 + height2)/2.0; for (unsigned k=1; k<vertexCount; ++k) { const CCVector3* A = vertices->getPoint(k-1); const CCVector3* B = vertices->getPoint(k); double alpha = (height_middle - A->y)/(B->y - A->y); if (alpha >= 0.0 && alpha <= 1.0) { r_th1 = A->x + (height1 - A->y)/(B->y - A->y) * (B->x - A->x); r_th2 = A->x + (height2 - A->y)/(B->y - A->y) * (B->x - A->x); break; //FIXME: we hope that there's only one segment facing this particular height?! } } if (r_th1 >= 0.0 /* && r_th2 >= 0.0*/) { //for each column for (unsigned i=0; i<map->xSteps; ++i, ++cell) { //deviation from theory double d = (cell->count != 0 ? cell->value : 0.0); //"real" radius double r1 = r_th1 + d; //see ComputeRadialDist --> << double dist = radius - radius_th; >> double r2 = r_th2 + d; //FIXME: works only if the "scalar field" used for map creation was radial distances!!! //surface of the element (truncated cone external face) { double s = sqrt((r2 - r1) * (r2 - r1) + map->yStep * map->yStep); double externalSurface = /*surfPart * */(r1 + r2) * s; surface.total += externalSurface; //dispatch in 'positive' and 'negative' surface if (d >= 0.0) surface.positive += externalSurface; else surface.negative += externalSurface; } //volume of the element { volume.total += /*volPart * */(r1*r1 + r2*r2 + r1*r2); //volume of the gain (or loss) of matter double diffVolume = /*volPart * */fabs(3.0*d * (r_th1 + r_th2 + d)); // = (r*r) * part - (r_th*r_th) * part = [(r_th+d)*(r_th+d)-r_th*r_th] * part if (d >= 0.0) volume.positive += diffVolume; else volume.negative += diffVolume; } } } else { cell += map->xSteps; } } //don't forget to mult. by constants surface.total *= surfPart; surface.positive *= surfPart; surface.negative *= surfPart; volume.total *= volPart; volume.positive *= volPart; volume.negative *= volPart; return true; }
bool DistanceMapGenerationTool::ComputeRadialDist( ccPointCloud* cloud, ccPolyline* profile, bool storeRadiiAsSF/*=false*/, ccMainAppInterface* app/*=0*/) { //check input cloud and profile/polyline if (!cloud || !profile) { if (app) app->dispToConsole(QString("Internal error: invalid input parameters"),ccMainAppInterface::ERR_CONSOLE_MESSAGE); return false; } assert(cloud && profile); //number of vertices for the profile CCLib::GenericIndexedCloudPersist* vertices = profile->getAssociatedCloud(); unsigned vertexCount = vertices->size(); if (vertexCount < 2) { if (app) app->dispToConsole(QString("Invalid polyline (not enough vertices)"),ccMainAppInterface::ERR_CONSOLE_MESSAGE); return false; } //profile meta-data ProfileMetaData profileDesc; if (!GetPoylineMetaData(profile, profileDesc)) { if (app) app->dispToConsole(QString("Invalid polyline (bad or missing meta-data)"),ccMainAppInterface::ERR_CONSOLE_MESSAGE); return false; } //reserve a new scalar field (or take the old one if it already exists) int sfIdx = cloud->getScalarFieldIndexByName(RADIAL_DIST_SF_NAME); if (sfIdx < 0) sfIdx = cloud->addScalarField(RADIAL_DIST_SF_NAME); if (sfIdx < 0) { if (app) app->dispToConsole(QString("Failed to allocate a new scalar field for computing distances! Try to free some memory ..."),ccMainAppInterface::ERR_CONSOLE_MESSAGE); return false; } ccScalarField* sf = static_cast<ccScalarField*>(cloud->getScalarField(sfIdx)); unsigned pointCount = cloud->size(); sf->resize(pointCount); //should always be ok assert(sf); ccScalarField* radiiSf = 0; if (storeRadiiAsSF) { int sfIdxRadii = cloud->getScalarFieldIndexByName(RADII_SF_NAME); if (sfIdxRadii < 0) sfIdxRadii = cloud->addScalarField(RADII_SF_NAME); if (sfIdxRadii < 0) { if (app) app->dispToConsole(QString("Failed to allocate a new scalar field for storing radii! You should try to free some memory ..."),ccMainAppInterface::WRN_CONSOLE_MESSAGE); //return false; } else { radiiSf = static_cast<ccScalarField*>(cloud->getScalarField(sfIdxRadii)); radiiSf->resize(pointCount); //should always be ok } } bool success = true; //now compute the distance between the cloud and the (implicit) surface of revolution { ccGLMatrix cloudToSurface = profileDesc.computeProfileToSurfaceTrans(); //we deduce the horizontal dimensions from the revolution axis const unsigned char dim1 = static_cast<unsigned char>(profileDesc.revolDim < 2 ? profileDesc.revolDim+1 : 0); const unsigned char dim2 = (dim1 < 2 ? dim1+1 : 0); ccProgressDialog dlg(true, app ? app->getMainWindow() : 0); dlg.setMethodTitle("Cloud to profile radial distance"); dlg.setInfo(qPrintable(QString("Polyline: %1 vertices\nCloud: %2 points").arg(vertexCount).arg(pointCount))); dlg.start(); CCLib::NormalizedProgress nProgress(static_cast<CCLib::GenericProgressCallback*>(&dlg),pointCount); for (unsigned i=0; i<pointCount; ++i) { const CCVector3* P = cloud->getPoint(i); //relative point position CCVector3 Prel = cloudToSurface * (*P); //deduce point height and radius (i.e. in profile 2D coordinate system) double height = Prel.u[profileDesc.revolDim] - profileDesc.heightShift; //TODO FIXME: we assume the surface of revolution is smooth! double radius = sqrt(Prel.u[dim1]*Prel.u[dim1] + Prel.u[dim2]*Prel.u[dim2]); if (radiiSf) { ScalarType radiusVal = static_cast<ScalarType>(radius); radiiSf->setValue(i,radiusVal); } //search nearest "segment" in polyline ScalarType minDist = NAN_VALUE; for (unsigned j=1; j<vertexCount; ++j) { const CCVector3* A = vertices->getPoint(j-1); const CCVector3* B = vertices->getPoint(j); double alpha = (height - A->y)/(B->y - A->y); if (alpha >= 0.0 && alpha <= 1.0) { //we deduce the right radius by linear interpolation double radius_th = A->x + alpha * (B->x - A->x); double dist = radius - radius_th; //we look at the closest segment (if the polyline is concave!) if (!CCLib::ScalarField::ValidValue(minDist) || dist*dist < minDist*minDist) { minDist = static_cast<ScalarType>(dist); } } } sf->setValue(i,minDist); if (!nProgress.oneStep()) { //cancelled by user for (unsigned j=i; j<pointCount; ++j) sf->setValue(j,NAN_VALUE); success = false; break; } //TEST //*const_cast<CCVector3*>(P) = Prel; } //TEST //cloud->invalidateBoundingBox(); } sf->computeMinAndMax(); cloud->setCurrentDisplayedScalarField(sfIdx); cloud->showSF(true); return success; }
ccPointCloud* DistanceMapGenerationTool::ConvertMapToCloud( const QSharedPointer<Map>& map, ccPolyline* profile, double baseRadius/*=1.0*/, bool keepNaNPoints/*=true*/) { if (!map || !profile) return 0; unsigned count = map->ySteps * map->xSteps; ccPointCloud* cloud = new ccPointCloud("map"); ccScalarField* sf = new ccScalarField("values"); if (!cloud->reserve(count) || !sf->reserve(count)) { //not enough memory delete cloud; sf->release(); return 0; } //number of vertices CCLib::GenericIndexedCloudPersist* polyVertices = profile->getAssociatedCloud(); unsigned polyVertCount = polyVertices->size(); if (polyVertCount < 2) return 0; //profile meta-data (we only need the height shift) PointCoordinateType heightShift = 0; GetPolylineHeightShift(profile, heightShift); const double xStep = baseRadius * (2.0*M_PI) / static_cast<double>(map->xSteps); const MapCell* cell = &map->at(0); for (unsigned j=0; j<map->ySteps; ++j) { CCVector3 P(0,static_cast<PointCoordinateType>(map->yMin + (static_cast<double>(j) + 0.5) * map->yStep),0); //for each column for (unsigned i=0; i<map->xSteps; ++i, ++cell) { if (keepNaNPoints || cell->count != 0) { P.x = static_cast<PointCoordinateType>(map->xMin + (static_cast<double>(i) + 0.5) * xStep); //search nearest "segment" in polyline for (unsigned k=1; k<polyVertCount; ++k) { const CCVector3* A = polyVertices->getPoint(k-1); const CCVector3* B = polyVertices->getPoint(k); double alpha = (P.y - heightShift - A->y)/(B->y - A->y); if (alpha >= 0.0 && alpha <= 1.0) { //we deduce the right radius by linear interpolation double radius_th = A->x + alpha * (B->x - A->x); //TODO: we take the first radius (even if there are other segments at //this particular height, because we can't guess which one is the 'right' one! P.z = static_cast<PointCoordinateType>(radius_th); break; } } cloud->addPoint(P); ScalarType val = cell->count ? static_cast<ScalarType>(cell->value) : NAN_VALUE; sf->addElement(val); } } } sf->computeMinAndMax(); int sfIdx = cloud->addScalarField(sf); cloud->setCurrentDisplayedScalarField(sfIdx); cloud->showSF(true); cloud->resize(cloud->size()); //if we have skipped NaN values! return cloud; }
ccMesh* DistanceMapGenerationTool::ConvertProfileToMesh(ccPolyline* profile, const ccGLMatrix& cloudToSurface, bool counterclockwise, unsigned angularSteps/*=36*/, QImage mapTexture/*=QImage()*/) { if (!profile || angularSteps < 3) { return 0; } //profile vertices CCLib::GenericIndexedCloudPersist* profileVertices = profile->getAssociatedCloud(); unsigned profVertCount = profileVertices->size(); if (profVertCount < 2) { return 0; } //profile meta-data ProfileMetaData profileDesc; if (!GetPoylineMetaData(profile, profileDesc)) { assert(false); return 0; } unsigned char Z = static_cast<unsigned char>(profileDesc.revolDim); //we deduce the 2 other ('horizontal') dimensions const unsigned char X = (Z < 2 ? Z+1 : 0); const unsigned char Y = (X < 2 ? X+1 : 0); unsigned meshVertCount = profVertCount * angularSteps; unsigned meshFaceCount = (profVertCount-1) * angularSteps * 2; ccPointCloud* cloud = new ccPointCloud("vertices"); ccMesh* mesh = new ccMesh(cloud); if (!cloud->reserve(meshVertCount) || !mesh->reserve(meshFaceCount)) { //not enough memory delete cloud; delete mesh; return 0; } ccGLMatrix surfaceToCloud = cloudToSurface.inverse(); //create vertices { double cwSign = (counterclockwise ? -1.0 : 1.0); for (unsigned j=0; j<angularSteps; ++j) { double angle_rad = static_cast<double>(j)/angularSteps * (2*M_PI); CCVector3d N(sin(angle_rad) * cwSign, cos(angle_rad), 0); for (unsigned i=0; i<profVertCount; ++i) { const CCVector3* P = profileVertices->getPoint(i); double radius = static_cast<double>(P->x); CCVector3 Pxyz; Pxyz.u[X] = static_cast<PointCoordinateType>(radius * N.x); Pxyz.u[Y] = static_cast<PointCoordinateType>(radius * N.y); Pxyz.u[Z] = P->y + profileDesc.heightShift; surfaceToCloud.apply(Pxyz); cloud->addPoint(Pxyz); } } mesh->addChild(cloud); } PointCoordinateType h0 = profileVertices->getPoint(0)->y; PointCoordinateType dH = profileVertices->getPoint(profVertCount-1)->y - h0; bool invertedHeight = (dH < 0); //create facets { for (unsigned j=0; j<angularSteps; ++j) { unsigned nextJ = ((j+1) % angularSteps); for (unsigned i=0; i+1<profVertCount; ++i) { unsigned vertA = j*profVertCount+i; unsigned vertB = nextJ*profVertCount+i; unsigned vertC = vertB+1; unsigned vertD = vertA+1; if (invertedHeight) { mesh->addTriangle(vertB,vertC,vertD); mesh->addTriangle(vertB,vertD,vertA); } else { mesh->addTriangle(vertB,vertD,vertC); mesh->addTriangle(vertB,vertA,vertD); } } } } //do we have a texture as well? if (!mapTexture.isNull()) { //texture coordinates TextureCoordsContainer* texCoords = new TextureCoordsContainer(); mesh->addChild(texCoords); if (!texCoords->reserve(meshVertCount+profVertCount)) //we add a column for correct wrapping! { //not enough memory to finish the job! return mesh; } //create default texture coordinates for (unsigned j=0; j<=angularSteps; ++j) { float T[2] = {static_cast<float>(j)/static_cast<float>(angularSteps), 0.0f}; for (unsigned i=0; i<profVertCount; ++i) { T[1] = (profileVertices->getPoint(i)->y - h0) / dH; if (invertedHeight) T[1] = 1.0f - T[1]; texCoords->addElement(T); } } if (!mesh->reservePerTriangleTexCoordIndexes()) { //not enough memory to finish the job! return mesh; } //set texture indexes { for (unsigned j=0; j<angularSteps; ++j) { unsigned nextJ = ((j+1)/*% angularSteps*/); for (unsigned i=0; i+1<profVertCount; ++i) { unsigned vertA = j*profVertCount+i; unsigned vertB = nextJ*profVertCount+i; unsigned vertC = vertB+1; unsigned vertD = vertA+1; if (invertedHeight) { mesh->addTriangleTexCoordIndexes(vertB,vertC,vertD); mesh->addTriangleTexCoordIndexes(vertB,vertD,vertA); } else { mesh->addTriangleTexCoordIndexes(vertB,vertD,vertC); mesh->addTriangleTexCoordIndexes(vertB,vertA,vertD); } } } } //set material indexes if (!mesh->reservePerTriangleMtlIndexes()) { //not enough memory to finish the job! mesh->removeChild(texCoords); mesh->removePerTriangleTexCoordIndexes(); return mesh; } for (unsigned i=0; i<meshFaceCount; ++i) { mesh->addTriangleMtlIndex(0); } //set material { ccMaterial::Shared material(new ccMaterial("texture")); material->setTexture(mapTexture, QString(), false); ccMaterialSet* materialSet = new ccMaterialSet(); materialSet->addMaterial(material); mesh->setMaterialSet(materialSet); } mesh->setTexCoordinatesTable(texCoords); mesh->showMaterials(true); mesh->setVisible(true); cloud->setVisible(false); } return mesh; }
void ccGraphicalSegmentationTool::doExportSegmentationPolyline() { MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow && m_segmentationPoly) { QMessageBox messageBox(0); messageBox.setWindowTitle("Choose export type"); messageBox.setText("Export polyline in:\n - 2D (with coordinates relative to the screen)\n - 3D (with coordinates relative to the segmented entities)"); QPushButton* button2D = new QPushButton("2D"); QPushButton* button3D = new QPushButton("3D"); messageBox.addButton(button2D,QMessageBox::AcceptRole); messageBox.addButton(button3D,QMessageBox::AcceptRole); messageBox.addButton(QMessageBox::Cancel); messageBox.setDefaultButton(button3D); messageBox.exec(); if (messageBox.clickedButton() == messageBox.button(QMessageBox::Cancel)) { //process cancelled by user return; } ccPolyline* poly = new ccPolyline(*m_segmentationPoly); //if we export the polyline in 3D, we must project its vertices bool mode2D = (messageBox.clickedButton() == button2D); if (!mode2D) { //get current display parameters const double* MM = m_associatedWin->getModelViewMatd(); //viewMat const double* MP = m_associatedWin->getProjectionMatd(); //projMat const GLdouble half_w = static_cast<GLdouble>(m_associatedWin->width())/2; const GLdouble half_h = static_cast<GLdouble>(m_associatedWin->height())/2; int VP[4]; m_associatedWin->getViewportArray(VP); //project the 2D polyline in 3D CCLib::GenericIndexedCloudPersist* vertices = poly->getAssociatedCloud(); ccPointCloud* verticesPC = dynamic_cast<ccPointCloud*>(vertices); if (verticesPC) { for (unsigned i=0; i<vertices->size(); ++i) { CCVector3* Pscreen = const_cast<CCVector3*>(verticesPC->getPoint(i)); GLdouble xp,yp,zp; gluUnProject(half_w+Pscreen->x,half_h+Pscreen->y,0/*Pscreen->z*/,MM,MP,VP,&xp,&yp,&zp); Pscreen->x = static_cast<PointCoordinateType>(xp); Pscreen->y = static_cast<PointCoordinateType>(yp); Pscreen->z = static_cast<PointCoordinateType>(zp); } verticesPC->invalidateBoundingBox(); } else { assert(false); ccLog::Warning("[Segmentation] Failed to convert 2D polyline to 3D! (internal inconsistency)"); mode2D = false; } } poly->setName(QString("Segmentation polyline #%1").arg(++s_polylineExportCount)); poly->setEnabled(false); //we don't want it to appear while the segmentation mode is enabled! (anyway it's 2D only...) poly->set2DMode(mode2D); poly->setColor(ccColor::yellow); //we use a different color so as to differentiate them from the active polyline! mainWindow->addToDB(poly); ccLog::Print(QString("[Segmentation] Polyline exported (%1 vertices)").arg(poly->size())); } }
void ccGraphicalSegmentationTool::doActionUseExistingPolyline() { MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow) { ccHObject* root = mainWindow->dbRootObject(); ccHObject::Container polylines; if (root) { root->filterChildren(polylines,true,CC_TYPES::POLY_LINE); } if (!polylines.empty()) { ccEntityPickerDlg epDlg(polylines,0,this); if (!epDlg.exec()) return; int index = epDlg.getSelectedIndex(); assert(index >= 0 && index < static_cast<int>(polylines.size())); assert(polylines[index]->isA(CC_TYPES::POLY_LINE)); ccPolyline* poly = static_cast<ccPolyline*>(polylines[index]); CCLib::GenericIndexedCloudPersist* vertices = poly->getAssociatedCloud(); bool mode3D = !poly->is2DMode(); //viewing parameters (for conversion from 3D to 2D) const double* MM = m_associatedWin->getModelViewMatd(); //viewMat const double* MP = m_associatedWin->getProjectionMatd(); //projMat const GLdouble half_w = static_cast<GLdouble>(m_associatedWin->width())/2; const GLdouble half_h = static_cast<GLdouble>(m_associatedWin->height())/2; int VP[4]; m_associatedWin->getViewportArray(VP); //force polygonal selection mode doSetPolylineSelection(); m_segmentationPoly->clear(); m_polyVertices->clear(); //duplicate polyline 'a minima' (only points and indexes + closed state) if ( m_polyVertices->reserve(vertices->size()) && m_segmentationPoly->reserve(poly->size())) { for (unsigned i=0; i<vertices->size(); ++i) { CCVector3 P = *vertices->getPoint(i); if (mode3D) { GLdouble xp,yp,zp; gluProject(P.x,P.y,P.z,MM,MP,VP,&xp,&yp,&zp); P.x = static_cast<PointCoordinateType>(xp-half_w); P.y = static_cast<PointCoordinateType>(yp-half_h); P.z = 0; } m_polyVertices->addPoint(P); } for (unsigned j=0; j<poly->size(); ++j) m_segmentationPoly->addPointIndex(poly->getPointGlobalIndex(j)); m_segmentationPoly->setClosed(poly->isClosed()); if (m_segmentationPoly->isClosed()) { //stop m_state &= (~RUNNING); } if (m_associatedWin) m_associatedWin->updateGL(); } else { ccLog::Error("Not enough memory!"); } } else { ccLog::Error("No polyline in DB!"); } } }
void qFacets::exportFacets() { assert(m_app); if (!m_app) return; //disclaimer accepted? if (!ShowDisclaimer(m_app)) return; //Retrive selected facets FacetSet facets; getFacetsInCurrentSelection(facets); if (facets.empty()) { m_app->dispToConsole("Couldn't find any facet in the current selection!",ccMainAppInterface::ERR_CONSOLE_MESSAGE); return; } assert(!facets.empty()); FacetsExportDlg fDlg(FacetsExportDlg::SHAPE_FILE_IO,m_app->getMainWindow()); //persistent settings (default export path) QSettings settings; settings.beginGroup("qFacets"); QString facetsSavePath = settings.value("exportPath",QApplication::applicationDirPath()).toString(); fDlg.destinationPathLineEdit->setText(facetsSavePath + QString("/facets.shp")); if (!fDlg.exec()) return; QString filename = fDlg.destinationPathLineEdit->text(); //save current export path to persistent settings settings.setValue("exportPath",QFileInfo(filename).absolutePath()); if (QFile(filename).exists()) { //if the file already exists, ask for confirmation! if (QMessageBox::warning(m_app->getMainWindow(),"File already exists!","File already exists! Are you sure you want to overwrite it?",QMessageBox::Yes,QMessageBox::No) == QMessageBox::No) return; } //fields (shapefile) - WARNING names must not have more than 10 chars! ShpFilter::IntegerField facetIndex("index"); ShpFilter::DoubleField facetSurface("surface"); ShpFilter::DoubleField facetRMS("rms"); ShpFilter::IntegerField facetDipDir("dip_dir"); ShpFilter::IntegerField facetDip("dip"); ShpFilter::IntegerField familyIndex("family_ind"); ShpFilter::IntegerField subfamilyIndex("subfam_ind"); ShpFilter::DoubleField3D facetNormal("normal"); ShpFilter::DoubleField3D facetBarycenter("center"); ShpFilter::DoubleField horizExtension("horiz_ext"); ShpFilter::DoubleField vertExtension("vert_ext"); ShpFilter::DoubleField surfaceExtension("surf_ext"); size_t facetCount = facets.size(); assert(facetCount != 0); try { facetIndex.values.reserve(facetCount); facetSurface.values.reserve(facetCount); facetRMS.values.reserve(facetCount); facetDipDir.values.reserve(facetCount); facetDip.values.reserve(facetCount); familyIndex.values.reserve(facetCount); subfamilyIndex.values.reserve(facetCount); facetNormal.values.reserve(facetCount); facetBarycenter.values.reserve(facetCount); horizExtension.values.reserve(facetCount); vertExtension.values.reserve(facetCount); surfaceExtension.values.reserve(facetCount); } catch (const std::bad_alloc&) { m_app->dispToConsole("Not enough memory!",ccMainAppInterface::ERR_CONSOLE_MESSAGE); return; } ccHObject toSave("facets"); //depending on the 'main orientation', the job is more or less easy ;) bool useNativeOrientation = fDlg.nativeOriRadioButton->isChecked(); bool useGlobalOrientation = fDlg.verticalOriRadioButton->isChecked(); bool useCustomOrientation = fDlg.customOriRadioButton->isChecked(); //Default base CCVector3 X(1,0,0), Y(0,1,0), Z(0,0,1); //'vertical' orientation (potentially specified by the user) if (!useNativeOrientation) { if (useCustomOrientation) { Z = CCVector3( static_cast<PointCoordinateType>(fDlg.nXLineEdit->text().toDouble()), static_cast<PointCoordinateType>(fDlg.nYLineEdit->text().toDouble()), static_cast<PointCoordinateType>(fDlg.nZLineEdit->text().toDouble()) ); Z.normalize(); } else if (useGlobalOrientation) { //we compute the mean orientation (weighted by each facet's surface) CCVector3d Nsum(0,0,0); for (FacetSet::iterator it = facets.begin(); it != facets.end(); ++it) { double surf = (*it)->getSurface(); CCVector3 N = (*it)->getNormal(); Nsum.x += static_cast<double>(N.x) * surf; Nsum.y += static_cast<double>(N.y) * surf; Nsum.z += static_cast<double>(N.z) * surf; } Nsum.normalize(); Z = CCVector3( static_cast<PointCoordinateType>(Nsum.x), static_cast<PointCoordinateType>(Nsum.y), static_cast<PointCoordinateType>(Nsum.z) ); } //update X & Y CCVector3 D = Z.cross(CCVector3(0,0,1)); if (D.norm2() > ZERO_TOLERANCE) //otherwise the vertical dir hasn't changed! { X = -D; X.normalize(); Y = Z.cross(X); } } //we compute the mean center (weighted by each facet's surface) CCVector3 C(0,0,0); { double weightSum = 0; for (FacetSet::iterator it = facets.begin(); it != facets.end(); ++it) { double surf = (*it)->getSurface(); CCVector3 Ci = (*it)->getCenter(); C += Ci * static_cast<PointCoordinateType>(surf); weightSum += surf; } if (weightSum) C /= static_cast<PointCoordinateType>(weightSum); } //determine the 'global' orientation matrix ccGLMatrix oriRotMat; oriRotMat.toIdentity(); if (!useNativeOrientation) { oriRotMat.getColumn(0)[0] = static_cast<float>(X.x); oriRotMat.getColumn(0)[1] = static_cast<float>(X.y); oriRotMat.getColumn(0)[2] = static_cast<float>(X.z); oriRotMat.getColumn(1)[0] = static_cast<float>(Y.x); oriRotMat.getColumn(1)[1] = static_cast<float>(Y.y); oriRotMat.getColumn(1)[2] = static_cast<float>(Y.z); oriRotMat.getColumn(2)[0] = static_cast<float>(Z.x); oriRotMat.getColumn(2)[1] = static_cast<float>(Z.y); oriRotMat.getColumn(2)[2] = static_cast<float>(Z.z); oriRotMat.invert(); ccGLMatrix transMat; transMat.setTranslation(-C); oriRotMat = oriRotMat * transMat; oriRotMat.setTranslation(oriRotMat.getTranslationAsVec3D() + C); } //for each facet for (FacetSet::iterator it=facets.begin(); it!=facets.end(); ++it) { ccFacet* facet = *it; ccPolyline* poly = facet->getContour(); //if necessary, we create a (temporary) new facet if (!useNativeOrientation) { CCLib::GenericIndexedCloudPersist* vertices = poly->getAssociatedCloud(); if (!vertices || vertices->size() < 3) continue; //create (temporary) new polyline ccPolyline* newPoly = new ccPolyline(*poly); ccPointCloud* pc = (newPoly ? dynamic_cast<ccPointCloud*>(newPoly->getAssociatedCloud()) : 0); if (pc) { pc->applyGLTransformation_recursive(&oriRotMat); } else { m_app->dispToConsole(QString("Failed to change the orientation of polyline '%1'! (not enough memory)").arg(poly->getName()),ccMainAppInterface::WRN_CONSOLE_MESSAGE); continue; } newPoly->set2DMode(true); poly = newPoly; } toSave.addChild(poly, useNativeOrientation ? ccHObject::DP_NONE : ccHObject::DP_PARENT_OF_OTHER); //save associated meta-data as 'shapefile' fields { //main parameters FacetMetaData data; GetFacetMetaData(facet, data); //horizontal and vertical extensions double horizExt = 0, vertExt = 0; ComputeFacetExtensions(data.normal,poly,horizExt,vertExt); facetIndex.values.push_back(data.facetIndex); facetSurface.values.push_back(data.surface); facetRMS.values.push_back(data.rms); facetDipDir.values.push_back(data.dipDir_deg); facetDip.values.push_back(data.dip_deg); familyIndex.values.push_back(data.familyIndex); subfamilyIndex.values.push_back(data.subfamilyIndex); facetNormal.values.push_back(CCVector3d(data.normal.x,data.normal.y,data.normal.z)); facetBarycenter.values.push_back(CCVector3d(data.center.x,data.center.y,data.center.z)); vertExtension.values.push_back(vertExt); horizExtension.values.push_back(horizExt); surfaceExtension.values.push_back(horizExt*vertExt); } } //save entities if (toSave.getChildrenNumber()) { std::vector<ShpFilter::GenericField*> fields; fields.push_back(&facetIndex); fields.push_back(&facetBarycenter); fields.push_back(&facetNormal); fields.push_back(&facetRMS); fields.push_back(&horizExtension); fields.push_back(&vertExtension); fields.push_back(&surfaceExtension); fields.push_back(&facetSurface); fields.push_back(&facetDipDir); fields.push_back(&facetDip); fields.push_back(&familyIndex); fields.push_back(&subfamilyIndex); ShpFilter filter; filter.treatClosedPolylinesAsPolygons(true); ShpFilter::SaveParameters params; params.alwaysDisplaySaveDialog = false; if (filter.saveToFile(&toSave,fields,filename,params) == CC_FERR_NO_ERROR) { m_app->dispToConsole(QString("[qFacets] File '%1' successfully saved").arg(filename),ccMainAppInterface::STD_CONSOLE_MESSAGE); } else { m_app->dispToConsole(QString("[qFacets] Failed to save file '%1'!").arg(filename),ccMainAppInterface::WRN_CONSOLE_MESSAGE); } } }
CC_FILE_ERROR SavePolyline(ccPolyline* poly, QFile& file, int32_t& bytesWritten, ESRI_SHAPE_TYPE outputShapeType) { bytesWritten = 0; if (!poly) { assert(false); return CC_FERR_BAD_ENTITY_TYPE; } CCLib::GenericIndexedCloudPersist* vertices = poly->getAssociatedCloud(); if (!vertices) return CC_FERR_BAD_ENTITY_TYPE; int32_t realNumPoints = poly->size(); if (realNumPoints < 3) return CC_FERR_BAD_ENTITY_TYPE; bool is2D = poly->is2DMode(); bool isClosed = poly->isClosed(); ccBBox box = poly->getBB(); assert(box.isValid()); //Shape Type { //Byte 0: Shape Type int32_t shapeTypeInt = qToLittleEndian<int32_t>(outputShapeType); file.write((const char*)&shapeTypeInt,4); bytesWritten += 4; } //Byte 4: Box { double xMin = qToLittleEndian<double>(box.minCorner().x); double xMax = qToLittleEndian<double>(box.maxCorner().x); double yMin = qToLittleEndian<double>(box.minCorner().y); double yMax = qToLittleEndian<double>(box.maxCorner().y); //The Bounding Box for the PolyLine stored in the order Xmin, Ymin, Xmax, Ymax /*Byte 4*/file.write((const char*)&xMin,8); /*Byte 12*/file.write((const char*)&yMin,8); /*Byte 20*/file.write((const char*)&xMax,8); /*Byte 28*/file.write((const char*)&yMax,8); bytesWritten += 32; } //Byte 36: NumParts (The number of parts in the PolyLine) { int32_t numParts = qToLittleEndian<int32_t>(1); file.write((const char*)&numParts,4); bytesWritten += 4; } //Byte 40: NumPoints (The total number of points for all parts) int32_t numPoints = realNumPoints; if (isClosed) numPoints++; { int32_t numPointsLE = qToLittleEndian<int32_t>(numPoints); file.write((const char*)&numPointsLE,4); bytesWritten += 4; } //Byte 44: Parts (An array of length NumParts) { //for each part, the index of its first point in the points array int32_t startIndex = qToLittleEndian<int32_t>(0); file.write((const char*)&startIndex,4); bytesWritten += 4; } //for polygons we must list the vertices in the right order: //"The neighborhood to the right of an observer walking along //the ring in vertex order is the inside of the polygon" bool inverseOrder = false; if (outputShapeType == SHP_POLYGON || outputShapeType == SHP_POLYGON_Z) { assert(isClosed); assert(numPoints > 2); //get bounding box ccBBox box = poly->getBB(); assert(box.isValid()); //get the two largest (ordered) dimensions (dim1, dim2) CCVector3 diag = box.getDiagVec(); unsigned char minDim = diag.y < diag.x ? 1 : 0; if (diag.z < diag.u[minDim]) minDim = 2; unsigned char dim1 = ((minDim+1) % 3); unsigned char dim2 = ((minDim+2) % 3); if (diag.u[dim1] > 0) //if the polyline is flat, no need to bother ;) { //look for the top-left-most point in this 'plane' int32_t leftMostPointIndex = 0; { const CCVector3* leftMostPoint = vertices->getPoint(0); for (int32_t i=1; i<realNumPoints; ++i) { const CCVector3* P = vertices->getPoint(i); if (P->u[dim1] < leftMostPoint->u[dim1] || (P->u[dim1] == leftMostPoint->u[dim1] && P->u[dim2] < leftMostPoint->u[dim2])) { leftMostPoint = P; leftMostPointIndex = i; } } } //we simply compare the angles betwween the two edges that have the top-left-most vertex in common { const CCVector3* B = vertices->getPoint(leftMostPointIndex > 0 ? leftMostPointIndex-1 : realNumPoints-1); const CCVector3* P = vertices->getPoint(leftMostPointIndex); const CCVector3* A = vertices->getPoint(leftMostPointIndex+1 < realNumPoints ? leftMostPointIndex+1 : 0); CCVector3 PA = *A-*P; CCVector3 PB = *B-*P; PointCoordinateType anglePA = atan2(PA.u[dim2],PA.u[dim1]); //forward PointCoordinateType anglePB = atan2(PB.u[dim2],PB.u[dim1]); //backward //angles should all be in [-PI/2;0] if (anglePA < anglePB) inverseOrder = true; } } } //Points (An array of length NumPoints) { for (int32_t i=0; i<numPoints; ++i) { int32_t ii = (inverseOrder ? numPoints-1-i : i); const CCVector3* P = vertices->getPoint(ii % realNumPoints); //warning: handle loop if polyline is closed double x = qToLittleEndian<double>(P->x); double y = qToLittleEndian<double>(P->y); /*Byte 0*/file.write((const char*)&x,8); /*Byte 8*/file.write((const char*)&y,8); bytesWritten += 16; } } //3D polylines if (!is2D) { //Z boundaries { double zMin = qToLittleEndian<double>(box.minCorner().z); double zMax = qToLittleEndian<double>(box.maxCorner().z); file.write((const char*)&zMin,8); file.write((const char*)&zMax,8); bytesWritten += 16; } //Z coordinates (for each part - just one here) { for (int32_t i=0; i<numPoints; ++i) { int32_t ii = (inverseOrder ? numPoints-1-i : i); const CCVector3* P = vertices->getPoint(ii % realNumPoints); //warning: handle loop if polyline is closed double z = qToLittleEndian<double>(P->z); file.write((const char*)&z,8); bytesWritten += 8; } } //M boundaries bool hasSF = vertices->isScalarFieldEnabled(); { double mMin = ESRI_NO_DATA; double mMax = ESRI_NO_DATA; if (hasSF) { for (int32_t i=0; i<realNumPoints; ++i) { ScalarType scalar = vertices->getPointScalarValue(i); if (i != 0) { if (mMin > scalar) mMin = static_cast<double>(scalar); else if (mMax < scalar) mMax = static_cast<double>(scalar); } else { mMin = mMax = static_cast<double>(scalar); } } } mMin = qToLittleEndian<double>(mMin); mMax = qToLittleEndian<double>(mMax); file.write((const char*)&mMin,8); file.write((const char*)&mMax,8); bytesWritten += 16; } //M values (for each part - just one here) { double scalar = qToLittleEndian<double>(ESRI_NO_DATA); for (int32_t i=0; i<numPoints; ++i) { if (hasSF) { scalar = static_cast<double>(vertices->getPointScalarValue(i % realNumPoints)); //warning: handle loop if polyline is closed scalar = qToLittleEndian<double>(scalar); } file.write((const char*)&scalar,8); bytesWritten += 8; } } } return CC_FERR_NO_ERROR; }
bool ccRegistrationTools::ICP( ccHObject* data, ccHObject* model, ccGLMatrix& transMat, double &finalScale, double& finalError, double minErrorDecrease, unsigned maxIterationCount, unsigned randomSamplingLimit, bool removeFarthestPoints, ConvergenceMethod method, bool adjustScale, bool useDataSFAsWeights/*=false*/, bool useModelSFAsWeights/*=false*/, QWidget* parent/*=0*/) { //progress bar ccProgressDialog pDlg(false,parent); //if the 'model' entity is a mesh, we need to sample points on it CCLib::GenericIndexedCloudPersist* modelCloud = 0; if (model->isKindOf(CC_TYPES::MESH)) { modelCloud = CCLib::MeshSamplingTools::samplePointsOnMesh(ccHObjectCaster::ToGenericMesh(model),s_defaultSampledPointsOnModelMesh,&pDlg); if (!modelCloud) { ccLog::Error("[ICP] Failed to sample points on 'model' mesh!"); return false; } } else { modelCloud = ccHObjectCaster::ToGenericPointCloud(model); } //if the 'data' entity is a mesh, we need to sample points on it CCLib::GenericIndexedCloudPersist* dataCloud = 0; if (data->isKindOf(CC_TYPES::MESH)) { dataCloud = CCLib::MeshSamplingTools::samplePointsOnMesh(ccHObjectCaster::ToGenericMesh(data),s_defaultSampledPointsOnDataMesh,&pDlg); if (!dataCloud) { ccLog::Error("[ICP] Failed to sample points on 'data' mesh!"); return false; } } else { dataCloud = ccHObjectCaster::ToGenericPointCloud(data); } //we activate a temporary scalar field for registration distances computation CCLib::ScalarField* dataDisplayedSF = 0; int oldDataSfIdx=-1, dataSfIdx=-1; //if the 'data' entity is a real ccPointCloud, we can even create a temporary SF for registration distances if (data->isA(CC_TYPES::POINT_CLOUD)) { ccPointCloud* pc = static_cast<ccPointCloud*>(data); dataDisplayedSF = pc->getCurrentDisplayedScalarField(); oldDataSfIdx = pc->getCurrentInScalarFieldIndex(); dataSfIdx = pc->getScalarFieldIndexByName(REGISTRATION_DISTS_SF); if (dataSfIdx < 0) dataSfIdx = pc->addScalarField(REGISTRATION_DISTS_SF); if (dataSfIdx >= 0) pc->setCurrentScalarField(dataSfIdx); else ccLog::Warning("[ICP] Couldn't create temporary scalar field! Not enough memory?"); } else { dataCloud->enableScalarField(); } //parameters CCLib::PointProjectionTools::Transformation transform; CCLib::ScalarField* modelWeights = 0; if (useModelSFAsWeights) { if (modelCloud == dynamic_cast<CCLib::GenericIndexedCloudPersist*>(model) && model->isA(CC_TYPES::POINT_CLOUD)) { ccPointCloud* pc = static_cast<ccPointCloud*>(model); modelWeights = pc->getCurrentDisplayedScalarField(); if (!modelWeights) ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but model has no displayed scalar field!"); } else { ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but only point clouds scalar fields can be used as weights!"); } } CCLib::ScalarField* dataWeights = 0; if (useDataSFAsWeights) { if (!dataDisplayedSF) { if (dataCloud == (CCLib::GenericIndexedCloudPersist*)data && data->isA(CC_TYPES::POINT_CLOUD)) ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but data has no displayed scalar field!"); else ccLog::Warning("[ICP] 'useDataSFAsWeights' is true but inly point clouds scalar fields can be used as weights!"); } else dataWeights = dataDisplayedSF; } CCLib::ICPRegistrationTools::CC_ICP_RESULT result; result = CCLib::ICPRegistrationTools::RegisterClouds( modelCloud, dataCloud, transform, method, minErrorDecrease, maxIterationCount, finalError, adjustScale, static_cast<CCLib::GenericProgressCallback*>(&pDlg), removeFarthestPoints, randomSamplingLimit, modelWeights, dataWeights); if (result >= CCLib::ICPRegistrationTools::ICP_ERROR) { ccLog::Error("Registration failed: an error occurred (code %i)",result); } else if (result == CCLib::ICPRegistrationTools::ICP_APPLY_TRANSFO) { transMat = ccGLMatrix(transform.R, transform.T, transform.s); finalScale = transform.s; } //if we had to sample points an the data mesh if (!data->isKindOf(CC_TYPES::POINT_CLOUD)) { delete dataCloud; dataCloud = 0; } else { if (data->isA(CC_TYPES::POINT_CLOUD)) { ccPointCloud* pc = static_cast<ccPointCloud*>(data); pc->setCurrentScalarField(oldDataSfIdx); if (dataSfIdx >= 0) pc->deleteScalarField(dataSfIdx); dataSfIdx=-1; } } //if we had to sample points an the model mesh if (!model->isKindOf(CC_TYPES::POINT_CLOUD)) { delete modelCloud; modelCloud = 0; } return (result < CCLib::ICPRegistrationTools::ICP_ERROR); }
void ccGraphicalSegmentationTool::doExportSegmentationPolyline() { MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow && m_segmentationPoly) { bool mode2D = false; #ifdef ALLOW_2D_OR_3D_EXPORT QMessageBox messageBox(0); messageBox.setWindowTitle("Choose export type"); messageBox.setText("Export polyline in:\n - 2D (with coordinates relative to the screen)\n - 3D (with coordinates relative to the segmented entities)"); QPushButton* button2D = new QPushButton("2D"); QPushButton* button3D = new QPushButton("3D"); messageBox.addButton(button2D,QMessageBox::AcceptRole); messageBox.addButton(button3D,QMessageBox::AcceptRole); messageBox.addButton(QMessageBox::Cancel); messageBox.setDefaultButton(button3D); messageBox.exec(); if (messageBox.clickedButton() == messageBox.button(QMessageBox::Cancel)) { //process cancelled by user return; } mode2D = (messageBox.clickedButton() == button2D); #endif ccPolyline* poly = new ccPolyline(*m_segmentationPoly); //if the polyline is 2D and we export the polyline in 3D, we must project its vertices if (!mode2D) { //get current display parameters ccGLCameraParameters camera; m_associatedWin->getGLCameraParameters(camera); const double half_w = camera.viewport[2] / 2.0; const double half_h = camera.viewport[3] / 2.0; //project the 2D polyline in 3D CCLib::GenericIndexedCloudPersist* vertices = poly->getAssociatedCloud(); ccPointCloud* verticesPC = dynamic_cast<ccPointCloud*>(vertices); if (verticesPC) { for (unsigned i=0; i<vertices->size(); ++i) { CCVector3* Pscreen = const_cast<CCVector3*>(verticesPC->getPoint(i)); CCVector3d Pd(half_w + Pscreen->x, half_h + Pscreen->y, 0/*Pscreen->z*/); CCVector3d Q3D; camera.unproject(Pd, Q3D); *Pscreen = CCVector3::fromArray(Q3D.u); } verticesPC->invalidateBoundingBox(); } else { assert(false); ccLog::Warning("[Segmentation] Failed to convert 2D polyline to 3D! (internal inconsistency)"); mode2D = false; } } QString polyName = QString("Segmentation polyline #%1").arg(++s_polylineExportCount); poly->setName(polyName); poly->setEnabled(false); //we don't want it to appear while the segmentation mode is enabled! (anyway it's 2D only...) poly->set2DMode(mode2D); poly->setColor(ccColor::yellow); //we use a different color so as to differentiate them from the active polyline! //save associated viewport cc2DViewportObject* viewportObject = new cc2DViewportObject(polyName + QString(" viewport")); viewportObject->setParameters(m_associatedWin->getViewportParameters()); viewportObject->setDisplay(m_associatedWin); poly->addChild(viewportObject); mainWindow->addToDB(poly,false,false,false); ccLog::Print(QString("[Segmentation] Polyline exported (%1 vertices)").arg(poly->size())); } }
void ccGraphicalSegmentationTool::doActionUseExistingPolyline() { MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow) { ccHObject* root = mainWindow->dbRootObject(); ccHObject::Container polylines; if (root) { root->filterChildren(polylines,true,CC_TYPES::POLY_LINE); } if (!polylines.empty()) { ccEntityPickerDlg epDlg(polylines,false,0,this); if (!epDlg.exec()) return; int index = epDlg.getSelectedIndex(); assert(index >= 0 && index < static_cast<int>(polylines.size())); assert(polylines[index]->isA(CC_TYPES::POLY_LINE)); ccPolyline* poly = static_cast<ccPolyline*>(polylines[index]); //look for an asociated viewport ccHObject::Container viewports; if (poly->filterChildren(viewports,false,CC_TYPES::VIEWPORT_2D_OBJECT,true) == 1) { //shall we apply this viewport? if (QMessageBox::question( m_associatedWin ? m_associatedWin->asWidget() : 0, "Associated viewport", "The selected polyline has an associated viewport: do you want to apply it?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { m_associatedWin->setViewportParameters(static_cast<cc2DViewportObject*>(viewports.front())->getParameters()); m_associatedWin->redraw(false); } } CCLib::GenericIndexedCloudPersist* vertices = poly->getAssociatedCloud(); bool mode3D = !poly->is2DMode(); //viewing parameters (for conversion from 3D to 2D) ccGLCameraParameters camera; m_associatedWin->getGLCameraParameters(camera); const double half_w = camera.viewport[2] / 2.0; const double half_h = camera.viewport[3] / 2.0; //force polygonal selection mode doSetPolylineSelection(); m_segmentationPoly->clear(); m_polyVertices->clear(); allowPolylineExport(false); //duplicate polyline 'a minima' (only points and indexes + closed state) if ( m_polyVertices->reserve(vertices->size()) && m_segmentationPoly->reserve(poly->size())) { for (unsigned i=0; i<vertices->size(); ++i) { CCVector3 P = *vertices->getPoint(i); if (mode3D) { CCVector3d Q2D; camera.project(P, Q2D); P.x = static_cast<PointCoordinateType>(Q2D.x-half_w); P.y = static_cast<PointCoordinateType>(Q2D.y-half_h); P.z = 0; } m_polyVertices->addPoint(P); } for (unsigned j=0; j<poly->size(); ++j) { m_segmentationPoly->addPointIndex(poly->getPointGlobalIndex(j)); } m_segmentationPoly->setClosed(poly->isClosed()); if (m_segmentationPoly->isClosed()) { //stop m_state &= (~RUNNING); allowPolylineExport(m_segmentationPoly->size() > 1); } if (m_associatedWin) m_associatedWin->redraw(true, false); } else { ccLog::Error("Not enough memory!"); } } else { ccLog::Error("No polyline in DB!"); } } }