ccEntityAction.cpp 92 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235
  1. //##########################################################################
  2. //# #
  3. //# CLOUDCOMPARE #
  4. //# #
  5. //# This program is free software; you can redistribute it and/or modify #
  6. //# it under the terms of the GNU General Public License as published by #
  7. //# the Free Software Foundation; version 2 or later of the License. #
  8. //# #
  9. //# This program is distributed in the hope that it will be useful, #
  10. //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
  11. //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
  12. //# GNU General Public License for more details. #
  13. //# #
  14. //# COPYRIGHT: CloudCompare project #
  15. //# #
  16. //##########################################################################
  17. //Qt
  18. #include <QColorDialog>
  19. #include <QElapsedTimer>
  20. #include <QInputDialog>
  21. #include <QMessageBox>
  22. #include <QPushButton>
  23. //CCCoreLib
  24. #include <NormalDistribution.h>
  25. #include <ScalarFieldTools.h>
  26. #include <StatisticalTestingTools.h>
  27. #include <WeibullDistribution.h>
  28. #include <ReferenceCloud.h>
  29. //qCC_db
  30. #include <ccColorScalesManager.h>
  31. #include <ccFacet.h>
  32. #include <ccGenericPrimitive.h>
  33. #include <ccOctreeProxy.h>
  34. #include <ccPointCloud.h>
  35. #include <ccPointCloudInterpolator.h>
  36. #include <ccPolyline.h>
  37. #include <ccSensor.h>
  38. //qCC_gl
  39. #include "ccGuiParameters.h"
  40. //common
  41. #include <ccPickOneElementDlg.h>
  42. //Local
  43. #include "ccAskTwoDoubleValuesDlg.h"
  44. #include "ccAskThreeDoubleValuesDlg.h"
  45. #include "ccColorGradientDlg.h"
  46. #include "ccColorLevelsDlg.h"
  47. #include "ccComputeOctreeDlg.h"
  48. #include "ccExportCoordToSFDlg.h"
  49. #include "ccInterpolationDlg.h"
  50. #include "ccItemSelectionDlg.h"
  51. #include "ccNormalComputationDlg.h"
  52. #include "ccOrderChoiceDlg.h"
  53. #include "ccProgressDialog.h"
  54. #include "ccScalarFieldArithmeticsDlg.h"
  55. #include "ccScalarFieldFromColorDlg.h"
  56. #include "ccSetSFAsVec3Dlg.h"
  57. #include "ccStatisticalTestDlg.h"
  58. #include "ccCommon.h"
  59. #include "ccConsole.h"
  60. #include "ccEntityAction.h"
  61. #include "ccHistogramWindow.h"
  62. #include "ccLibAlgorithms.h"
  63. #include "ccUtils.h"
  64. // This is included only for temporarily removing an object from the tree.
  65. #include "ccMainAppInterface.h"
  66. // System
  67. #include <unordered_set>
  68. #include <array>
  69. namespace ccEntityAction
  70. {
  71. static QString GetFirstAvailableSFName(const ccPointCloud* cloud, const QString& baseName)
  72. {
  73. if (cloud == nullptr)
  74. {
  75. Q_ASSERT(false);
  76. return {};
  77. }
  78. QString name = baseName;
  79. for (int trials = 0; trials < 99; ++trials)
  80. {
  81. if (cloud->getScalarFieldIndexByName(name.toStdString()) < 0)
  82. {
  83. // Scalar field name is available
  84. return name;
  85. }
  86. // else, generate a new name
  87. name = QString("%1 #%2").arg(baseName).arg(trials + 1);
  88. }
  89. // we couldn't find an available name!
  90. return {};
  91. }
  92. //////////
  93. // Colours
  94. bool setColor(ccHObject::Container selectedEntities, bool colorize, QWidget* parent/*=nullptr*/)
  95. {
  96. static QColor s_lastColor = Qt::white;
  97. QColor colour = QColorDialog::getColor(s_lastColor, parent, QString(), QColorDialog::ShowAlphaChannel);
  98. if (!colour.isValid())
  99. return false;
  100. s_lastColor = colour;
  101. while (!selectedEntities.empty())
  102. {
  103. ccHObject* ent = selectedEntities.back();
  104. selectedEntities.pop_back();
  105. if (ent->isA(CC_TYPES::HIERARCHY_OBJECT))
  106. {
  107. //automatically parse a group's children set
  108. for (unsigned i = 0; i < ent->getChildrenNumber(); ++i)
  109. selectedEntities.push_back(ent->getChild(i));
  110. }
  111. else if (ent->isA(CC_TYPES::POINT_CLOUD) || ent->isA(CC_TYPES::MESH))
  112. {
  113. ccPointCloud* cloud = nullptr;
  114. if (ent->isA(CC_TYPES::POINT_CLOUD))
  115. {
  116. cloud = static_cast<ccPointCloud*>(ent);
  117. }
  118. else
  119. {
  120. ccMesh* mesh = static_cast<ccMesh*>(ent);
  121. ccGenericPointCloud* vertices = mesh->getAssociatedCloud();
  122. if ( !vertices
  123. || !vertices->isA(CC_TYPES::POINT_CLOUD)
  124. || (vertices->isLocked() && !mesh->isAncestorOf(vertices)) )
  125. {
  126. ccLog::Warning(QObject::tr("[SetColor] Can't set color for mesh '%1' (vertices are not accessible)").arg(ent->getName()));
  127. continue;
  128. }
  129. cloud = static_cast<ccPointCloud*>(vertices);
  130. }
  131. if (colorize)
  132. {
  133. cloud->colorize(static_cast<float>(colour.redF()),
  134. static_cast<float>(colour.greenF()),
  135. static_cast<float>(colour.blueF()),
  136. static_cast<float>(colour.alphaF()));
  137. }
  138. else
  139. {
  140. cloud->setColor(ccColor::FromQColora(colour));
  141. }
  142. cloud->showColors(true);
  143. cloud->showSF(false); //just in case
  144. cloud->prepareDisplayForRefresh();
  145. if (ent != cloud)
  146. {
  147. ent->showColors(true);
  148. }
  149. else if (cloud->getParent() && cloud->getParent()->isKindOf(CC_TYPES::MESH))
  150. {
  151. cloud->getParent()->showColors(true);
  152. cloud->getParent()->showSF(false); //just in case
  153. }
  154. }
  155. else if (ent->isKindOf(CC_TYPES::PRIMITIVE))
  156. {
  157. ccGenericPrimitive* prim = ccHObjectCaster::ToPrimitive(ent);
  158. ccColor::Rgb col( static_cast<ColorCompType>(colour.red()),
  159. static_cast<ColorCompType>(colour.green()),
  160. static_cast<ColorCompType>(colour.blue()) );
  161. prim->setColor(col);
  162. ent->showColors(true);
  163. ent->showSF(false); //just in case
  164. ent->prepareDisplayForRefresh();
  165. }
  166. else if (ent->isA(CC_TYPES::POLY_LINE))
  167. {
  168. ccPolyline* poly = ccHObjectCaster::ToPolyline(ent);
  169. poly->setColor(ccColor::FromQColor(colour));
  170. ent->showColors(true);
  171. ent->showSF(false); //just in case
  172. ent->prepareDisplayForRefresh();
  173. }
  174. else if (ent->isA(CC_TYPES::FACET))
  175. {
  176. ccFacet* facet = ccHObjectCaster::ToFacet(ent);
  177. facet->setColor(ccColor::FromQColor(colour));
  178. ent->showColors(true);
  179. ent->showSF(false); //just in case
  180. ent->prepareDisplayForRefresh();
  181. }
  182. else
  183. {
  184. ccLog::Warning(QObject::tr("[SetColor] Can't change color of entity '%1'").arg(ent->getName()));
  185. }
  186. }
  187. return true;
  188. }
  189. bool rgbToGreyScale(const ccHObject::Container &selectedEntities)
  190. {
  191. for (ccHObject* ent : selectedEntities)
  192. {
  193. bool lockedVertices = false;
  194. ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
  195. if (lockedVertices)
  196. {
  197. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  198. continue;
  199. }
  200. if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD))
  201. {
  202. ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
  203. if (pc->hasColors())
  204. {
  205. pc->convertRGBToGreyScale();
  206. pc->showColors(true);
  207. pc->showSF(false); //just in case
  208. pc->prepareDisplayForRefresh();
  209. }
  210. }
  211. }
  212. return true;
  213. }
  214. bool setColorGradient(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  215. {
  216. ccColorGradientDlg dlg(parent);
  217. if (!dlg.exec())
  218. return false;
  219. unsigned char dim = dlg.getDimension();
  220. ccColorGradientDlg::GradientType ramp = dlg.getType();
  221. ccColorScale::Shared colorScale(nullptr);
  222. if (ramp == ccColorGradientDlg::Default)
  223. {
  224. colorScale = ccColorScalesManager::GetDefaultScale();
  225. }
  226. else if (ramp == ccColorGradientDlg::TwoColors)
  227. {
  228. colorScale = ccColorScale::Create("Temp scale");
  229. QColor first;
  230. QColor second;
  231. dlg.getColors(first,second);
  232. colorScale->insert(ccColorScaleElement(0.0, first), false);
  233. colorScale->insert(ccColorScaleElement(1.0, second), true);
  234. }
  235. Q_ASSERT(colorScale || ramp == ccColorGradientDlg::Banding);
  236. const double frequency = dlg.getBandingFrequency();
  237. for (ccHObject* ent : selectedEntities)
  238. {
  239. bool lockedVertices = false;
  240. ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
  241. if (lockedVertices)
  242. {
  243. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  244. continue;
  245. }
  246. if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD)) // TODO
  247. {
  248. ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
  249. bool success = false;
  250. if (ramp == ccColorGradientDlg::Banding)
  251. success = pc->setRGBColorByBanding(dim, frequency);
  252. else
  253. success = pc->setRGBColorByHeight(dim, colorScale);
  254. if (success)
  255. {
  256. ent->showColors(true);
  257. ent->showSF(false); //just in case
  258. ent->prepareDisplayForRefresh();
  259. }
  260. }
  261. }
  262. return true;
  263. }
  264. bool changeColorLevels(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  265. {
  266. if (selectedEntities.size() != 1)
  267. {
  268. ccConsole::Error(QObject::tr("Select one and only one colored cloud or mesh!"));
  269. return false;
  270. }
  271. bool lockedVertices;
  272. ccPointCloud* pointCloud = ccHObjectCaster::ToPointCloud(selectedEntities[0], &lockedVertices);
  273. if (!pointCloud || lockedVertices)
  274. {
  275. if (lockedVertices && pointCloud)
  276. ccUtils::DisplayLockedVerticesWarning(pointCloud->getName(), true);
  277. return false;
  278. }
  279. if (!pointCloud->hasColors())
  280. {
  281. ccConsole::Error(QObject::tr("Selected entity has no colors!"));
  282. return false;
  283. }
  284. ccColorLevelsDlg dlg(parent, pointCloud);
  285. dlg.exec();
  286. return true;
  287. }
  288. //! Interpolate colors from on entity and transfer them to another one
  289. bool interpolateColors(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  290. {
  291. if (selectedEntities.size() != 2)
  292. {
  293. ccConsole::Error(QObject::tr("Select 2 entities (clouds or meshes)!"));
  294. return false;
  295. }
  296. ccHObject* ent1 = selectedEntities[0];
  297. ccHObject* ent2 = selectedEntities[1];
  298. ccGenericPointCloud* cloud1 = ccHObjectCaster::ToGenericPointCloud(ent1);
  299. ccGenericPointCloud* cloud2 = ccHObjectCaster::ToGenericPointCloud(ent2);
  300. if (!cloud1 || !cloud2)
  301. {
  302. ccConsole::Error(QObject::tr("Select 2 entities (clouds or meshes)!"));
  303. return false;
  304. }
  305. if (!cloud1->hasColors() && !cloud2->hasColors())
  306. {
  307. ccConsole::Error(QObject::tr("None of the selected entities has per-point or per-vertex colors!"));
  308. return false;
  309. }
  310. else if (cloud1->hasColors() && cloud2->hasColors())
  311. {
  312. ccConsole::Error(QObject::tr("Both entities have colors! Remove the colors on the entity you wish to import the colors to!"));
  313. return false;
  314. }
  315. ccGenericPointCloud* source = cloud1;
  316. ccGenericPointCloud* dest = cloud2;
  317. if ( cloud2->hasColors())
  318. {
  319. std::swap(source, dest);
  320. std::swap(cloud1, cloud2);
  321. std::swap(ent1, ent2);
  322. }
  323. if (!dest->isA(CC_TYPES::POINT_CLOUD))
  324. {
  325. ccConsole::Error(QObject::tr("Destination cloud (or vertices) must be a real point cloud!"));
  326. return false;
  327. }
  328. ccProgressDialog pDlg(true, parent);
  329. if (static_cast<ccPointCloud*>(dest)->interpolateColorsFrom(source, &pDlg))
  330. {
  331. ent2->showColors(true);
  332. ent2->showSF(false); //just in case
  333. }
  334. else
  335. {
  336. ccConsole::Error(QObject::tr("An error occurred! (see console)"));
  337. }
  338. ent2->prepareDisplayForRefresh_recursive();
  339. return true;
  340. }
  341. //! Interpolate scalar fields from one entity and transfer them to another one
  342. bool interpolateSFs(const ccHObject::Container &selectedEntities, ccMainAppInterface* app)
  343. {
  344. if (selectedEntities.size() != 2)
  345. {
  346. ccConsole::Error(QObject::tr("Select 2 entities (clouds or meshes)!"));
  347. return false;
  348. }
  349. ccHObject* ent1 = selectedEntities[0];
  350. ccHObject* ent2 = selectedEntities[1];
  351. ccPointCloud* cloud1 = ccHObjectCaster::ToPointCloud(ent1);
  352. ccPointCloud* cloud2 = ccHObjectCaster::ToPointCloud(ent2);
  353. if (!cloud1 || !cloud2)
  354. {
  355. ccConsole::Error(QObject::tr("Select 2 entities (clouds or meshes)!"));
  356. return false;
  357. }
  358. if (!cloud1->hasScalarFields() && !cloud2->hasScalarFields())
  359. {
  360. ccConsole::Error(QObject::tr("None of the selected entities has per-point or per-vertex colors!"));
  361. return false;
  362. }
  363. else if (cloud1->hasScalarFields() && cloud2->hasScalarFields())
  364. {
  365. //ask the user to chose which will be the 'source' cloud
  366. ccOrderChoiceDlg ocDlg(cloud1, QObject::tr("Source"), cloud2, QObject::tr("Destination"), app);
  367. if (!ocDlg.exec())
  368. {
  369. //process cancelled by the user
  370. return false;
  371. }
  372. if (cloud1 != ocDlg.getFirstEntity())
  373. {
  374. std::swap(cloud1, cloud2);
  375. }
  376. }
  377. else if (cloud2->hasScalarFields())
  378. {
  379. std::swap(cloud1, cloud2);
  380. }
  381. ccPointCloud* source = cloud1;
  382. ccPointCloud* dest = cloud2;
  383. //show the list of scalar fields available on the source point cloud
  384. std::vector<int> sfIndexes;
  385. try
  386. {
  387. unsigned sfCount = source->getNumberOfScalarFields();
  388. if (sfCount == 1)
  389. {
  390. sfIndexes.push_back(0);
  391. }
  392. else if (sfCount > 1)
  393. {
  394. ccItemSelectionDlg isDlg(true, app->getMainWindow(), QObject::tr("entity"));
  395. QStringList scalarFields;
  396. {
  397. for (unsigned i = 0; i < sfCount; ++i)
  398. {
  399. scalarFields << QString::fromStdString(source->getScalarFieldName(i));
  400. }
  401. }
  402. isDlg.setItems(scalarFields, 0);
  403. if (!isDlg.exec())
  404. {
  405. //cancelled by the user
  406. return false;
  407. }
  408. isDlg.getSelectedIndexes(sfIndexes);
  409. if (sfIndexes.empty())
  410. {
  411. ccConsole::Error(QObject::tr("No scalar field was selected"));
  412. return false;
  413. }
  414. }
  415. else
  416. {
  417. assert(false);
  418. }
  419. }
  420. catch (const std::bad_alloc&)
  421. {
  422. ccConsole::Error(QObject::tr("Not enough memory!"));
  423. return false;
  424. }
  425. //semi-persistent parameters
  426. static ccPointCloudInterpolator::Parameters::Method s_interpMethod = ccPointCloudInterpolator::Parameters::RADIUS;
  427. static ccPointCloudInterpolator::Parameters::Algo s_interpAlgo = ccPointCloudInterpolator::Parameters::NORMAL_DIST;
  428. static int s_interpKNN = 6;
  429. ccInterpolationDlg iDlg(app->getMainWindow());
  430. iDlg.setInterpolationMethod(s_interpMethod);
  431. iDlg.setInterpolationAlgorithm(s_interpAlgo);
  432. iDlg.knnSpinBox->setValue(s_interpKNN);
  433. iDlg.radiusDoubleSpinBox->setValue(dest->getOwnBB().getDiagNormd() / 100);
  434. if (!iDlg.exec())
  435. {
  436. //process cancelled by the user
  437. return false;
  438. }
  439. //setup parameters
  440. ccPointCloudInterpolator::Parameters params;
  441. params.method = s_interpMethod = iDlg.getInterpolationMethod();
  442. params.algo = s_interpAlgo = iDlg.getInterpolationAlgorithm();
  443. params.knn = s_interpKNN = iDlg.knnSpinBox->value();
  444. params.radius = iDlg.radiusDoubleSpinBox->value();
  445. params.sigma = iDlg.kernelDoubleSpinBox->value();
  446. ccProgressDialog pDlg(true, app->getMainWindow());
  447. unsigned sfCountBefore = dest->getNumberOfScalarFields();
  448. if (ccPointCloudInterpolator::InterpolateScalarFieldsFrom(dest, source, sfIndexes, params, &pDlg))
  449. {
  450. dest->setCurrentDisplayedScalarField(static_cast<int>(std::min(sfCountBefore + 1, dest->getNumberOfScalarFields())) - 1);
  451. dest->showSF(true);
  452. }
  453. else
  454. {
  455. ccConsole::Error(QObject::tr("An error occurred! (see console)"));
  456. }
  457. dest->prepareDisplayForRefresh_recursive();
  458. return true;
  459. }
  460. //! Interpolate scalar fields from one entity and transfer them to another one without the dialog
  461. bool interpolateSFs(ccPointCloud *source, ccPointCloud *dest, int sfIndex, ccPointCloudInterpolator::Parameters& params, QWidget* parent/*=nullptr*/)
  462. {
  463. if (!source || !dest)
  464. {
  465. ccConsole::Error(QObject::tr("Unexpected null cloud pointers!"));
  466. return false;
  467. }
  468. if (!source->hasScalarFields())
  469. {
  470. ccConsole::Error(QObject::tr("[ccEntityAction::interpolateSFs] The source cloud has no scalar field!"));
  471. return false;
  472. }
  473. unsigned sfCount = source->getNumberOfScalarFields();
  474. if (sfIndex > static_cast<int>(sfCount))
  475. {
  476. ccConsole::Error(QObject::tr("[ccEntityAction::interpolateSFs] Invalid scalar field index!"));
  477. return false;
  478. }
  479. ccProgressDialog pDlg(true, parent);
  480. std::vector<int> sfIndexes({ sfIndex });
  481. if (!ccPointCloudInterpolator::InterpolateScalarFieldsFrom(dest, source, sfIndexes, params, parent ? &pDlg : nullptr))
  482. {
  483. ccConsole::Error(QObject::tr("[ccEntityAction::interpolateSFs] An error occurred! (see console)"));
  484. return false;
  485. }
  486. return true;
  487. }
  488. bool convertTextureToColor(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/)
  489. {
  490. for (ccHObject* ent : selectedEntities)
  491. {
  492. if (ent->isA(CC_TYPES::MESH)/*|| ent->isKindOf(CC_TYPES::PRIMITIVE)*/) //TODO
  493. {
  494. ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
  495. Q_ASSERT(mesh);
  496. if (!mesh->hasMaterials())
  497. {
  498. ccLog::Warning(QObject::tr("[ConvertTextureToColor] Mesh '%1' has no material/texture!").arg(mesh->getName()));
  499. continue;
  500. }
  501. else
  502. {
  503. if ( mesh->hasColors()
  504. && QMessageBox::warning( parent,
  505. QObject::tr("Mesh already has colors"),
  506. QObject::tr("Mesh '%1' already has colors! Overwrite them?").arg(mesh->getName()),
  507. QMessageBox::Yes | QMessageBox::No,
  508. QMessageBox::No) != QMessageBox::Yes)
  509. {
  510. continue;
  511. }
  512. if (mesh->convertMaterialsToVertexColors())
  513. {
  514. mesh->showColors(true);
  515. mesh->showSF(false); //just in case
  516. mesh->showMaterials(false);
  517. mesh->prepareDisplayForRefresh_recursive();
  518. }
  519. else
  520. {
  521. ccLog::Warning(QObject::tr("[ConvertTextureToColor] Failed to convert texture on mesh '%1'!").arg(mesh->getName()));
  522. }
  523. }
  524. }
  525. }
  526. return true;
  527. }
  528. bool enhanceRGBWithIntensities(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/)
  529. {
  530. QString defaultSFName("Intensity");
  531. bool useCustomIntensityRange = false;
  532. static double s_minI = 0.0;
  533. static double s_maxI = 1.0;
  534. 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)
  535. {
  536. ccAskTwoDoubleValuesDlg atdvDlg(QObject::tr("Min"), QObject::tr("Max"), -1000000.0, 1000000.0, s_minI, s_maxI, 3, QObject::tr("Theroetical intensity"), parent);
  537. if (!atdvDlg.exec())
  538. {
  539. //process cancelled by the user
  540. return false;
  541. }
  542. s_minI = atdvDlg.doubleSpinBox1->value();
  543. s_maxI = atdvDlg.doubleSpinBox2->value();
  544. useCustomIntensityRange = true;
  545. }
  546. for (ccHObject* ent : selectedEntities)
  547. {
  548. bool lockedVertices = false;
  549. ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
  550. if (!pc || lockedVertices)
  551. {
  552. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  553. continue;
  554. }
  555. if (!pc->hasColors())
  556. {
  557. ccLog::Warning(QObject::tr("[EnhanceRGBWithIntensities] Entity '%1' has no RGB color!").arg(ent->getName()));
  558. continue;
  559. }
  560. if (!pc->hasScalarFields())
  561. {
  562. ccLog::Warning(QObject::tr("[EnhanceRGBWithIntensities] Entity '%1' has no scalar field!").arg(ent->getName()));
  563. continue;
  564. }
  565. int sfIdx = -1;
  566. if (pc->getNumberOfScalarFields() > 1)
  567. {
  568. //does the previously selected SF works?
  569. if (!defaultSFName.isEmpty())
  570. {
  571. //if it's valid, we'll keep this SF!
  572. sfIdx = pc->getScalarFieldIndexByName(defaultSFName.toStdString());
  573. }
  574. if (sfIdx < 0)
  575. {
  576. //let the user choose the right scalar field
  577. ccPickOneElementDlg poeDlg(QObject::tr("Intensity scalar field"), QObject::tr("Choose scalar field"), parent);
  578. for (unsigned i = 0; i < pc->getNumberOfScalarFields(); ++i)
  579. {
  580. CCCoreLib::ScalarField* sf = pc->getScalarField(i);
  581. assert(sf);
  582. QString sfName = QString::fromStdString(sf->getName());
  583. poeDlg.addElement(sfName);
  584. if (sfIdx < 0 && sfName.contains("intensity", Qt::CaseInsensitive))
  585. {
  586. sfIdx = static_cast<int>(i);
  587. }
  588. }
  589. poeDlg.setDefaultIndex(std::max(0, sfIdx));
  590. if (!poeDlg.exec())
  591. {
  592. //process cancelled by the user
  593. return false;
  594. }
  595. sfIdx = poeDlg.getSelectedIndex();
  596. defaultSFName = QString::fromStdString(pc->getScalarField(sfIdx)->getName());
  597. }
  598. }
  599. else
  600. {
  601. sfIdx = 0;
  602. }
  603. assert(sfIdx >= 0);
  604. if (pc->enhanceRGBWithIntensitySF(sfIdx, useCustomIntensityRange, s_minI, s_maxI))
  605. {
  606. ent->prepareDisplayForRefresh();
  607. ent->showColors(true);
  608. ent->showSF(false);
  609. }
  610. else
  611. {
  612. ccLog::Warning(QObject::tr("[EnhanceRGBWithIntensities] Failed to apply the process on entity '%1'!").arg(ent->getName()));
  613. }
  614. }
  615. return true;
  616. }
  617. bool rgbGaussianFilter(const ccHObject::Container& selectedEntities, ccPointCloud::RgbFilterOptions filterParams, QWidget* parent/*=nullptr*/)
  618. {
  619. if (selectedEntities.empty())
  620. {
  621. return false;
  622. }
  623. // select only the clouds (or vertices) with RGB colors
  624. std::vector<std::pair<ccHObject*, ccPointCloud*>> selectedCloudsWithColors;
  625. double spatialSigma = std::numeric_limits<double>::max();
  626. for (ccHObject* ent : selectedEntities)
  627. {
  628. bool lockedVertices = false;
  629. ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
  630. if (!pc || lockedVertices)
  631. {
  632. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  633. continue;
  634. }
  635. //check if the cloud has color
  636. if (pc->hasColors())
  637. {
  638. if ((filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL) && !pc->hasDisplayedScalarField())
  639. {
  640. continue;
  641. }
  642. selectedCloudsWithColors.push_back({ ent, pc });
  643. double sigmaCloud = ccLibAlgorithms::GetDefaultCloudKernelSize(pc);
  644. //we keep the smallest value
  645. if (sigmaCloud < spatialSigma)
  646. {
  647. spatialSigma = sigmaCloud;
  648. }
  649. }
  650. }
  651. if (filterParams.spatialSigma > 0)
  652. {
  653. spatialSigma = filterParams.spatialSigma;
  654. }
  655. if (selectedCloudsWithColors.empty())
  656. {
  657. if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
  658. ccConsole::Error(QObject::tr("Select at least one cloud or mesh with RGB colors and an active scalar field"));
  659. else
  660. ccConsole::Error(QObject::tr("Select at least one cloud or mesh with RGB colors"));
  661. return false;
  662. }
  663. double sigmaSF = -1.0;
  664. if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
  665. {
  666. CCCoreLib::ScalarField* sf = selectedCloudsWithColors.front().second->getCurrentDisplayedScalarField();
  667. if (sf)
  668. {
  669. ScalarType sfRange = sf->getMax() - sf->getMin();
  670. sigmaSF = sfRange / 4; // using 1/4 of total range
  671. }
  672. if (filterParams.sigmaSF > 0)
  673. {
  674. sigmaSF = filterParams.sigmaSF;
  675. }
  676. }
  677. QScopedPointer<ccProgressDialog> pDlg;
  678. if (!filterParams.commandLine)
  679. {
  680. bool ok = false;
  681. if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
  682. {
  683. ccAskThreeDoubleValuesDlg dlg( QObject::tr("Spatial sigma"),
  684. QObject::tr("Scalar sigma"),
  685. QObject::tr("Color threshold"),
  686. DBL_MIN,
  687. 1.0e9,
  688. spatialSigma,
  689. sigmaSF,
  690. filterParams.burntOutColorThreshold,
  691. 8,
  692. nullptr,
  693. parent);
  694. dlg.setWindowTitle(QObject::tr("RGB bilateral filter"));
  695. dlg.doubleSpinBox1->setStatusTip(QObject::tr("3*sigma = 99.7% attenuation"));
  696. 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"));
  697. dlg.doubleSpinBox3->setStatusTip(QObject::tr("For averaging, it will only use colors for which all components are in the range[threshold:255 - threshold]"));
  698. if (!dlg.exec())
  699. {
  700. return false;
  701. }
  702. //get values
  703. spatialSigma = dlg.doubleSpinBox1->value();
  704. sigmaSF = dlg.doubleSpinBox2->value();
  705. filterParams.burntOutColorThreshold = dlg.doubleSpinBox3->value();
  706. }
  707. else
  708. {
  709. ccAskTwoDoubleValuesDlg dlg(QObject::tr("Spatial sigma"),
  710. QObject::tr("Color threshold"),
  711. DBL_MIN,
  712. 1.0e9,
  713. spatialSigma,
  714. filterParams.burntOutColorThreshold,
  715. 8,
  716. nullptr,
  717. parent);
  718. dlg.setWindowTitle(QObject::tr("RGB gaussian/mean/median filter"));
  719. dlg.doubleSpinBox1->setStatusTip(QObject::tr("3*sigma = 99.7% attenuation"));
  720. dlg.doubleSpinBox2->setStatusTip(QObject::tr("For averaging, it will only use colors for which all components are in the range [threshold:255-threshold]"));
  721. if (!dlg.exec())
  722. {
  723. return false;
  724. }
  725. //get values
  726. spatialSigma = dlg.doubleSpinBox1->value();
  727. filterParams.burntOutColorThreshold = dlg.doubleSpinBox2->value();
  728. }
  729. }
  730. if (parent)
  731. {
  732. pDlg.reset(new ccProgressDialog(true, parent));
  733. pDlg->setAutoClose(false);
  734. }
  735. for (auto entAndPC : selectedCloudsWithColors )
  736. {
  737. ccPointCloud* pc = entAndPC.second;
  738. assert(pc);
  739. int sfIdx = 0;
  740. if ((filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL) || filterParams.applyToSFduringRGB)
  741. {
  742. //we set the displayed SF as "OUT" SF
  743. int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
  744. Q_ASSERT(outSfIdx >= 0);
  745. pc->setCurrentOutScalarField(outSfIdx);
  746. if (filterParams.applyToSFduringRGB)
  747. {
  748. CCCoreLib::ScalarField* outSF = pc->getCurrentOutScalarField();
  749. Q_ASSERT(outSF != nullptr);
  750. QString sfName;
  751. if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
  752. {
  753. sfName = QString("%1.bilsmooth(%2,%3)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma).arg(sigmaSF);
  754. }
  755. else if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN)
  756. {
  757. sfName = QString("%1.smooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
  758. }
  759. else if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::MEAN)
  760. {
  761. sfName = QString("%1.meansmooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
  762. }
  763. else
  764. {
  765. sfName = QString("%1.medsmooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
  766. }
  767. sfIdx = pc->getScalarFieldIndexByName(sfName.toStdString());
  768. if (sfIdx < 0)
  769. sfIdx = pc->addScalarField(sfName.toStdString()); //output SF has same type as input SF
  770. if (sfIdx >= 0)
  771. pc->setCurrentInScalarField(sfIdx);
  772. else
  773. {
  774. ccConsole::Error(QObject::tr("Failed to create scalar field for cloud '%1' (not enough memory?)").arg(pc->getName()));
  775. return false;
  776. }
  777. }
  778. }
  779. ccOctree::Shared octree = pc->getOctree();
  780. if (!octree)
  781. {
  782. octree = pc->computeOctree(parent ? pDlg.data() : nullptr);
  783. if (!octree)
  784. {
  785. ccConsole::Error(QObject::tr("Couldn't compute octree for cloud '%1'!").arg(pc->getName()));
  786. continue;
  787. }
  788. }
  789. QElapsedTimer eTimer;
  790. eTimer.start();
  791. pc->applyFilterToRGB( static_cast<PointCoordinateType>(spatialSigma),
  792. static_cast<PointCoordinateType>(sigmaSF),
  793. filterParams,
  794. parent ? pDlg.data() : nullptr);
  795. ccConsole::Print("[RGBFilter] Timing: %3.2f s.", eTimer.elapsed() / 1000.0);
  796. if (filterParams.applyToSFduringRGB)
  797. {
  798. //calc sf min/max for correct display.
  799. pc->setCurrentDisplayedScalarField(sfIdx);
  800. pc->showSF(sfIdx >= 0);
  801. CCCoreLib::ScalarField* sf = pc->getCurrentDisplayedScalarField();
  802. if (sf)
  803. sf->computeMinAndMax();
  804. }
  805. // automatically hide any SF and show the colors instead
  806. entAndPC.first->prepareDisplayForRefresh_recursive();
  807. entAndPC.first->showColors(true);
  808. entAndPC.first->showSF(false);
  809. }
  810. return true;
  811. }
  812. //////////
  813. // Scalar Fields
  814. bool sfGaussianFilter(const ccHObject::Container& selectedEntities, ccPointCloud::RgbFilterOptions filterParams, QWidget* parent/*=nullptr*/)
  815. {
  816. if (selectedEntities.empty())
  817. return false;
  818. double spatialSigma = filterParams.spatialSigma == -1 ? ccLibAlgorithms::GetDefaultCloudKernelSize(selectedEntities) : filterParams.spatialSigma;
  819. if (spatialSigma < 0.0)
  820. {
  821. ccConsole::Error(QObject::tr("No eligible point cloud in selection!"));
  822. return false;
  823. }
  824. //estimate a good value for scalar field sigma, based on the first cloud
  825. //and its displayed scalar field
  826. double scalarFieldSigma = -1.0;
  827. if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
  828. {
  829. ccPointCloud* testPC = ccHObjectCaster::ToPointCloud(selectedEntities.front());
  830. CCCoreLib::ScalarField* testSF = testPC->getCurrentDisplayedScalarField();
  831. if (!testSF)
  832. {
  833. ccConsole::Error(QObject::tr("No active scalar field"));
  834. return false;
  835. }
  836. if (filterParams.sigmaSF == -1)
  837. {
  838. ScalarType range = testSF->getMax() - testSF->getMin();
  839. scalarFieldSigma = range / 4; // using 1/4 of total range
  840. }
  841. else
  842. {
  843. scalarFieldSigma = filterParams.sigmaSF;
  844. }
  845. }
  846. QScopedPointer<ccProgressDialog> pDlg;
  847. if (!filterParams.commandLine)
  848. {
  849. if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
  850. {
  851. ccAskTwoDoubleValuesDlg dlg(QObject::tr("Spatial sigma"),
  852. QObject::tr("Scalar sigma"),
  853. DBL_MIN,
  854. 1.0e9,
  855. spatialSigma,
  856. scalarFieldSigma,
  857. 8,
  858. nullptr,
  859. parent);
  860. dlg.setWindowTitle(QObject::tr("SF bilateral filter"));
  861. dlg.doubleSpinBox1->setStatusTip(QObject::tr("3*sigma = 99.7% attenuation"));
  862. 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"));
  863. if (!dlg.exec())
  864. return false;
  865. //get values
  866. spatialSigma = dlg.doubleSpinBox1->value();
  867. scalarFieldSigma = dlg.doubleSpinBox2->value();
  868. }
  869. else
  870. {
  871. bool ok = false;
  872. spatialSigma = QInputDialog::getDouble( parent,
  873. QObject::tr("SF gaussian/mean/median filter"),
  874. "sigma:",
  875. spatialSigma,
  876. DBL_MIN,
  877. 1.0e9,
  878. 8,
  879. &ok);
  880. if (!ok)
  881. {
  882. return false;
  883. }
  884. }
  885. }
  886. if (parent)
  887. {
  888. pDlg.reset(new ccProgressDialog(true, parent));
  889. pDlg->setAutoClose(false);
  890. }
  891. for (ccHObject* ent : selectedEntities)
  892. {
  893. bool lockedVertices = false;
  894. ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
  895. if (!pc || lockedVertices)
  896. {
  897. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  898. continue;
  899. }
  900. //the algorithm will use the currently displayed SF
  901. CCCoreLib::ScalarField* sf = pc->getCurrentDisplayedScalarField();
  902. if (sf)
  903. {
  904. //we set the displayed SF as "OUT" SF
  905. int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
  906. Q_ASSERT(outSfIdx >= 0);
  907. pc->setCurrentOutScalarField(outSfIdx);
  908. CCCoreLib::ScalarField* outSF = pc->getCurrentOutScalarField();
  909. Q_ASSERT(outSF != nullptr);
  910. QString sfName;
  911. if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::BILATERAL)
  912. {
  913. sfName = QString("%1.bilsmooth(%2,%3)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma).arg(scalarFieldSigma);
  914. }
  915. else if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::GAUSSIAN)
  916. {
  917. sfName = QString("%1.smooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
  918. }
  919. else if (filterParams.filterType == ccPointCloud::RGB_FILTER_TYPES::MEAN)
  920. {
  921. sfName = QString("%1.meansmooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
  922. }
  923. else
  924. {
  925. sfName = QString("%1.medsmooth(%2)").arg(QString::fromStdString(outSF->getName())).arg(spatialSigma);
  926. }
  927. int sfIdx = pc->getScalarFieldIndexByName(sfName.toStdString());
  928. if (sfIdx < 0)
  929. sfIdx = pc->addScalarField(sfName.toStdString()); //output SF has same type as input SF
  930. if (sfIdx >= 0)
  931. pc->setCurrentInScalarField(sfIdx);
  932. else
  933. {
  934. ccConsole::Error(QObject::tr("Failed to create scalar field for cloud '%1' (not enough memory?)").arg(pc->getName()));
  935. return false;
  936. }
  937. ccOctree::Shared octree = pc->getOctree();
  938. if (!octree)
  939. {
  940. octree = pc->computeOctree(parent ? pDlg.data() : nullptr);
  941. if (!octree)
  942. {
  943. ccConsole::Error(QObject::tr("Couldn't compute octree for cloud '%1'!").arg(pc->getName()));
  944. return false;
  945. }
  946. }
  947. QElapsedTimer eTimer;
  948. eTimer.start();
  949. if (!CCCoreLib::ScalarFieldTools::applyScalarFieldGaussianFilter( static_cast<PointCoordinateType>(spatialSigma),
  950. pc,
  951. static_cast<PointCoordinateType>(scalarFieldSigma),
  952. parent ? pDlg.data() : nullptr,
  953. octree.data()))
  954. {
  955. ccConsole::Warning(QObject::tr("[Bilateral/Gaussian/Mean/Median filter] Failed to apply filter"));
  956. return false;
  957. }
  958. ccConsole::Print("SF [Bilateral/Gaussian/Mean/Median filter] Timing: %3.2f s.", eTimer.elapsed() / 1000.0);
  959. pc->setCurrentDisplayedScalarField(sfIdx);
  960. pc->showSF(sfIdx >= 0);
  961. sf = pc->getCurrentDisplayedScalarField();
  962. if (sf)
  963. sf->computeMinAndMax();
  964. pc->prepareDisplayForRefresh_recursive();
  965. }
  966. else
  967. {
  968. ccConsole::Warning(QObject::tr("Entity [%1] has no active scalar field!").arg(pc->getName()));
  969. }
  970. }
  971. return true;
  972. }
  973. bool sfConvertToRGB(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  974. {
  975. //we first ask the user if the SF colors should be mixed with existing colors
  976. bool mixWithExistingColors = false;
  977. QMessageBox::StandardButton answer = QMessageBox::warning( parent,
  978. QObject::tr("Scalar Field to RGB"),
  979. QObject::tr("Mix with existing colors (if any)?"),
  980. QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
  981. QMessageBox::Yes );
  982. if (answer == QMessageBox::Yes)
  983. mixWithExistingColors = true;
  984. else if (answer == QMessageBox::Cancel)
  985. return false;
  986. for (ccHObject* ent : selectedEntities)
  987. {
  988. ccGenericPointCloud* cloud = nullptr;
  989. bool lockedVertices = false;
  990. cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
  991. if (lockedVertices)
  992. {
  993. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  994. continue;
  995. }
  996. if (cloud != nullptr) //TODO
  997. {
  998. ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
  999. //if there is no displayed SF --> nothing to do!
  1000. if (pc->getCurrentDisplayedScalarField())
  1001. {
  1002. if (pc->convertCurrentScalarFieldToColors(mixWithExistingColors))
  1003. {
  1004. ent->showColors(true);
  1005. ent->showSF(false); //just in case
  1006. }
  1007. }
  1008. cloud->prepareDisplayForRefresh_recursive();
  1009. }
  1010. }
  1011. return true;
  1012. }
  1013. bool sfConvertToRandomRGB(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  1014. {
  1015. static int s_randomColorsNumber = 256;
  1016. bool ok = false;
  1017. s_randomColorsNumber = QInputDialog::getInt(parent,
  1018. QObject::tr("Random colors"),
  1019. QObject::tr("Number of random colors (will be regularly sampled over the SF interval):"),
  1020. s_randomColorsNumber,
  1021. 2,
  1022. INT_MAX,
  1023. 16,
  1024. &ok);
  1025. if (!ok)
  1026. return false;
  1027. Q_ASSERT(s_randomColorsNumber > 1);
  1028. RGBAColorsTableType* randomColors = new RGBAColorsTableType;
  1029. if (!randomColors->reserveSafe(static_cast<unsigned>(s_randomColorsNumber)))
  1030. {
  1031. ccConsole::Error(QObject::tr("Not enough memory!"));
  1032. return false;
  1033. }
  1034. //generate random colors
  1035. for (int i = 0; i < s_randomColorsNumber; ++i)
  1036. {
  1037. ccColor::Rgba col(ccColor::Generator::Random(), ccColor::MAX);
  1038. randomColors->addElement(col);
  1039. }
  1040. //apply random colors
  1041. for (ccHObject* ent : selectedEntities)
  1042. {
  1043. ccGenericPointCloud* cloud = nullptr;
  1044. bool lockedVertices = false;
  1045. cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
  1046. if (lockedVertices)
  1047. {
  1048. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  1049. continue;
  1050. }
  1051. if (cloud != nullptr) //TODO
  1052. {
  1053. ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
  1054. ccScalarField* sf = pc->getCurrentDisplayedScalarField();
  1055. //if there is no displayed SF --> nothing to do!
  1056. if (sf && sf->currentSize() >= pc->size())
  1057. {
  1058. if (!pc->resizeTheRGBTable(false))
  1059. {
  1060. ccConsole::Error(QObject::tr("Not enough memory!"));
  1061. break;
  1062. }
  1063. else
  1064. {
  1065. ScalarType minSF = sf->getMin();
  1066. ScalarType maxSF = sf->getMax();
  1067. ScalarType step = (maxSF - minSF) / (s_randomColorsNumber - 1);
  1068. if (step == 0)
  1069. step = static_cast<ScalarType>(1.0);
  1070. for (unsigned i = 0; i < pc->size(); ++i)
  1071. {
  1072. ScalarType val = sf->getValue(i);
  1073. unsigned colIndex = static_cast<unsigned>((val - minSF) / step);
  1074. if (colIndex == s_randomColorsNumber)
  1075. --colIndex;
  1076. pc->setPointColor(i, randomColors->getValue(colIndex));
  1077. }
  1078. pc->showColors(true);
  1079. pc->showSF(false); //just in case
  1080. }
  1081. }
  1082. cloud->prepareDisplayForRefresh_recursive();
  1083. }
  1084. }
  1085. return true;
  1086. }
  1087. bool sfRename(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  1088. {
  1089. for (ccHObject* ent : selectedEntities)
  1090. {
  1091. ccGenericPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent);
  1092. if (cloud != nullptr) //TODO
  1093. {
  1094. ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
  1095. ccScalarField* sf = pc->getCurrentDisplayedScalarField();
  1096. //if there is no displayed SF --> nothing to do!
  1097. if (sf == nullptr)
  1098. {
  1099. ccConsole::Warning(QObject::tr("Cloud %1 has no displayed scalar field!").arg(pc->getName()));
  1100. }
  1101. else
  1102. {
  1103. const std::string& sfName = sf->getName();
  1104. bool ok = false;
  1105. QString newName = QInputDialog::getText(parent,
  1106. QObject::tr("SF name"),
  1107. QObject::tr("name:"),
  1108. QLineEdit::Normal,
  1109. QString(!sfName.empty() ? QString::fromStdString(sfName) : QObject::tr("unknown")),
  1110. &ok);
  1111. if (ok)
  1112. sf->setName(newName.toStdString());
  1113. }
  1114. }
  1115. }
  1116. return true;
  1117. }
  1118. bool sfAddIdField(const ccHObject::Container& selectedEntities, bool storeAsInt)
  1119. {
  1120. for (ccHObject* ent : selectedEntities)
  1121. {
  1122. ccGenericPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent);
  1123. if (nullptr != cloud) //TODO
  1124. {
  1125. ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
  1126. int sfIdx = pc->getScalarFieldIndexByName(CC_DEFAULT_ID_SF_NAME);
  1127. if (sfIdx < 0)
  1128. sfIdx = pc->addScalarField(CC_DEFAULT_ID_SF_NAME);
  1129. if (sfIdx < 0)
  1130. {
  1131. ccLog::Warning(QObject::tr("Not enough memory!"));
  1132. return false;
  1133. }
  1134. CCCoreLib::ScalarField* sf = pc->getScalarField(sfIdx);
  1135. Q_ASSERT(sf->currentSize() == pc->size());
  1136. for (unsigned j = 0; j < cloud->size(); j++)
  1137. {
  1138. ScalarType idValue = 0;
  1139. if (!storeAsInt)
  1140. {
  1141. idValue = static_cast<ScalarType>(j);
  1142. }
  1143. else
  1144. {
  1145. uint8_t* valuePtr = reinterpret_cast<uint8_t*>(&idValue);
  1146. #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  1147. valuePtr[0] = static_cast<unsigned char>(j & 0xff);
  1148. valuePtr[1] = static_cast<unsigned char>((j >> 8) & 0xff);
  1149. valuePtr[2] = static_cast<unsigned char>((j >> 16) & 0xff);
  1150. valuePtr[3] = static_cast<unsigned char>((j >> 24) & 0xff);
  1151. #else
  1152. unsigned char *valuePtr = (unsigned char *)(&idValue);
  1153. valuePtr[3] = static_cast<unsigned char>(j & 0xff);
  1154. valuePtr[2] = static_cast<unsigned char>((j >> 8) & 0xff);
  1155. valuePtr[1] = static_cast<unsigned char>((j >> 16) & 0xff);
  1156. valuePtr[0] = static_cast<unsigned char>((j >> 24) & 0xff);
  1157. #endif
  1158. }
  1159. sf->setValue(j, idValue);
  1160. }
  1161. sf->computeMinAndMax();
  1162. pc->setCurrentDisplayedScalarField(sfIdx);
  1163. pc->showSF(true);
  1164. pc->prepareDisplayForRefresh();
  1165. }
  1166. }
  1167. return true;
  1168. }
  1169. bool sfAddConstant(ccPointCloud* cloud, QString sfName, bool integerValue, QWidget* parent/*=nullptr*/)
  1170. {
  1171. if (!cloud)
  1172. {
  1173. assert(false);
  1174. return false;
  1175. }
  1176. if (sfName.isNull())
  1177. {
  1178. ccLog::Error(QT_TR_NOOP("Invalid name"));
  1179. return false;
  1180. }
  1181. if (cloud->getScalarFieldIndexByName(sfName.toStdString()) >= 0)
  1182. {
  1183. ccLog::Error(QT_TR_NOOP("A SF with a similar name already exists!"));
  1184. return false;
  1185. }
  1186. ScalarType sfValue = 0;
  1187. bool ok = false;
  1188. if (integerValue)
  1189. {
  1190. static int s_constantIntSFValue = 0;
  1191. int iValue = QInputDialog::getInt(parent, QT_TR_NOOP("Add classification SF"), QT_TR_NOOP("value"), s_constantIntSFValue, -1000000, 1000000, 1, &ok);
  1192. if (ok)
  1193. {
  1194. s_constantIntSFValue = iValue;
  1195. sfValue = static_cast<ScalarType>(iValue);
  1196. }
  1197. }
  1198. else
  1199. {
  1200. static double s_constantDoubleSFValue = 0.0;
  1201. 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));
  1202. if (ok)
  1203. {
  1204. s_constantDoubleSFValue = dValue;
  1205. sfValue = static_cast<ScalarType>(dValue);
  1206. }
  1207. }
  1208. if (!ok)
  1209. {
  1210. // cancelled by the user
  1211. return false;
  1212. }
  1213. int sfIdx = cloud->getScalarFieldIndexByName(sfName.toStdString());
  1214. if (sfIdx < 0)
  1215. sfIdx = cloud->addScalarField(sfName.toStdString());
  1216. if (sfIdx < 0)
  1217. {
  1218. ccLog::Error(QT_TR_NOOP("An error occurred! (see console)"));
  1219. return false;
  1220. }
  1221. CCCoreLib::ScalarField* sf = cloud->getScalarField(sfIdx);
  1222. assert(sf);
  1223. if (!sf)
  1224. {
  1225. assert(false);
  1226. return false;
  1227. }
  1228. sf->fill(sfValue);
  1229. sf->computeMinAndMax();
  1230. cloud->setCurrentDisplayedScalarField(sfIdx);
  1231. cloud->showSF(true);
  1232. ccLog::Print(QObject::tr("New scalar field '%1' added to %2 (value = %3)").arg(sfName).arg(cloud->getName()).arg(sfValue));
  1233. return true;
  1234. }
  1235. bool sfSplitCloud(const ccHObject::Container& selectedEntities, ccMainAppInterface* app)
  1236. {
  1237. bool tooManyCloudsQuestionAsked = false;
  1238. for (ccHObject* ent : selectedEntities)
  1239. {
  1240. ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent);
  1241. if (!cloud)
  1242. {
  1243. continue;
  1244. }
  1245. ccScalarField* sf = cloud->getCurrentDisplayedScalarField();
  1246. if (sf == nullptr)
  1247. {
  1248. ccLog::Warning(QString("Cloud %1 has no active scalar field").arg(cloud->getName()));
  1249. return false;
  1250. }
  1251. // count integer values
  1252. size_t N = sf->size();
  1253. std::set<int> classes;
  1254. for (size_t i = 0; i < sf->size(); ++i)
  1255. {
  1256. classes.insert(static_cast<int>(sf->getValue(i)));
  1257. }
  1258. ccLog::Print("[sfSplitCloud] " + QString::number(classes.size()) + " classe(s) found in the current scalar field");
  1259. if (classes.size() == 1)
  1260. {
  1261. ccLog::Warning(QString("[sfSplitCloud] Cloud %1: SF has only one value").arg(cloud->getName()));
  1262. continue;
  1263. }
  1264. if (classes.size() > 30 && app && app->getMainWindow() && !tooManyCloudsQuestionAsked) // only in GUI mode
  1265. {
  1266. // ask the user if creating this many clouds is expected
  1267. 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))
  1268. {
  1269. ccLog::Warning("[sfSplitCloud] Process cancelled by the user");
  1270. return false;
  1271. }
  1272. tooManyCloudsQuestionAsked = true;
  1273. }
  1274. ccHObject* destObject = new ccHObject(cloud->getName() + " classes");
  1275. if (cloud->getParent())
  1276. {
  1277. cloud->getParent()->addChild(destObject);
  1278. }
  1279. // create as many clouds as the number of classes
  1280. for (int pointClass : classes)
  1281. {
  1282. ccLog::Print("[sfSplitCloud] build cloud corresponding to class #" + QString::number(pointClass));
  1283. try
  1284. {
  1285. // create the reference cloud
  1286. CCCoreLib::ReferenceCloud referenceCloud(cloud);
  1287. // populate the cloud with the points which have the selected class
  1288. for (unsigned index = 0; index < static_cast<unsigned>(cloud->size()); index++)
  1289. {
  1290. if (static_cast<int>(sf->getValue(index)) == pointClass)
  1291. {
  1292. referenceCloud.addPointIndex(index);
  1293. }
  1294. }
  1295. ccPointCloud* pc = cloud->partialClone(&referenceCloud);
  1296. if (pc)
  1297. {
  1298. pc->setName("class #" + QString::number(pointClass));
  1299. destObject->addChild(pc);
  1300. }
  1301. else
  1302. {
  1303. ccLog::Warning("[sfSplitCloud] Failed to create cloud");
  1304. }
  1305. }
  1306. catch (const std::bad_alloc&)
  1307. {
  1308. delete destObject;
  1309. destObject = nullptr;
  1310. ccLog::Error(QT_TR_NOOP("Not enough memory"));
  1311. return false;
  1312. }
  1313. }
  1314. // add to database
  1315. app->addToDB(destObject);
  1316. cloud->setEnabled(false);
  1317. cloud->prepareDisplayForRefresh();
  1318. }
  1319. return true;
  1320. }
  1321. static void SetValueFromSF(PointCoordinateType& value, int fieldIndex, CCCoreLib::ScalarField* sf, unsigned pointIndex, PointCoordinateType defaultValueForNaN)
  1322. {
  1323. if (sf)
  1324. {
  1325. ScalarType s = sf->getValue(pointIndex);
  1326. if (CCCoreLib::ScalarField::ValidValue(s))
  1327. {
  1328. value = s;
  1329. }
  1330. else
  1331. {
  1332. value = defaultValueForNaN;
  1333. }
  1334. }
  1335. else
  1336. {
  1337. switch (fieldIndex)
  1338. {
  1339. case ccSetSFsAsVec3Dialog::SF_INDEX_ZERO:
  1340. value = 0;
  1341. break;
  1342. case ccSetSFsAsVec3Dialog::SF_INDEX_ONE:
  1343. value = static_cast<ScalarType>(1);
  1344. break;
  1345. case ccSetSFsAsVec3Dialog::SF_INDEX_UNCHANGED:
  1346. // nothing to do
  1347. break;
  1348. case ccSetSFsAsVec3Dialog::SF_INDEX_NO:
  1349. default:
  1350. assert(false);
  1351. value = defaultValueForNaN;
  1352. break;
  1353. }
  1354. }
  1355. }
  1356. static PointCoordinateType GetDefaultValueForNaN(PointCoordinateType minSFValue, QWidget* parent)
  1357. {
  1358. bool ok = false;
  1359. double out = QInputDialog::getDouble( parent,
  1360. QObject::tr("SF --> coordinate"),
  1361. QObject::tr("Enter the coordinate equivalent to NaN values:"),
  1362. minSFValue,
  1363. -1.0e9,
  1364. 1.0e9,
  1365. 6,
  1366. &ok);
  1367. if (ok)
  1368. {
  1369. return static_cast<PointCoordinateType>(out);
  1370. }
  1371. else
  1372. {
  1373. ccLog::Warning(QObject::tr("[SetSFAsCoord] By default the coordinate equivalent to NaN values will be the minimum SF value"));
  1374. return minSFValue;
  1375. }
  1376. }
  1377. bool sfSetAsCoord(ccHObject* entity, QWidget* parent/*=nullptr*/)
  1378. {
  1379. if (!entity)
  1380. {
  1381. assert(false);
  1382. return false;
  1383. }
  1384. ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(entity);
  1385. if (!cloud || !cloud->isA(CC_TYPES::POINT_CLOUD))
  1386. {
  1387. assert(false);
  1388. ccLog::Warning("[sfSetAsCoord] Expecting a cloud or a mesh");
  1389. return false;
  1390. }
  1391. ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
  1392. ccSetSFsAsVec3Dialog dlg(pc, "X", "Y", "Z", true, parent);
  1393. dlg.setWindowTitle(QObject::tr("Set SFs as coords"));
  1394. static bool s_firstTime = true;
  1395. static int xIndex = ccSetSFsAsVec3Dialog::SF_INDEX_UNCHANGED;
  1396. static int yIndex = ccSetSFsAsVec3Dialog::SF_INDEX_UNCHANGED;
  1397. static int zIndex = ccSetSFsAsVec3Dialog::SF_INDEX_UNCHANGED;
  1398. // restore the previous parameters
  1399. dlg.setSFIndexes(xIndex, yIndex, zIndex);
  1400. if (!dlg.exec())
  1401. {
  1402. return false;
  1403. }
  1404. dlg.getSFIndexes(xIndex, yIndex, zIndex);
  1405. CCCoreLib::ScalarField* sfX = (xIndex >= 0 ? pc->getScalarField(xIndex) : nullptr);
  1406. CCCoreLib::ScalarField* sfY = (yIndex >= 0 ? pc->getScalarField(yIndex) : nullptr);
  1407. CCCoreLib::ScalarField* sfZ = (zIndex >= 0 ? pc->getScalarField(zIndex) : nullptr);
  1408. std::array<CCCoreLib::ScalarField*, 3> scalarFields{ sfX, sfY, sfZ };
  1409. PointCoordinateType defaultCoordForNaN = std::numeric_limits<PointCoordinateType>::quiet_NaN();
  1410. for (CCCoreLib::ScalarField* sf : scalarFields)
  1411. {
  1412. if (sf)
  1413. {
  1414. if (sf->countValidValues() < sf->size())
  1415. {
  1416. //we have some invalid values, let's ask the user what they should be replaced with
  1417. defaultCoordForNaN = GetDefaultValueForNaN(sf->getMin(), parent);
  1418. break;
  1419. }
  1420. }
  1421. }
  1422. unsigned ptsCount = pc->size();
  1423. for (unsigned i = 0; i < ptsCount; ++i)
  1424. {
  1425. const CCVector3* P = pc->getPoint(i);
  1426. CCVector3 newP = *P;
  1427. SetValueFromSF(newP.x, xIndex, sfX, i, defaultCoordForNaN);
  1428. SetValueFromSF(newP.y, yIndex, sfY, i, defaultCoordForNaN);
  1429. SetValueFromSF(newP.z, zIndex, sfZ, i, defaultCoordForNaN);
  1430. *const_cast<CCVector3*>(P) = newP;
  1431. }
  1432. pc->invalidateBoundingBox();
  1433. if (entity->isKindOf(CC_TYPES::MESH))
  1434. {
  1435. static_cast<ccGenericMesh*>(entity)->refreshBB();
  1436. }
  1437. entity->prepareDisplayForRefresh();
  1438. return true;
  1439. }
  1440. bool sfSetAsCoord(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/)
  1441. {
  1442. if (selectedEntities.size() == 1)
  1443. {
  1444. // shortcut for single entities, with a smarter dialog
  1445. return sfSetAsCoord(selectedEntities.front(), parent);
  1446. }
  1447. ccExportCoordToSFDlg ectsDlg(parent);
  1448. ectsDlg.warningLabel->setVisible(false);
  1449. ectsDlg.setWindowTitle(QObject::tr("Export SF to coordinate(s)"));
  1450. if (!ectsDlg.exec())
  1451. {
  1452. return false;
  1453. }
  1454. bool importDim[3] { ectsDlg.exportX(), ectsDlg.exportY(), ectsDlg.exportZ() };
  1455. if (!importDim[0] && !importDim[1] && !importDim[2]) //nothing to do?!
  1456. {
  1457. return false;
  1458. }
  1459. ScalarType defaultValueForNaN = CCCoreLib::NAN_VALUE;
  1460. //for each selected cloud (or vertices set)
  1461. for (ccHObject* ent : selectedEntities)
  1462. {
  1463. ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent);
  1464. if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD))
  1465. {
  1466. ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
  1467. ccScalarField* sf = pc->getCurrentDisplayedScalarField();
  1468. if (sf != nullptr)
  1469. {
  1470. if (std::isnan(defaultValueForNaN) && (sf->countValidValues() < sf->size()))
  1471. {
  1472. //we have some invalid values, let's ask the user what they should be replaced with
  1473. defaultValueForNaN = GetDefaultValueForNaN(sf->getMin(), parent);
  1474. break;
  1475. }
  1476. pc->setCoordFromSF(importDim, sf, defaultValueForNaN);
  1477. }
  1478. }
  1479. if (ent->isKindOf(CC_TYPES::MESH))
  1480. {
  1481. static_cast<ccGenericMesh*>(ent)->refreshBB();
  1482. }
  1483. }
  1484. return true;
  1485. }
  1486. bool exportCoordToSF(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/)
  1487. {
  1488. ccExportCoordToSFDlg ectsDlg(parent);
  1489. if (!ectsDlg.exec())
  1490. {
  1491. return false;
  1492. }
  1493. bool exportDims[3] { ectsDlg.exportX(),
  1494. ectsDlg.exportY(),
  1495. ectsDlg.exportZ() };
  1496. if (!exportDims[0] && !exportDims[1] && !exportDims[2]) //nothing to do?!
  1497. {
  1498. return false;
  1499. }
  1500. //for each selected cloud (or vertices set)
  1501. for (ccHObject* entity : selectedEntities)
  1502. {
  1503. ccPointCloud* pc = ccHObjectCaster::ToPointCloud(entity);
  1504. if (pc == nullptr)
  1505. {
  1506. // TODO do something with error?
  1507. continue;
  1508. }
  1509. if (!pc->exportCoordToSF(exportDims))
  1510. {
  1511. ccLog::Error(QObject::tr("The process failed!"));
  1512. return true; //true because we want the UI to be updated anyway
  1513. }
  1514. if (entity != pc)
  1515. {
  1516. entity->showSF(true); //for meshes
  1517. }
  1518. entity->prepareDisplayForRefresh_recursive();
  1519. }
  1520. return true;
  1521. }
  1522. bool setSFsAsNormal(ccHObject* entity, QWidget* parent/*=nullptr*/)
  1523. {
  1524. ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity);
  1525. if (cloud == nullptr)
  1526. {
  1527. // unhandled entity
  1528. return false;
  1529. }
  1530. const bool cloudHadNormals = cloud->hasNormals();
  1531. ccSetSFsAsVec3Dialog dlg(cloud, "Nx", "Ny", "Nz", cloudHadNormals, parent);
  1532. dlg.setWindowTitle(QObject::tr("Set SFs as normals"));
  1533. static bool s_firstTime = true;
  1534. static int nxIndex = ccSetSFsAsVec3Dialog::SF_INDEX_ZERO;
  1535. static int nyIndex = ccSetSFsAsVec3Dialog::SF_INDEX_ZERO;
  1536. static int nzIndex = ccSetSFsAsVec3Dialog::SF_INDEX_ZERO;
  1537. if (s_firstTime)
  1538. {
  1539. s_firstTime = false;
  1540. }
  1541. else
  1542. {
  1543. // restore the previous parameters
  1544. dlg.setSFIndexes(nxIndex, nyIndex, nzIndex);
  1545. }
  1546. if (!dlg.exec())
  1547. {
  1548. return false;
  1549. }
  1550. dlg.getSFIndexes(nxIndex, nyIndex, nzIndex);
  1551. if (!cloud->resizeTheNormsTable())
  1552. {
  1553. ccLog::Error("Not enough memory");
  1554. }
  1555. CCCoreLib::ScalarField* sfX = (nxIndex >= 0 ? cloud->getScalarField(nxIndex) : nullptr);
  1556. CCCoreLib::ScalarField* sfY = (nyIndex >= 0 ? cloud->getScalarField(nyIndex) : nullptr);
  1557. CCCoreLib::ScalarField* sfZ = (nzIndex >= 0 ? cloud->getScalarField(nzIndex) : nullptr);
  1558. for (unsigned i = 0; i < cloud->size(); ++i)
  1559. {
  1560. CCVector3f N(0, 0, 0);
  1561. if (cloudHadNormals)
  1562. {
  1563. N = cloud->getPointNormal(i);
  1564. }
  1565. SetValueFromSF(N.x, nxIndex, sfX, i, 0);
  1566. SetValueFromSF(N.y, nyIndex, sfY, i, 0);
  1567. SetValueFromSF(N.z, nzIndex, sfZ, i, 0);
  1568. N.normalize();
  1569. cloud->setPointNormal(i, N);
  1570. }
  1571. cloud->showNormals(true);
  1572. cloud->prepareDisplayForRefresh();
  1573. if (entity != cloud)
  1574. {
  1575. entity->showNormals(true);
  1576. entity->prepareDisplayForRefresh();
  1577. if (entity->isKindOf(CC_TYPES::MESH))
  1578. {
  1579. static_cast<ccGenericMesh*>(entity)->showTriNorms(false); // hide the per-triangle normals (if any)
  1580. }
  1581. }
  1582. return true;
  1583. }
  1584. bool exportNormalToSF(const ccHObject::Container& selectedEntities, QWidget* parent/*=nullptr*/, bool* exportDimensions/*=nullptr*/)
  1585. {
  1586. bool exportDims[3] { false, false, false };
  1587. if (exportDimensions)
  1588. {
  1589. exportDims[0] = exportDimensions[0];
  1590. exportDims[1] = exportDimensions[1];
  1591. exportDims[2] = exportDimensions[2];
  1592. }
  1593. else
  1594. {
  1595. //ask the user
  1596. ccExportCoordToSFDlg ectsDlg(parent);
  1597. ectsDlg.setWindowTitle(QObject::tr("Export normals to SF(s)"));
  1598. if (!ectsDlg.exec())
  1599. {
  1600. return false;
  1601. }
  1602. exportDims[0] = ectsDlg.exportX();
  1603. exportDims[1] = ectsDlg.exportY();
  1604. exportDims[2] = ectsDlg.exportZ();
  1605. }
  1606. if (!exportDims[0] && !exportDims[1] && !exportDims[2]) //nothing to do?!
  1607. {
  1608. return false;
  1609. }
  1610. //for each selected cloud (or vertices set)
  1611. for (ccHObject* entity : selectedEntities)
  1612. {
  1613. ccPointCloud* pc = ccHObjectCaster::ToPointCloud(entity);
  1614. if (pc == nullptr)
  1615. {
  1616. // TODO do something with error?
  1617. continue;
  1618. }
  1619. if (!pc->hasNormals())
  1620. {
  1621. ccLog::Warning(QObject::tr("Cloud '%1' has no normals").arg(pc->getName()));
  1622. continue;
  1623. }
  1624. if (!pc->exportNormalToSF(exportDims))
  1625. {
  1626. ccLog::Error(QObject::tr("The process failed!"));
  1627. return true; //true because we want the UI to be updated anyway
  1628. }
  1629. if (entity != pc)
  1630. {
  1631. entity->showSF(true); //for meshes
  1632. }
  1633. entity->prepareDisplayForRefresh_recursive();
  1634. }
  1635. return true;
  1636. }
  1637. bool sfArithmetic(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  1638. {
  1639. Q_ASSERT(!selectedEntities.empty());
  1640. ccHObject* entity = selectedEntities[0];
  1641. bool lockedVertices;
  1642. ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(entity, &lockedVertices);
  1643. if (lockedVertices)
  1644. {
  1645. ccUtils::DisplayLockedVerticesWarning(entity->getName(), true);
  1646. return false;
  1647. }
  1648. if (cloud == nullptr)
  1649. {
  1650. return false;
  1651. }
  1652. ccScalarFieldArithmeticsDlg sfaDlg(cloud, parent);
  1653. if (!sfaDlg.exec())
  1654. {
  1655. return false;
  1656. }
  1657. if (!sfaDlg.apply(cloud))
  1658. {
  1659. ccConsole::Error(QObject::tr("An error occurred (see Console for more details)"));
  1660. }
  1661. cloud->showSF(true);
  1662. cloud->prepareDisplayForRefresh_recursive();
  1663. return true;
  1664. }
  1665. bool sfFromColor(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  1666. {
  1667. ccScalarFieldFromColorDlg dialog(parent);
  1668. if (!dialog.exec())
  1669. return false;
  1670. const bool exportR = dialog.getRStatus();
  1671. const bool exportG = dialog.getGStatus();
  1672. const bool exportB = dialog.getBStatus();
  1673. const bool exportAlpha = dialog.getAlphaStatus();
  1674. const bool exportComposite = dialog.getCompositeStatus();
  1675. return sfFromColor(selectedEntities, exportR, exportG, exportB, exportAlpha, exportComposite);
  1676. }
  1677. bool sfFromColor(const ccHObject::Container &selectedEntities, bool exportR, bool exportG, bool exportB, bool exportAlpha, bool exportComposite)
  1678. {
  1679. //candidates
  1680. std::unordered_set<ccPointCloud*> clouds;
  1681. for (ccHObject* ent : selectedEntities)
  1682. {
  1683. ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent);
  1684. if (cloud && ent->hasColors()) //only for clouds (or vertices)
  1685. clouds.insert( cloud );
  1686. }
  1687. if (clouds.empty())
  1688. return false;
  1689. for (const auto cloud : clouds)
  1690. {
  1691. std::vector<ccScalarField*> fields(5, nullptr);
  1692. fields[0] = (exportR ? new ccScalarField(GetFirstAvailableSFName(cloud, "R").toStdString()) : nullptr);
  1693. fields[1] = (exportG ? new ccScalarField(GetFirstAvailableSFName(cloud, "G").toStdString()) : nullptr);
  1694. fields[2] = (exportB ? new ccScalarField(GetFirstAvailableSFName(cloud, "B").toStdString()) : nullptr);
  1695. fields[3] = (exportAlpha ? new ccScalarField(GetFirstAvailableSFName(cloud, "Alpha").toStdString()) : nullptr);
  1696. fields[4] = (exportComposite ? new ccScalarField(GetFirstAvailableSFName(cloud, "Composite").toStdString()) : nullptr);
  1697. //try to instantiate memory for each field
  1698. unsigned count = cloud->size();
  1699. for (ccScalarField*& sf : fields)
  1700. {
  1701. if (sf && !sf->reserveSafe(count))
  1702. {
  1703. ccLog::Warning(QObject::tr("[SfFromColor] Not enough memory to instantiate SF '%1' on cloud '%2'").arg(QString::fromStdString(sf->getName()), cloud->getName()));
  1704. sf->release();
  1705. sf = nullptr;
  1706. }
  1707. }
  1708. //export points
  1709. for (unsigned j = 0; j < cloud->size(); ++j)
  1710. {
  1711. const ccColor::Rgba& col = cloud->getPointColor(j);
  1712. if (fields[0])
  1713. fields[0]->addElement(col.r);
  1714. if (fields[1])
  1715. fields[1]->addElement(col.g);
  1716. if (fields[2])
  1717. fields[2]->addElement(col.b);
  1718. if (fields[3])
  1719. fields[3]->addElement(col.a);
  1720. if (fields[4])
  1721. fields[4]->addElement(static_cast<ScalarType>(col.r + col.g + col.b) / 3);
  1722. }
  1723. QString fieldsStr;
  1724. for (ccScalarField*& sf : fields)
  1725. {
  1726. if (sf == nullptr)
  1727. continue;
  1728. sf->computeMinAndMax();
  1729. int sfIdx = cloud->getScalarFieldIndexByName(sf->getName());
  1730. if (sfIdx >= 0)
  1731. cloud->deleteScalarField(sfIdx);
  1732. sfIdx = cloud->addScalarField(sf);
  1733. Q_ASSERT(sfIdx >= 0);
  1734. if (sfIdx >= 0)
  1735. {
  1736. cloud->setCurrentDisplayedScalarField(sfIdx);
  1737. cloud->showSF(true);
  1738. cloud->prepareDisplayForRefresh();
  1739. //mesh vertices?
  1740. if (cloud->getParent() && cloud->getParent()->isKindOf(CC_TYPES::MESH))
  1741. {
  1742. cloud->getParent()->showSF(true);
  1743. cloud->getParent()->prepareDisplayForRefresh();
  1744. }
  1745. if (!fieldsStr.isEmpty())
  1746. {
  1747. fieldsStr.append(", ");
  1748. }
  1749. fieldsStr.append(QString::fromStdString(sf->getName()));
  1750. }
  1751. else
  1752. {
  1753. ccConsole::Warning(QObject::tr("[SfFromColor] Failed to add scalar field '%1' to cloud '%2'?!").arg(QString::fromStdString(sf->getName()), cloud->getName()));
  1754. sf->release();
  1755. sf = nullptr;
  1756. }
  1757. }
  1758. if (!fieldsStr.isEmpty())
  1759. ccLog::Print(QObject::tr("[SfFromColor] New scalar fields (%1) added to '%2'").arg(fieldsStr, cloud->getName()));
  1760. }
  1761. return true;
  1762. }
  1763. bool processMeshSF(const ccHObject::Container &selectedEntities, ccMesh::MESH_SCALAR_FIELD_PROCESS process, QWidget* parent/*=nullptr*/)
  1764. {
  1765. for (ccHObject* ent : selectedEntities)
  1766. {
  1767. if (ent->isKindOf(CC_TYPES::MESH) || ent->isKindOf(CC_TYPES::PRIMITIVE)) //TODO
  1768. {
  1769. ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
  1770. if (mesh == nullptr)
  1771. continue;
  1772. ccGenericPointCloud* cloud = mesh->getAssociatedCloud();
  1773. if (cloud == nullptr)
  1774. continue;
  1775. if (cloud->isA(CC_TYPES::POINT_CLOUD)) //TODO
  1776. {
  1777. ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
  1778. //on active le champ scalaire actuellement affiche
  1779. int sfIdx = pc->getCurrentDisplayedScalarFieldIndex();
  1780. if (sfIdx >= 0)
  1781. {
  1782. pc->setCurrentScalarField(sfIdx);
  1783. mesh->processScalarField(process);
  1784. pc->getCurrentInScalarField()->computeMinAndMax();
  1785. mesh->prepareDisplayForRefresh_recursive();
  1786. }
  1787. else
  1788. {
  1789. ccConsole::Warning(QObject::tr("Mesh [%1] vertices have no activated scalar field!").arg(mesh->getName()));
  1790. }
  1791. }
  1792. }
  1793. }
  1794. return true;
  1795. }
  1796. //////////
  1797. // Normals
  1798. bool computeNormals(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  1799. {
  1800. if (selectedEntities.empty())
  1801. {
  1802. ccConsole::Error(QObject::tr("Select at least one point cloud"));
  1803. return false;
  1804. }
  1805. static const QString s_NormalScaleKey("Normal scale");
  1806. //look for clouds and meshes
  1807. std::vector<ccPointCloud*> clouds;
  1808. bool withScanGrid = false;
  1809. bool withSensor = false;
  1810. std::vector<ccMesh*> meshes;
  1811. PointCoordinateType defaultRadius = 0;
  1812. try
  1813. {
  1814. for (const auto entity : selectedEntities)
  1815. {
  1816. if (entity->isA(CC_TYPES::POINT_CLOUD))
  1817. {
  1818. ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
  1819. clouds.push_back(cloud);
  1820. if (!withScanGrid)
  1821. {
  1822. withScanGrid = (cloud->gridCount() > 0);
  1823. }
  1824. if (!withSensor)
  1825. {
  1826. for (unsigned i = 0; i < cloud->getChildrenNumber(); ++i)
  1827. {
  1828. if (cloud->hasSensor())
  1829. {
  1830. withSensor = true;
  1831. break; //no need to look anyfurther
  1832. }
  1833. }
  1834. }
  1835. //does the cloud have a former radius value saved as meta-data?
  1836. if (cloud->hasMetaData(s_NormalScaleKey))
  1837. {
  1838. bool ok = false;
  1839. double formerRadius = cloud->getMetaData(s_NormalScaleKey).toDouble(&ok);
  1840. if (ok)
  1841. {
  1842. //remember the largest radius
  1843. defaultRadius = std::max(defaultRadius, static_cast<PointCoordinateType>(formerRadius));
  1844. }
  1845. else
  1846. {
  1847. assert(false);
  1848. }
  1849. }
  1850. if (defaultRadius == 0.0)
  1851. {
  1852. //default radius
  1853. defaultRadius = ccOctree::GuessNaiveRadius(cloud);
  1854. }
  1855. }
  1856. else if (entity->isKindOf(CC_TYPES::MESH))
  1857. {
  1858. if (entity->isA(CC_TYPES::MESH))
  1859. {
  1860. ccMesh* mesh = ccHObjectCaster::ToMesh(entity);
  1861. meshes.push_back(mesh);
  1862. }
  1863. else
  1864. {
  1865. ccConsole::Error(QObject::tr("Can't compute normals on sub-meshes! Select the parent mesh instead"));
  1866. return false;
  1867. }
  1868. }
  1869. }
  1870. }
  1871. catch (const std::bad_alloc&)
  1872. {
  1873. ccConsole::Error(QObject::tr("Not enough memory!"));
  1874. return false;
  1875. }
  1876. //compute normals for each selected cloud
  1877. if (!clouds.empty())
  1878. {
  1879. static CCCoreLib::LOCAL_MODEL_TYPES s_lastModelType = CCCoreLib::LS;
  1880. static ccNormalVectors::Orientation s_lastNormalOrientation = ccNormalVectors::UNDEFINED;
  1881. static int s_lastMSTNeighborCount = 6;
  1882. static double s_lastMinGridAngle_deg = 1.0;
  1883. static bool s_orientNormals = true;
  1884. ccNormalComputationDlg ncDlg(withScanGrid, withSensor, parent);
  1885. ncDlg.setLocalModel(s_lastModelType);
  1886. ncDlg.setRadius(defaultRadius);
  1887. ncDlg.setPreferredOrientation(s_lastNormalOrientation);
  1888. ncDlg.setOrientNormals(s_orientNormals);
  1889. ncDlg.setMSTNeighborCount(s_lastMSTNeighborCount);
  1890. ncDlg.setMinGridAngle_deg(s_lastMinGridAngle_deg);
  1891. if (clouds.size() == 1)
  1892. {
  1893. ncDlg.setCloud(clouds.front());
  1894. }
  1895. if (!ncDlg.exec())
  1896. {
  1897. return false;
  1898. }
  1899. //normals computation
  1900. CCCoreLib::LOCAL_MODEL_TYPES model = s_lastModelType = ncDlg.getLocalModel();
  1901. bool useGridStructure = withScanGrid && ncDlg.useScanGridsForComputation();
  1902. defaultRadius = ncDlg.getRadius();
  1903. double minGridAngle_deg = s_lastMinGridAngle_deg = ncDlg.getMinGridAngle_deg();
  1904. //normals orientation
  1905. s_orientNormals = ncDlg.orientNormals();
  1906. bool orientNormalsWithGrids = withScanGrid && ncDlg.useScanGridsForOrientation();
  1907. bool orientNormalsWithSensors = withSensor && ncDlg.useSensorsForOrientation();
  1908. ccNormalVectors::Orientation preferredOrientation = s_lastNormalOrientation = ncDlg.getPreferredOrientation();
  1909. bool orientNormalsMST = ncDlg.useMSTOrientation();
  1910. int mstNeighbors = s_lastMSTNeighborCount = ncDlg.getMSTNeighborCount();
  1911. ccProgressDialog pDlg(true, parent);
  1912. pDlg.setAutoClose(false);
  1913. size_t errors = 0;
  1914. for (auto cloud : clouds)
  1915. {
  1916. Q_ASSERT(cloud != nullptr);
  1917. bool result = false;
  1918. bool normalsAlreadyOriented = false;
  1919. if (useGridStructure && cloud->gridCount())
  1920. {
  1921. #if 0
  1922. ccPointCloud* newCloud = new ccPointCloud("temp");
  1923. newCloud->reserve(cloud->size());
  1924. for (size_t gi=0; gi<cloud->gridCount(); ++gi)
  1925. {
  1926. const ccPointCloud::Grid::Shared& scanGrid = cloud->grid(gi);
  1927. if (scanGrid && scanGrid->indexes.empty())
  1928. {
  1929. //empty grid, we skip it
  1930. continue;
  1931. }
  1932. ccGLMatrixd toSensor = scanGrid->sensorPosition.inverse();
  1933. const int* _indexGrid = scanGrid->indexes.data();
  1934. for (int j = 0; j < static_cast<int>(scanGrid->h); ++j)
  1935. {
  1936. for (int i = 0; i < static_cast<int>(scanGrid->w); ++i, ++_indexGrid)
  1937. {
  1938. if (*_indexGrid >= 0)
  1939. {
  1940. unsigned pointIndex = static_cast<unsigned>(*_indexGrid);
  1941. const CCVector3* P = cloud->getPoint(pointIndex);
  1942. CCVector3 Q = toSensor * (*P);
  1943. newCloud->addPoint(Q);
  1944. }
  1945. }
  1946. }
  1947. addToDB(newCloud);
  1948. }
  1949. #endif
  1950. //compute normals with the associated scan grid(s)
  1951. normalsAlreadyOriented = true;
  1952. result = cloud->computeNormalsWithGrids(minGridAngle_deg, &pDlg);
  1953. }
  1954. else
  1955. {
  1956. //compute normals with the octree
  1957. normalsAlreadyOriented = s_orientNormals && (preferredOrientation != ccNormalVectors::UNDEFINED);
  1958. result = cloud->computeNormalsWithOctree(model, s_orientNormals ? preferredOrientation : ccNormalVectors::UNDEFINED, defaultRadius, &pDlg);
  1959. if (result)
  1960. {
  1961. //save the normal computation radius as meta-data
  1962. cloud->setMetaData(s_NormalScaleKey, defaultRadius);
  1963. }
  1964. }
  1965. //do we need to orient the normals? (this may have been already done if 'orientNormalsForThisCloud' is true)
  1966. if (result && s_orientNormals && !normalsAlreadyOriented)
  1967. {
  1968. if (cloud->gridCount() && orientNormalsWithGrids)
  1969. {
  1970. //we can still use the grid structure(s) to orient the normals!
  1971. result = cloud->orientNormalsWithGrids();
  1972. }
  1973. else if (cloud->hasSensor() && orientNormalsWithSensors)
  1974. {
  1975. result = false;
  1976. // RJ: TODO: the issue here is that a cloud can have multiple sensors.
  1977. // As the association to sensor is not explicit in CC, given a cloud
  1978. // some points can belong to one sensor and some others can belongs to others sensors.
  1979. // so it's why here grid orientation has precedence over sensor orientation because in this
  1980. // case association is more explicit.
  1981. // Here we take the first valid viewpoint for now even if it's not a good one...
  1982. for (unsigned i = 0; i < cloud->getChildrenNumber(); ++i)
  1983. {
  1984. ccHObject* child = cloud->getChild(i);
  1985. if (child && child->isKindOf(CC_TYPES::SENSOR))
  1986. {
  1987. ccSensor* sensor = ccHObjectCaster::ToSensor(child);
  1988. CCVector3 sensorPosition;
  1989. if (sensor->getActiveAbsoluteCenter(sensorPosition))
  1990. {
  1991. result = cloud->orientNormalsTowardViewPoint(sensorPosition, &pDlg);
  1992. break;
  1993. }
  1994. }
  1995. }
  1996. }
  1997. else if (orientNormalsMST)
  1998. {
  1999. //use Minimum Spanning Tree to resolve normals direction
  2000. result = cloud->orientNormalsWithMST(mstNeighbors, &pDlg);
  2001. }
  2002. }
  2003. if (!result)
  2004. {
  2005. ++errors;
  2006. }
  2007. cloud->prepareDisplayForRefresh();
  2008. }
  2009. if (errors != 0)
  2010. {
  2011. if (errors < clouds.size())
  2012. ccConsole::Error(QObject::tr("Failed to compute or orient the normals on some clouds! (see console)"));
  2013. else
  2014. ccConsole::Error(QObject::tr("Failed to compute or orient the normals! (see console)"));
  2015. }
  2016. }
  2017. //compute normals for each selected mesh
  2018. if (!meshes.empty())
  2019. {
  2020. QMessageBox question( QMessageBox::Question,
  2021. QObject::tr("Mesh normals"),
  2022. QObject::tr("Compute per-vertex normals (smooth) or per-triangle (faceted)?"),
  2023. QMessageBox::NoButton,
  2024. parent);
  2025. QPushButton* perVertexButton = question.addButton(QObject::tr("Per-vertex"), QMessageBox::YesRole);
  2026. QPushButton* perTriangleButton = question.addButton(QObject::tr("Per-triangle"), QMessageBox::NoRole);
  2027. question.exec();
  2028. bool computePerVertexNormals = (question.clickedButton() == perVertexButton);
  2029. for (auto mesh : meshes)
  2030. {
  2031. Q_ASSERT(mesh != nullptr);
  2032. //we remove temporarily the mesh as its normals may be removed (and they can be a child object)
  2033. ccMainAppInterface* instance = dynamic_cast<ccMainAppInterface*>(parent);
  2034. ccMainAppInterface::ccHObjectContext objContext;
  2035. if (instance)
  2036. objContext = instance->removeObjectTemporarilyFromDBTree(mesh);
  2037. mesh->clearTriNormals();
  2038. mesh->showNormals(false);
  2039. bool result = mesh->computeNormals(computePerVertexNormals);
  2040. if (instance)
  2041. instance->putObjectBackIntoDBTree(mesh, objContext);
  2042. if (!result)
  2043. {
  2044. ccConsole::Error(QObject::tr("Failed to compute normals on mesh '%1'").arg(mesh->getName()));
  2045. continue;
  2046. }
  2047. mesh->prepareDisplayForRefresh_recursive();
  2048. }
  2049. }
  2050. return true;
  2051. }
  2052. bool invertNormals(const ccHObject::Container &selectedEntities)
  2053. {
  2054. for (ccHObject* ent : selectedEntities)
  2055. {
  2056. // is it a mesh?
  2057. ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
  2058. if (mesh && mesh->hasNormals())
  2059. {
  2060. mesh->invertNormals();
  2061. mesh->showNormals(true);
  2062. mesh->prepareDisplayForRefresh_recursive();
  2063. continue;
  2064. }
  2065. // is it a cloud?
  2066. bool lockedVertices;
  2067. ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(ent, &lockedVertices);
  2068. if (cloud && cloud->hasNormals())
  2069. {
  2070. if (lockedVertices)
  2071. {
  2072. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  2073. continue;
  2074. }
  2075. cloud->invertNormals();
  2076. cloud->showNormals(true);
  2077. cloud->prepareDisplayForRefresh_recursive();
  2078. }
  2079. }
  2080. return true;
  2081. }
  2082. bool orientNormalsFM(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  2083. {
  2084. if (selectedEntities.empty())
  2085. {
  2086. ccConsole::Error(QObject::tr("Select at least one point cloud"));
  2087. return false;
  2088. }
  2089. bool ok = false;
  2090. const int s_defaultLevel = 6;
  2091. int value = QInputDialog::getInt( parent,
  2092. QObject::tr("Orient normals (FM)"),
  2093. QObject::tr("Octree level"),
  2094. s_defaultLevel,
  2095. 1, CCCoreLib::DgmOctree::MAX_OCTREE_LEVEL,
  2096. 1,
  2097. &ok);
  2098. if (!ok)
  2099. return false;
  2100. Q_ASSERT(value >= 0 && value <= 255);
  2101. unsigned char level = static_cast<unsigned char>(value);
  2102. ccProgressDialog pDlg(false, parent);
  2103. pDlg.setAutoClose(false);
  2104. size_t errors = 0;
  2105. for (ccHObject* entity : selectedEntities)
  2106. {
  2107. if (!entity->isA(CC_TYPES::POINT_CLOUD))
  2108. continue;
  2109. ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
  2110. if (!cloud->hasNormals())
  2111. {
  2112. ccConsole::Warning(QObject::tr("Cloud '%1' has no normals!").arg(cloud->getName()));
  2113. continue;
  2114. }
  2115. //orient normals with Fast Marching
  2116. if (cloud->orientNormalsWithFM(level, &pDlg))
  2117. {
  2118. cloud->prepareDisplayForRefresh();
  2119. }
  2120. else
  2121. {
  2122. ++errors;
  2123. }
  2124. }
  2125. if (errors)
  2126. {
  2127. ccConsole::Error(QObject::tr("Process failed (check console)"));
  2128. }
  2129. else
  2130. {
  2131. ccLog::Warning(QObject::tr("Normals have been oriented: you may still have to globally invert the cloud normals however (Edit > Normals > Invert)."));
  2132. }
  2133. return true;
  2134. }
  2135. bool orientNormalsMST(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  2136. {
  2137. if (selectedEntities.empty())
  2138. {
  2139. ccConsole::Error(QObject::tr("Select at least one point cloud"));
  2140. return false;
  2141. }
  2142. bool ok = false;
  2143. static unsigned s_defaultKNN = 6;
  2144. unsigned kNN = static_cast<unsigned>(QInputDialog::getInt( parent,
  2145. QObject::tr("Neighborhood size"),
  2146. QObject::tr("Neighbors"),
  2147. s_defaultKNN ,
  2148. 1, 1000,
  2149. 1,
  2150. &ok));
  2151. if (!ok)
  2152. return false;
  2153. s_defaultKNN = kNN;
  2154. ccProgressDialog pDlg(true, parent);
  2155. pDlg.setAutoClose(false);
  2156. size_t errors = 0;
  2157. for (ccHObject* entity : selectedEntities)
  2158. {
  2159. if (!entity->isA(CC_TYPES::POINT_CLOUD))
  2160. continue;
  2161. ccPointCloud* cloud = static_cast<ccPointCloud*>(entity);
  2162. if (!cloud->hasNormals())
  2163. {
  2164. ccConsole::Warning(QObject::tr("Cloud '%1' has no normals!").arg(cloud->getName()));
  2165. continue;
  2166. }
  2167. //use Minimum Spanning Tree to resolve normals direction
  2168. if (cloud->orientNormalsWithMST(kNN, &pDlg))
  2169. {
  2170. cloud->prepareDisplayForRefresh();
  2171. }
  2172. else
  2173. {
  2174. ccConsole::Warning(QObject::tr("Process failed on cloud '%1'").arg(cloud->getName()));
  2175. ++errors;
  2176. }
  2177. }
  2178. if (errors)
  2179. {
  2180. ccConsole::Error(QObject::tr("Process failed (check console)"));
  2181. }
  2182. else
  2183. {
  2184. ccLog::Warning(QObject::tr("Normals have been oriented: you may still have to globally invert the cloud normals however (Edit > Normals > Invert)."));
  2185. }
  2186. return true;
  2187. }
  2188. bool convertNormalsTo(const ccHObject::Container &selectedEntities, NORMAL_CONVERSION_DEST dest)
  2189. {
  2190. size_t errorCount = 0;
  2191. size_t successCount = 0;
  2192. size_t selNum = selectedEntities.size();
  2193. for (size_t i = 0; i < selNum; ++i)
  2194. {
  2195. ccHObject* ent = selectedEntities[i];
  2196. bool lockedVertices = false;
  2197. ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
  2198. if (lockedVertices)
  2199. {
  2200. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selNum == 1);
  2201. continue;
  2202. }
  2203. if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD)) // TODO
  2204. {
  2205. ccPointCloud* ccCloud = static_cast<ccPointCloud*>(cloud);
  2206. if (ccCloud->hasNormals())
  2207. {
  2208. bool success = true;
  2209. switch(dest)
  2210. {
  2211. case NORMAL_CONVERSION_DEST::HSV_COLORS:
  2212. {
  2213. success = ccCloud->convertNormalToRGB();
  2214. if (success)
  2215. {
  2216. ccCloud->showSF(false);
  2217. ccCloud->showNormals(false);
  2218. ccCloud->showColors(true);
  2219. ++successCount;
  2220. }
  2221. }
  2222. break;
  2223. case NORMAL_CONVERSION_DEST::DIP_DIR_SFS:
  2224. {
  2225. //get/create 'dip' scalar field
  2226. int dipSFIndex = ccCloud->getScalarFieldIndexByName(CC_DEFAULT_DIP_SF_NAME);
  2227. if (dipSFIndex < 0)
  2228. dipSFIndex = ccCloud->addScalarField(CC_DEFAULT_DIP_SF_NAME);
  2229. if (dipSFIndex < 0)
  2230. {
  2231. ccLog::Warning(QObject::tr("[ccEntityAction::convertNormalsTo] Not enough memory!"));
  2232. success = false;
  2233. break;
  2234. }
  2235. //get/create 'dip direction' scalar field
  2236. int dipDirSFIndex = ccCloud->getScalarFieldIndexByName(CC_DEFAULT_DIP_DIR_SF_NAME);
  2237. if (dipDirSFIndex < 0)
  2238. dipDirSFIndex = ccCloud->addScalarField(CC_DEFAULT_DIP_DIR_SF_NAME);
  2239. if (dipDirSFIndex < 0)
  2240. {
  2241. ccCloud->deleteScalarField(dipSFIndex);
  2242. ccLog::Warning(QObject::tr("[ccEntityAction::convertNormalsTo] Not enough memory!"));
  2243. success = false;
  2244. break;
  2245. }
  2246. ccScalarField* dipSF = static_cast<ccScalarField*>(ccCloud->getScalarField(dipSFIndex));
  2247. ccScalarField* dipDirSF = static_cast<ccScalarField*>(ccCloud->getScalarField(dipDirSFIndex));
  2248. Q_ASSERT(dipSF && dipDirSF);
  2249. success = ccCloud->convertNormalToDipDirSFs(dipSF, dipDirSF);
  2250. if (success)
  2251. {
  2252. //apply default 360 degrees color scale!
  2253. ccColorScale::Shared dipScale = ccColorScalesManager::GetDefaultScale(ccColorScalesManager::DIP_BRYW);
  2254. ccColorScale::Shared dipDirScale = ccColorScalesManager::GetDefaultScale(ccColorScalesManager::DIP_DIR_REPEAT);
  2255. dipSF->setColorScale(dipScale);
  2256. dipDirSF->setColorScale(dipDirScale);
  2257. ccCloud->setCurrentDisplayedScalarField(dipDirSFIndex); //dip dir. seems more interesting by default
  2258. ccCloud->showSF(true);
  2259. ++successCount;
  2260. }
  2261. else
  2262. {
  2263. ccCloud->deleteScalarField(dipSFIndex);
  2264. ccCloud->deleteScalarField(dipDirSFIndex);
  2265. }
  2266. }
  2267. break;
  2268. default:
  2269. Q_ASSERT(false);
  2270. ccLog::Warning(QObject::tr("[ccEntityAction::convertNormalsTo] Internal error: unhandled destination!"));
  2271. success = false;
  2272. i = selNum; //no need to process the selected entities anymore!
  2273. break;
  2274. }
  2275. if (success)
  2276. {
  2277. ccCloud->prepareDisplayForRefresh_recursive();
  2278. }
  2279. else
  2280. {
  2281. ++errorCount;
  2282. }
  2283. }
  2284. }
  2285. }
  2286. //errors should have been sent to console as warnings
  2287. if (errorCount)
  2288. {
  2289. ccConsole::Error(QObject::tr("Error(s) occurred! (see console)"));
  2290. }
  2291. return (successCount != 0);
  2292. }
  2293. //////////
  2294. // Octree
  2295. bool computeOctree(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  2296. {
  2297. ccBBox bbox;
  2298. std::unordered_set<ccGenericPointCloud*> clouds;
  2299. PointCoordinateType maxBoxSize = -1;
  2300. for (ccHObject* ent : selectedEntities)
  2301. {
  2302. //specific test for locked vertices
  2303. bool lockedVertices = false;
  2304. ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);
  2305. if (cloud == nullptr)
  2306. {
  2307. continue;
  2308. }
  2309. if (lockedVertices)
  2310. {
  2311. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  2312. continue;
  2313. }
  2314. clouds.insert(cloud);
  2315. //we look for the biggest box so as to define the "minimum cell size"
  2316. const ccBBox thisBBox = cloud->getOwnBB();
  2317. if (thisBBox.isValid())
  2318. {
  2319. CCVector3 dd = thisBBox.maxCorner() - thisBBox.minCorner();
  2320. PointCoordinateType maxd = std::max(dd.x, std::max(dd.y, dd.z));
  2321. if (maxBoxSize < 0.0 || maxd > maxBoxSize)
  2322. maxBoxSize = maxd;
  2323. }
  2324. bbox += thisBBox;
  2325. }
  2326. if (clouds.empty() || maxBoxSize < 0.0)
  2327. {
  2328. ccLog::Warning(QObject::tr("[DoActionComputeOctree] No eligible entities in selection!"));
  2329. return false;
  2330. }
  2331. //min(cellSize) = max(dim)/2^N with N = max subidivision level
  2332. const double minCellSize = static_cast<double>(maxBoxSize) / (1 << ccOctree::MAX_OCTREE_LEVEL);
  2333. ccComputeOctreeDlg coDlg(bbox, minCellSize, parent);
  2334. if (!coDlg.exec())
  2335. return false;
  2336. ccProgressDialog pDlg(true, parent);
  2337. pDlg.setAutoClose(false);
  2338. //if we must use a custom bounding box, we update 'bbox'
  2339. if (coDlg.getMode() == ccComputeOctreeDlg::CUSTOM_BBOX)
  2340. bbox = coDlg.getCustomBBox();
  2341. for (const auto cloud : clouds)
  2342. {
  2343. //we temporarily detach entity, as it may undergo
  2344. //'severe' modifications (octree deletion, etc.) --> see ccPointCloud::computeOctree
  2345. ccMainAppInterface* instance = dynamic_cast<ccMainAppInterface*>(parent);
  2346. ccMainAppInterface::ccHObjectContext objContext;
  2347. if (instance)
  2348. objContext = instance->removeObjectTemporarilyFromDBTree(cloud);
  2349. //computation
  2350. QElapsedTimer eTimer;
  2351. eTimer.start();
  2352. ccOctree::Shared octree(nullptr);
  2353. switch (coDlg.getMode())
  2354. {
  2355. case ccComputeOctreeDlg::DEFAULT:
  2356. octree = cloud->computeOctree(&pDlg);
  2357. break;
  2358. case ccComputeOctreeDlg::MIN_CELL_SIZE:
  2359. case ccComputeOctreeDlg::CUSTOM_BBOX:
  2360. {
  2361. //for a cell-size based custom box, we must update it for each cloud!
  2362. if (coDlg.getMode() == ccComputeOctreeDlg::MIN_CELL_SIZE)
  2363. {
  2364. double cellSize = coDlg.getMinCellSize();
  2365. PointCoordinateType halfBoxWidth = static_cast<PointCoordinateType>(cellSize * (1 << ccOctree::MAX_OCTREE_LEVEL) / 2.0);
  2366. ccBBox bbBox = cloud->getOwnBB();
  2367. CCVector3 C = bbBox.getCenter();
  2368. bbox = ccBBox( C - CCVector3(halfBoxWidth, halfBoxWidth, halfBoxWidth),
  2369. C + CCVector3(halfBoxWidth, halfBoxWidth, halfBoxWidth),
  2370. bbBox.isValid() );
  2371. }
  2372. cloud->deleteOctree();
  2373. octree = ccOctree::Shared(new ccOctree(cloud));
  2374. if (octree->build(bbox.minCorner(), bbox.maxCorner(), nullptr, nullptr, &pDlg) > 0)
  2375. {
  2376. ccOctreeProxy* proxy = new ccOctreeProxy(octree);
  2377. proxy->setDisplay(cloud->getDisplay());
  2378. cloud->addChild(proxy);
  2379. }
  2380. else
  2381. {
  2382. octree.clear();
  2383. }
  2384. }
  2385. break;
  2386. default:
  2387. Q_ASSERT(false);
  2388. return false;
  2389. }
  2390. qint64 elapsedTime_ms = eTimer.elapsed();
  2391. //put object back in tree
  2392. if (instance)
  2393. instance->putObjectBackIntoDBTree(cloud, objContext);
  2394. if (octree)
  2395. {
  2396. ccConsole::Print("[doActionComputeOctree] Timing: %2.3f s", static_cast<double>(elapsedTime_ms) / 1000.0);
  2397. cloud->setEnabled(true); //for mesh vertices!
  2398. ccOctreeProxy* proxy = cloud->getOctreeProxy();
  2399. assert(proxy);
  2400. proxy->setVisible(true);
  2401. proxy->prepareDisplayForRefresh();
  2402. }
  2403. else
  2404. {
  2405. ccConsole::Warning(QObject::tr("Octree computation on cloud '%1' failed!").arg(cloud->getName()));
  2406. }
  2407. }
  2408. return true;
  2409. }
  2410. //////////
  2411. // Properties
  2412. bool clearProperty(ccHObject::Container selectedEntities, CLEAR_PROPERTY property, QWidget* parent/*=nullptr*/)
  2413. {
  2414. for (ccHObject* ent : selectedEntities)
  2415. {
  2416. //specific case: clear normals on a mesh
  2417. if (property == CLEAR_PROPERTY::NORMALS && ( ent->isA(CC_TYPES::MESH) /*|| ent->isKindOf(CC_TYPES::PRIMITIVE)*/ )) //TODO
  2418. {
  2419. ccMesh* mesh = ccHObjectCaster::ToMesh(ent);
  2420. if (!mesh)
  2421. {
  2422. assert(false);
  2423. continue;
  2424. }
  2425. if (mesh->hasTriNormals())
  2426. {
  2427. mesh->showNormals(false);
  2428. ccMainAppInterface* instance = dynamic_cast<ccMainAppInterface*>(parent);
  2429. ccMainAppInterface::ccHObjectContext objContext;
  2430. if (instance)
  2431. objContext = instance->removeObjectTemporarilyFromDBTree(mesh);
  2432. mesh->clearTriNormals();
  2433. if (instance)
  2434. instance->putObjectBackIntoDBTree(mesh,objContext);
  2435. ent->prepareDisplayForRefresh();
  2436. continue;
  2437. }
  2438. else if (mesh->hasNormals()) //per-vertex normals?
  2439. {
  2440. if (mesh->getParent()
  2441. && (mesh->getParent()->isA(CC_TYPES::MESH)/*|| mesh->getParent()->isKindOf(CC_TYPES::PRIMITIVE)*/) //TODO
  2442. && ccHObjectCaster::ToMesh(mesh->getParent())->getAssociatedCloud() == mesh->getAssociatedCloud())
  2443. {
  2444. ccLog::Warning(QObject::tr("[DoActionClearNormals] Can't remove normals per-vertex on a sub mesh!"));
  2445. }
  2446. else //mesh is alone, we can freely remove normals
  2447. {
  2448. if (mesh->getAssociatedCloud() && mesh->getAssociatedCloud()->isA(CC_TYPES::POINT_CLOUD))
  2449. {
  2450. mesh->showNormals(false);
  2451. static_cast<ccPointCloud*>(mesh->getAssociatedCloud())->unallocateNorms();
  2452. mesh->prepareDisplayForRefresh();
  2453. continue;
  2454. }
  2455. }
  2456. }
  2457. }
  2458. bool lockedVertices;
  2459. ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent,&lockedVertices);
  2460. if (lockedVertices)
  2461. {
  2462. ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
  2463. continue;
  2464. }
  2465. if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD)) // TODO
  2466. {
  2467. auto pointCloud = static_cast<ccPointCloud*>(cloud);
  2468. switch (property)
  2469. {
  2470. case CLEAR_PROPERTY::COLORS:
  2471. if (cloud->hasColors())
  2472. {
  2473. pointCloud->unallocateColors();
  2474. ent->prepareDisplayForRefresh();
  2475. }
  2476. break;
  2477. case CLEAR_PROPERTY::NORMALS:
  2478. if (cloud->hasNormals())
  2479. {
  2480. pointCloud->unallocateNorms();
  2481. ent->prepareDisplayForRefresh();
  2482. }
  2483. break;
  2484. case CLEAR_PROPERTY::CURRENT_SCALAR_FIELD:
  2485. if (cloud->hasDisplayedScalarField())
  2486. {
  2487. pointCloud->deleteScalarField( pointCloud->getCurrentDisplayedScalarFieldIndex() );
  2488. ent->prepareDisplayForRefresh();
  2489. }
  2490. break;
  2491. case CLEAR_PROPERTY::ALL_SCALAR_FIELDS:
  2492. if (cloud->hasScalarFields())
  2493. {
  2494. pointCloud->deleteAllScalarFields();
  2495. ent->prepareDisplayForRefresh();
  2496. }
  2497. break;
  2498. }
  2499. }
  2500. }
  2501. return true;
  2502. }
  2503. bool toggleProperty(const ccHObject::Container &selectedEntities, TOGGLE_PROPERTY property)
  2504. {
  2505. ccHObject baseEntities;
  2506. ConvertToGroup(selectedEntities, baseEntities, ccHObject::DP_NONE);
  2507. for (unsigned i = 0; i < baseEntities.getChildrenNumber(); ++i)
  2508. {
  2509. ccHObject* child = baseEntities.getChild(i);
  2510. switch(property)
  2511. {
  2512. case TOGGLE_PROPERTY::ACTIVE:
  2513. child->toggleActivation/*_recursive*/();
  2514. break;
  2515. case TOGGLE_PROPERTY::VISIBLE:
  2516. child->toggleVisibility_recursive();
  2517. break;
  2518. case TOGGLE_PROPERTY::COLOR:
  2519. child->toggleColors_recursive();
  2520. break;
  2521. case TOGGLE_PROPERTY::NORMALS:
  2522. child->toggleNormals_recursive();
  2523. break;
  2524. case TOGGLE_PROPERTY::SCALAR_FIELD:
  2525. child->toggleSF_recursive();
  2526. break;
  2527. case TOGGLE_PROPERTY::MATERIAL:
  2528. child->toggleMaterials_recursive();
  2529. break;
  2530. case TOGGLE_PROPERTY::NAME:
  2531. child->toggleShowName_recursive();
  2532. break;
  2533. default:
  2534. Q_ASSERT(false);
  2535. return false;
  2536. }
  2537. child->prepareDisplayForRefresh_recursive();
  2538. }
  2539. return true;
  2540. }
  2541. //////////
  2542. // Stats
  2543. bool statisticalTest(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  2544. {
  2545. ccPickOneElementDlg poeDlg(QObject::tr("Distribution"), QObject::tr("Choose distribution"), parent);
  2546. poeDlg.addElement("Gauss");
  2547. poeDlg.addElement("Weibull");
  2548. poeDlg.setDefaultIndex(0);
  2549. if (!poeDlg.exec())
  2550. {
  2551. return false;
  2552. }
  2553. int distribIndex = poeDlg.getSelectedIndex();
  2554. ccStatisticalTestDlg* sDlg = nullptr;
  2555. switch (distribIndex)
  2556. {
  2557. case 0: //Gauss
  2558. sDlg = new ccStatisticalTestDlg("mu", "sigma", QString(), QObject::tr("Local Statistical Test (Gauss)"), parent);
  2559. break;
  2560. case 1: //Weibull
  2561. sDlg = new ccStatisticalTestDlg("a", "b", "shift", QObject::tr("Local Statistical Test (Weibull)"), parent);
  2562. break;
  2563. default:
  2564. ccConsole::Error(QObject::tr("Invalid distribution!"));
  2565. return false;
  2566. }
  2567. if (!sDlg->exec())
  2568. {
  2569. sDlg->deleteLater();
  2570. return false;
  2571. }
  2572. //build up corresponding distribution
  2573. CCCoreLib::GenericDistribution* distrib = nullptr;
  2574. {
  2575. ScalarType a = static_cast<ScalarType>(sDlg->getParam1());
  2576. ScalarType b = static_cast<ScalarType>(sDlg->getParam2());
  2577. ScalarType c = static_cast<ScalarType>(sDlg->getParam3());
  2578. switch (distribIndex)
  2579. {
  2580. case 0: //Gauss
  2581. {
  2582. CCCoreLib::NormalDistribution* N = new CCCoreLib::NormalDistribution();
  2583. N->setParameters(a,b*b); //warning: we input sigma2 here (not sigma)
  2584. distrib = static_cast<CCCoreLib::GenericDistribution*>(N);
  2585. break;
  2586. }
  2587. case 1: //Weibull
  2588. CCCoreLib::WeibullDistribution* W = new CCCoreLib::WeibullDistribution();
  2589. W->setParameters(a,b,c);
  2590. distrib = static_cast<CCCoreLib::GenericDistribution*>(W);
  2591. break;
  2592. }
  2593. }
  2594. const double pChi2 = sDlg->getProbability();
  2595. const int nn = sDlg->getNeighborsNumber();
  2596. ccProgressDialog pDlg(true, parent);
  2597. pDlg.setAutoClose(false);
  2598. for (ccHObject* ent : selectedEntities)
  2599. {
  2600. ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent);
  2601. if (pc == nullptr)
  2602. {
  2603. // TODO handle error?
  2604. continue;
  2605. }
  2606. //we apply method on currently displayed SF
  2607. ccScalarField* inSF = pc->getCurrentDisplayedScalarField();
  2608. if (inSF == nullptr)
  2609. {
  2610. // TODO handle error?
  2611. continue;
  2612. }
  2613. Q_ASSERT(inSF->capacity() != 0);
  2614. //force SF as 'OUT' field (in case of)
  2615. const int outSfIdx = pc->getCurrentDisplayedScalarFieldIndex();
  2616. pc->setCurrentOutScalarField(outSfIdx);
  2617. //force Chi2 Distances field as 'IN' field (create it by the way if necessary)
  2618. int chi2SfIdx = pc->getScalarFieldIndexByName(CC_CHI2_DISTANCES_DEFAULT_SF_NAME);
  2619. if (chi2SfIdx < 0)
  2620. chi2SfIdx = pc->addScalarField(CC_CHI2_DISTANCES_DEFAULT_SF_NAME);
  2621. if (chi2SfIdx < 0)
  2622. {
  2623. ccConsole::Error(QObject::tr("Couldn't allocate a new scalar field for computing chi2 distances! Try to free some memory ..."));
  2624. break;
  2625. }
  2626. pc->setCurrentInScalarField(chi2SfIdx);
  2627. //compute octree if necessary
  2628. ccOctree::Shared theOctree = pc->getOctree();
  2629. if (!theOctree)
  2630. {
  2631. theOctree = pc->computeOctree(&pDlg);
  2632. if (!theOctree)
  2633. {
  2634. ccConsole::Error(QObject::tr("Couldn't compute octree for cloud '%1'!").arg(pc->getName()));
  2635. break;
  2636. }
  2637. }
  2638. QElapsedTimer eTimer;
  2639. eTimer.start();
  2640. double chi2dist = CCCoreLib::StatisticalTestingTools::testCloudWithStatisticalModel(distrib, pc, nn, pChi2, &pDlg, theOctree.data());
  2641. ccConsole::Print("[Chi2 Test] Timing: %3.2f ms.", eTimer.elapsed() / 1000.0);
  2642. ccConsole::Print(QObject::tr("[Chi2 Test] %1 test result = %2").arg(distrib->getName()).arg(chi2dist));
  2643. //we set the theoretical Chi2 distance limit as the minimum displayed SF value so that all points below are grayed
  2644. {
  2645. ccScalarField* chi2SF = static_cast<ccScalarField*>(pc->getCurrentInScalarField());
  2646. Q_ASSERT(chi2SF);
  2647. chi2SF->computeMinAndMax();
  2648. chi2dist *= chi2dist;
  2649. chi2SF->setMinDisplayed(static_cast<ScalarType>(chi2dist));
  2650. chi2SF->setSymmetricalScale(false);
  2651. chi2SF->setSaturationStart(static_cast<ScalarType>(chi2dist));
  2652. //chi2SF->setSaturationStop(chi2dist);
  2653. pc->setCurrentDisplayedScalarField(chi2SfIdx);
  2654. pc->showSF(true);
  2655. pc->prepareDisplayForRefresh_recursive();
  2656. }
  2657. }
  2658. delete distrib;
  2659. distrib = nullptr;
  2660. sDlg->deleteLater();
  2661. return true;
  2662. }
  2663. bool computeStatParams(const ccHObject::Container &selectedEntities, QWidget* parent/*=nullptr*/)
  2664. {
  2665. ccPickOneElementDlg pDlg(QObject::tr("Distribution"), QObject::tr("Distribution Fitting"), parent);
  2666. pDlg.addElement("Gauss");
  2667. pDlg.addElement("Weibull");
  2668. pDlg.setDefaultIndex(0);
  2669. if (!pDlg.exec())
  2670. return false;
  2671. CCCoreLib::GenericDistribution* distrib = nullptr;
  2672. {
  2673. switch (pDlg.getSelectedIndex())
  2674. {
  2675. case 0: //GAUSS
  2676. distrib = new CCCoreLib::NormalDistribution();
  2677. break;
  2678. case 1: //WEIBULL
  2679. distrib = new CCCoreLib::WeibullDistribution();
  2680. break;
  2681. default:
  2682. Q_ASSERT(false);
  2683. return false;
  2684. }
  2685. }
  2686. Q_ASSERT(distrib != nullptr);
  2687. for (ccHObject* ent : selectedEntities)
  2688. {
  2689. ccPointCloud* pc = ccHObjectCaster::ToPointCloud(ent);
  2690. if (pc == nullptr)
  2691. {
  2692. // TODO report error?
  2693. continue;
  2694. }
  2695. //we apply method on currently displayed SF
  2696. ccScalarField* sf = pc->getCurrentDisplayedScalarField();
  2697. if (sf == nullptr)
  2698. {
  2699. // TODO report error?
  2700. continue;
  2701. }
  2702. //compute the number of valid values
  2703. size_t sfValidCount = sf->countValidValues();
  2704. if (sfValidCount == 0)
  2705. {
  2706. ccLog::Warning(QObject::tr("Scalar field '%1' of cloud %2 has no valid values").arg(QString::fromStdString(sf->getName())).arg(pc->getName()));
  2707. continue;
  2708. }
  2709. Q_ASSERT(!sf->empty());
  2710. if (sf && distrib->computeParameters(CCCoreLib::GenericDistribution::SFAsScalarContainer(*sf)))
  2711. {
  2712. QString description;
  2713. const unsigned precision = ccGui::Parameters().displayedNumPrecision;
  2714. switch (pDlg.getSelectedIndex())
  2715. {
  2716. case 0: //GAUSS
  2717. {
  2718. CCCoreLib::NormalDistribution* normal = static_cast<CCCoreLib::NormalDistribution*>(distrib);
  2719. description = QObject::tr("mean = %1 / std.dev. = %2").arg(normal->getMu(), 0, 'f', precision).arg(sqrt(normal->getSigma2()), 0, 'f', precision);
  2720. }
  2721. break;
  2722. case 1: //WEIBULL
  2723. {
  2724. CCCoreLib::WeibullDistribution* weibull = static_cast<CCCoreLib::WeibullDistribution*>(distrib);
  2725. ScalarType a;
  2726. ScalarType b;
  2727. weibull->getParameters(a, b);
  2728. description = QString("a = %1 / b = %2 / shift = %3").arg(a, 0, 'f', precision).arg(b, 0, 'f', precision).arg(weibull->getValueShift(), 0, 'f', precision);
  2729. ccLog::Print(QObject::tr("[Distribution fitting] Additional Weibull distrib. parameters: mode = %1 / skewness = %2").arg(weibull->computeMode()).arg(weibull->computeSkewness()));
  2730. }
  2731. break;
  2732. default:
  2733. {
  2734. Q_ASSERT(false);
  2735. return false;
  2736. }
  2737. }
  2738. description.prepend(QString("%1: ").arg(distrib->getName()));
  2739. ccConsole::Print(QObject::tr("[Distribution fitting] %1").arg(description));
  2740. const unsigned numberOfClasses = static_cast<unsigned>(ceil(sqrt(static_cast<double>(sfValidCount))));
  2741. std::vector<unsigned> histo;
  2742. std::vector<double> npis;
  2743. try
  2744. {
  2745. histo.resize(numberOfClasses, 0);
  2746. npis.resize(numberOfClasses, 0.0);
  2747. }
  2748. catch (const std::bad_alloc&)
  2749. {
  2750. ccConsole::Warning(QObject::tr("[Distribution fitting] Not enough memory!"));
  2751. continue;
  2752. }
  2753. //compute the Chi2 distance
  2754. {
  2755. unsigned finalNumberOfClasses = 0;
  2756. const double chi2dist = CCCoreLib::StatisticalTestingTools::computeAdaptativeChi2Dist(distrib, pc, numberOfClasses, finalNumberOfClasses, false, nullptr, nullptr, histo.data(), npis.data());
  2757. if (chi2dist >= 0.0)
  2758. {
  2759. ccConsole::Print(QObject::tr("[Distribution fitting] %1: Chi2 Distance = %2").arg(distrib->getName()).arg(chi2dist));
  2760. }
  2761. else
  2762. {
  2763. ccConsole::Warning(QObject::tr("[Distribution fitting] Failed to compute Chi2 distance?!"));
  2764. continue;
  2765. }
  2766. }
  2767. //compute RMS
  2768. {
  2769. unsigned n = pc->size();
  2770. double squareSum = 0;
  2771. double sum = 0;
  2772. for (unsigned i = 0; i < n; ++i)
  2773. {
  2774. ScalarType v = pc->getPointScalarValue(i);
  2775. if (CCCoreLib::ScalarField::ValidValue(v))
  2776. {
  2777. sum += static_cast<double>(v);
  2778. squareSum += static_cast<double>(v) * v;
  2779. }
  2780. }
  2781. double rms = sqrt(squareSum / sfValidCount);
  2782. ccConsole::Print(QObject::tr("Scalar field statistics:"));
  2783. 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));
  2784. ccConsole::Print(QObject::tr("Sum of all valid values = %1").arg(QString::number(sum, 'f', 6)));
  2785. ccConsole::Print(QObject::tr("Sum of all valid squared values = %1").arg(QString::number(squareSum, 'f', 6)));
  2786. ccConsole::Print(QObject::tr("Average value = %1").arg(sum / sfValidCount));
  2787. ccConsole::Print(QObject::tr("RMS (Root Mean Square) = %1").arg(rms));
  2788. }
  2789. //show histogram
  2790. ccHistogramWindowDlg* hDlg = new ccHistogramWindowDlg(parent);
  2791. hDlg->setWindowTitle(QObject::tr("[Distribution fitting]"));
  2792. ccHistogramWindow* histogram = hDlg->window();
  2793. histogram->fromBinArray(histo, sf);
  2794. histo.clear();
  2795. histogram->setCurveValues(npis);
  2796. npis.clear();
  2797. histogram->setTitle(description);
  2798. histogram->setAxisLabels(QString::fromStdString(sf->getName()), QObject::tr("Count"));
  2799. histogram->refresh();
  2800. hDlg->show();
  2801. }
  2802. else
  2803. {
  2804. ccConsole::Warning(QObject::tr("[Entity: %1]-[SF: %2] Couldn't compute distribution parameters!").arg(pc->getName(), QString::fromStdString(sf->getName())));
  2805. }
  2806. }
  2807. delete distrib;
  2808. distrib = nullptr;
  2809. return true;
  2810. }
  2811. }