/* * 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 "Graph/BoostGraphCreator.hpp" #include "Graph/BoostGraphHelpers.hpp" #include "Graph/BreadthFirstSearchGatherer.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; } static bool addEdgePredicate( const bond &_bond, const std::vector &_atomids) { ASSERT(_atomids.size() == (size_t)2, "addEdgePredicate() - atomids must contain exactly two ids."); // do not add selected edge return ((_bond.leftatom->getId() != _atomids[0]) || (_bond.rightatom->getId() != _atomids[1])); } 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)); } } // gather sorted ids std::vector atomids(2); atomids[0] = atoms[0]->getId(); atomids[1] = atoms[1]->getId(); std::sort(atomids.begin(), atomids.end()); LOG(1, "DEBUG: Selected nodes are " << atomids); // get nodes on either side of selected bond via BFS discovery BoostGraphCreator BGcreator; BGcreator.createFromMolecule(*mol, boost::bind(addEdgePredicate, _1, boost::ref(atomids))); BreadthFirstSearchGatherer NodeGatherer(BGcreator); BoostGraphHelpers::Nodeset_t bondside_sets = NodeGatherer(params.bondside.get() ? atoms[0]->getId() : atoms[1]->getId()); std::sort(bondside_sets.begin(), bondside_sets.end()); // 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); // for each atom determine in which set of nodes it is and shift accordingly const atomId_t &atomid = (*iter)->getId(); LOG(3, "DEBUG: Inspecting atom " << **iter << " at " << position); if (std::binary_search(bondside_sets.begin(), bondside_sets.end(), atomid)) { 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 ====================== */