// Viewer.hxx -- alternative flightgear viewer application // // Copyright (C) 2009 - 2012 Mathias Froehlich // // 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; either version 2 of the // License, or (at your option) any later version. // // 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. // // 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. #ifdef HAVE_CONFIG_H #include #endif #include "SlaveCamera.hxx" #include #include "Viewer.hxx" #ifdef FG_HAVE_HLA #include "HLAViewerFederate.hxx" #include "HLAPerspectiveViewer.hxx" #endif namespace fgviewer { class NoUpdateCallback : public osg::NodeCallback { public: virtual ~NoUpdateCallback() { } virtual void operator()(osg::Node* node, osg::NodeVisitor* nodeVisitor) { } }; SlaveCamera::SlaveCamera(const std::string& name) : _name(name), _viewport(new osg::Viewport()) { _referencePointMap["lowerLeft"] = osg::Vec2(-1, -1); _referencePointMap["lowerRight"] = osg::Vec2(1, -1); _referencePointMap["upperRight"] = osg::Vec2(1, 1); _referencePointMap["upperLeft"] = osg::Vec2(-1, 1); } SlaveCamera::~SlaveCamera() { } bool SlaveCamera::setDrawableName(const std::string& drawableName) { if (_camera.valid()) return false; _drawableName = drawableName; return true; } bool SlaveCamera::setViewport(const SGVec4i& viewport) { _viewport->setViewport(viewport[0], viewport[1], viewport[2], viewport[3]); _frustum.setAspectRatio(getAspectRatio()); return true; } bool SlaveCamera::setViewOffset(const osg::Matrix& viewOffset) { _viewOffset = viewOffset; return true; } bool SlaveCamera::setViewOffsetDeg(double headingDeg, double pitchDeg, double rollDeg) { osg::Matrix viewOffset = osg::Matrix::identity(); viewOffset.postMultRotate(osg::Quat(SGMiscd::deg2rad(headingDeg), osg::Vec3(0, 1, 0))); viewOffset.postMultRotate(osg::Quat(SGMiscd::deg2rad(pitchDeg), osg::Vec3(-1, 0, 0))); viewOffset.postMultRotate(osg::Quat(SGMiscd::deg2rad(rollDeg), osg::Vec3(0, 0, 1))); return setViewOffset(viewOffset); } bool SlaveCamera::setFrustum(const Frustum& frustum) { _frustum = frustum; return true; } void SlaveCamera::setFustumByFieldOfViewDeg(double fieldOfViewDeg) { Frustum frustum(getAspectRatio()); frustum.setFieldOfViewDeg(fieldOfViewDeg); setFrustum(frustum); } bool SlaveCamera::setRelativeFrustum(const std::string names[2], const SlaveCamera& referenceCameraData, const std::string referenceNames[2]) { // Track the way from one projection space to the other: // We want // P = T2*S*T*P0 // where P0 is the projection template sensible for the given window size, // S a scale matrix and T is a translation matrix. // We need to determine T and S so that the reference points in the parents // projection space match the two reference points in this cameras projection space. // Starting from the parents camera projection space, we get into this cameras // projection space by the transform matrix: // P*R*inv(pP*pR) = T2*S*T*P0*R*inv(pP*pR) // So, at first compute that matrix without T2*S*T and determine S and T* from that // The initial projeciton matrix to build upon osg::Matrix P = Frustum(getAspectRatio()).getMatrix(); osg::Matrix R = getViewOffset(); osg::Matrix pP = referenceCameraData.getFrustum().getMatrix(); osg::Matrix pR = referenceCameraData.getViewOffset(); // Transform from the reference cameras projection space into this cameras eye space. osg::Matrix pPtoEye = osg::Matrix::inverse(pR*pP)*R; osg::Vec2 pRef[2] = { referenceCameraData.getProjectionReferencePoint(referenceNames[0]), referenceCameraData.getProjectionReferencePoint(referenceNames[1]) }; // The first reference point transformed to this cameras projection space osg::Vec3d pRefInThis0 = P.preMult(pPtoEye.preMult(osg::Vec3d(pRef[0], 1))); // Translate this proejction matrix so that the first reference point is at the origin P.postMultTranslate(-pRefInThis0); // Transform the second reference point and get the scaling correct. osg::Vec3d pRefInThis1 = P.preMult(pPtoEye.preMult(osg::Vec3d(pRef[1], 1))); double s = osg::Vec2d(pRefInThis1[0], pRefInThis1[1]).length(); if (s <= std::numeric_limits::min()) return false; osg::Vec2 ref[2] = { getProjectionReferencePoint(names[0]), getProjectionReferencePoint(names[1]) }; s = (ref[0] - ref[1]).length()/s; P.postMultScale(osg::Vec3d(s, s, 1)); // The first reference point still maps to the origin in this projection space. // Translate the origin to the desired first reference point. P.postMultTranslate(osg::Vec3d(ref[0], 1)); // Now osg::Matrix::inverse(pR*pP)*R*P should map pRef[i] exactly onto ref[i] for i = 0, 1. // Note that osg::Matrix::inverse(pR*pP)*R*P should exactly map pRef[0] at the near plane // to ref[0] at the near plane. The far plane is not taken care of. Frustum frustum; if (!frustum.setMatrix(P)) return false; return setFrustum(frustum); } void SlaveCamera::setProjectionReferencePoint(const std::string& name, const osg::Vec2& point) { _referencePointMap[name] = point; } osg::Vec2 SlaveCamera::getProjectionReferencePoint(const std::string& name) const { NameReferencePointMap::const_iterator i = _referencePointMap.find(name); if (i != _referencePointMap.end()) return i->second; return osg::Vec2(0, 0); } void SlaveCamera::setMonitorProjectionReferences(double width, double height, double bezelTop, double bezelBottom, double bezelLeft, double bezelRight) { double left = 1 + 2*bezelLeft/width; double right = 1 + 2*bezelRight/width; double bottom = 1 + 2*bezelBottom/height; double top = 1 + 2*bezelTop/height; setProjectionReferencePoint("lowerLeft", osg::Vec2(-left, -bottom)); setProjectionReferencePoint("lowerRight", osg::Vec2(right, -bottom)); setProjectionReferencePoint("upperRight", osg::Vec2(right, top)); setProjectionReferencePoint("upperLeft", osg::Vec2(-left, top)); } osg::Vec3 SlaveCamera::getLeftEyeOffset(const Viewer& viewer) const { #ifdef FG_HAVE_HLA const HLAViewerFederate* viewerFederate = viewer.getViewerFederate(); if (!viewerFederate) return osg::Vec3(0, 0, 0); const HLAPerspectiveViewer* perspectiveViewer = viewerFederate->getViewer(); if (!perspectiveViewer) return osg::Vec3(0, 0, 0); return toOsg(perspectiveViewer->getLeftEyeOffset()); #else return osg::Vec3(0, 0, 0); #endif } osg::Vec3 SlaveCamera::getRightEyeOffset(const Viewer& viewer) const { #ifdef FG_HAVE_HLA const HLAViewerFederate* viewerFederate = viewer.getViewerFederate(); if (!viewerFederate) return osg::Vec3(0, 0, 0); const HLAPerspectiveViewer* perspectiveViewer = viewerFederate->getViewer(); if (!perspectiveViewer) return osg::Vec3(0, 0, 0); return toOsg(perspectiveViewer->getRightEyeOffset()); #else return osg::Vec3(0, 0, 0); #endif } double SlaveCamera::getZoomFactor(const Viewer& viewer) const { #ifdef FG_HAVE_HLA const HLAViewerFederate* viewerFederate = viewer.getViewerFederate(); if (!viewerFederate) return 1; const HLAPerspectiveViewer* perspectiveViewer = viewerFederate->getViewer(); if (!perspectiveViewer) return 1; return perspectiveViewer->getZoomFactor(); #else return 1; #endif } osg::Matrix SlaveCamera::getEffectiveViewOffset(const Viewer& viewer) const { // The eye offset in the master cameras coordinates. osg::Vec3 eyeOffset = getLeftEyeOffset(viewer); // Transform the eye offset into this slaves coordinates eyeOffset = eyeOffset*getViewOffset(); // The slaves view matrix is composed of the master matrix osg::Matrix viewOffset = viewer.getCamera()->getViewMatrix(); // ... its view offset ... viewOffset.postMult(getViewOffset()); // ... and the inverse of the eye offset that is required // to keep the world at the same position wrt the projection walls viewOffset.postMultTranslate(-eyeOffset); return viewOffset; } Frustum SlaveCamera::getEffectiveFrustum(const Viewer& viewer) const { // The eye offset in the master cameras coordinates. osg::Vec3 eyeOffset = getLeftEyeOffset(viewer); // Transform the eye offset into this slaves coordinates eyeOffset = eyeOffset*getViewOffset(); /// FIXME read that from external osg::Vec3 zoomScaleCenter(0, 0, -1); double zoomFactor = getZoomFactor(viewer); /// Transform into the local cameras orientation. zoomScaleCenter = getViewOffset().preMult(zoomScaleCenter); // The unmodified frustum Frustum frustum = getFrustum(); // For unresized views this is a noop frustum.setAspectRatio(getAspectRatio()); // need to correct this for the eye position within the projection system frustum.translate(-eyeOffset); // Scale the whole geometric extent of the projection surfaces by the zoom factor frustum.scale(1/zoomFactor, zoomScaleCenter); return frustum; } bool SlaveCamera::realize(Viewer& viewer) { if (_camera.valid()) return false; _camera = _realizeImplementation(viewer); return _camera.valid(); } bool SlaveCamera::update(Viewer& viewer) { return _updateImplementation(viewer); } osg::Camera* SlaveCamera::_realizeImplementation(Viewer& viewer) { Drawable* drawable = viewer.getDrawable(_drawableName); if (!drawable) return 0; osg::GraphicsContext* graphicsContext = drawable->getGraphicsContext(); if (!graphicsContext) return 0; osg::Camera* camera = new osg::Camera; camera->setName(getName()); camera->setGraphicsContext(graphicsContext); camera->setViewport(_viewport.get()); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); // Not seriously consider someting different camera->setDrawBuffer(GL_BACK); camera->setReadBuffer(GL_BACK); camera->setUpdateCallback(new NoUpdateCallback); return camera; } bool SlaveCamera::_updateImplementation(Viewer& viewer) { if (!_camera.valid()) return false; return true; } } // namespace fgviewer