/* * Project: MoleCuilder * Description: creates and alters molecular systems * Copyright (C) 2017 Frederik Heber. All rights reserved. * * * This file is part of MoleCuilder. * * MoleCuilder 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, either version 2 of the License, or * (at your option) any later version. * * MoleCuilder 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. * * You should have received a copy of the GNU General Public License * along with MoleCuilder. If not, see . */ /* * RotateAroundBondAction.cpp * * Created on: Mar 22, 2017 * Author: heber */ // include config.h #ifdef HAVE_CONFIG_H #include #endif //#include "CodePatterns/MemDebug.hpp" #include "Actions/MoleculeAction/RotateAroundBondAction.hpp" #include "CodePatterns/Assert.hpp" #include "CodePatterns/Log.hpp" #include "LinearAlgebra/Line.hpp" #include "LinearAlgebra/Plane.hpp" #include "Actions/UndoRedoHelpers.hpp" #include "Graph/CyclicStructureAnalysis.hpp" #include "Graph/DepthFirstSearchAnalysis.hpp" #include "Atom/atom.hpp" #include "Atom/AtomicInfo.hpp" #include "Bond/bond.hpp" #include "molecule.hpp" #include "World.hpp" #include "WorldTime.hpp" using namespace MoleCuilder; // and construct the stuff #include "RotateAroundBondAction.def" #include "Action_impl_pre.hpp" /** =========== define the function ====================== */ static bool IsBondContainedInCycle(const bond::ptr &_bond) { // get the BackEdgeStack from somewhere DepthFirstSearchAnalysis DFS; DFS(); DFS.CyclicBondAnalysis(); // std::deque BackEdgeStack = DFS.getBackEdgeStack(); // // then we analyse the cycles and get them // CyclicStructureAnalysis CycleAnalysis(ExcludeHydrogen); // hydrogens never contained in cycles // CycleAnalysis(&BackEdgeStack); return _bond->Cyclic; } ActionState::ptr MoleculeRotateAroundBondAction::performCall() { // check preconditions const std::vector< atom *> atoms = World::getInstance().getSelectedAtoms(); if (atoms.size() != 2) { STATUS("Exactly two atoms must be selected to specify a bond."); return Action::failure; } if (!atoms[0]->IsBondedTo(WorldTime::getTime(), atoms[1])) { STATUS("Two given atoms are not bonded."); return Action::failure; } molecule *mol = World::getInstance(). getMolecule(MoleculeById(atoms[0]->getMolecule()->getId())); if (mol != atoms[1]->getMolecule()) { STATUS("The two selected atoms must belong to the same molecule."); return Action::failure; } if (IsBondContainedInCycle(atoms[0]->getBond(atoms[1]))) { STATUS("The given bond is contained in a cycle, cannot rotate!"); return Action::failure; } // gather undo information: store position of all atoms of molecule std::vector UndoInfo; UndoInfo.reserve(mol->size()); { for (molecule::const_iterator iter = const_cast(mol)->begin(); iter != const_cast(mol)->end(); ++iter) { const atom * const Walker = *iter; UndoInfo.push_back(AtomicInfo(*Walker)); } } // convert from degrees to radian const double angle_radian = params.angle.get() * M_PI/180.; // create the bond plane and mid-distance Vector NormalVector = (atoms[0]->getPosition() - atoms[1]->getPosition()); NormalVector.Normalize(); const Vector OffsetVector = 0.5*(atoms[0]->getPosition() + atoms[1]->getPosition()); Plane bondplane(NormalVector, OffsetVector); Line RotationAxis(OffsetVector, NormalVector); // go through the molecule and rotate each atom relative two plane for (molecule::iterator iter = mol->begin(); iter != mol->end(); ++iter) { const Vector &position = (*iter)->getPosition(); const double signed_distance = bondplane.SignedDistance(position); LOG(3, "DEBUG: Inspecting atom " << **iter << " at " << position); if ((params.bondside.get() && (signed_distance > 0)) || (!params.bondside.get() && (signed_distance < 0))) { LOG(4, "DEBUG: Rotating atom " << **iter << " by " << angle_radian << " rad around " << RotationAxis); (*iter)->setPosition(RotationAxis.rotateVector((*iter)->getPosition(), angle_radian)); LOG(4, "DEBUG: New position of atom " << **iter); ASSERT( fabs(signed_distance - bondplane.SignedDistance(position)) < MYEPSILON, "MoleculeRotateAroundBondAction::performCall() - signed distance was " +toString(signed_distance)+" and is now "+toString(bondplane.SignedDistance(position))); } } MoleculeRotateAroundBondState *UndoState = new MoleculeRotateAroundBondState(UndoInfo, OffsetVector, NormalVector, mol, params); return ActionState::ptr(UndoState); } ActionState::ptr MoleculeRotateAroundBondAction::performUndo(ActionState::ptr _state) { MoleculeRotateAroundBondState *state = assert_cast(_state.get()); // set stored old state SetAtomsFromAtomicInfo(state->UndoInfo); return ActionState::ptr(_state); } ActionState::ptr MoleculeRotateAroundBondAction::performRedo(ActionState::ptr _state){ MoleculeRotateAroundBondState *state = assert_cast(_state.get()); // create the bond plane and mid-distance Plane bondplane(state->NormalVector, state->OffsetVector); Line RotationAxis(state->OffsetVector, state->NormalVector); const double angle_radian = params.angle.get() * M_PI/180.; // go through the molecule and rotate each atom relative two plane for (molecule::iterator iter = state->mol->begin(); iter != state->mol->end(); ++iter) { const Vector &position = (*iter)->getPosition(); if ((params.bondside.get() && (bondplane.SignedDistance(position) > 0)) || (!params.bondside.get() && (bondplane.SignedDistance(position) < 0))) { (*iter)->setPosition(RotationAxis.rotateVector((*iter)->getPosition(), angle_radian)); } } return ActionState::ptr(_state); } bool MoleculeRotateAroundBondAction::canUndo() { return true; } bool MoleculeRotateAroundBondAction::shouldUndo() { return true; } /** =========== end of function ====================== */