ccScalarFieldArithmeticsDlg.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  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: EDF R&D / TELECOM ParisTech (ENST-TSI) #
  15. //# #
  16. //##########################################################################
  17. #include "ccScalarFieldArithmeticsDlg.h"
  18. #include "ui_sfArithmeticsDlg.h"
  19. //Qt
  20. #include <QMessageBox>
  21. #include <QPushButton>
  22. //qCC_db
  23. #include <ccPointCloud.h>
  24. #include <ccScalarField.h>
  25. //system
  26. #include <cassert>
  27. #ifdef _MSC_VER
  28. #include <windows.h>
  29. #endif
  30. #include <cmath>
  31. //number of valid operations
  32. constexpr unsigned s_opCount = 22;
  33. //operation names
  34. constexpr char s_opNames[s_opCount][8] {"add", "sub", "mult", "div", "min", "max", "sqrt", "pow2", "pow3", "exp", "log", "log10", "cos", "sin", "tan", "acos", "asin", "atan", "int", "inverse", "set", "abs" };
  35. //semi persitent
  36. static int s_previouslySelectedOperationIndex = 1;
  37. static bool s_applyInPlace = false;
  38. static double s_previousConstValue = 1.0;
  39. ccScalarFieldArithmeticsDlg::ccScalarFieldArithmeticsDlg( ccPointCloud* cloud,
  40. QWidget* parent/*=nullptr*/)
  41. : QDialog(parent, Qt::Tool)
  42. , m_ui( new Ui::SFArithmeticsDlg )
  43. {
  44. assert(cloud);
  45. m_ui->setupUi(this);
  46. QStringList sfLabels;
  47. unsigned sfCount = cloud ? cloud->getNumberOfScalarFields() : 0;
  48. if (sfCount < 1)
  49. {
  50. m_ui->sf1ComboBox->setEnabled(false);
  51. m_ui->sf2ComboBox->setEnabled(false);
  52. m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
  53. }
  54. else
  55. {
  56. for (unsigned i = 0; i < sfCount; ++i)
  57. {
  58. sfLabels << QString::fromStdString(cloud->getScalarFieldName(i));
  59. }
  60. m_ui->sf1ComboBox->addItems(sfLabels);
  61. m_ui->sf1ComboBox->setCurrentIndex(0);
  62. sfLabels << "[Constant value]";
  63. m_ui->sf2ComboBox->addItems(sfLabels);
  64. m_ui->sf2ComboBox->setCurrentIndex(std::min<unsigned>(1, sfCount - 1));
  65. }
  66. //connect signals/slots
  67. connect(m_ui->operationComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccScalarFieldArithmeticsDlg::onOperationIndexChanged);
  68. connect(m_ui->sf2ComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccScalarFieldArithmeticsDlg::onSF2IndexChanged);
  69. m_ui->operationComboBox->setCurrentIndex(s_previouslySelectedOperationIndex);
  70. m_ui->constantDoubleSpinBox->setValue(s_previousConstValue);
  71. m_ui->updateSF1CheckBox->setChecked(s_applyInPlace);
  72. }
  73. ccScalarFieldArithmeticsDlg::~ccScalarFieldArithmeticsDlg()
  74. {
  75. delete m_ui;
  76. m_ui = nullptr;
  77. }
  78. void ccScalarFieldArithmeticsDlg::onOperationIndexChanged(int index)
  79. {
  80. if (index == Operation::SET)
  81. {
  82. //force the last element of the SF2 field (= always the 'constant' field)
  83. m_ui->sf2ComboBox->setCurrentIndex(m_ui->sf2ComboBox->count() - 1);
  84. m_ui->sf2ComboBox->setEnabled(false);
  85. m_ui->updateSF1CheckBox->setChecked(true);
  86. m_ui->updateSF1CheckBox->setEnabled(false);
  87. }
  88. else
  89. {
  90. m_ui->sf2ComboBox->setEnabled(index <= MAX); //only the 6 first operations are applied with 2 SFs
  91. m_ui->updateSF1CheckBox->setEnabled(true);
  92. }
  93. }
  94. void ccScalarFieldArithmeticsDlg::onSF2IndexChanged(int index)
  95. {
  96. //the last element is always the 'constant' field
  97. m_ui->constantDoubleSpinBox->setEnabled(m_ui->sf2ComboBox->currentIndex() + 1 == m_ui->sf2ComboBox->count());
  98. }
  99. int ccScalarFieldArithmeticsDlg::getSF1Index()
  100. {
  101. return m_ui->sf1ComboBox->currentIndex();
  102. }
  103. int ccScalarFieldArithmeticsDlg::getSF2Index()
  104. {
  105. return m_ui->sf2ComboBox->currentIndex();
  106. }
  107. ccScalarFieldArithmeticsDlg::Operation ccScalarFieldArithmeticsDlg::getOperation() const
  108. {
  109. int opIndex = m_ui->operationComboBox->currentIndex();
  110. if (opIndex < s_opCount)
  111. {
  112. return static_cast<ccScalarFieldArithmeticsDlg::Operation>(opIndex);
  113. }
  114. else
  115. {
  116. assert(false);
  117. return INVALID;
  118. }
  119. }
  120. ccScalarFieldArithmeticsDlg::Operation ccScalarFieldArithmeticsDlg::GetOperationByName(const QString& name)
  121. {
  122. auto lowerName = name.toLower();
  123. //test all known names...
  124. for (unsigned i = 0; i < s_opCount; ++i)
  125. {
  126. if (lowerName == QString( s_opNames[i] ))
  127. {
  128. return static_cast<ccScalarFieldArithmeticsDlg::Operation>(i);
  129. }
  130. }
  131. return INVALID;
  132. }
  133. QString ccScalarFieldArithmeticsDlg::GetOperationName(Operation op, const QString& sf1, const QString& sf2/*=QString()*/)
  134. {
  135. switch (op)
  136. {
  137. case PLUS:
  138. return QString("%1 + %2").arg(sf1, sf2);
  139. case MINUS:
  140. return QString("%1 - %2").arg(sf1, sf2);
  141. case MULTIPLY:
  142. return QString("%1 * %2").arg(sf1, sf2);
  143. case DIVIDE:
  144. return QString("%1 / %2").arg(sf1, sf2);
  145. case MIN:
  146. return QString("min(%1, %2)").arg(sf1, sf2);
  147. case MAX:
  148. return QString("max(%1, %2)").arg(sf1, sf2);
  149. case SET:
  150. return sf1;
  151. default:
  152. if (op != INVALID)
  153. return QString("%1(%2)").arg(s_opNames[op], sf1);
  154. else
  155. assert(false);
  156. break;
  157. }
  158. return QString();
  159. }
  160. bool ccScalarFieldArithmeticsDlg::apply(ccPointCloud* cloud)
  161. {
  162. Operation op = getOperation();
  163. int sf1Idx = getSF1Index();
  164. int sf2Idx = getSF2Index();
  165. //save persistent parameters
  166. s_previouslySelectedOperationIndex = m_ui->operationComboBox->currentIndex();
  167. s_previousConstValue = m_ui->constantDoubleSpinBox->value();
  168. s_applyInPlace = m_ui->updateSF1CheckBox->isChecked();
  169. SF2 sf2Desc;
  170. sf2Desc.isConstantValue = m_ui->constantDoubleSpinBox->isEnabled() || (sf1Idx == Operation::SET);
  171. sf2Desc.constantValue = m_ui->constantDoubleSpinBox->value();
  172. sf2Desc.sfIndex = sf2Desc.isConstantValue ? -1 : sf2Idx;
  173. return Apply(cloud, op, sf1Idx, s_applyInPlace, &sf2Desc, this);
  174. }
  175. bool ccScalarFieldArithmeticsDlg::Apply(ccPointCloud* cloud,
  176. Operation op,
  177. int sf1Idx,
  178. bool inplace,
  179. SF2* sf2Desc/*=nullptr*/,
  180. QWidget* parent/*=nullptr*/)
  181. {
  182. assert(cloud);
  183. if (!cloud || !cloud->hasScalarFields())
  184. {
  185. ccLog::Warning("[ccScalarFieldArithmeticsDlg::apply] No input cloud, or cloud has no SF?!");
  186. assert(false);
  187. return false;
  188. }
  189. if (op == INVALID)
  190. {
  191. ccLog::Warning("[ccScalarFieldArithmeticsDlg::apply] Invalid/unhandled operation");
  192. assert(false);
  193. return false;
  194. }
  195. unsigned sfCount = cloud->getNumberOfScalarFields();
  196. CCCoreLib::ScalarField* sf1 = nullptr;
  197. {
  198. if (sf1Idx >= static_cast<int>(sfCount))
  199. {
  200. ccLog::Warning("[ccScalarFieldArithmeticsDlg::apply] Invalid SF1 index!");
  201. assert(false);
  202. return false;
  203. }
  204. sf1 = cloud->getScalarField(sf1Idx);
  205. assert(sf1);
  206. }
  207. double sf1PreviousOffset = sf1->getOffset(); // remember the offset, as we may have to change it BEFORE reading the values
  208. CCCoreLib::ScalarField* sf2 = nullptr;
  209. if (op <= MAX)
  210. {
  211. if (!sf2Desc || (!sf2Desc->isConstantValue && sf2Desc->sfIndex >= static_cast<int>(sfCount)))
  212. {
  213. ccLog::Warning("[ccScalarFieldArithmeticsDlg::apply] Invalid SF2 index/descriptor!");
  214. assert(false);
  215. return false;
  216. }
  217. if (sf2Desc->isConstantValue)
  218. {
  219. if (op == DIVIDE && sf2Desc->constantValue == 0)
  220. {
  221. ccLog::Error("Invalid constant value (can't divide by zero)");
  222. return false;
  223. }
  224. }
  225. sf2 = (!sf2Desc->isConstantValue && sf2Desc->sfIndex >= 0 ? cloud->getScalarField(sf2Desc->sfIndex) : nullptr);
  226. }
  227. //output SF
  228. int sfIdx = -1;
  229. if (!inplace)
  230. {
  231. //generate new sf name based on the operation
  232. QString sf1Name = QString::fromStdString(sf1->getName());
  233. QString sf2Name;
  234. if (sf2)
  235. {
  236. sf2Name = QString::fromStdString(sf2->getName());
  237. QString sfName = GetOperationName(op,sf1Name,sf2Name);
  238. if (sfName.length() > 24)
  239. {
  240. assert(sf2Desc);
  241. //if the resulting SF name is too long, we use shortcuts instead
  242. sf1Name = QString("(SF#%1)").arg(sf1Idx);
  243. sf2Name = QString("(SF#%1)").arg(sf2Desc->sfIndex);
  244. }
  245. }
  246. else if (sf2Desc && sf2Desc->isConstantValue)
  247. {
  248. sf2Name = QString("%1").arg(sf2Desc->constantValue);
  249. }
  250. QString sfName = GetOperationName(op, sf1Name, sf2Name);
  251. sfIdx = cloud->getScalarFieldIndexByName(sfName.toStdString());
  252. if (sfIdx >= 0)
  253. {
  254. if (sfIdx == sf1Idx || (sf2Desc && sfIdx == sf2Desc->sfIndex))
  255. {
  256. ccLog::Warning(QString("[ccScalarFieldArithmeticsDlg::apply] Resulting scalar field would have the same name as one of the operand (%1)! Rename it first...").arg(sfName));
  257. return false;
  258. }
  259. if (parent && QMessageBox::warning( parent,
  260. "Same scalar field name",
  261. "Resulting scalar field already exists! Overwrite it?",
  262. QMessageBox::Ok | QMessageBox::Cancel,
  263. QMessageBox::Ok ) != QMessageBox::Ok)
  264. {
  265. return false;
  266. }
  267. cloud->deleteScalarField(sfIdx);
  268. }
  269. sfIdx = cloud->addScalarField(sfName.toStdString());
  270. if (sfIdx < 0)
  271. {
  272. ccLog::Warning("[ccScalarFieldArithmeticsDlg::apply] Failed to create destination SF! (not enough memory?)");
  273. return false;
  274. }
  275. }
  276. else // we keep the original scalar field
  277. {
  278. // we might want to update the offset as best as we can
  279. switch (op)
  280. {
  281. case PLUS:
  282. {
  283. if (sf2Desc && sf2Desc->isConstantValue)
  284. {
  285. // shortcut
  286. sf1->setOffset(sf1->getOffset() + sf2Desc->constantValue);
  287. sf1->computeMinAndMax();
  288. cloud->setCurrentDisplayedScalarField(sf1Idx);
  289. ccLog::PrintVerbose(QString("SF %1 offset has been changed: from %2 to %3")
  290. .arg(QString::fromStdString(sf1->getName()))
  291. .arg(sf1PreviousOffset)
  292. .arg(sf1->getOffset()));
  293. return true;
  294. }
  295. else
  296. {
  297. sf1->setOffset(sf1->getOffset() + sf2->getOffset());
  298. }
  299. break;
  300. }
  301. case MINUS:
  302. {
  303. if (sf2Desc && sf2Desc->isConstantValue)
  304. {
  305. // shortcut
  306. sf1->setOffset(sf1->getOffset() - sf2Desc->constantValue);
  307. sf1->computeMinAndMax();
  308. cloud->setCurrentDisplayedScalarField(sf1Idx);
  309. ccLog::PrintVerbose(QString("SF %1 offset has been changed: from %2 to %3")
  310. .arg(QString::fromStdString(sf1->getName()))
  311. .arg(sf1PreviousOffset)
  312. .arg(sf1->getOffset()));
  313. return true;
  314. }
  315. else
  316. {
  317. sf1->setOffset(sf1->getOffset() - sf2->getOffset());
  318. }
  319. break;
  320. }
  321. case MULTIPLY:
  322. {
  323. if (sf2Desc && sf2Desc->isConstantValue)
  324. {
  325. sf1->setOffset(sf1->getOffset() * sf2Desc->constantValue); // it's safe was long as we use sf1PreviousOffset to read the values later
  326. }
  327. else
  328. {
  329. sf1->setOffset(sf1->getOffset() * sf2->getOffset()); // it's safe was long as we use sf1PreviousOffset to read the values later
  330. }
  331. break;
  332. }
  333. case DIVIDE:
  334. {
  335. if (sf2Desc && sf2Desc->isConstantValue)
  336. {
  337. if (sf2Desc->constantValue != 0)
  338. {
  339. sf1->setOffset(sf1->getOffset() / sf2Desc->constantValue); // it's safe was long as we use sf1PreviousOffset to read the values later
  340. }
  341. }
  342. else
  343. {
  344. // not obvious...
  345. }
  346. break;
  347. }
  348. case SQRT:
  349. {
  350. sf1->setOffset(sqrt(std::max(0.0, sf1->getOffset()))); // it's safe was long as we use sf1PreviousOffset to read the values later
  351. break;
  352. }
  353. case POW2:
  354. {
  355. sf1->setOffset(sf1->getOffset() * sf1->getOffset()); // it's safe was long as we use sf1PreviousOffset to read the values later
  356. break;
  357. }
  358. case POW3:
  359. {
  360. sf1->setOffset(sf1->getOffset() * sf1->getOffset() * sf1->getOffset()); // it's safe was long as we use sf1PreviousOffset to read the values later
  361. break;
  362. }
  363. case EXP:
  364. {
  365. // hard to predict anything...
  366. break;
  367. }
  368. case LOG:
  369. {
  370. if (sf1->getOffset() > 1.0)
  371. {
  372. sf1->setOffset(log(sf1->getOffset()));
  373. }
  374. else
  375. {
  376. sf1->setOffset(0.0);
  377. }
  378. break;
  379. }
  380. case LOG10:
  381. case COS:
  382. case SIN:
  383. case TAN:
  384. case ACOS:
  385. case ASIN:
  386. case ATAN:
  387. {
  388. sf1->setOffset(0.0); // it's safe was long as we use sf1PreviousOffset to read the values later
  389. break;
  390. }
  391. case INT:
  392. {
  393. // no need to change the offset
  394. break;
  395. }
  396. case INVERSE:
  397. {
  398. // if all values were close to 0, the result could be a very large number,
  399. // but we'll assume that in most of the cases they will be >> 0
  400. if (sf1->getMin() > 0.0 && sf1->getMax() < 1.0)
  401. {
  402. sf1->setOffset(1.0 / sf1->getMax());
  403. }
  404. else if (sf1->getMin() > -1.0 && sf1->getMax() < 0.0)
  405. {
  406. sf1->setOffset(1.0 / sf1->getMin());
  407. }
  408. else
  409. {
  410. sf1->setOffset(0.0);
  411. }
  412. break;
  413. }
  414. case SET:
  415. {
  416. if (sf2Desc && sf2Desc->isConstantValue)
  417. {
  418. sf1->setOffset(sf2Desc->constantValue); // it's safe was long as we use sf1PreviousOffset to read the values later
  419. }
  420. else
  421. {
  422. assert(false);
  423. }
  424. break;
  425. }
  426. case ABS:
  427. {
  428. sf1->setOffset(std::abs(sf1->getOffset())); // it's safe was long as we use sf1PreviousOffset to read the values later
  429. break;
  430. }
  431. default:
  432. {
  433. // don't modify the offset, hopefully this should be enough...
  434. break;
  435. }
  436. }
  437. if (sf1->getOffset() != sf1PreviousOffset)
  438. {
  439. ccLog::PrintVerbose(QString("SF %1 offset has been changed: from %2 to %3")
  440. .arg(QString::fromStdString(sf1->getName()))
  441. .arg(sf1PreviousOffset)
  442. .arg(sf1->getOffset()));
  443. }
  444. sfIdx = sf1Idx;
  445. }
  446. CCCoreLib::ScalarField* sfDest = cloud->getScalarField(sfIdx);
  447. assert(sfDest);
  448. unsigned valCount = sf1->currentSize();
  449. assert(!sf2 || valCount == sf2->currentSize());
  450. //resize destination SF
  451. if (!sfDest->resizeSafe(valCount))
  452. {
  453. ccLog::Warning("[ccScalarFieldArithmeticsDlg::apply] Not enough memory!");
  454. cloud->deleteScalarField(sfIdx);
  455. sfDest = nullptr;
  456. return false;
  457. }
  458. assert(valCount == sfDest->currentSize());
  459. for (unsigned i = 0; i < valCount; ++i)
  460. {
  461. double val = std::numeric_limits<double>::quiet_NaN();
  462. //we must handle 'invalid' values
  463. double val1 = sf1PreviousOffset + sf1->getLocalValue(i);
  464. if (ccScalarField::ValidValue(val1))
  465. {
  466. switch (op)
  467. {
  468. case PLUS:
  469. {
  470. assert(sf2Desc);
  471. if (sf2Desc->isConstantValue)
  472. {
  473. val = val1 + sf2Desc->constantValue;
  474. }
  475. else
  476. {
  477. assert(sf2);
  478. ScalarType val2 = sf2->getValue(i);
  479. if (ccScalarField::ValidValue(val2))
  480. {
  481. val = val1 + val2;
  482. }
  483. }
  484. }
  485. break;
  486. case MINUS:
  487. {
  488. assert(sf2Desc);
  489. if (sf2Desc->isConstantValue)
  490. {
  491. val = val1 - sf2Desc->constantValue;
  492. }
  493. else
  494. {
  495. assert(sf2);
  496. ScalarType val2 = sf2->getValue(i);
  497. if (ccScalarField::ValidValue(val2))
  498. {
  499. val = val1 - val2;
  500. }
  501. }
  502. }
  503. break;
  504. case MULTIPLY:
  505. {
  506. assert(sf2Desc);
  507. if (sf2Desc->isConstantValue)
  508. {
  509. val = val1 * sf2Desc->constantValue;
  510. }
  511. else
  512. {
  513. assert(sf2);
  514. const ScalarType& val2 = sf2->getValue(i);
  515. if (ccScalarField::ValidValue(val2))
  516. val = val1 * val2;
  517. }
  518. }
  519. break;
  520. case DIVIDE:
  521. {
  522. assert(sf2Desc);
  523. if (sf2Desc->isConstantValue)
  524. {
  525. val = val1 / static_cast<ScalarType>(sf2Desc->constantValue);
  526. }
  527. else
  528. {
  529. assert(sf2);
  530. const ScalarType& val2 = sf2->getValue(i);
  531. if (ccScalarField::ValidValue(val2) && CCCoreLib::GreaterThanEpsilon(std::abs(val2) ) )
  532. {
  533. val = val1 / val2;
  534. }
  535. }
  536. }
  537. break;
  538. case SQRT:
  539. if (val1 >= 0)
  540. {
  541. val = std::sqrt(val1);
  542. }
  543. break;
  544. case POW2:
  545. val = val1 * val1;
  546. break;
  547. case POW3:
  548. val = val1 * val1 * val1;
  549. break;
  550. case EXP:
  551. val = std::exp(val1);
  552. break;
  553. case LOG:
  554. if (val1 >= 0)
  555. {
  556. val = std::log(val1);
  557. }
  558. break;
  559. case LOG10:
  560. if (val1 >= 0)
  561. {
  562. val = std::log10(val1);
  563. }
  564. break;
  565. case COS:
  566. val = std::cos(val1);
  567. break;
  568. case SIN:
  569. val = std::sin(val1);
  570. break;
  571. case TAN:
  572. val = std::tan(val1);
  573. break;
  574. case ACOS:
  575. if (val1 >= -1.0 && val1 <= 1.0)
  576. {
  577. val = std::acos(val1);
  578. }
  579. break;
  580. case ASIN:
  581. if (val1 >= -1.0 && val1 <= 1.0)
  582. {
  583. val = std::asin(val1);
  584. }
  585. break;
  586. case ATAN:
  587. val = std::atan(val1);
  588. break;
  589. case INT:
  590. val = std::round(val1); //integer part
  591. break;
  592. case INVERSE:
  593. val = CCCoreLib::LessThanEpsilon(std::abs(val1)) ? std::numeric_limits<double>::quiet_NaN() : (1.0 / val1);
  594. break;
  595. case SET:
  596. assert(sf2Desc);
  597. val = sf2Desc->constantValue;
  598. break;
  599. case ABS:
  600. val = std::abs(val1);
  601. break;
  602. case MIN:
  603. case MAX:
  604. {
  605. ScalarType val2 = 0;
  606. assert(sf2Desc);
  607. if (sf2Desc->isConstantValue)
  608. {
  609. val2 = sf2Desc->constantValue;
  610. }
  611. else
  612. {
  613. assert(sf2);
  614. val2 = sf2->getValue(i);
  615. }
  616. if (ccScalarField::ValidValue(val2))
  617. {
  618. val = op == MIN ? std::min(val1, val2) : std::max(val1, val2);
  619. }
  620. }
  621. break;
  622. default:
  623. assert(false);
  624. break;
  625. }
  626. }
  627. sfDest->setValue(i,val);
  628. }
  629. sfDest->computeMinAndMax();
  630. cloud->setCurrentDisplayedScalarField(sfIdx);
  631. return true;
  632. }