| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235 |
- //##########################################################################
- //# #
- //# CLOUDCOMPARE #
- //# #
- //# 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; version 2 or later of the License. #
- //# #
- //# 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. #
- //# #
- //# COPYRIGHT: CloudCompare project #
- //# #
- //##########################################################################
- //Qt
- #include <QColorDialog>
- #include <QElapsedTimer>
- #include <QInputDialog>
- #include <QMessageBox>
- #include <QPushButton>
- //CCCoreLib
- #include <NormalDistribution.h>
- #include <ScalarFieldTools.h>
- #include <StatisticalTestingTools.h>
- #include <WeibullDistribution.h>
- #include <ReferenceCloud.h>
- //qCC_db
- #include <ccColorScalesManager.h>
- #include <ccFacet.h>
- #include <ccGenericPrimitive.h>
- #include <ccOctreeProxy.h>
- #include <ccPointCloud.h>
- #include <ccPointCloudInterpolator.h>
- #include <ccPolyline.h>
- #include <ccSensor.h>
- //qCC_gl
- #include "ccGuiParameters.h"
- //common
- #include <ccPickOneElementDlg.h>
- //Local
- #include "ccAskTwoDoubleValuesDlg.h"
- #include "ccAskThreeDoubleValuesDlg.h"
- #include "ccColorGradientDlg.h"
- #include "ccColorLevelsDlg.h"
- #include "ccComputeOctreeDlg.h"
- #include "ccExportCoordToSFDlg.h"
- #include "ccInterpolationDlg.h"
- #include "ccItemSelectionDlg.h"
- #include "ccNormalComputationDlg.h"
- #include "ccOrderChoiceDlg.h"
- #include "ccProgressDialog.h"
- #include "ccScalarFieldArithmeticsDlg.h"
- #include "ccScalarFieldFromColorDlg.h"
- #include "ccSetSFAsVec3Dlg.h"
- #include "ccStatisticalTestDlg.h"
- #include "ccCommon.h"
- #include "ccConsole.h"
- #include "ccEntityAction.h"
- #include "ccHistogramWindow.h"
- #include "ccLibAlgorithms.h"
- #include "ccUtils.h"
- // This is included only for temporarily removing an object from the tree.
- #include "ccMainAppInterface.h"
- // System
- #include <unordered_set>
- #include <array>
- namespace ccEntityAction
- {
- static QString GetFirstAvailableSFName(const ccPointCloud* cloud, const QString& baseName)
- {
- if (cloud == nullptr)
- {
- Q_ASSERT(false);
- return {};
- }
-
- QString name = baseName;
- for (int trials = 0; trials < 99; ++trials)
- {
- if (cloud->getScalarFieldIndexByName(name.toStdString()) < 0)
- {
- // Scalar field name is available
- return name;
- }
- // else, generate a new name
- name = QString("%1 #%2").arg(baseName).arg(trials + 1);
- }
- // we couldn't find an available name!
- return {};
- }
-
- //////////
- // Colours
- bool setColor(ccHObject::Container selectedEntities, bool colorize, QWidget* parent/*=nullptr*/)
- {
- static QColor s_lastColor = Qt::white;
- QColor colour = QColorDialog::getColor(s_lastColor, parent, QString(), QColorDialog::ShowAlphaChannel);
-
- if (!colour.isValid())
- return false;
- s_lastColor = colour;
-
- while (!selectedEntities.empty())
- {
- ccHObject* ent = selectedEntities.back();
- selectedEntities.pop_back();
- if (ent->isA(CC_TYPES::HIERARCHY_OBJECT))
- {
- //automatically parse a group's children set
- for (unsigned i = 0; i < ent->getChildrenNumber(); ++i)
- selectedEntities.push_back(ent->getChild(i));
- }
- else if (ent->isA(CC_TYPES::POINT_CLOUD) || ent->isA(CC_TYPES::MESH))
- {
- ccPointCloud* cloud = nullptr;
- if (ent->isA(CC_TYPES::POINT_CLOUD))
- {
- cloud = static_cast<ccPointCloud*>(ent);
- }
- else
- {
- ccMesh* mesh = static_cast<ccMesh*>(ent);
- ccGenericPointCloud* vertices = mesh->getAssociatedCloud();
- if ( !vertices
- || !vertices->isA(CC_TYPES::POINT_CLOUD)
- || (vertices->isLocked() && !mesh->isAncestorOf(vertices)) )
- {
- ccLog::Warning(QObject::tr("[SetColor] Can't set color for mesh '%1' (vertices are not accessible)").arg(ent->getName()));
- continue;
- }
-
- cloud = static_cast<ccPointCloud*>(vertices);
- }
-
- if (colorize)
- {
- cloud->colorize(static_cast<float>(colour.redF()),
- static_cast<float>(colour.greenF()),
- static_cast<float>(colour.blueF()),
- static_cast<float>(colour.alphaF()));
- }
- else
- {
- cloud->setColor(ccColor::FromQColora(colour));
- }
- cloud->showColors(true);
- cloud->showSF(false); //just in case
- cloud->prepareDisplayForRefresh();
-
- if (ent != cloud)
- {
- ent->showColors(true);
- }
- else if (cloud->getParent() && cloud->getParent()->isKindOf(CC_TYPES::MESH))
- {
- cloud->getParent()->showColors(true);
- cloud->getParent()->showSF(false); //just in case
- }
- }
- else if (ent->isKindOf(CC_TYPES::PRIMITIVE))
- {
- ccGenericPrimitive* prim = ccHObjectCaster::ToPrimitive(ent);
- ccColor::Rgb col( static_cast<ColorCompType>(colour.red()),
- static_cast<ColorCompType>(colour.green()),
- static_cast<ColorCompType>(colour.blue()) );
- prim->setColor(col);
- ent->showColors(true);
- ent->showSF(false); //just in case
- ent->prepareDisplayForRefresh();
- }
- else if (ent->isA(CC_TYPES::POLY_LINE))
- {
- ccPolyline* poly = ccHObjectCaster::ToPolyline(ent);
- poly->setColor(ccColor::FromQColor(colour));
- ent->showColors(true);
- ent->showSF(false); //just in case
- ent->prepareDisplayForRefresh();
- }
- else if (ent->isA(CC_TYPES::FACET))
- {
- ccFacet* facet = ccHObjectCaster::ToFacet(ent);
- facet->setColor(ccColor::FromQColor(colour));
- ent->showColors(true);
- ent->showSF(false); //just in case
- ent->prepareDisplayForRefresh();
- }
- else
- {
- ccLog::Warning(QObject::tr("[SetColor] Can't change color of entity '%1'").arg(ent->getName()));
- }
- }
-
- return true;
- }
-
- bool rgbToGreyScale(const ccHObject::Container &selectedEntities)
- {
- for (ccHObject* ent : selectedEntities)
- {
- bool lockedVertices = false;
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
-
- if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
- if (pc->hasColors())
- {
- pc->convertRGBToGreyScale();
- pc->showColors(true);
- pc->showSF(false); //just in case
- pc->prepareDisplayForRefresh();
- }
- }
- }
-
- return true;
- }
-
- bool setColorGradient(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- ccColorGradientDlg dlg(parent);
- if (!dlg.exec())
- return false;
-
- unsigned char dim = dlg.getDimension();
- ccColorGradientDlg::GradientType ramp = dlg.getType();
-
- ccColorScale::Shared colorScale(nullptr);
- if (ramp == ccColorGradientDlg::Default)
- {
- colorScale = ccColorScalesManager::GetDefaultScale();
- }
- else if (ramp == ccColorGradientDlg::TwoColors)
- {
- colorScale = ccColorScale::Create("Temp scale");
- QColor first;
- QColor second;
- dlg.getColors(first,second);
- colorScale->insert(ccColorScaleElement(0.0, first), false);
- colorScale->insert(ccColorScaleElement(1.0, second), true);
- }
-
- Q_ASSERT(colorScale || ramp == ccColorGradientDlg::Banding);
-
- const double frequency = dlg.getBandingFrequency();
-
- for (ccHObject* ent : selectedEntities)
- {
- bool lockedVertices = false;
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
-
- if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD)) // TODO
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
-
- bool success = false;
- if (ramp == ccColorGradientDlg::Banding)
- success = pc->setRGBColorByBanding(dim, frequency);
- else
- success = pc->setRGBColorByHeight(dim, colorScale);
-
- if (success)
- {
- ent->showColors(true);
- ent->showSF(false); //just in case
- ent->prepareDisplayForRefresh();
- }
- }
- }
-
- return true;
- }
-
- bool changeColorLevels(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- if (selectedEntities.size() != 1)
- {
- ccConsole::Error(QObject::tr("Select one and only one colored cloud or mesh!"));
- return false;
- }
-
- bool lockedVertices;
- ccPointCloud* pointCloud = ccHObjectCaster::ToPointCloud(selectedEntities[0], &lockedVertices);
- if (!pointCloud || lockedVertices)
- {
- if (lockedVertices && pointCloud)
- ccUtils::DisplayLockedVerticesWarning(pointCloud->getName(), true);
- return false;
- }
-
- if (!pointCloud->hasColors())
- {
- ccConsole::Error(QObject::tr("Selected entity has no colors!"));
- return false;
- }
-
- ccColorLevelsDlg dlg(parent, pointCloud);
- dlg.exec();
-
- return true;
- }
-
- //! Interpolate colors from on entity and transfer them to another one
- bool interpolateColors(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- if (selectedEntities.size() != 2)
- {
- ccConsole::Error(QObject::tr("Select 2 entities (clouds or meshes)!"));
- return false;
- }
-
- ccHObject* ent1 = selectedEntities[0];
- ccHObject* ent2 = selectedEntities[1];
-
- ccGenericPointCloud* cloud1 = ccHObjectCaster::ToGenericPointCloud(ent1);
- ccGenericPointCloud* cloud2 = ccHObjectCaster::ToGenericPointCloud(ent2);
-
- if (!cloud1 || !cloud2)
- {
- ccConsole::Error(QObject::tr("Select 2 entities (clouds or meshes)!"));
- return false;
- }
-
- if (!cloud1->hasColors() && !cloud2->hasColors())
- {
- ccConsole::Error(QObject::tr("None of the selected entities has per-point or per-vertex colors!"));
- return false;
- }
- else if (cloud1->hasColors() && cloud2->hasColors())
- {
- ccConsole::Error(QObject::tr("Both entities have colors! Remove the colors on the entity you wish to import the colors to!"));
- return false;
- }
-
- ccGenericPointCloud* source = cloud1;
- ccGenericPointCloud* dest = cloud2;
-
- if ( cloud2->hasColors())
- {
- std::swap(source, dest);
- std::swap(cloud1, cloud2);
- std::swap(ent1, ent2);
- }
-
- if (!dest->isA(CC_TYPES::POINT_CLOUD))
- {
- ccConsole::Error(QObject::tr("Destination cloud (or vertices) must be a real point cloud!"));
- return false;
- }
-
- ccProgressDialog pDlg(true, parent);
-
- if (static_cast<ccPointCloud*>(dest)->interpolateColorsFrom(source, &pDlg))
- {
- ent2->showColors(true);
- ent2->showSF(false); //just in case
- }
- else
- {
- ccConsole::Error(QObject::tr("An error occurred! (see console)"));
- }
-
- ent2->prepareDisplayForRefresh_recursive();
-
- return true;
- }
- //! Interpolate scalar fields from one entity and transfer them to another one
- bool interpolateSFs(const ccHObject::Container &selectedEntities, ccMainAppInterface* app)
- {
- if (selectedEntities.size() != 2)
- {
- ccConsole::Error(QObject::tr("Select 2 entities (clouds or meshes)!"));
- return false;
- }
-
- ccHObject* ent1 = selectedEntities[0];
- ccHObject* ent2 = selectedEntities[1];
-
- ccPointCloud* cloud1 = ccHObjectCaster::ToPointCloud(ent1);
- ccPointCloud* cloud2 = ccHObjectCaster::ToPointCloud(ent2);
- if (!cloud1 || !cloud2)
- {
- ccConsole::Error(QObject::tr("Select 2 entities (clouds or meshes)!"));
- return false;
- }
-
- if (!cloud1->hasScalarFields() && !cloud2->hasScalarFields())
- {
- ccConsole::Error(QObject::tr("None of the selected entities has per-point or per-vertex colors!"));
- return false;
- }
- else if (cloud1->hasScalarFields() && cloud2->hasScalarFields())
- {
- //ask the user to chose which will be the 'source' cloud
- ccOrderChoiceDlg ocDlg(cloud1, QObject::tr("Source"), cloud2, QObject::tr("Destination"), app);
- if (!ocDlg.exec())
- {
- //process cancelled by the user
- return false;
- }
- if (cloud1 != ocDlg.getFirstEntity())
- {
- std::swap(cloud1, cloud2);
- }
- }
- else if (cloud2->hasScalarFields())
- {
- std::swap(cloud1, cloud2);
- }
-
- ccPointCloud* source = cloud1;
- ccPointCloud* dest = cloud2;
- //show the list of scalar fields available on the source point cloud
- std::vector<int> sfIndexes;
- try
- {
- unsigned sfCount = source->getNumberOfScalarFields();
- if (sfCount == 1)
- {
- sfIndexes.push_back(0);
- }
- else if (sfCount > 1)
- {
- ccItemSelectionDlg isDlg(true, app->getMainWindow(), QObject::tr("entity"));
- QStringList scalarFields;
- {
- for (unsigned i = 0; i < sfCount; ++i)
- {
- scalarFields << QString::fromStdString(source->getScalarFieldName(i));
- }
- }
- isDlg.setItems(scalarFields, 0);
- if (!isDlg.exec())
- {
- //cancelled by the user
- return false;
- }
- isDlg.getSelectedIndexes(sfIndexes);
- if (sfIndexes.empty())
- {
- ccConsole::Error(QObject::tr("No scalar field was selected"));
- return false;
- }
- }
- else
- {
- assert(false);
- }
- }
- catch (const std::bad_alloc&)
- {
- ccConsole::Error(QObject::tr("Not enough memory!"));
- return false;
- }
- //semi-persistent parameters
- static ccPointCloudInterpolator::Parameters::Method s_interpMethod = ccPointCloudInterpolator::Parameters::RADIUS;
- static ccPointCloudInterpolator::Parameters::Algo s_interpAlgo = ccPointCloudInterpolator::Parameters::NORMAL_DIST;
- static int s_interpKNN = 6;
- ccInterpolationDlg iDlg(app->getMainWindow());
- iDlg.setInterpolationMethod(s_interpMethod);
- iDlg.setInterpolationAlgorithm(s_interpAlgo);
- iDlg.knnSpinBox->setValue(s_interpKNN);
- iDlg.radiusDoubleSpinBox->setValue(dest->getOwnBB().getDiagNormd() / 100);
- if (!iDlg.exec())
- {
- //process cancelled by the user
- return false;
- }
- //setup parameters
- ccPointCloudInterpolator::Parameters params;
- params.method = s_interpMethod = iDlg.getInterpolationMethod();
- params.algo = s_interpAlgo = iDlg.getInterpolationAlgorithm();
- params.knn = s_interpKNN = iDlg.knnSpinBox->value();
- params.radius = iDlg.radiusDoubleSpinBox->value();
- params.sigma = iDlg.kernelDoubleSpinBox->value();
- ccProgressDialog pDlg(true, app->getMainWindow());
- unsigned sfCountBefore = dest->getNumberOfScalarFields();
- if (ccPointCloudInterpolator::InterpolateScalarFieldsFrom(dest, source, sfIndexes, params, &pDlg))
- {
- dest->setCurrentDisplayedScalarField(static_cast<int>(std::min(sfCountBefore + 1, dest->getNumberOfScalarFields())) - 1);
- dest->showSF(true);
- }
- else
- {
- ccConsole::Error(QObject::tr("An error occurred! (see console)"));
- }
-
- dest->prepareDisplayForRefresh_recursive();
-
- return true;
- }
-
- //! Interpolate scalar fields from one entity and transfer them to another one without the dialog
- bool interpolateSFs(ccPointCloud *source, ccPointCloud *dest, int sfIndex, ccPointCloudInterpolator::Parameters& params, QWidget* parent/*=nullptr*/)
- {
- if (!source || !dest)
- {
- ccConsole::Error(QObject::tr("Unexpected null cloud pointers!"));
- return false;
- }
- if (!source->hasScalarFields())
- {
- ccConsole::Error(QObject::tr("[ccEntityAction::interpolateSFs] The source cloud has no scalar field!"));
- return false;
- }
- unsigned sfCount = source->getNumberOfScalarFields();
- if (sfIndex > static_cast<int>(sfCount))
- {
- ccConsole::Error(QObject::tr("[ccEntityAction::interpolateSFs] Invalid scalar field index!"));
- return false;
- }
- ccProgressDialog pDlg(true, parent);
- std::vector<int> sfIndexes({ sfIndex });
- if (!ccPointCloudInterpolator::InterpolateScalarFieldsFrom(dest, source, sfIndexes, params, parent ? &pDlg : nullptr))
- {
- ccConsole::Error(QObject::tr("[ccEntityAction::interpolateSFs] An error occurred! (see console)"));
- return false;
- }
-
- return true;
- }
- bool convertTextureToColor(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/)
- {
- for (ccHObject* ent : selectedEntities)
- {
- if (ent->isA(CC_TYPES::MESH)/*|| ent->isKindOf(CC_TYPES::PRIMITIVE)*/) //TODO
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
- Q_ASSERT(mesh);
-
- if (!mesh->hasMaterials())
- {
- ccLog::Warning(QObject::tr("[ConvertTextureToColor] Mesh '%1' has no material/texture!").arg(mesh->getName()));
- continue;
- }
- else
- {
- if ( mesh->hasColors()
- && QMessageBox::warning( parent,
- QObject::tr("Mesh already has colors"),
- QObject::tr("Mesh '%1' already has colors! Overwrite them?").arg(mesh->getName()),
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::No) != QMessageBox::Yes)
- {
- continue;
- }
-
- if (mesh->convertMaterialsToVertexColors())
- {
- mesh->showColors(true);
- mesh->showSF(false); //just in case
- mesh->showMaterials(false);
- mesh->prepareDisplayForRefresh_recursive();
- }
- else
- {
- ccLog::Warning(QObject::tr("[ConvertTextureToColor] Failed to convert texture on mesh '%1'!").arg(mesh->getName()));
- }
- }
- }
- }
-
- return true;
- }
- bool enhanceRGBWithIntensities(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/)
- {
- QString defaultSFName("Intensity");
- bool useCustomIntensityRange = false;
- static double s_minI = 0.0;
- static double s_maxI = 1.0;
- if (QMessageBox::question(parent, QObject::tr("Intensity range"), QObject::tr("Do you want to define the theoretical intensity range (yes)\nor use the actual one (no)?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
- {
- ccAskTwoDoubleValuesDlg atdvDlg(QObject::tr("Min"), QObject::tr("Max"), -1000000.0, 1000000.0, s_minI, s_maxI, 3, QObject::tr("Theroetical intensity"), parent);
- if (!atdvDlg.exec())
- {
- //process cancelled by the user
- return false;
- }
- s_minI = atdvDlg.doubleSpinBox1->value();
- s_maxI = atdvDlg.doubleSpinBox2->value();
- useCustomIntensityRange = true;
- }
- for (ccHObject* ent : selectedEntities)
- {
- bool lockedVertices = false;
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
- if (!pc || lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
- if (!pc->hasColors())
- {
- ccLog::Warning(QObject::tr("[EnhanceRGBWithIntensities] Entity '%1' has no RGB color!").arg(ent->getName()));
- continue;
- }
- if (!pc->hasScalarFields())
- {
- ccLog::Warning(QObject::tr("[EnhanceRGBWithIntensities] Entity '%1' has no scalar field!").arg(ent->getName()));
- continue;
- }
- int sfIdx = -1;
- if (pc->getNumberOfScalarFields() > 1)
- {
- //does the previously selected SF works?
- if (!defaultSFName.isEmpty())
- {
- //if it's valid, we'll keep this SF!
- sfIdx = pc->getScalarFieldIndexByName(defaultSFName.toStdString());
- }
- if (sfIdx < 0)
- {
- //let the user choose the right scalar field
- ccPickOneElementDlg poeDlg(QObject::tr("Intensity scalar field"), QObject::tr("Choose scalar field"), parent);
- for (unsigned i = 0; i < pc->getNumberOfScalarFields(); ++i)
- {
- CCCoreLib::ScalarField* sf = pc->getScalarField(i);
- assert(sf);
- QString sfName = QString::fromStdString(sf->getName());
- poeDlg.addElement(sfName);
- if (sfIdx < 0 && sfName.contains("intensity", Qt::CaseInsensitive))
- {
- sfIdx = static_cast<int>(i);
- }
- }
- poeDlg.setDefaultIndex(std::max(0, sfIdx));
- if (!poeDlg.exec())
- {
- //process cancelled by the user
- return false;
- }
- sfIdx = poeDlg.getSelectedIndex();
- defaultSFName = QString::fromStdString(pc->getScalarField(sfIdx)->getName());
- }
- }
- else
- {
- sfIdx = 0;
- }
- assert(sfIdx >= 0);
- if (pc->enhanceRGBWithIntensitySF(sfIdx, useCustomIntensityRange, s_minI, s_maxI))
- {
- ent->prepareDisplayForRefresh();
- ent->showColors(true);
- ent->showSF(false);
- }
- else
- {
- ccLog::Warning(QObject::tr("[EnhanceRGBWithIntensities] Failed to apply the process on entity '%1'!").arg(ent->getName()));
- }
- }
- return true;
- }
- bool rgbGaussianFilter(const ccHObject::Container& selectedEntities, ccPointCloud::RgbFilterOptions filterParams, QWidget* parent/*=nullptr*/)
- {
- if (selectedEntities.empty())
- {
- return false;
- }
- // select only the clouds (or vertices) with RGB colors
- std::vector<std::pair<ccHObject*, ccPointCloud*>> selectedCloudsWithColors;
- double spatialSigma = std::numeric_limits<double>::max();
- for (ccHObject* ent : selectedEntities)
- {
- bool lockedVertices = false;
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
- if (!pc || lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
- //check if the cloud has color
- if (pc->hasColors())
- {
- if ((filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL) && !pc->hasDisplayedScalarField())
- {
- continue;
- }
- selectedCloudsWithColors.push_back({ ent, pc });
- double sigmaCloud = ccLibAlgorithms::GetDefaultCloudKernelSize(pc);
- //we keep the smallest value
- if (sigmaCloud < spatialSigma)
- {
- spatialSigma = sigmaCloud;
- }
- }
- }
- if (filterParams.spatialSigma > 0)
- {
- spatialSigma = filterParams.spatialSigma;
- }
- if (selectedCloudsWithColors.empty())
- {
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
- ccConsole::Error(QObject::tr("Select at least one cloud or mesh with RGB colors and an active scalar field"));
- else
- ccConsole::Error(QObject::tr("Select at least one cloud or mesh with RGB colors"));
- return false;
- }
- double sigmaSF = -1.0;
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
- {
- CCCoreLib::ScalarField* sf = selectedCloudsWithColors.front().second->getCurrentDisplayedScalarField();
- if (sf)
- {
- ScalarType sfRange = sf->getMax() - sf->getMin();
- sigmaSF = sfRange / 4; // using 1/4 of total range
- }
- if (filterParams.sigmaSF > 0)
- {
- sigmaSF = filterParams.sigmaSF;
- }
- }
- QScopedPointer<ccProgressDialog> pDlg;
- if (!filterParams.commandLine)
- {
- bool ok = false;
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
- {
- ccAskThreeDoubleValuesDlg dlg( QObject::tr("Spatial sigma"),
- QObject::tr("Scalar sigma"),
- QObject::tr("Color threshold"),
- DBL_MIN,
- 1.0e9,
- spatialSigma,
- sigmaSF,
- filterParams.burntOutColorThreshold,
- 8,
- nullptr,
- parent);
- dlg.setWindowTitle(QObject::tr("RGB bilateral filter"));
- dlg.doubleSpinBox1->setStatusTip(QObject::tr("3*sigma = 99.7% attenuation"));
- dlg.doubleSpinBox2->setStatusTip(QObject::tr("Scalar sigma controls how much the filter behaves as a Gaussian Filter\nSigma at +inf uses the whole range of scalars"));
- dlg.doubleSpinBox3->setStatusTip(QObject::tr("For averaging, it will only use colors for which all components are in the range[threshold:255 - threshold]"));
- if (!dlg.exec())
- {
- return false;
- }
- //get values
- spatialSigma = dlg.doubleSpinBox1->value();
- sigmaSF = dlg.doubleSpinBox2->value();
- filterParams.burntOutColorThreshold = dlg.doubleSpinBox3->value();
- }
- else
- {
- ccAskTwoDoubleValuesDlg dlg(QObject::tr("Spatial sigma"),
- QObject::tr("Color threshold"),
- DBL_MIN,
- 1.0e9,
- spatialSigma,
- filterParams.burntOutColorThreshold,
- 8,
- nullptr,
- parent);
- dlg.setWindowTitle(QObject::tr("RGB gaussian/mean/median filter"));
- dlg.doubleSpinBox1->setStatusTip(QObject::tr("3*sigma = 99.7% attenuation"));
- dlg.doubleSpinBox2->setStatusTip(QObject::tr("For averaging, it will only use colors for which all components are in the range [threshold:255-threshold]"));
- if (!dlg.exec())
- {
- return false;
- }
- //get values
- spatialSigma = dlg.doubleSpinBox1->value();
- filterParams.burntOutColorThreshold = dlg.doubleSpinBox2->value();
- }
- }
- if (parent)
- {
- pDlg.reset(new ccProgressDialog(true, parent));
- pDlg->setAutoClose(false);
- }
- for (auto entAndPC : selectedCloudsWithColors )
- {
- ccPointCloud* pc = entAndPC.second;
- assert(pc);
- int sfIdx = 0;
- if ((filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL) || filterParams.applyToSFduringRGB)
- {
- //we set the displayed SF as "OUT" SF
- int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
- Q_ASSERT(outSfIdx >= 0);
- pc->setCurrentOutScalarField(outSfIdx);
- if (filterParams.applyToSFduringRGB)
- {
- CCCoreLib::ScalarField* outSF = pc->getCurrentOutScalarField();
- Q_ASSERT(outSF != nullptr);
- QString sfName;
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
- {
- sfName = QString("%1.bilsmooth(%2,%3)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma).arg(sigmaSF);
- }
- else if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN)
- {
- sfName = QString("%1.smooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
- }
- else if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::MEAN)
- {
- sfName = QString("%1.meansmooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
- }
- else
- {
- sfName = QString("%1.medsmooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
- }
- sfIdx = pc->getScalarFieldIndexByName(sfName.toStdString());
- if (sfIdx < 0)
- sfIdx = pc->addScalarField(sfName.toStdString()); //output SF has same type as input SF
- if (sfIdx >= 0)
- pc->setCurrentInScalarField(sfIdx);
- else
- {
- ccConsole::Error(QObject::tr("Failed to create scalar field for cloud '%1' (not enough memory?)").arg(pc->getName()));
- return false;
- }
- }
- }
- ccOctree::Shared octree = pc->getOctree();
- if (!octree)
- {
- octree = pc->computeOctree(parent ? pDlg.data() : nullptr);
- if (!octree)
- {
- ccConsole::Error(QObject::tr("Couldn't compute octree for cloud '%1'!").arg(pc->getName()));
- continue;
- }
- }
- QElapsedTimer eTimer;
- eTimer.start();
- pc->applyFilterToRGB( static_cast<PointCoordinateType>(spatialSigma),
- static_cast<PointCoordinateType>(sigmaSF),
- filterParams,
- parent ? pDlg.data() : nullptr);
- ccConsole::Print("[RGBFilter] Timing: %3.2f s.", eTimer.elapsed() / 1000.0);
- if (filterParams.applyToSFduringRGB)
- {
- //calc sf min/max for correct display.
- pc->setCurrentDisplayedScalarField(sfIdx);
- pc->showSF(sfIdx >= 0);
- CCCoreLib::ScalarField* sf = pc->getCurrentDisplayedScalarField();
- if (sf)
- sf->computeMinAndMax();
- }
- // automatically hide any SF and show the colors instead
- entAndPC.first->prepareDisplayForRefresh_recursive();
- entAndPC.first->showColors(true);
- entAndPC.first->showSF(false);
- }
- return true;
- }
- //////////
- // Scalar Fields
- bool sfGaussianFilter(const ccHObject::Container& selectedEntities, ccPointCloud::RgbFilterOptions filterParams, QWidget* parent/*=nullptr*/)
- {
- if (selectedEntities.empty())
- return false;
-
- double spatialSigma = filterParams.spatialSigma == -1 ? ccLibAlgorithms::GetDefaultCloudKernelSize(selectedEntities) : filterParams.spatialSigma;
- if (spatialSigma < 0.0)
- {
- ccConsole::Error(QObject::tr("No eligible point cloud in selection!"));
- return false;
- }
-
- //estimate a good value for scalar field sigma, based on the first cloud
- //and its displayed scalar field
- double scalarFieldSigma = -1.0;
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
- {
- ccPointCloud* testPC = ccHObjectCaster::ToPointCloud(selectedEntities.front());
- CCCoreLib::ScalarField* testSF = testPC->getCurrentDisplayedScalarField();
- if (!testSF)
- {
- ccConsole::Error(QObject::tr("No active scalar field"));
- return false;
- }
- if (filterParams.sigmaSF == -1)
- {
- ScalarType range = testSF->getMax() - testSF->getMin();
- scalarFieldSigma = range / 4; // using 1/4 of total range
- }
- else
- {
- scalarFieldSigma = filterParams.sigmaSF;
- }
- }
- QScopedPointer<ccProgressDialog> pDlg;
- if (!filterParams.commandLine)
- {
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
- {
- ccAskTwoDoubleValuesDlg dlg(QObject::tr("Spatial sigma"),
- QObject::tr("Scalar sigma"),
- DBL_MIN,
- 1.0e9,
- spatialSigma,
- scalarFieldSigma,
- 8,
- nullptr,
- parent);
- dlg.setWindowTitle(QObject::tr("SF bilateral filter"));
- dlg.doubleSpinBox1->setStatusTip(QObject::tr("3*sigma = 99.7% attenuation"));
- dlg.doubleSpinBox2->setStatusTip(QObject::tr("Scalar field's sigma controls how much the filter behaves as a Gaussian Filter\nSigma at +inf uses the whole range of scalars"));
- if (!dlg.exec())
- return false;
- //get values
- spatialSigma = dlg.doubleSpinBox1->value();
- scalarFieldSigma = dlg.doubleSpinBox2->value();
- }
- else
- {
- bool ok = false;
- spatialSigma = QInputDialog::getDouble( parent,
- QObject::tr("SF gaussian/mean/median filter"),
- "sigma:",
- spatialSigma,
- DBL_MIN,
- 1.0e9,
- 8,
- &ok);
- if (!ok)
- {
- return false;
- }
- }
- }
- if (parent)
- {
- pDlg.reset(new ccProgressDialog(true, parent));
- pDlg->setAutoClose(false);
- }
- for (ccHObject* ent : selectedEntities)
- {
- bool lockedVertices = false;
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
- if (!pc || lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
-
- //the algorithm will use the currently displayed SF
- CCCoreLib::ScalarField* sf = pc->getCurrentDisplayedScalarField();
- if (sf)
- {
- //we set the displayed SF as "OUT" SF
- int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
- Q_ASSERT(outSfIdx >= 0);
-
- pc->setCurrentOutScalarField(outSfIdx);
- CCCoreLib::ScalarField* outSF = pc->getCurrentOutScalarField();
- Q_ASSERT(outSF != nullptr);
-
- QString sfName;
- if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
- {
- sfName = QString("%1.bilsmooth(%2,%3)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma).arg(scalarFieldSigma);
- }
- else if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN)
- {
- sfName = QString("%1.smooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
- }
- else if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::MEAN)
- {
- sfName = QString("%1.meansmooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
- }
- else
- {
- sfName = QString("%1.medsmooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
- }
- int sfIdx = pc->getScalarFieldIndexByName(sfName.toStdString());
- if (sfIdx < 0)
- sfIdx = pc->addScalarField(sfName.toStdString()); //output SF has same type as input SF
- if (sfIdx >= 0)
- pc->setCurrentInScalarField(sfIdx);
- else
- {
- ccConsole::Error(QObject::tr("Failed to create scalar field for cloud '%1' (not enough memory?)").arg(pc->getName()));
- return false;
- }
-
- ccOctree::Shared octree = pc->getOctree();
- if (!octree)
- {
- octree = pc->computeOctree(parent ? pDlg.data() : nullptr);
- if (!octree)
- {
- ccConsole::Error(QObject::tr("Couldn't compute octree for cloud '%1'!").arg(pc->getName()));
- return false;
- }
- }
-
- QElapsedTimer eTimer;
- eTimer.start();
-
- if (!CCCoreLib::ScalarFieldTools::applyScalarFieldGaussianFilter( static_cast<PointCoordinateType>(spatialSigma),
- pc,
- static_cast<PointCoordinateType>(scalarFieldSigma),
- parent ? pDlg.data() : nullptr,
- octree.data()))
- {
- ccConsole::Warning(QObject::tr("[Bilateral/Gaussian/Mean/Median filter] Failed to apply filter"));
- return false;
- }
-
- ccConsole::Print("SF [Bilateral/Gaussian/Mean/Median filter] Timing: %3.2f s.", eTimer.elapsed() / 1000.0);
- pc->setCurrentDisplayedScalarField(sfIdx);
- pc->showSF(sfIdx >= 0);
- sf = pc->getCurrentDisplayedScalarField();
- if (sf)
- sf->computeMinAndMax();
- pc->prepareDisplayForRefresh_recursive();
- }
- else
- {
- ccConsole::Warning(QObject::tr("Entity [%1] has no active scalar field!").arg(pc->getName()));
- }
- }
-
- return true;
- }
-
- bool sfConvertToRGB(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- //we first ask the user if the SF colors should be mixed with existing colors
- bool mixWithExistingColors = false;
-
- QMessageBox::StandardButton answer = QMessageBox::warning( parent,
- QObject::tr("Scalar Field to RGB"),
- QObject::tr("Mix with existing colors (if any)?"),
- QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
- QMessageBox::Yes );
- if (answer == QMessageBox::Yes)
- mixWithExistingColors = true;
- else if (answer == QMessageBox::Cancel)
- return false;
-
- for (ccHObject* ent : selectedEntities)
- {
- ccGenericPointCloud* cloud = nullptr;
-
- bool lockedVertices = false;
- cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
- if (cloud != nullptr) //TODO
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
- //if there is no displayed SF --> nothing to do!
- if (pc->getCurrentDisplayedScalarField())
- {
- if (pc->convertCurrentScalarFieldToColors(mixWithExistingColors))
- {
- ent->showColors(true);
- ent->showSF(false); //just in case
- }
- }
-
- cloud->prepareDisplayForRefresh_recursive();
- }
- }
-
- return true;
- }
-
- bool sfConvertToRandomRGB(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- static int s_randomColorsNumber = 256;
-
- bool ok = false;
- s_randomColorsNumber = QInputDialog::getInt(parent,
- QObject::tr("Random colors"),
- QObject::tr("Number of random colors (will be regularly sampled over the SF interval):"),
- s_randomColorsNumber,
- 2,
- INT_MAX,
- 16,
- &ok);
- if (!ok)
- return false;
- Q_ASSERT(s_randomColorsNumber > 1);
-
- RGBAColorsTableType* randomColors = new RGBAColorsTableType;
- if (!randomColors->reserveSafe(static_cast<unsigned>(s_randomColorsNumber)))
- {
- ccConsole::Error(QObject::tr("Not enough memory!"));
- return false;
- }
-
- //generate random colors
- for (int i = 0; i < s_randomColorsNumber; ++i)
- {
- ccColor::Rgba col(ccColor::Generator::Random(), ccColor::MAX);
- randomColors->addElement(col);
- }
-
- //apply random colors
- for (ccHObject* ent : selectedEntities)
- {
- ccGenericPointCloud* cloud = nullptr;
-
- bool lockedVertices = false;
- cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
- if (cloud != nullptr) //TODO
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
- ccScalarField* sf = pc->getCurrentDisplayedScalarField();
- //if there is no displayed SF --> nothing to do!
- if (sf && sf->currentSize() >= pc->size())
- {
- if (!pc->resizeTheRGBTable(false))
- {
- ccConsole::Error(QObject::tr("Not enough memory!"));
- break;
- }
- else
- {
- ScalarType minSF = sf->getMin();
- ScalarType maxSF = sf->getMax();
-
- ScalarType step = (maxSF - minSF) / (s_randomColorsNumber - 1);
- if (step == 0)
- step = static_cast<ScalarType>(1.0);
-
- for (unsigned i = 0; i < pc->size(); ++i)
- {
- ScalarType val = sf->getValue(i);
- unsigned colIndex = static_cast<unsigned>((val - minSF) / step);
- if (colIndex == s_randomColorsNumber)
- --colIndex;
-
- pc->setPointColor(i, randomColors->getValue(colIndex));
- }
-
- pc->showColors(true);
- pc->showSF(false); //just in case
- }
- }
-
- cloud->prepareDisplayForRefresh_recursive();
- }
- }
-
- return true;
- }
-
- bool sfRename(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- for (ccHObject* ent : selectedEntities)
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent);
- if (cloud != nullptr) //TODO
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
- ccScalarField* sf = pc->getCurrentDisplayedScalarField();
- //if there is no displayed SF --> nothing to do!
- if (sf == nullptr)
- {
- ccConsole::Warning(QObject::tr("Cloud %1 has no displayed scalar field!").arg(pc->getName()));
- }
- else
- {
- const std::string& sfName = sf->getName();
- bool ok = false;
- QString newName = QInputDialog::getText(parent,
- QObject::tr("SF name"),
- QObject::tr("name:"),
- QLineEdit::Normal,
- QString(!sfName.empty() ? QString::fromStdString(sfName) : QObject::tr("unknown")),
- &ok);
- if (ok)
- sf->setName(newName.toStdString());
- }
- }
- }
-
- return true;
- }
-
- bool sfAddIdField(const ccHObject::Container& selectedEntities, bool storeAsInt)
- {
- for (ccHObject* ent : selectedEntities)
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent);
- if (nullptr != cloud) //TODO
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
-
- int sfIdx = pc->getScalarFieldIndexByName(CC_DEFAULT_ID_SF_NAME);
- if (sfIdx < 0)
- sfIdx = pc->addScalarField(CC_DEFAULT_ID_SF_NAME);
- if (sfIdx < 0)
- {
- ccLog::Warning(QObject::tr("Not enough memory!"));
- return false;
- }
-
- CCCoreLib::ScalarField* sf = pc->getScalarField(sfIdx);
- Q_ASSERT(sf->currentSize() == pc->size());
- for (unsigned j = 0; j < cloud->size(); j++)
- {
- ScalarType idValue = 0;
- if (!storeAsInt)
- {
- idValue = static_cast<ScalarType>(j);
- }
- else
- {
- uint8_t* valuePtr = reinterpret_cast<uint8_t*>(&idValue);
- #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
- valuePtr[0] = static_cast<unsigned char>(j & 0xff);
- valuePtr[1] = static_cast<unsigned char>((j >> 8) & 0xff);
- valuePtr[2] = static_cast<unsigned char>((j >> 16) & 0xff);
- valuePtr[3] = static_cast<unsigned char>((j >> 24) & 0xff);
- #else
- unsigned char *valuePtr = (unsigned char *)(&idValue);
- valuePtr[3] = static_cast<unsigned char>(j & 0xff);
- valuePtr[2] = static_cast<unsigned char>((j >> 8) & 0xff);
- valuePtr[1] = static_cast<unsigned char>((j >> 16) & 0xff);
- valuePtr[0] = static_cast<unsigned char>((j >> 24) & 0xff);
- #endif
- }
- sf->setValue(j, idValue);
- }
-
- sf->computeMinAndMax();
- pc->setCurrentDisplayedScalarField(sfIdx);
- pc->showSF(true);
- pc->prepareDisplayForRefresh();
- }
- }
-
- return true;
- }
-
- bool sfAddConstant(ccPointCloud* cloud, QString sfName, bool integerValue, QWidget* parent/*=nullptr*/)
- {
- if (!cloud)
- {
- assert(false);
- return false;
- }
- if (sfName.isNull())
- {
- ccLog::Error(QT_TR_NOOP("Invalid name"));
- return false;
- }
- if (cloud->getScalarFieldIndexByName(sfName.toStdString()) >= 0)
- {
- ccLog::Error(QT_TR_NOOP("A SF with a similar name already exists!"));
- return false;
- }
- ScalarType sfValue = 0;
- bool ok = false;
- if (integerValue)
- {
- static int s_constantIntSFValue = 0;
- int iValue = QInputDialog::getInt(parent, QT_TR_NOOP("Add classification SF"), QT_TR_NOOP("value"), s_constantIntSFValue, -1000000, 1000000, 1, &ok);
- if (ok)
- {
- s_constantIntSFValue = iValue;
- sfValue = static_cast<ScalarType>(iValue);
- }
- }
- else
- {
- static double s_constantDoubleSFValue = 0.0;
- double dValue = static_cast<ScalarType>(QInputDialog::getDouble(parent, QT_TR_NOOP("Add constant value"), QT_TR_NOOP("value"), s_constantDoubleSFValue, -1.0e9, 1.0e9, 8, &ok));
- if (ok)
- {
- s_constantDoubleSFValue = dValue;
- sfValue = static_cast<ScalarType>(dValue);
- }
- }
- if (!ok)
- {
- // cancelled by the user
- return false;
- }
- int sfIdx = cloud->getScalarFieldIndexByName(sfName.toStdString());
- if (sfIdx < 0)
- sfIdx = cloud->addScalarField(sfName.toStdString());
- if (sfIdx < 0)
- {
- ccLog::Error(QT_TR_NOOP("An error occurred! (see console)"));
- return false;
- }
- CCCoreLib::ScalarField* sf = cloud->getScalarField(sfIdx);
- assert(sf);
- if (!sf)
- {
- assert(false);
- return false;
- }
-
- sf->fill(sfValue);
- sf->computeMinAndMax();
- cloud->setCurrentDisplayedScalarField(sfIdx);
- cloud->showSF(true);
- ccLog::Print(QObject::tr("New scalar field '%1' added to %2 (value = %3)").arg(sfName).arg(cloud->getName()).arg(sfValue));
- return true;
- }
- bool sfSplitCloud(const ccHObject::Container& selectedEntities, ccMainAppInterface* app)
- {
- bool tooManyCloudsQuestionAsked = false;
- for (ccHObject* ent : selectedEntities)
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent);
- if (!cloud)
- {
- continue;
- }
-
- ccScalarField* sf = cloud->getCurrentDisplayedScalarField();
- if (sf == nullptr)
- {
- ccLog::Warning(QString("Cloud %1 has no active scalar field").arg(cloud->getName()));
- return false;
- }
- // count integer values
- size_t N = sf->size();
- std::set<int> classes;
- for (size_t i = 0; i < sf->size(); ++i)
- {
- classes.insert(static_cast<int>(sf->getValue(i)));
- }
- ccLog::Print("[sfSplitCloud] " + QString::number(classes.size()) + " classe(s) found in the current scalar field");
- if (classes.size() == 1)
- {
- ccLog::Warning(QString("[sfSplitCloud] Cloud %1: SF has only one value").arg(cloud->getName()));
- continue;
- }
- if (classes.size() > 30 && app && app->getMainWindow() && !tooManyCloudsQuestionAsked) // only in GUI mode
- {
- // ask the user if creating this many clouds is expected
- if (QMessageBox::No == QMessageBox::question(app->getMainWindow(), "Too many values", QString("Do you confirm that you want to create %1 clouds").arg(classes.size()), QMessageBox::Yes, QMessageBox::No))
- {
- ccLog::Warning("[sfSplitCloud] Process cancelled by the user");
- return false;
- }
- tooManyCloudsQuestionAsked = true;
- }
- ccHObject* destObject = new ccHObject(cloud->getName() + " classes");
- if (cloud->getParent())
- {
- cloud->getParent()->addChild(destObject);
- }
-
- // create as many clouds as the number of classes
- for (int pointClass : classes)
- {
- ccLog::Print("[sfSplitCloud] build cloud corresponding to class #" + QString::number(pointClass));
-
- try
- {
- // create the reference cloud
- CCCoreLib::ReferenceCloud referenceCloud(cloud);
-
- // populate the cloud with the points which have the selected class
- for (unsigned index = 0; index < static_cast<unsigned>(cloud->size()); index++)
- {
- if (static_cast<int>(sf->getValue(index)) == pointClass)
- {
- referenceCloud.addPointIndex(index);
- }
- }
- ccPointCloud* pc = cloud->partialClone(&referenceCloud);
- if (pc)
- {
- pc->setName("class #" + QString::number(pointClass));
- destObject->addChild(pc);
- }
- else
- {
- ccLog::Warning("[sfSplitCloud] Failed to create cloud");
- }
- }
- catch (const std::bad_alloc&)
- {
- delete destObject;
- destObject = nullptr;
- ccLog::Error(QT_TR_NOOP("Not enough memory"));
- return false;
- }
- }
-
- // add to database
- app->addToDB(destObject);
- cloud->setEnabled(false);
- cloud->prepareDisplayForRefresh();
- }
- return true;
- }
- static void SetValueFromSF(PointCoordinateType& value, int fieldIndex, CCCoreLib::ScalarField* sf, unsigned pointIndex, PointCoordinateType defaultValueForNaN)
- {
- if (sf)
- {
- ScalarType s = sf->getValue(pointIndex);
- if (CCCoreLib::ScalarField::ValidValue(s))
- {
- value = s;
- }
- else
- {
- value = defaultValueForNaN;
- }
- }
- else
- {
- switch (fieldIndex)
- {
- case ccSetSFsAsVec3Dialog::SF_INDEX_ZERO:
- value = 0;
- break;
- case ccSetSFsAsVec3Dialog::SF_INDEX_ONE:
- value = static_cast<ScalarType>(1);
- break;
- case ccSetSFsAsVec3Dialog::SF_INDEX_UNCHANGED:
- // nothing to do
- break;
- case ccSetSFsAsVec3Dialog::SF_INDEX_NO:
- default:
- assert(false);
- value = defaultValueForNaN;
- break;
- }
- }
- }
- static PointCoordinateType GetDefaultValueForNaN(PointCoordinateType minSFValue, QWidget* parent)
- {
- bool ok = false;
- double out = QInputDialog::getDouble( parent,
- QObject::tr("SF --> coordinate"),
- QObject::tr("Enter the coordinate equivalent to NaN values:"),
- minSFValue,
- -1.0e9,
- 1.0e9,
- 6,
- &ok);
- if (ok)
- {
- return static_cast<PointCoordinateType>(out);
- }
- else
- {
- ccLog::Warning(QObject::tr("[SetSFAsCoord] By default the coordinate equivalent to NaN values will be the minimum SF value"));
- return minSFValue;
- }
- }
- bool sfSetAsCoord(ccHObject* entity, QWidget* parent/*=nullptr*/)
- {
- if (!entity)
- {
- assert(false);
- return false;
- }
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity);
- if (!cloud || !cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- assert(false);
- ccLog::Warning("[sfSetAsCoord] Expecting a cloud or a mesh");
- return false;
- }
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
- ccSetSFsAsVec3Dialog dlg(pc, "X", "Y", "Z", true, parent);
- dlg.setWindowTitle(QObject::tr("Set SFs as coords"));
- static bool s_firstTime = true;
- static int xIndex = ccSetSFsAsVec3Dialog::SF_INDEX_UNCHANGED;
- static int yIndex = ccSetSFsAsVec3Dialog::SF_INDEX_UNCHANGED;
- static int zIndex = ccSetSFsAsVec3Dialog::SF_INDEX_UNCHANGED;
- // restore the previous parameters
- dlg.setSFIndexes(xIndex, yIndex, zIndex);
- if (!dlg.exec())
- {
- return false;
- }
- dlg.getSFIndexes(xIndex, yIndex, zIndex);
- CCCoreLib::ScalarField* sfX = (xIndex >= 0 ? pc->getScalarField(xIndex) : nullptr);
- CCCoreLib::ScalarField* sfY = (yIndex >= 0 ? pc->getScalarField(yIndex) : nullptr);
- CCCoreLib::ScalarField* sfZ = (zIndex >= 0 ? pc->getScalarField(zIndex) : nullptr);
- std::array<CCCoreLib::ScalarField*, 3> scalarFields{ sfX, sfY, sfZ };
- PointCoordinateType defaultCoordForNaN = std::numeric_limits<PointCoordinateType>::quiet_NaN();
- for (CCCoreLib::ScalarField* sf : scalarFields)
- {
- if (sf)
- {
- if (sf->countValidValues() < sf->size())
- {
- //we have some invalid values, let's ask the user what they should be replaced with
- defaultCoordForNaN = GetDefaultValueForNaN(sf->getMin(), parent);
- break;
- }
- }
- }
- unsigned ptsCount = pc->size();
- for (unsigned i = 0; i < ptsCount; ++i)
- {
- const CCVector3* P = pc->getPoint(i);
- CCVector3 newP = *P;
- SetValueFromSF(newP.x, xIndex, sfX, i, defaultCoordForNaN);
- SetValueFromSF(newP.y, yIndex, sfY, i, defaultCoordForNaN);
- SetValueFromSF(newP.z, zIndex, sfZ, i, defaultCoordForNaN);
- *const_cast<CCVector3*>(P) = newP;
- }
- pc->invalidateBoundingBox();
- if (entity->isKindOf(CC_TYPES::MESH))
- {
- static_cast<ccGenericMesh*>(entity)->refreshBB();
- }
- entity->prepareDisplayForRefresh();
- return true;
- }
- bool sfSetAsCoord(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/)
- {
- if (selectedEntities.size() == 1)
- {
- // shortcut for single entities, with a smarter dialog
- return sfSetAsCoord(selectedEntities.front(), parent);
- }
- ccExportCoordToSFDlg ectsDlg(parent);
- ectsDlg.warningLabel->setVisible(false);
- ectsDlg.setWindowTitle(QObject::tr("Export SF to coordinate(s)"));
-
- if (!ectsDlg.exec())
- {
- return false;
- }
-
- bool importDim[3] { ectsDlg.exportX(), ectsDlg.exportY(), ectsDlg.exportZ() };
- if (!importDim[0] && !importDim[1] && !importDim[2]) //nothing to do?!
- {
- return false;
- }
-
- ScalarType defaultValueForNaN = CCCoreLib::NAN_VALUE;
- //for each selected cloud (or vertices set)
- for (ccHObject* ent : selectedEntities)
- {
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent);
- if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
-
- ccScalarField* sf = pc->getCurrentDisplayedScalarField();
- if (sf != nullptr)
- {
- if (std::isnan(defaultValueForNaN) && (sf->countValidValues() < sf->size()))
- {
- //we have some invalid values, let's ask the user what they should be replaced with
- defaultValueForNaN = GetDefaultValueForNaN(sf->getMin(), parent);
- break;
- }
- pc->setCoordFromSF(importDim, sf, defaultValueForNaN);
- }
- }
- if (ent->isKindOf(CC_TYPES::MESH))
- {
- static_cast<ccGenericMesh*>(ent)->refreshBB();
- }
- }
-
- return true;
- }
- bool exportCoordToSF(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/)
- {
- ccExportCoordToSFDlg ectsDlg(parent);
- if (!ectsDlg.exec())
- {
- return false;
- }
- bool exportDims[3] { ectsDlg.exportX(),
- ectsDlg.exportY(),
- ectsDlg.exportZ() };
- if (!exportDims[0] && !exportDims[1] && !exportDims[2]) //nothing to do?!
- {
- return false;
- }
-
- //for each selected cloud (or vertices set)
- for (ccHObject* entity : selectedEntities)
- {
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(entity);
- if (pc == nullptr)
- {
- // TODO do something with error?
- continue;
- }
- if (!pc->exportCoordToSF(exportDims))
- {
- ccLog::Error(QObject::tr("The process failed!"));
- return true; //true because we want the UI to be updated anyway
- }
- if (entity != pc)
- {
- entity->showSF(true); //for meshes
- }
- entity->prepareDisplayForRefresh_recursive();
- }
-
- return true;
- }
- bool setSFsAsNormal(ccHObject* entity, QWidget* parent/*=nullptr*/)
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
- if (cloud == nullptr)
- {
- // unhandled entity
- return false;
- }
- const bool cloudHadNormals = cloud->hasNormals();
- ccSetSFsAsVec3Dialog dlg(cloud, "Nx", "Ny", "Nz", cloudHadNormals, parent);
- dlg.setWindowTitle(QObject::tr("Set SFs as normals"));
- static bool s_firstTime = true;
- static int nxIndex = ccSetSFsAsVec3Dialog::SF_INDEX_ZERO;
- static int nyIndex = ccSetSFsAsVec3Dialog::SF_INDEX_ZERO;
- static int nzIndex = ccSetSFsAsVec3Dialog::SF_INDEX_ZERO;
- if (s_firstTime)
- {
- s_firstTime = false;
- }
- else
- {
- // restore the previous parameters
- dlg.setSFIndexes(nxIndex, nyIndex, nzIndex);
- }
- if (!dlg.exec())
- {
- return false;
- }
- dlg.getSFIndexes(nxIndex, nyIndex, nzIndex);
- if (!cloud->resizeTheNormsTable())
- {
- ccLog::Error("Not enough memory");
- }
- CCCoreLib::ScalarField* sfX = (nxIndex >= 0 ? cloud->getScalarField(nxIndex) : nullptr);
- CCCoreLib::ScalarField* sfY = (nyIndex >= 0 ? cloud->getScalarField(nyIndex) : nullptr);
- CCCoreLib::ScalarField* sfZ = (nzIndex >= 0 ? cloud->getScalarField(nzIndex) : nullptr);
- for (unsigned i = 0; i < cloud->size(); ++i)
- {
- CCVector3f N(0, 0, 0);
- if (cloudHadNormals)
- {
- N = cloud->getPointNormal(i);
- }
- SetValueFromSF(N.x, nxIndex, sfX, i, 0);
- SetValueFromSF(N.y, nyIndex, sfY, i, 0);
- SetValueFromSF(N.z, nzIndex, sfZ, i, 0);
- N.normalize();
- cloud->setPointNormal(i, N);
- }
- cloud->showNormals(true);
- cloud->prepareDisplayForRefresh();
- if (entity != cloud)
- {
- entity->showNormals(true);
- entity->prepareDisplayForRefresh();
- if (entity->isKindOf(CC_TYPES::MESH))
- {
- static_cast<ccGenericMesh*>(entity)->showTriNorms(false); // hide the per-triangle normals (if any)
- }
- }
- return true;
- }
- bool exportNormalToSF(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/, bool* exportDimensions/*=nullptr*/)
- {
- bool exportDims[3] { false, false, false };
- if (exportDimensions)
- {
- exportDims[0] = exportDimensions[0];
- exportDims[1] = exportDimensions[1];
- exportDims[2] = exportDimensions[2];
- }
- else
- {
- //ask the user
- ccExportCoordToSFDlg ectsDlg(parent);
- ectsDlg.setWindowTitle(QObject::tr("Export normals to SF(s)"));
- if (!ectsDlg.exec())
- {
- return false;
- }
- exportDims[0] = ectsDlg.exportX();
- exportDims[1] = ectsDlg.exportY();
- exportDims[2] = ectsDlg.exportZ();
- }
- if (!exportDims[0] && !exportDims[1] && !exportDims[2]) //nothing to do?!
- {
- return false;
- }
- //for each selected cloud (or vertices set)
- for (ccHObject* entity : selectedEntities)
- {
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(entity);
- if (pc == nullptr)
- {
- // TODO do something with error?
- continue;
- }
- if (!pc->hasNormals())
- {
- ccLog::Warning(QObject::tr("Cloud '%1' has no normals").arg(pc->getName()));
- continue;
- }
- if (!pc->exportNormalToSF(exportDims))
- {
- ccLog::Error(QObject::tr("The process failed!"));
- return true; //true because we want the UI to be updated anyway
- }
- if (entity != pc)
- {
- entity->showSF(true); //for meshes
- }
- entity->prepareDisplayForRefresh_recursive();
- }
- return true;
- }
- bool sfArithmetic(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- Q_ASSERT(!selectedEntities.empty());
-
- ccHObject* entity = selectedEntities[0];
- bool lockedVertices;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity, &lockedVertices);
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(entity->getName(), true);
- return false;
- }
- if (cloud == nullptr)
- {
- return false;
- }
-
- ccScalarFieldArithmeticsDlg sfaDlg(cloud, parent);
-
- if (!sfaDlg.exec())
- {
- return false;
- }
-
- if (!sfaDlg.apply(cloud))
- {
- ccConsole::Error(QObject::tr("An error occurred (see Console for more details)"));
- }
-
- cloud->showSF(true);
- cloud->prepareDisplayForRefresh_recursive();
-
- return true;
- }
- bool sfFromColor(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- ccScalarFieldFromColorDlg dialog(parent);
- if (!dialog.exec())
- return false;
- const bool exportR = dialog.getRStatus();
- const bool exportG = dialog.getGStatus();
- const bool exportB = dialog.getBStatus();
- const bool exportAlpha = dialog.getAlphaStatus();
- const bool exportComposite = dialog.getCompositeStatus();
- return sfFromColor(selectedEntities, exportR, exportG, exportB, exportAlpha, exportComposite);
- }
- bool sfFromColor(const ccHObject::Container &selectedEntities, bool exportR, bool exportG, bool exportB, bool exportAlpha, bool exportComposite)
- {
- //candidates
- std::unordered_set<ccPointCloud*> clouds;
-
- for (ccHObject* ent : selectedEntities)
- {
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent);
- if (cloud && ent->hasColors()) //only for clouds (or vertices)
- clouds.insert( cloud );
- }
-
- if (clouds.empty())
- return false;
-
-
- for (const auto cloud : clouds)
- {
- std::vector<ccScalarField*> fields(5, nullptr);
- fields[0] = (exportR ? new ccScalarField(GetFirstAvailableSFName(cloud, "R").toStdString()) : nullptr);
- fields[1] = (exportG ? new ccScalarField(GetFirstAvailableSFName(cloud, "G").toStdString()) : nullptr);
- fields[2] = (exportB ? new ccScalarField(GetFirstAvailableSFName(cloud, "B").toStdString()) : nullptr);
- fields[3] = (exportAlpha ? new ccScalarField(GetFirstAvailableSFName(cloud, "Alpha").toStdString()) : nullptr);
- fields[4] = (exportComposite ? new ccScalarField(GetFirstAvailableSFName(cloud, "Composite").toStdString()) : nullptr);
-
- //try to instantiate memory for each field
- unsigned count = cloud->size();
- for (ccScalarField*& sf : fields)
- {
- if (sf && !sf->reserveSafe(count))
- {
- ccLog::Warning(QObject::tr("[SfFromColor] Not enough memory to instantiate SF '%1' on cloud '%2'").arg(QString::fromStdString(sf->getName()), cloud->getName()));
- sf->release();
- sf = nullptr;
- }
- }
-
- //export points
- for (unsigned j = 0; j < cloud->size(); ++j)
- {
- const ccColor::Rgba& col = cloud->getPointColor(j);
-
- if (fields[0])
- fields[0]->addElement(col.r);
- if (fields[1])
- fields[1]->addElement(col.g);
- if (fields[2])
- fields[2]->addElement(col.b);
- if (fields[3])
- fields[3]->addElement(col.a);
- if (fields[4])
- fields[4]->addElement(static_cast<ScalarType>(col.r + col.g + col.b) / 3);
- }
-
- QString fieldsStr;
-
- for (ccScalarField*& sf : fields)
- {
- if (sf == nullptr)
- continue;
-
- sf->computeMinAndMax();
-
- int sfIdx = cloud->getScalarFieldIndexByName(sf->getName());
- if (sfIdx >= 0)
- cloud->deleteScalarField(sfIdx);
- sfIdx = cloud->addScalarField(sf);
- Q_ASSERT(sfIdx >= 0);
-
- if (sfIdx >= 0)
- {
- cloud->setCurrentDisplayedScalarField(sfIdx);
- cloud->showSF(true);
- cloud->prepareDisplayForRefresh();
-
- //mesh vertices?
- if (cloud->getParent() && cloud->getParent()->isKindOf(CC_TYPES::MESH))
- {
- cloud->getParent()->showSF(true);
- cloud->getParent()->prepareDisplayForRefresh();
- }
-
- if (!fieldsStr.isEmpty())
- {
- fieldsStr.append(", ");
- }
- fieldsStr.append(QString::fromStdString(sf->getName()));
- }
- else
- {
- ccConsole::Warning(QObject::tr("[SfFromColor] Failed to add scalar field '%1' to cloud '%2'?!").arg(QString::fromStdString(sf->getName()), cloud->getName()));
- sf->release();
- sf = nullptr;
- }
- }
-
- if (!fieldsStr.isEmpty())
- ccLog::Print(QObject::tr("[SfFromColor] New scalar fields (%1) added to '%2'").arg(fieldsStr, cloud->getName()));
- }
- return true;
- }
-
- bool processMeshSF(const ccHObject::Container &selectedEntities, ccMesh::MESH_SCALAR_FIELD_PROCESS process, QWidget* parent/*=nullptr*/)
- {
- for (ccHObject* ent : selectedEntities)
- {
- if (ent->isKindOf(CC_TYPES::MESH) || ent->isKindOf(CC_TYPES::PRIMITIVE)) //TODO
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
- if (mesh == nullptr)
- continue;
-
- ccGenericPointCloud* cloud = mesh->getAssociatedCloud();
- if (cloud == nullptr)
- continue;
-
- if (cloud->isA(CC_TYPES::POINT_CLOUD)) //TODO
- {
- ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
-
- //on active le champ scalaire actuellement affiche
- int sfIdx = pc->getCurrentDisplayedScalarFieldIndex();
- if (sfIdx >= 0)
- {
- pc->setCurrentScalarField(sfIdx);
- mesh->processScalarField(process);
- pc->getCurrentInScalarField()->computeMinAndMax();
- mesh->prepareDisplayForRefresh_recursive();
- }
- else
- {
- ccConsole::Warning(QObject::tr("Mesh [%1] vertices have no activated scalar field!").arg(mesh->getName()));
- }
- }
- }
- }
-
- return true;
- }
-
- //////////
- // Normals
-
- bool computeNormals(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- if (selectedEntities.empty())
- {
- ccConsole::Error(QObject::tr("Select at least one point cloud"));
- return false;
- }
-
- static const QString s_NormalScaleKey("Normal scale");
- //look for clouds and meshes
- std::vector<ccPointCloud*> clouds;
- bool withScanGrid = false;
- bool withSensor = false;
- std::vector<ccMesh*> meshes;
- PointCoordinateType defaultRadius = 0;
-
- try
- {
- for (const auto entity : selectedEntities)
- {
- if (entity->isA(CC_TYPES::POINT_CLOUD))
- {
- ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
- clouds.push_back(cloud);
-
- if (!withScanGrid)
- {
- withScanGrid = (cloud->gridCount() > 0);
- }
- if (!withSensor)
- {
- for (unsigned i = 0; i < cloud->getChildrenNumber(); ++i)
- {
- if (cloud->hasSensor())
- {
- withSensor = true;
- break; //no need to look anyfurther
- }
- }
- }
- //does the cloud have a former radius value saved as meta-data?
- if (cloud->hasMetaData(s_NormalScaleKey))
- {
- bool ok = false;
- double formerRadius = cloud->getMetaData(s_NormalScaleKey).toDouble(&ok);
- if (ok)
- {
- //remember the largest radius
- defaultRadius = std::max(defaultRadius, static_cast<PointCoordinateType>(formerRadius));
- }
- else
- {
- assert(false);
- }
- }
- if (defaultRadius == 0.0)
- {
- //default radius
- defaultRadius = ccOctree::GuessNaiveRadius(cloud);
- }
- }
- else if (entity->isKindOf(CC_TYPES::MESH))
- {
- if (entity->isA(CC_TYPES::MESH))
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(entity);
- meshes.push_back(mesh);
- }
- else
- {
- ccConsole::Error(QObject::tr("Can't compute normals on sub-meshes! Select the parent mesh instead"));
- return false;
- }
- }
- }
- }
- catch (const std::bad_alloc&)
- {
- ccConsole::Error(QObject::tr("Not enough memory!"));
- return false;
- }
-
- //compute normals for each selected cloud
- if (!clouds.empty())
- {
- static CCCoreLib::LOCAL_MODEL_TYPES s_lastModelType = CCCoreLib::LS;
- static ccNormalVectors::Orientation s_lastNormalOrientation = ccNormalVectors::UNDEFINED;
- static int s_lastMSTNeighborCount = 6;
- static double s_lastMinGridAngle_deg = 1.0;
- static bool s_orientNormals = true;
-
- ccNormalComputationDlg ncDlg(withScanGrid, withSensor, parent);
- ncDlg.setLocalModel(s_lastModelType);
- ncDlg.setRadius(defaultRadius);
- ncDlg.setPreferredOrientation(s_lastNormalOrientation);
- ncDlg.setOrientNormals(s_orientNormals);
- ncDlg.setMSTNeighborCount(s_lastMSTNeighborCount);
- ncDlg.setMinGridAngle_deg(s_lastMinGridAngle_deg);
- if (clouds.size() == 1)
- {
- ncDlg.setCloud(clouds.front());
- }
-
- if (!ncDlg.exec())
- {
- return false;
- }
-
- //normals computation
- CCCoreLib::LOCAL_MODEL_TYPES model = s_lastModelType = ncDlg.getLocalModel();
- bool useGridStructure = withScanGrid && ncDlg.useScanGridsForComputation();
- defaultRadius = ncDlg.getRadius();
- double minGridAngle_deg = s_lastMinGridAngle_deg = ncDlg.getMinGridAngle_deg();
-
- //normals orientation
- s_orientNormals = ncDlg.orientNormals();
- bool orientNormalsWithGrids = withScanGrid && ncDlg.useScanGridsForOrientation();
- bool orientNormalsWithSensors = withSensor && ncDlg.useSensorsForOrientation();
- ccNormalVectors::Orientation preferredOrientation = s_lastNormalOrientation = ncDlg.getPreferredOrientation();
- bool orientNormalsMST = ncDlg.useMSTOrientation();
- int mstNeighbors = s_lastMSTNeighborCount = ncDlg.getMSTNeighborCount();
-
- ccProgressDialog pDlg(true, parent);
- pDlg.setAutoClose(false);
- size_t errors = 0;
-
- for (auto cloud : clouds)
- {
- Q_ASSERT(cloud != nullptr);
-
- bool result = false;
- bool normalsAlreadyOriented = false;
-
- if (useGridStructure && cloud->gridCount())
- {
- #if 0
- ccPointCloud* newCloud = new ccPointCloud("temp");
- newCloud->reserve(cloud->size());
- for (size_t gi=0; gi<cloud->gridCount(); ++gi)
- {
- const ccPointCloud::Grid::Shared& scanGrid = cloud->grid(gi);
- if (scanGrid && scanGrid->indexes.empty())
- {
- //empty grid, we skip it
- continue;
- }
- ccGLMatrixd toSensor = scanGrid->sensorPosition.inverse();
-
- const int* _indexGrid = scanGrid->indexes.data();
- for (int j = 0; j < static_cast<int>(scanGrid->h); ++j)
- {
- for (int i = 0; i < static_cast<int>(scanGrid->w); ++i, ++_indexGrid)
- {
- if (*_indexGrid >= 0)
- {
- unsigned pointIndex = static_cast<unsigned>(*_indexGrid);
- const CCVector3* P = cloud->getPoint(pointIndex);
- CCVector3 Q = toSensor * (*P);
- newCloud->addPoint(Q);
- }
- }
- }
-
- addToDB(newCloud);
- }
- #endif
-
- //compute normals with the associated scan grid(s)
- normalsAlreadyOriented = true;
- result = cloud->computeNormalsWithGrids(minGridAngle_deg, &pDlg);
- }
- else
- {
- //compute normals with the octree
- normalsAlreadyOriented = s_orientNormals && (preferredOrientation != ccNormalVectors::UNDEFINED);
- result = cloud->computeNormalsWithOctree(model, s_orientNormals ? preferredOrientation : ccNormalVectors::UNDEFINED, defaultRadius, &pDlg);
- if (result)
- {
- //save the normal computation radius as meta-data
- cloud->setMetaData(s_NormalScaleKey, defaultRadius);
- }
- }
-
- //do we need to orient the normals? (this may have been already done if 'orientNormalsForThisCloud' is true)
- if (result && s_orientNormals && !normalsAlreadyOriented)
- {
- if (cloud->gridCount() && orientNormalsWithGrids)
- {
- //we can still use the grid structure(s) to orient the normals!
- result = cloud->orientNormalsWithGrids();
- }
- else if (cloud->hasSensor() && orientNormalsWithSensors)
- {
- result = false;
- // RJ: TODO: the issue here is that a cloud can have multiple sensors.
- // As the association to sensor is not explicit in CC, given a cloud
- // some points can belong to one sensor and some others can belongs to others sensors.
- // so it's why here grid orientation has precedence over sensor orientation because in this
- // case association is more explicit.
- // Here we take the first valid viewpoint for now even if it's not a good one...
- for (unsigned i = 0; i < cloud->getChildrenNumber(); ++i)
- {
- ccHObject* child = cloud->getChild(i);
- if (child && child->isKindOf(CC_TYPES::SENSOR))
- {
- ccSensor* sensor = ccHObjectCaster::ToSensor(child);
- CCVector3 sensorPosition;
- if (sensor->getActiveAbsoluteCenter(sensorPosition))
- {
- result = cloud->orientNormalsTowardViewPoint(sensorPosition, &pDlg);
- break;
- }
- }
- }
- }
- else if (orientNormalsMST)
- {
- //use Minimum Spanning Tree to resolve normals direction
- result = cloud->orientNormalsWithMST(mstNeighbors, &pDlg);
- }
- }
-
- if (!result)
- {
- ++errors;
- }
-
- cloud->prepareDisplayForRefresh();
- }
-
- if (errors != 0)
- {
- if (errors < clouds.size())
- ccConsole::Error(QObject::tr("Failed to compute or orient the normals on some clouds! (see console)"));
- else
- ccConsole::Error(QObject::tr("Failed to compute or orient the normals! (see console)"));
- }
- }
-
- //compute normals for each selected mesh
- if (!meshes.empty())
- {
- QMessageBox question( QMessageBox::Question,
- QObject::tr("Mesh normals"),
- QObject::tr("Compute per-vertex normals (smooth) or per-triangle (faceted)?"),
- QMessageBox::NoButton,
- parent);
-
- QPushButton* perVertexButton = question.addButton(QObject::tr("Per-vertex"), QMessageBox::YesRole);
- QPushButton* perTriangleButton = question.addButton(QObject::tr("Per-triangle"), QMessageBox::NoRole);
-
- question.exec();
-
- bool computePerVertexNormals = (question.clickedButton() == perVertexButton);
-
- for (auto mesh : meshes)
- {
- Q_ASSERT(mesh != nullptr);
-
- //we remove temporarily the mesh as its normals may be removed (and they can be a child object)
- ccMainAppInterface* instance = dynamic_cast<ccMainAppInterface*>(parent);
- ccMainAppInterface::ccHObjectContext objContext;
- if (instance)
- objContext = instance->removeObjectTemporarilyFromDBTree(mesh);
- mesh->clearTriNormals();
- mesh->showNormals(false);
- bool result = mesh->computeNormals(computePerVertexNormals);
- if (instance)
- instance->putObjectBackIntoDBTree(mesh, objContext);
- if (!result)
- {
- ccConsole::Error(QObject::tr("Failed to compute normals on mesh '%1'").arg(mesh->getName()));
- continue;
- }
- mesh->prepareDisplayForRefresh_recursive();
- }
- }
-
- return true;
- }
-
- bool invertNormals(const ccHObject::Container &selectedEntities)
- {
- for (ccHObject* ent : selectedEntities)
- {
- // is it a mesh?
- ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
- if (mesh && mesh->hasNormals())
- {
- mesh->invertNormals();
- mesh->showNormals(true);
- mesh->prepareDisplayForRefresh_recursive();
- continue;
- }
- // is it a cloud?
- bool lockedVertices;
- ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
- if (cloud && cloud->hasNormals())
- {
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
-
- cloud->invertNormals();
- cloud->showNormals(true);
- cloud->prepareDisplayForRefresh_recursive();
- }
- }
-
- return true;
- }
-
- bool orientNormalsFM(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- if (selectedEntities.empty())
- {
- ccConsole::Error(QObject::tr("Select at least one point cloud"));
- return false;
- }
-
- bool ok = false;
- const int s_defaultLevel = 6;
- int value = QInputDialog::getInt( parent,
- QObject::tr("Orient normals (FM)"),
- QObject::tr("Octree level"),
- s_defaultLevel,
- 1, CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL,
- 1,
- &ok);
- if (!ok)
- return false;
-
- Q_ASSERT(value >= 0 && value <= 255);
-
- unsigned char level = static_cast<unsigned char>(value);
-
- ccProgressDialog pDlg(false, parent);
- pDlg.setAutoClose(false);
- size_t errors = 0;
- for (ccHObject* entity : selectedEntities)
- {
- if (!entity->isA(CC_TYPES::POINT_CLOUD))
- continue;
-
- ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
-
- if (!cloud->hasNormals())
- {
- ccConsole::Warning(QObject::tr("Cloud '%1' has no normals!").arg(cloud->getName()));
- continue;
- }
-
- //orient normals with Fast Marching
- if (cloud->orientNormalsWithFM(level, &pDlg))
- {
- cloud->prepareDisplayForRefresh();
- }
- else
- {
- ++errors;
- }
- }
-
- if (errors)
- {
- ccConsole::Error(QObject::tr("Process failed (check console)"));
- }
- else
- {
- ccLog::Warning(QObject::tr("Normals have been oriented: you may still have to globally invert the cloud normals however (Edit > Normals > Invert)."));
- }
-
- return true;
- }
-
- bool orientNormalsMST(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- if (selectedEntities.empty())
- {
- ccConsole::Error(QObject::tr("Select at least one point cloud"));
- return false;
- }
-
- bool ok = false;
- static unsigned s_defaultKNN = 6;
- unsigned kNN = static_cast<unsigned>(QInputDialog::getInt( parent,
- QObject::tr("Neighborhood size"),
- QObject::tr("Neighbors"),
- s_defaultKNN ,
- 1, 1000,
- 1,
- &ok));
- if (!ok)
- return false;
-
- s_defaultKNN = kNN;
-
- ccProgressDialog pDlg(true, parent);
- pDlg.setAutoClose(false);
-
- size_t errors = 0;
- for (ccHObject* entity : selectedEntities)
- {
- if (!entity->isA(CC_TYPES::POINT_CLOUD))
- continue;
-
- ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
-
- if (!cloud->hasNormals())
- {
- ccConsole::Warning(QObject::tr("Cloud '%1' has no normals!").arg(cloud->getName()));
- continue;
- }
-
- //use Minimum Spanning Tree to resolve normals direction
- if (cloud->orientNormalsWithMST(kNN, &pDlg))
- {
- cloud->prepareDisplayForRefresh();
- }
- else
- {
- ccConsole::Warning(QObject::tr("Process failed on cloud '%1'").arg(cloud->getName()));
- ++errors;
- }
- }
-
- if (errors)
- {
- ccConsole::Error(QObject::tr("Process failed (check console)"));
- }
- else
- {
- ccLog::Warning(QObject::tr("Normals have been oriented: you may still have to globally invert the cloud normals however (Edit > Normals > Invert)."));
- }
-
- return true;
- }
-
- bool convertNormalsTo(const ccHObject::Container &selectedEntities, NORMAL_CONVERSION_DEST dest)
- {
- size_t errorCount = 0;
- size_t successCount = 0;
- size_t selNum = selectedEntities.size();
- for (size_t i = 0; i < selNum; ++i)
- {
- ccHObject* ent = selectedEntities[i];
- bool lockedVertices = false;
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selNum == 1);
- continue;
- }
-
- if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD)) // TODO
- {
- ccPointCloud* ccCloud = static_cast<ccPointCloud*>(cloud);
- if (ccCloud->hasNormals())
- {
- bool success = true;
- switch(dest)
- {
- case NORMAL_CONVERSION_DEST::HSV_COLORS:
- {
- success = ccCloud->convertNormalToRGB();
- if (success)
- {
- ccCloud->showSF(false);
- ccCloud->showNormals(false);
- ccCloud->showColors(true);
- ++successCount;
- }
- }
- break;
-
- case NORMAL_CONVERSION_DEST::DIP_DIR_SFS:
- {
- //get/create 'dip' scalar field
- int dipSFIndex = ccCloud->getScalarFieldIndexByName(CC_DEFAULT_DIP_SF_NAME);
- if (dipSFIndex < 0)
- dipSFIndex = ccCloud->addScalarField(CC_DEFAULT_DIP_SF_NAME);
- if (dipSFIndex < 0)
- {
- ccLog::Warning(QObject::tr("[ccEntityAction::convertNormalsTo] Not enough memory!"));
- success = false;
- break;
- }
-
- //get/create 'dip direction' scalar field
- int dipDirSFIndex = ccCloud->getScalarFieldIndexByName(CC_DEFAULT_DIP_DIR_SF_NAME);
- if (dipDirSFIndex < 0)
- dipDirSFIndex = ccCloud->addScalarField(CC_DEFAULT_DIP_DIR_SF_NAME);
- if (dipDirSFIndex < 0)
- {
- ccCloud->deleteScalarField(dipSFIndex);
- ccLog::Warning(QObject::tr("[ccEntityAction::convertNormalsTo] Not enough memory!"));
- success = false;
- break;
- }
-
- ccScalarField* dipSF = static_cast<ccScalarField*>(ccCloud->getScalarField(dipSFIndex));
- ccScalarField* dipDirSF = static_cast<ccScalarField*>(ccCloud->getScalarField(dipDirSFIndex));
- Q_ASSERT(dipSF && dipDirSF);
-
- success = ccCloud->convertNormalToDipDirSFs(dipSF, dipDirSF);
-
- if (success)
- {
- //apply default 360 degrees color scale!
- ccColorScale::Shared dipScale = ccColorScalesManager::GetDefaultScale(ccColorScalesManager::DIP_BRYW);
- ccColorScale::Shared dipDirScale = ccColorScalesManager::GetDefaultScale(ccColorScalesManager::DIP_DIR_REPEAT);
- dipSF->setColorScale(dipScale);
- dipDirSF->setColorScale(dipDirScale);
- ccCloud->setCurrentDisplayedScalarField(dipDirSFIndex); //dip dir. seems more interesting by default
- ccCloud->showSF(true);
- ++successCount;
- }
- else
- {
- ccCloud->deleteScalarField(dipSFIndex);
- ccCloud->deleteScalarField(dipDirSFIndex);
- }
- }
- break;
-
- default:
- Q_ASSERT(false);
- ccLog::Warning(QObject::tr("[ccEntityAction::convertNormalsTo] Internal error: unhandled destination!"));
- success = false;
- i = selNum; //no need to process the selected entities anymore!
- break;
- }
-
- if (success)
- {
- ccCloud->prepareDisplayForRefresh_recursive();
- }
- else
- {
- ++errorCount;
- }
- }
- }
- }
-
- //errors should have been sent to console as warnings
- if (errorCount)
- {
- ccConsole::Error(QObject::tr("Error(s) occurred! (see console)"));
- }
-
- return (successCount != 0);
- }
-
- //////////
- // Octree
- bool computeOctree(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- ccBBox bbox;
- std::unordered_set<ccGenericPointCloud*> clouds;
- PointCoordinateType maxBoxSize = -1;
- for (ccHObject* ent : selectedEntities)
- {
- //specific test for locked vertices
- bool lockedVertices = false;
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
-
- if (cloud == nullptr)
- {
- continue;
- }
-
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
- clouds.insert(cloud);
- //we look for the biggest box so as to define the "minimum cell size"
- const ccBBox thisBBox = cloud->getOwnBB();
- if (thisBBox.isValid())
- {
- CCVector3 dd = thisBBox.maxCorner() - thisBBox.minCorner();
- PointCoordinateType maxd = std::max(dd.x, std::max(dd.y, dd.z));
- if (maxBoxSize < 0.0 || maxd > maxBoxSize)
- maxBoxSize = maxd;
- }
- bbox += thisBBox;
- }
- if (clouds.empty() || maxBoxSize < 0.0)
- {
- ccLog::Warning(QObject::tr("[DoActionComputeOctree] No eligible entities in selection!"));
- return false;
- }
- //min(cellSize) = max(dim)/2^N with N = max subidivision level
- const double minCellSize = static_cast<double>(maxBoxSize) / (1 << ccOctree::MAX_OCTREE_LEVEL);
- ccComputeOctreeDlg coDlg(bbox, minCellSize, parent);
- if (!coDlg.exec())
- return false;
- ccProgressDialog pDlg(true, parent);
- pDlg.setAutoClose(false);
- //if we must use a custom bounding box, we update 'bbox'
- if (coDlg.getMode() == ccComputeOctreeDlg::CUSTOM_BBOX)
- bbox = coDlg.getCustomBBox();
- for (const auto cloud : clouds)
- {
- //we temporarily detach entity, as it may undergo
- //'severe' modifications (octree deletion, etc.) --> see ccPointCloud::computeOctree
- ccMainAppInterface* instance = dynamic_cast<ccMainAppInterface*>(parent);
- ccMainAppInterface::ccHObjectContext objContext;
- if (instance)
- objContext = instance->removeObjectTemporarilyFromDBTree(cloud);
- //computation
- QElapsedTimer eTimer;
- eTimer.start();
- ccOctree::Shared octree(nullptr);
- switch (coDlg.getMode())
- {
- case ccComputeOctreeDlg::DEFAULT:
- octree = cloud->computeOctree(&pDlg);
- break;
- case ccComputeOctreeDlg::MIN_CELL_SIZE:
- case ccComputeOctreeDlg::CUSTOM_BBOX:
- {
- //for a cell-size based custom box, we must update it for each cloud!
- if (coDlg.getMode() == ccComputeOctreeDlg::MIN_CELL_SIZE)
- {
- double cellSize = coDlg.getMinCellSize();
- PointCoordinateType halfBoxWidth = static_cast<PointCoordinateType>(cellSize * (1 << ccOctree::MAX_OCTREE_LEVEL) / 2.0);
- ccBBox bbBox = cloud->getOwnBB();
- CCVector3 C = bbBox.getCenter();
- bbox = ccBBox( C - CCVector3(halfBoxWidth, halfBoxWidth, halfBoxWidth),
- C + CCVector3(halfBoxWidth, halfBoxWidth, halfBoxWidth),
- bbBox.isValid() );
- }
- cloud->deleteOctree();
- octree = ccOctree::Shared(new ccOctree(cloud));
- if (octree->build(bbox.minCorner(), bbox.maxCorner(), nullptr, nullptr, &pDlg) > 0)
- {
- ccOctreeProxy* proxy = new ccOctreeProxy(octree);
- proxy->setDisplay(cloud->getDisplay());
- cloud->addChild(proxy);
- }
- else
- {
- octree.clear();
- }
- }
- break;
- default:
- Q_ASSERT(false);
- return false;
- }
- qint64 elapsedTime_ms = eTimer.elapsed();
- //put object back in tree
- if (instance)
- instance->putObjectBackIntoDBTree(cloud, objContext);
- if (octree)
- {
- ccConsole::Print("[doActionComputeOctree] Timing: %2.3f s", static_cast<double>(elapsedTime_ms) / 1000.0);
- cloud->setEnabled(true); //for mesh vertices!
- ccOctreeProxy* proxy = cloud->getOctreeProxy();
- assert(proxy);
- proxy->setVisible(true);
- proxy->prepareDisplayForRefresh();
- }
- else
- {
- ccConsole::Warning(QObject::tr("Octree computation on cloud '%1' failed!").arg(cloud->getName()));
- }
- }
- return true;
- }
-
- //////////
- // Properties
-
- bool clearProperty(ccHObject::Container selectedEntities, CLEAR_PROPERTY property, QWidget* parent/*=nullptr*/)
- {
- for (ccHObject* ent : selectedEntities)
- {
- //specific case: clear normals on a mesh
- if (property == CLEAR_PROPERTY::NORMALS && ( ent->isA(CC_TYPES::MESH) /*|| ent->isKindOf(CC_TYPES::PRIMITIVE)*/ )) //TODO
- {
- ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
- if (!mesh)
- {
- assert(false);
- continue;
- }
- if (mesh->hasTriNormals())
- {
- mesh->showNormals(false);
-
- ccMainAppInterface* instance = dynamic_cast<ccMainAppInterface*>(parent);
- ccMainAppInterface::ccHObjectContext objContext;
- if (instance)
- objContext = instance->removeObjectTemporarilyFromDBTree(mesh);
- mesh->clearTriNormals();
- if (instance)
- instance->putObjectBackIntoDBTree(mesh,objContext);
-
- ent->prepareDisplayForRefresh();
- continue;
- }
- else if (mesh->hasNormals()) //per-vertex normals?
- {
- if (mesh->getParent()
- && (mesh->getParent()->isA(CC_TYPES::MESH)/*|| mesh->getParent()->isKindOf(CC_TYPES::PRIMITIVE)*/) //TODO
- && ccHObjectCaster::ToMesh(mesh->getParent())->getAssociatedCloud() == mesh->getAssociatedCloud())
- {
- ccLog::Warning(QObject::tr("[DoActionClearNormals] Can't remove normals per-vertex on a sub mesh!"));
- }
- else //mesh is alone, we can freely remove normals
- {
- if (mesh->getAssociatedCloud() && mesh->getAssociatedCloud()->isA(CC_TYPES::POINT_CLOUD))
- {
- mesh->showNormals(false);
- static_cast<ccPointCloud*>(mesh->getAssociatedCloud())->unallocateNorms();
- mesh->prepareDisplayForRefresh();
- continue;
- }
- }
- }
- }
-
- bool lockedVertices;
- ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent,&lockedVertices);
- if (lockedVertices)
- {
- ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
- continue;
- }
-
- if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD)) // TODO
- {
- auto pointCloud = static_cast<ccPointCloud*>(cloud);
-
- switch (property)
- {
- case CLEAR_PROPERTY::COLORS:
- if (cloud->hasColors())
- {
- pointCloud->unallocateColors();
- ent->prepareDisplayForRefresh();
- }
- break;
-
- case CLEAR_PROPERTY::NORMALS:
- if (cloud->hasNormals())
- {
- pointCloud->unallocateNorms();
- ent->prepareDisplayForRefresh();
- }
- break;
-
- case CLEAR_PROPERTY::CURRENT_SCALAR_FIELD:
- if (cloud->hasDisplayedScalarField())
- {
- pointCloud->deleteScalarField( pointCloud->getCurrentDisplayedScalarFieldIndex() );
- ent->prepareDisplayForRefresh();
- }
- break;
-
- case CLEAR_PROPERTY::ALL_SCALAR_FIELDS:
- if (cloud->hasScalarFields())
- {
- pointCloud->deleteAllScalarFields();
- ent->prepareDisplayForRefresh();
- }
- break;
- }
- }
- }
-
- return true;
- }
-
- bool toggleProperty(const ccHObject::Container &selectedEntities, TOGGLE_PROPERTY property)
- {
- ccHObject baseEntities;
- ConvertToGroup(selectedEntities, baseEntities, ccHObject::DP_NONE);
-
- for (unsigned i = 0; i < baseEntities.getChildrenNumber(); ++i)
- {
- ccHObject* child = baseEntities.getChild(i);
- switch(property)
- {
- case TOGGLE_PROPERTY::ACTIVE:
- child->toggleActivation/*_recursive*/();
- break;
- case TOGGLE_PROPERTY::VISIBLE:
- child->toggleVisibility_recursive();
- break;
- case TOGGLE_PROPERTY::COLOR:
- child->toggleColors_recursive();
- break;
- case TOGGLE_PROPERTY::NORMALS:
- child->toggleNormals_recursive();
- break;
- case TOGGLE_PROPERTY::SCALAR_FIELD:
- child->toggleSF_recursive();
- break;
- case TOGGLE_PROPERTY::MATERIAL:
- child->toggleMaterials_recursive();
- break;
- case TOGGLE_PROPERTY::NAME:
- child->toggleShowName_recursive();
- break;
- default:
- Q_ASSERT(false);
- return false;
- }
- child->prepareDisplayForRefresh_recursive();
- }
-
- return true;
- }
-
- //////////
- // Stats
-
- bool statisticalTest(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- ccPickOneElementDlg poeDlg(QObject::tr("Distribution"), QObject::tr("Choose distribution"), parent);
- poeDlg.addElement("Gauss");
- poeDlg.addElement("Weibull");
- poeDlg.setDefaultIndex(0);
- if (!poeDlg.exec())
- {
- return false;
- }
-
- int distribIndex = poeDlg.getSelectedIndex();
-
- ccStatisticalTestDlg* sDlg = nullptr;
- switch (distribIndex)
- {
- case 0: //Gauss
- sDlg = new ccStatisticalTestDlg("mu", "sigma", QString(), QObject::tr("Local Statistical Test (Gauss)"), parent);
- break;
- case 1: //Weibull
- sDlg = new ccStatisticalTestDlg("a", "b", "shift", QObject::tr("Local Statistical Test (Weibull)"), parent);
- break;
- default:
- ccConsole::Error(QObject::tr("Invalid distribution!"));
- return false;
- }
-
- if (!sDlg->exec())
- {
- sDlg->deleteLater();
- return false;
- }
-
- //build up corresponding distribution
- CCCoreLib::GenericDistribution* distrib = nullptr;
- {
- ScalarType a = static_cast<ScalarType>(sDlg->getParam1());
- ScalarType b = static_cast<ScalarType>(sDlg->getParam2());
- ScalarType c = static_cast<ScalarType>(sDlg->getParam3());
-
- switch (distribIndex)
- {
- case 0: //Gauss
- {
- CCCoreLib::NormalDistribution* N = new CCCoreLib::NormalDistribution();
- N->setParameters(a,b*b); //warning: we input sigma2 here (not sigma)
- distrib = static_cast<CCCoreLib::GenericDistribution*>(N);
- break;
- }
- case 1: //Weibull
- CCCoreLib::WeibullDistribution* W = new CCCoreLib::WeibullDistribution();
- W->setParameters(a,b,c);
- distrib = static_cast<CCCoreLib::GenericDistribution*>(W);
- break;
- }
- }
-
- const double pChi2 = sDlg->getProbability();
- const int nn = sDlg->getNeighborsNumber();
-
- ccProgressDialog pDlg(true, parent);
- pDlg.setAutoClose(false);
-
- for (ccHObject* ent : selectedEntities)
- {
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent);
- if (pc == nullptr)
- {
- // TODO handle error?
- continue;
- }
-
- //we apply method on currently displayed SF
- ccScalarField* inSF = pc->getCurrentDisplayedScalarField();
- if (inSF == nullptr)
- {
- // TODO handle error?
- continue;
- }
-
- Q_ASSERT(inSF->capacity() != 0);
-
- //force SF as 'OUT' field (in case of)
- const int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
- pc->setCurrentOutScalarField(outSfIdx);
-
- //force Chi2 Distances field as 'IN' field (create it by the way if necessary)
- int chi2SfIdx = pc->getScalarFieldIndexByName(CC_CHI2_DISTANCES_DEFAULT_SF_NAME);
-
- if (chi2SfIdx < 0)
- chi2SfIdx = pc->addScalarField(CC_CHI2_DISTANCES_DEFAULT_SF_NAME);
-
- if (chi2SfIdx < 0)
- {
- ccConsole::Error(QObject::tr("Couldn't allocate a new scalar field for computing chi2 distances! Try to free some memory ..."));
- break;
- }
- pc->setCurrentInScalarField(chi2SfIdx);
-
- //compute octree if necessary
- ccOctree::Shared theOctree = pc->getOctree();
- if (!theOctree)
- {
- theOctree = pc->computeOctree(&pDlg);
- if (!theOctree)
- {
- ccConsole::Error(QObject::tr("Couldn't compute octree for cloud '%1'!").arg(pc->getName()));
- break;
- }
- }
-
- QElapsedTimer eTimer;
- eTimer.start();
-
- double chi2dist = CCCoreLib::StatisticalTestingTools::testCloudWithStatisticalModel(distrib, pc, nn, pChi2, &pDlg, theOctree.data());
-
- ccConsole::Print("[Chi2 Test] Timing: %3.2f ms.", eTimer.elapsed() / 1000.0);
- ccConsole::Print(QObject::tr("[Chi2 Test] %1 test result = %2").arg(distrib->getName()).arg(chi2dist));
-
- //we set the theoretical Chi2 distance limit as the minimum displayed SF value so that all points below are grayed
- {
- ccScalarField* chi2SF = static_cast<ccScalarField*>(pc->getCurrentInScalarField());
- Q_ASSERT(chi2SF);
- chi2SF->computeMinAndMax();
- chi2dist *= chi2dist;
- chi2SF->setMinDisplayed(static_cast<ScalarType>(chi2dist));
- chi2SF->setSymmetricalScale(false);
- chi2SF->setSaturationStart(static_cast<ScalarType>(chi2dist));
- //chi2SF->setSaturationStop(chi2dist);
-
- pc->setCurrentDisplayedScalarField(chi2SfIdx);
- pc->showSF(true);
- pc->prepareDisplayForRefresh_recursive();
- }
- }
-
- delete distrib;
- distrib = nullptr;
-
- sDlg->deleteLater();
-
- return true;
- }
-
- bool computeStatParams(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
- {
- ccPickOneElementDlg pDlg(QObject::tr("Distribution"), QObject::tr("Distribution Fitting"), parent);
- pDlg.addElement("Gauss");
- pDlg.addElement("Weibull");
- pDlg.setDefaultIndex(0);
- if (!pDlg.exec())
- return false;
-
- CCCoreLib::GenericDistribution* distrib = nullptr;
- {
- switch (pDlg.getSelectedIndex())
- {
- case 0: //GAUSS
- distrib = new CCCoreLib::NormalDistribution();
- break;
- case 1: //WEIBULL
- distrib = new CCCoreLib::WeibullDistribution();
- break;
- default:
- Q_ASSERT(false);
- return false;
- }
- }
- Q_ASSERT(distrib != nullptr);
-
- for (ccHObject* ent : selectedEntities)
- {
- ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent);
- if (pc == nullptr)
- {
- // TODO report error?
- continue;
- }
-
- //we apply method on currently displayed SF
- ccScalarField* sf = pc->getCurrentDisplayedScalarField();
- if (sf == nullptr)
- {
- // TODO report error?
- continue;
- }
- //compute the number of valid values
- size_t sfValidCount = sf->countValidValues();
- if (sfValidCount == 0)
- {
- ccLog::Warning(QObject::tr("Scalar field '%1' of cloud %2 has no valid values").arg(QString::fromStdString(sf->getName())).arg(pc->getName()));
- continue;
- }
-
- Q_ASSERT(!sf->empty());
-
- if (sf && distrib->computeParameters(CCCoreLib::GenericDistribution::SFAsScalarContainer(*sf)))
- {
- QString description;
- const unsigned precision = ccGui::Parameters().displayedNumPrecision;
- switch (pDlg.getSelectedIndex())
- {
- case 0: //GAUSS
- {
- CCCoreLib::NormalDistribution* normal = static_cast<CCCoreLib::NormalDistribution*>(distrib);
- description = QObject::tr("mean = %1 / std.dev. = %2").arg(normal->getMu(), 0, 'f', precision).arg(sqrt(normal->getSigma2()), 0, 'f', precision);
- }
- break;
-
- case 1: //WEIBULL
- {
- CCCoreLib::WeibullDistribution* weibull = static_cast<CCCoreLib::WeibullDistribution*>(distrib);
- ScalarType a;
- ScalarType b;
- weibull->getParameters(a, b);
- description = QString("a = %1 / b = %2 / shift = %3").arg(a, 0, 'f', precision).arg(b, 0, 'f', precision).arg(weibull->getValueShift(), 0, 'f', precision);
- ccLog::Print(QObject::tr("[Distribution fitting] Additional Weibull distrib. parameters: mode = %1 / skewness = %2").arg(weibull->computeMode()).arg(weibull->computeSkewness()));
- }
- break;
- default:
- {
- Q_ASSERT(false);
- return false;
- }
- }
- description.prepend(QString("%1: ").arg(distrib->getName()));
- ccConsole::Print(QObject::tr("[Distribution fitting] %1").arg(description));
-
- const unsigned numberOfClasses = static_cast<unsigned>(ceil(sqrt(static_cast<double>(sfValidCount))));
- std::vector<unsigned> histo;
- std::vector<double> npis;
- try
- {
- histo.resize(numberOfClasses, 0);
- npis.resize(numberOfClasses, 0.0);
- }
- catch (const std::bad_alloc&)
- {
- ccConsole::Warning(QObject::tr("[Distribution fitting] Not enough memory!"));
- continue;
- }
-
- //compute the Chi2 distance
- {
- unsigned finalNumberOfClasses = 0;
- const double chi2dist = CCCoreLib::StatisticalTestingTools::computeAdaptativeChi2Dist(distrib, pc, numberOfClasses, finalNumberOfClasses, false, nullptr, nullptr, histo.data(), npis.data());
- if (chi2dist >= 0.0)
- {
- ccConsole::Print(QObject::tr("[Distribution fitting] %1: Chi2 Distance = %2").arg(distrib->getName()).arg(chi2dist));
- }
- else
- {
- ccConsole::Warning(QObject::tr("[Distribution fitting] Failed to compute Chi2 distance?!"));
- continue;
- }
- }
- //compute RMS
- {
- unsigned n = pc->size();
- double squareSum = 0;
- double sum = 0;
- for (unsigned i = 0; i < n; ++i)
- {
- ScalarType v = pc->getPointScalarValue(i);
- if (CCCoreLib::ScalarField::ValidValue(v))
- {
- sum += static_cast<double>(v);
- squareSum += static_cast<double>(v) * v;
- }
- }
- double rms = sqrt(squareSum / sfValidCount);
- ccConsole::Print(QObject::tr("Scalar field statistics:"));
- ccConsole::Print(QObject::tr("Number of valid values = %1 / %2 (%3%)").arg(sfValidCount).arg(pc->size()).arg((100.0 * sfValidCount) / pc->size(), 0, 'f', 2));
- ccConsole::Print(QObject::tr("Sum of all valid values = %1").arg(QString::number(sum, 'f', 6)));
- ccConsole::Print(QObject::tr("Sum of all valid squared values = %1").arg(QString::number(squareSum, 'f', 6)));
- ccConsole::Print(QObject::tr("Average value = %1").arg(sum / sfValidCount));
- ccConsole::Print(QObject::tr("RMS (Root Mean Square) = %1").arg(rms));
- }
- //show histogram
- ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(parent);
- hDlg->setWindowTitle(QObject::tr("[Distribution fitting]"));
-
- ccHistogramWindow* histogram = hDlg->window();
- histogram->fromBinArray(histo, sf);
- histo.clear();
- histogram->setCurveValues(npis);
- npis.clear();
- histogram->setTitle(description);
- histogram->setAxisLabels(QString::fromStdString(sf->getName()), QObject::tr("Count"));
- histogram->refresh();
-
- hDlg->show();
- }
- else
- {
- ccConsole::Warning(QObject::tr("[Entity: %1]-[SF: %2] Couldn't compute distribution parameters!").arg(pc->getName(), QString::fromStdString(sf->getName())));
- }
- }
-
- delete distrib;
- distrib = nullptr;
-
- return true;
- }
- }
|