/********************************************************************** Camera - Class for representing the view. Copyright (C) 2007 Benoit Jacob This file is part of the Avogadro molecular editor project. For more information, see Avogadro 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. Avogadro 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **********************************************************************/ #include #include #include #include using namespace Eigen; namespace Avogadro { class CameraPrivate { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW CameraPrivate() {}; Eigen::Transform3d modelview, projection; const GLWidget *parent; double angleOfViewY; }; Camera::Camera(const GLWidget *parent, double angleOfViewY) : d(new CameraPrivate) { d->modelview.setIdentity(); d->projection.setIdentity(); d->parent = parent; d->angleOfViewY = angleOfViewY; } Camera::~Camera() { delete d; } Camera::Camera(const Camera *camera) : d(new CameraPrivate) { d->modelview = camera->d->modelview; d->projection = camera->d->projection; d->parent = camera->d->parent; d->angleOfViewY = camera->d->angleOfViewY; } void Camera::setParent(const GLWidget *parent) { d->parent = parent; } void Camera::normalize() { Eigen::Block c0(d->modelview.matrix(), 0, 0), c1(d->modelview.matrix(), 0, 1), c2(d->modelview.matrix(), 0, 2); c0.normalize(); c1.normalize(); c1 -= c0.dot(c1) * c0; c1.normalize(); c2.normalize(); c2 -= c0.dot(c2) * c0; c2 -= c1.dot(c2) * c1; c2.normalize(); d->modelview.matrix().row(3) << 0, 0, 0, 1; } const GLWidget *Camera::parent() const { return d->parent; } void Camera::setAngleOfViewY(double angleOfViewY) { d->angleOfViewY = angleOfViewY; } double Camera::angleOfViewY() const { return d->angleOfViewY; } void Camera::translate(const Eigen::Vector3d &vector) { d->modelview.translate(vector); } void Camera::pretranslate(const Eigen::Vector3d &vector) { d->modelview.pretranslate(vector); } void Camera::rotate(const double &angle, const Eigen::Vector3d &axis) { d->modelview.rotate(Eigen::AngleAxisd(angle, axis)); normalize(); } void Camera::prerotate(const double &angle, const Eigen::Vector3d &axis) { d->modelview.prerotate(Eigen::AngleAxisd(angle, axis)); normalize(); } double Camera::distance(const Eigen::Vector3d & point) const { return ( d->modelview * point ).norm(); } void Camera::setModelview(const Eigen::Transform3d &matrix) { d->modelview = matrix; } const Eigen::Transform3d & Camera::modelview() const { return d->modelview; } Eigen::Transform3d & Camera::modelview() { return d->modelview; } void Camera::initializeViewPoint() { d->modelview.setIdentity(); if( d->parent == 0 ) return; if( d->parent->molecule() == 0 ) return; // if the molecule is empty, we want to look at its center // (which is probably at the origin, but who knows) from some distance // (here 20.0) -- this gives us some room to work PR#1964674 if( d->parent->molecule()->numAtoms() < 2 ) { d->modelview.translate( d->parent->center() - Vector3d( 0.0, 0.0, 20.0 ) ); return; } // if we're here, the molecule is not empty, i.e. has atoms. // we want a top-down view on it, i.e. the molecule should fit as well as // possible in the (X,Y)-plane. Equivalently, we want the Z axis to be parallel // to the normal vector of the molecule's fitting plane. // Thus we construct a suitable base-change rotation. Matrix3d rotation; rotation.row(2) = d->parent->normalVector(); rotation.row(0) = rotation.row(2).unitOrthogonal(); rotation.row(1) = rotation.row(2).cross(rotation.row(0)); // set the camera's matrix to be (the 4x4 version of) this rotation. d->modelview.linear() = rotation; // now we want to move backwards, in order // to view the molecule from a distance, not from inside it. // This translation must be applied after the above rotation, so we // want a left-multiplication here. Whence pretranslate(). const Vector3d Zaxis(0,0,1); pretranslate( - 3.0 * ( d->parent->radius() + CAMERA_NEAR_DISTANCE ) * Zaxis ); // the above rotation is meant to be a rotation around the molecule's // center. So before this rotation is applied, the molecule's center // must be brought to the origin of the coordinate systemby a translation. // As this translation must be applied first, we want a right-multiplication here. // Whence translate(). translate( - d->parent->center() ); } void Camera::applyPerspective() const { if( d->parent == 0 ) return; if( d->parent->molecule() == 0 ) return; double molRadius = d->parent->radius() + CAMERA_MOL_RADIUS_MARGIN; double distanceToMolCenter = distance( d->parent->center() ); double zNear = std::max( CAMERA_NEAR_DISTANCE, distanceToMolCenter - molRadius ); double zFar = distanceToMolCenter + molRadius; double aspectRatio = static_cast(d->parent->width()) / d->parent->height(); gluPerspective( d->angleOfViewY, aspectRatio, zNear, zFar ); glGetDoublev(GL_PROJECTION_MATRIX, d->projection.data()); } void Camera::applyModelview() const { glMultMatrixd( d->modelview.data() ); } Eigen::Vector3d Camera::unProject(const Eigen::Vector3d & v) const { GLint viewport[4] = {0, 0, parent()->width(), parent()->height() }; Eigen::Vector3d pos; gluUnProject(v.x(), parent()->height() - v.y(), v.z(), d->modelview.data(), d->projection.data(), viewport, &pos.x(), &pos.y(), &pos.z()); return pos; } Eigen::Vector3d Camera::unProject(const QPoint& p, const Eigen::Vector3d& ref) const { return unProject( Eigen::Vector3d( p.x(), p.y(), project(ref).z() )); } Eigen::Vector3d Camera::unProject(const QPoint& p) const { return unProject(p, parent()->center()); } Eigen::Vector3d Camera::project(const Eigen::Vector3d & v) const { GLint viewport[4] = {0, 0, parent()->width(), parent()->height() }; Eigen::Vector3d pos; gluProject(v.x(), v.y(), v.z(), d->modelview.data(), d->projection.data(), viewport, &pos.x(), &pos.y(), &pos.z()); pos.y() = parent()->height() - pos.y(); return pos; } Eigen::Vector3d Camera::backTransformedXAxis() const { return d->modelview.linear().row(0).transpose(); } Eigen::Vector3d Camera::backTransformedYAxis() const { return d->modelview.linear().row(1).transpose(); } Eigen::Vector3d Camera::backTransformedZAxis() const { return d->modelview.linear().row(2).transpose(); } Eigen::Vector3d Camera::transformedXAxis() const { return d->modelview.linear().col(0); } Eigen::Vector3d Camera::transformedYAxis() const { return d->modelview.linear().col(1); } Eigen::Vector3d Camera::transformedZAxis() const { return d->modelview.linear().col(2); } } // end namespace Avogadro