flightgear/utils/fgviewer/SlaveCamera.cxx
2022-10-20 20:29:11 +08:00

350 lines
11 KiB
C++

// 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 <config.h>
#endif
#include "SlaveCamera.hxx"
#include <simgear/scene/util/OsgMath.hxx>
#include "Viewer.hxx"
#if 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<double>::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
{
#if 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
{
#if 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
{
#if 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