//########################################################################## //# # //# CLOUDCOMPARE # //# # //# This program is free software; you can redistribute it and/or modify # //# it under the terms of the GNU General Public License as published by # //# the Free Software Foundation; version 2 or later of the License. # //# # //# This program is distributed in the hope that it will be useful, # //# but WITHOUT ANY WARRANTY; without even the implied warranty of # //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # //# GNU General Public License for more details. # //# # //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) # //# # //########################################################################## #include "ccGraphicalTransformationTool.h" #include "mainwindow.h" #include #include //qCC_db #include #include #include #include #include #include ccGraphicalTransformationTool::ccGraphicalTransformationTool(QWidget* parent) : ccOverlayDialog(parent) , Ui::GraphicalTransformationDlg() , m_toTransform("transformed") { setupUi(this); rotComboBox->clear(); rotComboBox->insertItem(0, "XYZ", rotComboBoxItems::XYZ); rotComboBox->insertItem(1, "X", rotComboBoxItems::X); rotComboBox->insertItem(2, "Y", rotComboBoxItems::Y); rotComboBox->insertItem(3, "Z", rotComboBoxItems::Z); rotComboBox->insertItem(4, "None", rotComboBoxItems::NONE); rotComboBox->setCurrentIndex(rotComboBoxItems::XYZ); connect(pauseButton, &QAbstractButton::toggled, this, &ccGraphicalTransformationTool::pause); connect(okButton, &QAbstractButton::clicked, this, &ccGraphicalTransformationTool::apply); connect(razButton, &QAbstractButton::clicked, this, &ccGraphicalTransformationTool::reset); connect(cancelButton, &QAbstractButton::clicked, this, &ccGraphicalTransformationTool::cancel); connect(TxCheckBox, &QCheckBox::clicked, this, &ccGraphicalTransformationTool::incrementalTranslationToggle); connect(TyCheckBox, &QCheckBox::clicked, this, &ccGraphicalTransformationTool::incrementalTranslationToggle); connect(TzCheckBox, &QCheckBox::clicked, this, &ccGraphicalTransformationTool::incrementalTranslationToggle); connect(advPushButton, &QPushButton::toggled, this, &ccGraphicalTransformationTool::advModeToggle); connect(refAxisRadio, &QRadioButton::toggled, this, &ccGraphicalTransformationTool::advRefAxisRadioToggled); connect(objCenterRadio, &QRadioButton::toggled, this, &ccGraphicalTransformationTool::advObjectAxisRadioToggled); connect(advTranslateComboBox, qOverload(&QComboBox::currentIndexChanged), this, &ccGraphicalTransformationTool::advTranslateRefUpdate); connect(advRotateComboBox, qOverload(&QComboBox::currentIndexChanged), this, &ccGraphicalTransformationTool::advRotateRefUpdate); connect(rotComboBox, qOverload(&QComboBox::activated), this, &ccGraphicalTransformationTool::advRotateComboBoxUpdate); connect(incrementalForwardButton, &QAbstractButton::clicked, this, &ccGraphicalTransformationTool::incrementalTransform); connect(incrementalBackwardButton, &QAbstractButton::clicked, this, &ccGraphicalTransformationTool::incrementalTransform); //add shortcuts addOverriddenShortcut(Qt::Key_Space); //space bar for the "pause" button addOverriddenShortcut(Qt::Key_Escape); //escape key for the "cancel" button addOverriddenShortcut(Qt::Key_Return); //return key for the "ok" button connect(this, &ccOverlayDialog::shortcutTriggered, this, &ccGraphicalTransformationTool::onShortcutTriggered); objCenterRadio->setChecked(true); advModeToggle(false); } ccGraphicalTransformationTool::~ccGraphicalTransformationTool() { clear(); } void ccGraphicalTransformationTool::onShortcutTriggered(int key) { switch(key) { case Qt::Key_Space: pauseButton->toggle(); return; case Qt::Key_Return: okButton->click(); return; case Qt::Key_Escape: cancelButton->click(); return; default: //nothing to do break; } } void ccGraphicalTransformationTool::pause(bool state) { if (!m_associatedWin) { return; } if (state) { m_associatedWin->setInteractionMode(ccGLWindowInterface::MODE_TRANSFORM_CAMERA); m_associatedWin->displayNewMessage("Transformation [PAUSED]", ccGLWindowInterface::UPPER_CENTER_MESSAGE, false, 3600, ccGLWindowInterface::MANUAL_TRANSFORMATION_MESSAGE); m_associatedWin->displayNewMessage("Unpause to transform again", ccGLWindowInterface::UPPER_CENTER_MESSAGE, true, 3600, ccGLWindowInterface::MANUAL_TRANSFORMATION_MESSAGE); } else { m_associatedWin->setInteractionMode(ccGLWindowInterface::MODE_TRANSFORM_ENTITIES); updateDisplayMessage(); } //update mini-GUI pauseButton->blockSignals(true); pauseButton->setChecked(state); pauseButton->blockSignals(false); m_associatedWin->redraw(false, false); //we have to redraw the 3D layer for labels! } void ccGraphicalTransformationTool::advModeToggle(bool state) { advRotateComboBox->setVisible(state); advTranslateComboBox->setVisible(state); translateLabel->setVisible(state); rotateLabel->setVisible(state); rotAxisLabel->setVisible(state); objCenterRadio->setVisible(state); refAxisRadio->setVisible(state); incrementalRotLabel->setVisible(state); incrementalRotSpin->setVisible(state); incrementalTransLabel->setVisible(state); incrementalTransSpin->setVisible(state); incrementalForwardButton->setVisible(state); incrementalBackwardButton->setVisible(state); IncrementalTransformLabel->setVisible(state); m_advMode = state; int wPrev = this->width(); if (state) { this->setGeometry(this->x() + (-wPrev + 250), this->y(), 250, 235); if (advTranslateComboBox->currentIndex() != 0) { TxCheckBox->setEnabled(false); TyCheckBox->setEnabled(false); } advRotateRefUpdate(advRotateComboBox->currentIndex()); advTranslateRefUpdate(advTranslateComboBox->currentIndex()); } else { TxCheckBox->setEnabled(true); TyCheckBox->setEnabled(true); this->setGeometry(this->x() , this->y(), 0, 0); this->adjustSize(); //adjust size will minimize the display height with the dropdowns not visible this->setGeometry(this->x() + (wPrev - 250), this->y(), 250, this->height()); advRotateComboBox->setCurrentIndex(0); //index 0 is always the origin advTranslateComboBox->setCurrentIndex(0); //index 0 is always the origin } //update mini-GUI advPushButton->blockSignals(true); advPushButton->setChecked(state); advPushButton->blockSignals(false); updateDisplayMessage(); } void ccGraphicalTransformationTool::populateAdvModeItems() { advRotateComboBox->clear(); advTranslateComboBox->clear(); advRotateComboBox->insertItem(0, "Origin"); advTranslateComboBox->insertItem(0, "Origin"); MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow) { ccHObject* root = mainWindow->dbRootObject(); ccHObject::Container polylines; ccHObject::Container coordinateSystems; if (root) { root->filterChildren(polylines, true, CC_TYPES::POLY_LINE); root->filterChildren(coordinateSystems, true, CC_TYPES::COORDINATESYSTEM); root->filterChildren(m_advancedModeObjectList, true, CC_TYPES::PLANE); } if (!polylines.empty()) { for (size_t i = 0; i < polylines.size(); i++) { ccPolyline* poly = static_cast(polylines[i]); if (poly->size() == 2) //only single segment polylines allowed { m_advancedModeObjectList.push_back(polylines[i]); } } } if (!coordinateSystems.empty()) { for (size_t i = 0; i < coordinateSystems.size(); i++) { m_advancedModeObjectList.push_back(coordinateSystems[i]); } } if (!m_advancedModeObjectList.empty()) { for (size_t i = 0; i < m_advancedModeObjectList.size(); ++i) { QString item = QString("%1 (ID=%2)").arg(m_advancedModeObjectList[i]->getName()).arg(m_advancedModeObjectList[i]->getUniqueID()); advTranslateComboBox->insertItem(static_cast(i) + 1, item, QVariant(m_advancedModeObjectList[i]->getUniqueID())); advRotateComboBox->insertItem(static_cast(i) + 1, item, QVariant(m_advancedModeObjectList[i]->getUniqueID())); } } } } ccGLMatrixd ccGraphicalTransformationTool::arbitraryVectorTranslation(const CCVector3& vec) { double theta = 0; if (CCCoreLib::LessThanEpsilon(std::abs(vec.z))) { if (CCCoreLib::LessThanEpsilon(std::abs(vec.y))) { theta = 0; } else if (vec.y < 0) { theta = -M_PI_2; //atan of -infinity is -pi/2 } else { theta = M_PI_2; //atan of +infinity is pi/2 } } else { theta = std::atan(vec.y / vec.z); if (vec.y < 0 && vec.z < 0) { theta = theta - M_PI; } else if (vec.z < 0 && vec.y > 0) { theta = M_PI + theta; } } double phiDenominator = std::sqrt((vec.y * vec.y) + (vec.z * vec.z)); double phi = 0; if (CCCoreLib::LessThanEpsilon(phiDenominator)) { if (CCCoreLib::LessThanEpsilon(std::abs(vec.x))) { phi = 0; } else if (vec.x < 0) { phi = -M_PI_2; //atan of -infinity is -pi/2 } else { phi = M_PI_2; //atan of +infinity is pi/2 } } else { phi = std::atan(vec.x / phiDenominator); } ccGLMatrixd xRotation = ccGLMatrixd(); xRotation.setColumn(1, CCVector3d(0, std::cos(theta), -std::sin(theta))); xRotation.setColumn(2, CCVector3d(0, std::sin(theta), std::cos(theta))); ccGLMatrixd yRotation = ccGLMatrixd(); yRotation.setColumn(0, CCVector3d(std::cos(phi), 0, -std::sin(phi))); yRotation.setColumn(2, CCVector3d(std::sin(phi), 0, std::cos(phi))); ccGLMatrixd arbitraryVectorTranslationAdjust = xRotation * yRotation; //special case if (CCCoreLib::LessThanEpsilon(std::abs(vec.x)) && CCCoreLib::LessThanEpsilon(std::abs(vec.y)) && vec.z < 0) { arbitraryVectorTranslationAdjust.scaleRotation(-1); } return arbitraryVectorTranslationAdjust; } ccGLMatrixd ccGraphicalTransformationTool::arbitraryVectorRotation(double angle, const CCVector3d& arbitraryVector) { // advRotationTransform = (cos(theta)*I) + ((1-cos(theta)*u) (X) u) + (sin(theta)*u_skewsym) // (X) represents tensor product // Reference: Ch 4.7.3 in Geometric Tools for Computer Graphics - P. Schneider & D. Eberly double cosTheta = std::cos(angle); double sinTheta = std::sin(angle); ccGLMatrixd firstTerm = ccGLMatrixd(); firstTerm.scaleRotation(cosTheta); ccGLMatrixd secondTerm = ccGLMatrixd(); CCVector3d v = (1 - cosTheta) * arbitraryVector; CCVector3d w = arbitraryVector; secondTerm.setColumn(0, CCVector3d(v[0] * w[0], v[1] * w[0], v[2] * w[0])); secondTerm.setColumn(1, CCVector3d(v[0] * w[1], v[1] * w[1], v[2] * w[1])); secondTerm.setColumn(2, CCVector3d(v[0] * w[2], v[1] * w[2], v[2] * w[2])); ccGLMatrixd thirdTerm = ccGLMatrixd(); thirdTerm.setColumn(0, CCVector3d(0, arbitraryVector[2], -arbitraryVector[1])); thirdTerm.setColumn(1, CCVector3d(-arbitraryVector[2], 0, arbitraryVector[0])); thirdTerm.setColumn(2, CCVector3d(arbitraryVector[1], -arbitraryVector[0], 0)); thirdTerm.scaleRotation(sinTheta); ccGLMatrixd advRotationTransform = firstTerm; advRotationTransform += secondTerm; advRotationTransform.scaleRow(3, .5); advRotationTransform += thirdTerm; advRotationTransform.scaleRow(3, .5); return advRotationTransform; } bool ccGraphicalTransformationTool::setAdvTranslationTransform(ccHObject* translateRef) { if (!m_associatedWin) { assert(false); return false; } if (translateRef == nullptr) { return false; } if (translateRef->isA(CC_TYPES::POLY_LINE)) { ccPolyline* line = static_cast(translateRef); CCVector3 arbitraryVec = *line->getPoint(1) - *line->getPoint(0); m_advTranslationTransform = arbitraryVectorTranslation(arbitraryVec); TxCheckBox->setChecked(false); TyCheckBox->setChecked(false); TxCheckBox->setEnabled(false); TyCheckBox->setEnabled(false); return true; } else if (translateRef->isA(CC_TYPES::PLANE)) { ccPlane* plane = static_cast(translateRef); m_advTranslationTransform = ccGLMatrixd(plane->getTransformation().data()); TxCheckBox->setEnabled(true); TyCheckBox->setEnabled(true); return true; } else if (translateRef->isA(CC_TYPES::COORDINATESYSTEM)) { ccCoordinateSystem* cs = static_cast(translateRef); m_advTranslationTransform = ccGLMatrixd(cs->getTransformation().data()); TxCheckBox->setEnabled(true); TyCheckBox->setEnabled(true); return true; } else { advTranslateComboBox->setCurrentIndex(0); TxCheckBox->setEnabled(true); TyCheckBox->setEnabled(true); return false; } } bool ccGraphicalTransformationTool::setAdvRotationAxis(ccHObject* rotateRef, rotComboBoxItems selectedAxis) { if (!m_associatedWin || !rotateRef) { assert(false); return false; } CCVector3d newCenter; CCVector3d arbitraryVec; if (rotateRef->isA(CC_TYPES::POLY_LINE)) { ccPolyline* line = static_cast(rotateRef); CCVector3d end = *line->getPoint(1); CCVector3d start = *line->getPoint(0); arbitraryVec = end - start; rotComboBox->clear(); rotComboBox->insertItem(0, "Z", rotComboBoxItems::Z); rotComboBox->insertItem(1, "None", rotComboBoxItems::NONE); rotComboBox->setCurrentIndex(rotComboBoxItems::Z); incrementalRotationToggle(rotComboBoxItems::Z); m_advRotationRefObjCenter = (start + end) / 2; arbitraryVec.normalize(); } else if (rotateRef->isA(CC_TYPES::PLANE)) { ccPlane* plane = static_cast(rotateRef); arbitraryVec = plane->getNormal(); rotComboBox->clear(); rotComboBox->insertItem(0, "Z", rotComboBoxItems::Z); rotComboBox->insertItem(1, "None", rotComboBoxItems::NONE); rotComboBox->setCurrentIndex(rotComboBoxItems::Z); incrementalRotationToggle(rotComboBoxItems::Z); m_advRotationRefObjCenter = plane->getCenter(); } else if (rotateRef->isA(CC_TYPES::COORDINATESYSTEM)) { ccCoordinateSystem* cs = static_cast(rotateRef); switch (selectedAxis) { case rotComboBoxItems::X: { arbitraryVec = cs->getYZPlaneNormal(); break; } case rotComboBoxItems::Y: { arbitraryVec = cs->getZXPlaneNormal(); break; } case rotComboBoxItems::Z: default: { selectedAxis = rotComboBoxItems::Z; arbitraryVec = cs->getXYPlaneNormal(); break; } } rotComboBox->clear(); rotComboBox->insertItem(0, "X", rotComboBoxItems::X); rotComboBox->insertItem(1, "Y", rotComboBoxItems::Y); rotComboBox->insertItem(2, "Z", rotComboBoxItems::Z); rotComboBox->insertItem(3, "None", rotComboBoxItems::NONE); rotComboBox->setCurrentIndex(rotComboBoxItems::Z); incrementalRotationToggle(rotComboBoxItems::Z); m_advRotationRefObjCenter = cs->getOrigin(); } else //Not a supported primitive for rotateRef { CCVector3d newCenter = m_toTransform.getBB_recursive().getCenter(); setRotationCenter(newCenter); advRotateComboBox->setCurrentIndex(0); return false; } if (rotComboBox->findData(selectedAxis) == -1) { rotComboBox->setCurrentIndex(rotComboBox->findData(rotComboBoxItems::Z)); // Default to Z axis if passed an invalid axis selection } else { rotComboBox->setCurrentIndex(rotComboBox->findData(selectedAxis)); } if (refAxisRadio->isChecked()) { if (m_advRotateRefIsChild) { newCenter = m_advRotationRefObjCenter; m_position.applyRotation(arbitraryVec); } else { newCenter = (m_rotation.inverse() * m_advRotationRefObjCenter - m_rotation.inverse() * m_position.getTranslationAsVec3D()); } } else if (objCenterRadio->isChecked()) { if (m_advRotateRefIsChild) { m_position.applyRotation(arbitraryVec); } newCenter = m_toTransform.getBB_recursive().getCenter(); } m_advRotationAxis = m_rotation.inverse() * arbitraryVec; setRotationCenter(newCenter); return true; } bool ccGraphicalTransformationTool::entityInTransformList(ccHObject* entity) { for (unsigned j = 0; j < m_toTransform.getChildrenNumber(); j++) { if (entity == m_toTransform.getChild(j) || m_toTransform.getChild(j)->isAncestorOf(entity)) { return true; } } return false; } void ccGraphicalTransformationTool::advTranslateRefUpdate(int index) { if (index == 0 || m_advancedModeObjectList.empty()) // index 0 is always the origin { TxCheckBox->setEnabled(true); TyCheckBox->setEnabled(true); m_advTranslationTransform.toIdentity(); MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow && m_advTranslateRef != nullptr) { mainWindow->db()->unselectEntity(m_advTranslateRef); m_advTranslateRef = nullptr; } return; } int id = advTranslateComboBox->itemData(index).toInt(); for (size_t i = 0; i < m_advancedModeObjectList.size(); i++) { if (id == m_advancedModeObjectList[i]->getUniqueID()) { MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow) { if (m_advTranslateRef != nullptr && m_advTranslateRef != m_advRotateRef) //leave selected if rotate ref { mainWindow->db()->unselectEntity(m_advTranslateRef); } m_advTranslateRef = m_advancedModeObjectList[i]; m_advTranslateRefIsChild = entityInTransformList(m_advTranslateRef); if (m_advTranslateRef != m_advRotateRef) // already selected { mainWindow->db()->selectEntity(m_advTranslateRef, true); } } if (!setAdvTranslationTransform(m_advTranslateRef)) { ccLog::Error("Error calculating adv translation transform, cannot translate along selected item"); advTranslateComboBox->setCurrentIndex(0); } return; } } ccLog::Error("Error finding the selected object in DB Tree, cannot translate along selected object"); advTranslateComboBox->setCurrentIndex(0); } void ccGraphicalTransformationTool::advRotateComboBoxUpdate(int index) { rotComboBoxItems selectedAxis = static_cast(rotComboBox->itemData(index).toInt()); incrementalRotationToggle(selectedAxis); if (!m_advMode || !m_advRotateRef) { return; } if (selectedAxis == rotComboBoxItems::NONE) { return; } if (!setAdvRotationAxis(m_advRotateRef, selectedAxis)) { ccLog::Error("Error setting adv rotation axis, cannot rotate around selected item"); advRotateComboBox->setCurrentIndex(0); } } void ccGraphicalTransformationTool::advRotateRefUpdate(int index) { if (index == 0 || m_advancedModeObjectList.empty()) // index 0 is always the origin { if (m_advRotateRef != nullptr) { rotComboBox->clear(); rotComboBox->insertItem(0, "XYZ", rotComboBoxItems::XYZ); rotComboBox->insertItem(1, "X", rotComboBoxItems::X); rotComboBox->insertItem(2, "Y", rotComboBoxItems::Y); rotComboBox->insertItem(3, "Z", rotComboBoxItems::Z); rotComboBox->insertItem(4, "None", rotComboBoxItems::NONE); rotComboBox->setCurrentIndex(rotComboBoxItems::Z); incrementalRotationToggle(rotComboBoxItems::Z); } CCVector3d center = m_toTransform.getBB_recursive().getCenter(); setRotationCenter(center); m_advRotationRefObjCenter = CCVector3d(0, 0, 0); m_advRotationAxis = CCVector3d(0, 0, 1); objCenterRadio->setEnabled(true); objCenterRadio->setChecked(true); refAxisRadio->setEnabled(false); MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow && m_advRotateRef != nullptr) { mainWindow->db()->unselectEntity(m_advRotateRef); m_advRotateRef = nullptr; } return; } int id = advRotateComboBox->itemData(index).toInt(); for (size_t i = 0; i < m_advancedModeObjectList.size(); i++) { if (id == m_advancedModeObjectList[i]->getUniqueID()) { MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow) { if (m_advRotateRef != nullptr && m_advTranslateRef != m_advRotateRef)//leave selected if translate ref { mainWindow->db()->unselectEntity(m_advRotateRef); } m_advRotateRef = m_advancedModeObjectList[i]; if (m_advTranslateRef != m_advRotateRef) // already selected { mainWindow->db()->selectEntity(m_advRotateRef, true); } refAxisRadio->setEnabled(true); m_advRotateRefIsChild = entityInTransformList(m_advRotateRef); if (m_advRotateRefIsChild) { refAxisRadio->setChecked(true); objCenterRadio->setEnabled(false); } else { objCenterRadio->setEnabled(true); } } if (!setAdvRotationAxis(m_advRotateRef, static_cast(rotComboBox->itemData(index).toInt()))) { ccLog::Error("Error setting adv rotation axis, cannot rotate around selected item"); advRotateComboBox->setCurrentIndex(0); } return; } } ccLog::Error("Error finding the selected object in DB Tree, cannot translate along selected object"); advRotateComboBox->setCurrentIndex(0); } void ccGraphicalTransformationTool::advRefAxisRadioToggled(bool state) { if (state) { objCenterRadio->setChecked(false); advRotateRefUpdate(advRotateComboBox->currentIndex()); //force an update } } void ccGraphicalTransformationTool::incrementalTranslationToggle() { const bool isIncrementalActive = TxCheckBox->isChecked() || TxCheckBox->isChecked() || TzCheckBox->isChecked(); incrementalTransLabel->setEnabled(isIncrementalActive); incrementalTransSpin->setEnabled(isIncrementalActive); } void ccGraphicalTransformationTool::incrementalRotationToggle(const rotComboBoxItems & selectedRotationItem) { if(selectedRotationItem == rotComboBoxItems::NONE || selectedRotationItem == rotComboBoxItems::XYZ) { incrementalRotLabel->setDisabled(true); incrementalRotSpin->setDisabled(true); } else { incrementalRotLabel->setEnabled(true); incrementalRotSpin->setEnabled(true); } } void ccGraphicalTransformationTool::advObjectAxisRadioToggled(bool state) { if (state) { refAxisRadio->setChecked(false); advRotateRefUpdate(advRotateComboBox->currentIndex()); //force an update } } void ccGraphicalTransformationTool::clear() { m_toTransform.detachAllChildren(); m_rotation.toIdentity(); m_translation = CCVector3d(0,0,0); m_rotationCenter = CCVector3d(0,0,0); } void ccGraphicalTransformationTool::clearAdvModeEntities() { MainWindow* mainWindow = MainWindow::TheInstance(); if (mainWindow && m_advTranslateRef != nullptr && m_associatedWin) { mainWindow->db()->unselectEntity(m_advTranslateRef); m_advTranslateRef = nullptr; } if (mainWindow && m_advRotateRef != nullptr && m_associatedWin) { mainWindow->db()->unselectEntity(m_advRotateRef); m_advRotateRef = nullptr; } } bool ccGraphicalTransformationTool::addEntity(ccHObject* entity) { assert(entity); if (!entity) return false; //we don't handle entities associated to another context if (entity->getDisplay() != m_associatedWin) { ccLog::Warning(QString("[Graphical Transformation Tool] Can't use entity '%1' cause it's not displayed in the active 3D view!").arg(entity->getName())); return false; } //we can't transform locked entities if (entity->isLocked()) { ccLog::Warning(QString("[Graphical Transformation Tool] Can't transform entity '%1' cause it's locked!").arg(entity->getName())); return false; } //we can't transform child meshes if (entity->isA(CC_TYPES::MESH) && entity->getParent() && entity->getParent()->isKindOf(CC_TYPES::MESH)) { ccLog::Warning(QString("[Graphical Transformation Tool] Entity '%1' can't be modified as it is part of a mesh group. You should 'clone' it first.").arg(entity->getName())); return false; } //eventually, we must check that there is no "parent + sibling" in the selection! //otherwise, the sibling will be rotated twice! unsigned n = m_toTransform.getChildrenNumber(); for (unsigned i=0; iisAncestorOf(entity)) { //we have found a parent, we won't add this entity return false; } //if the inverse is true, then we get rid of the current element! else if (entity->isAncestorOf(previous)) { m_toTransform.detachChild(previous); --n; } else { //proceed ++i; } } m_toTransform.addChild(entity,ccHObject::DP_NONE); return true; } unsigned ccGraphicalTransformationTool::getNumberOfValidEntities() const { return m_toTransform.getChildrenNumber(); } bool ccGraphicalTransformationTool::linkWith(ccGLWindowInterface* win) { if (!ccOverlayDialog::linkWith(win)) { return false; } assert(!win || m_toTransform.getChildrenNumber() == 0); m_toTransform.setDisplay(win); return true; } void ccGraphicalTransformationTool::updateDisplayMessage() { if (!m_associatedWin || pauseButton->isChecked()) { return; } if (advPushButton->isChecked()) { m_associatedWin->displayNewMessage("[Advanced mode]", ccGLWindowInterface::UPPER_CENTER_MESSAGE, false, 3600, ccGLWindowInterface::MANUAL_TRANSFORMATION_MESSAGE); m_associatedWin->displayNewMessage("[Select Plane or Line to translate along/rotate around]", ccGLWindowInterface::UPPER_CENTER_MESSAGE, true, 3600, ccGLWindowInterface::MANUAL_TRANSFORMATION_MESSAGE); m_associatedWin->displayNewMessage("[If plane selected, rotation will be around normal vector]", ccGLWindowInterface::UPPER_CENTER_MESSAGE, true, 3600, ccGLWindowInterface::MANUAL_TRANSFORMATION_MESSAGE); } else { m_associatedWin->displayNewMessage("[Rotation/Translation mode]", ccGLWindowInterface::UPPER_CENTER_MESSAGE, false, 3600, ccGLWindowInterface::MANUAL_TRANSFORMATION_MESSAGE); } m_associatedWin->redraw(true, false); } bool ccGraphicalTransformationTool::start() { assert(!m_processing); assert(m_associatedWin); if (!m_associatedWin) return false; unsigned childNum = m_toTransform.getChildrenNumber(); if (childNum == 0) return false; m_rotation.toIdentity(); m_translation = CCVector3d(0,0,0); m_rotationCenter = m_toTransform.getBB_recursive().getCenter(); //m_rotation center == selected entities center //activate "moving mode" in associated GL window m_associatedWin->setInteractionMode(ccGLWindowInterface::MODE_TRANSFORM_ENTITIES); m_associatedWin->setPickingMode(ccGLWindowInterface::NO_PICKING, Qt::OpenHandCursor); //the user must not close this window! m_associatedWin->setUnclosable(true); connect(m_associatedWin->signalEmitter(), &ccGLWindowSignalEmitter::rotation, this, &ccGraphicalTransformationTool::glRotate); connect(m_associatedWin->signalEmitter(), &ccGLWindowSignalEmitter::translation, this, &ccGraphicalTransformationTool::glTranslate); m_associatedWin->displayNewMessage(QString(), ccGLWindowInterface::UPPER_CENTER_MESSAGE); //clear the area pauseButton->setChecked(false); m_advancedModeObjectList.clear(); populateAdvModeItems(); advRotateComboBox->setCurrentIndex(0); advTranslateComboBox->setCurrentIndex(0); updateDisplayMessage(); m_associatedWin->redraw(true, false); return ccOverlayDialog::start(); } void ccGraphicalTransformationTool::stop(bool state) { if (m_associatedWin) { //deactivate "moving mode" in associated GL window m_associatedWin->setInteractionMode(ccGLWindowInterface::MODE_TRANSFORM_CAMERA); m_associatedWin->setPickingMode(ccGLWindowInterface::DEFAULT_PICKING); m_associatedWin->setUnclosable(false); m_associatedWin->signalEmitter()->disconnect(this); m_associatedWin->displayNewMessage("[Rotation/Translation mode OFF]", ccGLWindowInterface::UPPER_CENTER_MESSAGE, false, 2, ccGLWindowInterface::MANUAL_TRANSFORMATION_MESSAGE); m_associatedWin->redraw(false, false); //we have to redraw the 3D layer for labels! } ccOverlayDialog::stop(state); } void ccGraphicalTransformationTool::glTranslate(const CCVector3d& realT) { CCVector3d t; if (m_advMode) { CCVector3d mouseMove = realT; m_advTranslationTransform.inverse().applyRotation(mouseMove); t = CCVector3d( mouseMove.x * (TxCheckBox->isChecked() ? 1 : 0), mouseMove.y * (TyCheckBox->isChecked() ? 1 : 0), mouseMove.z * (TzCheckBox->isChecked() ? 1 : 0)); m_advTranslationTransform.applyRotation(t); } else { t = CCVector3d( realT.x * (TxCheckBox->isChecked() ? 1 : 0), realT.y * (TyCheckBox->isChecked() ? 1 : 0), realT.z * (TzCheckBox->isChecked() ? 1 : 0)); } if (t.norm2() != 0) { m_translation += t; if (m_advMode && m_advRotateRef != nullptr && refAxisRadio->isChecked() && !m_advRotateRefIsChild) { //If we translate we have to update the rotation center when using the ref axis as center of rotation. //Dont update the rotation center if the rotation ref is contained in m_toTranslate. CCVector3d centerUpdate = m_rotation.inverse() * m_advRotationRefObjCenter - m_rotation.inverse() * m_position.getTranslationAsVec3D(); m_translation += (m_rotationCenter - centerUpdate) - m_rotation * (m_rotationCenter - centerUpdate); m_rotationCenter = centerUpdate; } updateAllGLTransformations(); } } void ccGraphicalTransformationTool::glRotate(const ccGLMatrixd& rotMat) { if (m_advMode && m_advRotateRef != nullptr) { rotComboBoxItems rotAxis = static_cast(rotComboBox->itemData(rotComboBox->currentIndex()).toInt()); double angle = 0; switch (rotAxis) { case rotComboBoxItems::X: angle = std::asin(rotMat.xRotation()(1, 2)); break; case rotComboBoxItems::Y: angle = std::asin(rotMat.yRotation()(2, 0)); break; case rotComboBoxItems::Z: angle = std::asin(rotMat.zRotation()(1, 0)); break; default: return; } ccLog::Print(QString("Angles(%1)").arg(angle)); m_rotation = m_rotation * arbitraryVectorRotation(angle, m_advRotationAxis); } else { switch (rotComboBox->currentIndex()) { case rotComboBoxItems::XYZ: m_rotation = rotMat * m_rotation; break; case rotComboBoxItems::X: m_rotation = rotMat.xRotation() * m_rotation; break; case rotComboBoxItems::Y: m_rotation = rotMat.yRotation() * m_rotation; break; case rotComboBoxItems::Z: m_rotation = rotMat.zRotation() * m_rotation; break; case rotComboBoxItems::NONE: break; } } updateAllGLTransformations(); } void ccGraphicalTransformationTool::incrementalTransform() { //We check the sender to handle forward or backward transformation QObject* senderButton = sender(); const bool isForwardTransform = senderButton == incrementalForwardButton; // The rotation part: // No incremental rotation if rotComboBoxItems == XYZ or rotComboBoxItems == NONE because it has no sense // it could be handled in the subsequent switch cases but it is somehiow more explicit to check // if the SpinBox is enabled if(incrementalRotSpin->isEnabled()) { const double alpha = isForwardTransform ? CCCoreLib::DegreesToRadians(incrementalRotSpin->value()) : -CCCoreLib::DegreesToRadians(incrementalRotSpin->value()); if (m_advMode && m_advRotateRef != nullptr) { m_rotation = m_rotation * arbitraryVectorRotation(alpha, m_advRotationAxis); } else { ccGLMatrixd rotMat; // compute rotation matrix from spinbox switch (static_cast(rotComboBox->itemData(rotComboBox->currentIndex()).toInt())) { case rotComboBoxItems::X: rotMat.initFromParameters(alpha, CCVector3d(1.0,0.0,0.0), CCVector3d(0.0,0.0,0.0)); break; case rotComboBoxItems::Y: rotMat.initFromParameters(alpha, CCVector3d(0.0,1.0,0.0), CCVector3d(0.0,0.0,0.0)); break; case rotComboBoxItems::Z: rotMat.initFromParameters(alpha, CCVector3d(0.0,0.0,1.0), CCVector3d(0.0,0.0,0.0)); break; default: return; } m_rotation = rotMat * m_rotation; } updateAllGLTransformations(); } // The translation part: // we pass a vector with equal magnitude in the three components; gltranslate will handle the correct transform for us. const double transMagnitude = isForwardTransform ? incrementalTransSpin->value() : -incrementalTransSpin->value() ; glTranslate(CCVector3d(transMagnitude,transMagnitude,transMagnitude)); } void ccGraphicalTransformationTool::reset() { m_rotation.toIdentity(); m_translation = CCVector3d(0, 0, 0); updateAllGLTransformations(); advRotateRefUpdate(advRotateComboBox->currentIndex()); //force an update advTranslateRefUpdate(advTranslateComboBox->currentIndex()); //force an update } void ccGraphicalTransformationTool::setRotationCenter(CCVector3d& center) { m_translation += (m_rotationCenter - center) - m_rotation * (m_rotationCenter - center); m_rotationCenter = center; updateAllGLTransformations(); } void ccGraphicalTransformationTool::updateAllGLTransformations() { //we recompute global GL transformation matrix m_position = m_rotation; m_position += m_rotationCenter + m_translation - m_rotation * m_rotationCenter; ccGLMatrix newTransf(m_position.data()); for (unsigned i = 0; i < m_toTransform.getChildrenNumber(); ++i) { ccHObject* child = m_toTransform.getChild(i); child->setGLTransformation(newTransf); child->prepareDisplayForRefresh_recursive(); } if (m_advTranslateRefIsChild && m_advTranslateRef != nullptr) { //update m_advTranslationTransform if the ref object is translated/rotated by virtue of being in m_toTransform if (m_advTranslateRef->isA(CC_TYPES::PLANE)) { m_advTranslationTransform = m_position * ccGLMatrixd(m_advTranslateRef->getGLTransformationHistory().data()); } else if (m_advTranslateRef->isA(CC_TYPES::POLY_LINE)) { ccPolyline* line = static_cast(m_advTranslateRef); CCVector3 arbitraryVec = line->getGLTransformation() * (*line->getPoint(1) - *line->getPoint(0)); m_advTranslationTransform = m_position * arbitraryVectorTranslation(arbitraryVec); } } MainWindow::RefreshAllGLWindow(false); } void ccGraphicalTransformationTool::apply() { //we recompute global GL transformation matrix and display it in console ccGLMatrixd finalTrans = m_rotation; finalTrans += m_rotationCenter + m_translation - m_rotation * m_rotationCenter; ccGLMatrixd finalTransCorrected = finalTrans; #define NORMALIZE_TRANSFORMATION_MATRIX_WITH_EULER #ifdef NORMALIZE_TRANSFORMATION_MATRIX_WITH_EULER { //convert matrix back and forth so as to be sure to get a 'true' rotation matrix //DGM: we use Euler angles, as the axis/angle method (formerly used) is not robust //enough! Shifts could be perceived by the user. double phi_rad = 0.0; double theta_rad = 0.0; double psi_rad = 0.0; CCVector3d t3D; finalTrans.getParameters(phi_rad,theta_rad,psi_rad,t3D); finalTransCorrected.initFromParameters(phi_rad,theta_rad,psi_rad,t3D); #ifdef QT_DEBUG ccLog::Print("[GraphicalTransformationTool] Final transformation (before correction):"); ccLog::Print(finalTrans.toString(12,' ')); //full precision ccLog::Print(QString("Angles(%1,%2,%3) T(%5,%6,%7)").arg(phi_rad).arg(theta_rad).arg(psi_rad).arg(t3D.x).arg(t3D.y).arg(t3D.z)); #endif } #endif //NORMALIZE_TRANSFORMATION_MATRIX_WITH_EULER #ifdef QT_DEBUG //test: compute rotation "norm" (as it may not be exactly 1 due to numerical (in)accuracy!) { ccGLMatrixd finalRotation = finalTransCorrected; finalRotation.setTranslation(CCVector3(0,0,0)); ccGLMatrixd finalRotationT = finalRotation.transposed(); ccGLMatrixd idTrans = finalRotation * finalRotationT; double norm = idTrans.data()[0] * idTrans.data()[5] * idTrans.data()[10]; ccLog::PrintDebug("[GraphicalTransformationTool] T*T-1:"); ccLog::PrintDebug(idTrans.toString(12,' ')); //full precision ccLog::PrintDebug(QString("Rotation norm = %1").arg(norm,0,'f',12)); } #endif //update GL transformation for all entities ccGLMatrix correctedFinalTrans(finalTransCorrected.data()); for (unsigned i = 0; i < m_toTransform.getChildrenNumber(); ++i) { ccHObject* toTransform = m_toTransform.getChild(i); toTransform->setGLTransformation(correctedFinalTrans); //DGM: warning, applyGLTransformation may delete the associated octree! MainWindow::ccHObjectContext objContext = MainWindow::TheInstance()->removeObjectTemporarilyFromDBTree(toTransform); toTransform->applyGLTransformation_recursive(); toTransform->prepareDisplayForRefresh_recursive(); MainWindow::TheInstance()->putObjectBackIntoDBTree(toTransform,objContext); //special case: if the object is a mesh vertices set, we may have to update the mesh normals! if (toTransform->isA(CC_TYPES::POINT_CLOUD) && toTransform->getParent() && toTransform->getParent()->isKindOf(CC_TYPES::MESH)) { ccMesh* mesh = static_cast(toTransform->getParent()); if (mesh->hasTriNormals() && !m_toTransform.isAncestorOf(mesh)) { mesh->transformTriNormals(correctedFinalTrans); } } } clearAdvModeEntities(); stop(true); clear(); //output resulting transformation matrix ccLog::Print("[GraphicalTransformationTool] Applied transformation:"); ccLog::Print(correctedFinalTrans.toString(12,' ')); //full precision #ifdef QT_DEBUG { float phi_rad = 0.0f; float theta_rad = 0.0f; float psi_rad = 0.0f; CCVector3f t3D; correctedFinalTrans.getParameters(phi_rad,theta_rad,psi_rad,t3D); ccLog::Print(QString("Angles(%1,%2,%3) T(%5,%6,%7)").arg(phi_rad).arg(theta_rad).arg(psi_rad).arg(t3D.x).arg(t3D.y).arg(t3D.z)); } #endif } void ccGraphicalTransformationTool::cancel() { for (unsigned i=0; iresetGLTransformation(); child->prepareDisplayForRefresh_recursive(); } clearAdvModeEntities(); stop(false); clear(); //MainWindow::RefreshAllGLWindow(); }