diff --git a/docs-mini/README.multiscreen b/docs-mini/README.multiscreen index 042bbac84..0f8a9b707 100644 --- a/docs-mini/README.multiscreen +++ b/docs-mini/README.multiscreen @@ -81,6 +81,31 @@ window, camera, or gui tags. width, height - int The dimensions of the viewport + physical-dimensions + The physical dimension of the projection surface. + Use this together with the master-perspective, right-of-perspective + left-of-perspective, above-perspective, below-perspective or + reference-points-perspective + + width, height - double + The dimensions of the projection plane, if unset the veiwport values + are taken as default. + + bezel + Gives informantion about the bezel of monitors for a seamless view. + + right + right bezel with in the same units than with and height above + + left + left bezel with in the same units than with and height above + + top + top bezel with in the same units than with and height above + + bottom + bottom bezel with in the same units than with and height above + view The view node specifies the origin and direction of the camera in relation to the whole camera group. The coordinate system is +y up, @@ -133,6 +158,63 @@ window, camera, or gui tags. This specifies an orthographic view. The parameters are the sames as the frustum node's. + master-perspective + Defines a persective projection matrix for use as the leading display + in a seamless multiscreen configuration. This kind of perspective + projection is zoomable. + + eye-distance - double + The distance of the eyepoint from the projection surface in units of + the physical-dimensions values above. + + x-offset, y-offset - double + Offset of the eyelpint from the center of the screen in units of + the physical-dimensions values above. + + left-of-perspective, right-of-perspective, above-perspective, + below-perspective + Defines a perspective projection matrix for use as derived display + in a seamless multiscreen configuration. The projection matrix + is computed so that the respective edge of this display matches the + assiciated other edge of the other display. For example the right edge + of a left-of-perspective display matches the left edge of the parent + display. This also works with different zoom levels, leading to distorted + but still seamless multiview configurations. + The bezel with configured in the physical dimensions of this screen and + the parent screen are taken into account for this type of projection. + + parent-camera - string + Name of the parent camera. + + reference-points-perspective + Defines a perspective projection matrix for use as derived display + in a seamless multiscreen configuration. This type is very similar to + left-of-perspective and friends. It is just a more flexible but less + convenient way to get the same effect. A child display is configured + by 2 sets of reference points one in this current camera and one in + the parrent camera which should match in the final view. + + parent-camera - string + Name of the parent camera. + + this + reference points for this projection. + + point - array of two points + + x, y - double + x and y coodinates of the reference points in units of this + physical-dimensions. + + parent + reference points for the parent projection. + + point - array of two points + + x, y - double + x and y coodinates of the reference points in units of the + parents physical-dimensions. + texture This tag indicates that the camera renders to a texture instead of the framebuffer. For now the following tags are supported, but obviously @@ -395,3 +477,170 @@ This example renders the scene for projection onto a spherical screen. +Here is an example for a 3 screen seamless zoomable multiscreen +configuration using 3 533mmx300mm displays each with a 23mm bezel. +The side views are angled with 45 deg. +The commented out reference-points-perspective shows the +aequivalent configuration than the active right-of-perspective. +This is done by just using two reference points at the outer +edge of the bezel of the respective display. + + + + + + 0.0 + + + + + + + 0.0 + + 0 + 0 + true + + + + 0.1 + + 0 + 1 + true + + + + CenterCamera + + 0.0 + + + 0 + 0 + 1920 + 1080 + + + 0.0 + 0.0 + 0.0 + + + + 533 + 300 + + 23 + 23 + 23 + 23 + + + + + 450 + 0 + 130 + + + + RightCamera + + 0.0 + + + 1920 + 0 + 1920 + 1080 + + + -45 + 0 + 0 + + + + 533 + 300 + + 23 + 23 + 23 + 23 + + + + CenterCamera + + + + + + + + + + + + + + + + + + + + + + + + + + + + LeftCamera + + 0.1 + + + 0 + 0 + 1920 + 1080 + + + 45 + 0 + 0 + + + + 533 + 300 + + 23 + 23 + 23 + 23 + + + + CenterCamera + + + + + 0.0 + + + + + + diff --git a/src/Main/CameraGroup.cxx b/src/Main/CameraGroup.cxx index 2a312391b..a69cebca6 100644 --- a/src/Main/CameraGroup.cxx +++ b/src/Main/CameraGroup.cxx @@ -21,6 +21,7 @@ #include "CameraGroup.hxx" +#include "fg_props.hxx" #include "globals.hxx" #include "renderer.hxx" #include "FGEventHandler.hxx" @@ -54,6 +55,71 @@ #include #include +static osg::Matrix +invert(const osg::Matrix& matrix) +{ + return osg::Matrix::inverse(matrix); +} + +/// Returns the zoom factor of the master camera. +/// The reference fov is the historic 55 deg +static double +zoomFactor() +{ + double fov = fgGetDouble("/sim/current-view/field-of-view", 55); + if (fov < 1) + fov = 1; + return tan(55*0.5*SG_DEGREES_TO_RADIANS)/tan(fov*0.5*SG_DEGREES_TO_RADIANS); +} + +static osg::Vec2d +preMult(const osg::Vec2d& v, const osg::Matrix& m) +{ + osg::Vec3d tmp = m.preMult(osg::Vec3(v, 0)); + return osg::Vec2d(tmp[0], tmp[1]); +} + +static osg::Matrix +relativeProjection(const osg::Matrix& P0, const osg::Matrix& R, const osg::Vec2d ref[2], + const osg::Matrix& pP, const osg::Matrix& pR, const osg::Vec2d pRef[2]) +{ + // Track the way from one projection space to the other: + // We want + // P = T*S*P0 + // where P0 is the projection template sensible for the given window size, + // T is a translation matrix and S a scale 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) = T*S*P0*R*inv(pP*pR) + // So, at first compute that matrix without T*S and determine S and T from that + + // Ok, now osg uses the inverse matrix multiplication order, thus: + osg::Matrix PtoPwithoutTS = invert(pR*pP)*R*P0; + // Compute the parents reference points in the current projection space + // without the yet unknown T and S + osg::Vec2d pRefInThis[2] = { + preMult(pRef[0], PtoPwithoutTS), + preMult(pRef[1], PtoPwithoutTS) + }; + + // To get the same zoom, rescale to match the parents size + double s = (ref[0] - ref[1]).length()/(pRefInThis[0] - pRefInThis[1]).length(); + osg::Matrix S = osg::Matrix::scale(s, s, 1); + + // For the translation offset, incorporate the now known scale + // and recompute the position ot the first reference point in the + // currents projection space without the yet unknown T. + pRefInThis[0] = preMult(pRef[0], PtoPwithoutTS*S); + // The translation is then the difference of the reference points + osg::Matrix T = osg::Matrix::translate(osg::Vec3d(ref[0] - pRefInThis[0], 0)); + + // Compose and return the desired final projection matrix + return P0*S*T; +} + namespace flightgear { using namespace osg; @@ -205,6 +271,7 @@ void CameraGroup::update(const osg::Vec3d& position, * osg::Matrix::rotate(orientation.inverse())); _viewer->getCamera()->setViewMatrix(masterView); const Matrix& masterProj = _viewer->getCamera()->getProjectionMatrix(); + double masterZoomFactor = zoomFactor(); for (CameraList::iterator i = _cameras.begin(); i != _cameras.end(); ++i) { const CameraInfo* info = i->get(); const View::Slave& slave = _viewer->getSlave(info->slaveIndex); @@ -220,10 +287,33 @@ void CameraGroup::update(const osg::Vec3d& position, viewMatrix = masterView * slave._viewOffset; camera->setViewMatrix(viewMatrix); Matrix projectionMatrix; - if ((info->flags & PROJECTION_ABSOLUTE) != 0) - projectionMatrix = slave._projectionOffset; - else + if ((info->flags & PROJECTION_ABSOLUTE) != 0) { + if (info->flags & ENABLE_MASTER_ZOOM) { + if (info->relativeCameraParent < _cameras.size()) { + // template projection matrix and view matrix of the current camera + osg::Matrix P0 = slave._projectionOffset; + osg::Matrix R = viewMatrix; + + // The already known projection and view matrix of the parent camera + const CameraInfo* parentInfo = _cameras[info->relativeCameraParent].get(); + osg::Matrix pP = parentInfo->camera->getProjectionMatrix(); + osg::Matrix pR = parentInfo->camera->getViewMatrix(); + + // And the projection matrix derived from P0 so that the reference points match + projectionMatrix = relativeProjection(P0, R, info->thisReference, + pP, pR, info->parentReference); + + } else { + // We want to zoom, so take the original matrix and apply the zoom to it. + projectionMatrix = slave._projectionOffset; + projectionMatrix.postMultScale(osg::Vec3d(masterZoomFactor, masterZoomFactor, 1)); + } + } else { + projectionMatrix = slave._projectionOffset; + } + } else { projectionMatrix = masterProj * slave._projectionOffset; + } if (!info->farCamera.valid()) { camera->setProjectionMatrix(projectionMatrix); @@ -575,7 +665,6 @@ CameraInfo* CameraGroup::buildCamera(SGPropertyNode* cameraNode) #endif )); - osg::Matrix pOff; osg::Matrix vOff; const SGPropertyNode* viewNode = cameraNode->getNode("view"); if (viewNode) { @@ -601,7 +690,31 @@ CameraInfo* CameraGroup::buildCamera(SGPropertyNode* cameraNode) double heading = cameraNode->getDoubleValue("heading-deg", 0.0); vOff.makeRotate(DegreesToRadians(heading), osg::Vec3(0, 1, 0)); } - const SGPropertyNode* projectionNode = 0; + // Configuring the physical dimensions of a monitor + SGPropertyNode* viewportNode = cameraNode->getNode("viewport", true); + double physicalWidth = viewportNode->getDoubleValue("width", 1024); + double physicalHeight = viewportNode->getDoubleValue("height", 768); + double bezelHeightTop = 0; + double bezelHeightBottom = 0; + double bezelWidthLeft = 0; + double bezelWidthRight = 0; + const SGPropertyNode* physicalDimensionsNode = 0; + if ((physicalDimensionsNode = cameraNode->getNode("physical-dimensions")) != 0) { + physicalWidth = physicalDimensionsNode->getDoubleValue("width", physicalWidth); + physicalHeight = physicalDimensionsNode->getDoubleValue("height", physicalHeight); + const SGPropertyNode* bezelNode = 0; + if ((bezelNode = physicalDimensionsNode->getNode("bezel")) != 0) { + bezelHeightTop = bezelNode->getDoubleValue("top", bezelHeightTop); + bezelHeightBottom = bezelNode->getDoubleValue("bottom", bezelHeightBottom); + bezelWidthLeft = bezelNode->getDoubleValue("left", bezelWidthLeft); + bezelWidthRight = bezelNode->getDoubleValue("right", bezelWidthRight); + } + } + osg::Matrix pOff; + unsigned parentCameraIndex = ~0u; + osg::Vec2d parentReference[2]; + osg::Vec2d thisReference[2]; + SGPropertyNode* projectionNode = 0; if ((projectionNode = cameraNode->getNode("perspective")) != 0) { double fovy = projectionNode->getDoubleValue("fovy-deg", 55.0); double aspectRatio = projectionNode->getDoubleValue("aspect-ratio", @@ -636,6 +749,83 @@ CameraInfo* CameraGroup::buildCamera(SGPropertyNode* cameraNode) } if (projectionNode->getBoolValue("fixed-near-far", true)) cameraFlags |= FIXED_NEAR_FAR; + } else if ((projectionNode = cameraNode->getNode("master-perspective")) != 0) { + double zNear = projectionNode->getDoubleValue("eye-distance", 0.4*physicalWidth); + double xoff = projectionNode->getDoubleValue("x-offset", 0); + double yoff = projectionNode->getDoubleValue("y-offset", 0); + double left = -0.5*physicalWidth - xoff; + double right = 0.5*physicalWidth - xoff; + double bottom = -0.5*physicalHeight - yoff; + double top = 0.5*physicalHeight - yoff; + pOff.makeFrustum(left, right, bottom, top, zNear, zNear*1000); + cameraFlags |= PROJECTION_ABSOLUTE | ENABLE_MASTER_ZOOM; + } else if ((projectionNode = cameraNode->getNode("right-of-perspective")) + || (projectionNode = cameraNode->getNode("left-of-perspective")) + || (projectionNode = cameraNode->getNode("above-perspective")) + || (projectionNode = cameraNode->getNode("below-perspective")) + || (projectionNode = cameraNode->getNode("reference-points-perspective"))) { + std::string name = projectionNode->getStringValue("parent-camera"); + for (unsigned i = 0; i < _cameras.size(); ++i) { + if (_cameras[i]->name != name) + continue; + parentCameraIndex = i; + } + if (_cameras.size() <= parentCameraIndex) { + SG_LOG(SG_GENERAL, SG_ALERT, "CameraGroup::buildCamera: " + "failed to find parent camera for relative camera!"); + return 0; + } + const CameraInfo* parentInfo = _cameras[parentCameraIndex].get(); + if (projectionNode->getNameString() == "right-of-perspective") { + double tmp = (parentInfo->physicalWidth + 2*parentInfo->bezelWidthRight)/parentInfo->physicalWidth; + parentReference[0] = osg::Vec2d(tmp, -1); + parentReference[1] = osg::Vec2d(tmp, 1); + tmp = (physicalWidth + 2*bezelWidthLeft)/physicalWidth; + thisReference[0] = osg::Vec2d(-tmp, -1); + thisReference[1] = osg::Vec2d(-tmp, 1); + } else if (projectionNode->getNameString() == "left-of-perspective") { + double tmp = (parentInfo->physicalWidth + 2*parentInfo->bezelWidthLeft)/parentInfo->physicalWidth; + parentReference[0] = osg::Vec2d(-tmp, -1); + parentReference[1] = osg::Vec2d(-tmp, 1); + tmp = (physicalWidth + 2*bezelWidthRight)/physicalWidth; + thisReference[0] = osg::Vec2d(tmp, -1); + thisReference[1] = osg::Vec2d(tmp, 1); + } else if (projectionNode->getNameString() == "above-perspective") { + double tmp = (parentInfo->physicalHeight + 2*parentInfo->bezelHeightTop)/parentInfo->physicalHeight; + parentReference[0] = osg::Vec2d(-1, tmp); + parentReference[1] = osg::Vec2d(1, tmp); + tmp = (physicalHeight + 2*bezelHeightBottom)/physicalHeight; + thisReference[0] = osg::Vec2d(-1, -tmp); + thisReference[1] = osg::Vec2d(1, -tmp); + } else if (projectionNode->getNameString() == "below-perspective") { + double tmp = (parentInfo->physicalHeight + 2*parentInfo->bezelHeightBottom)/parentInfo->physicalHeight; + parentReference[0] = osg::Vec2d(-1, -tmp); + parentReference[1] = osg::Vec2d(1, -tmp); + tmp = (physicalHeight + 2*bezelHeightTop)/physicalHeight; + thisReference[0] = osg::Vec2d(-1, tmp); + thisReference[1] = osg::Vec2d(1, tmp); + } else if (projectionNode->getNameString() == "reference-points-perspective") { + SGPropertyNode* parentNode = projectionNode->getNode("parent", true); + SGPropertyNode* thisNode = projectionNode->getNode("this", true); + SGPropertyNode* pointNode; + + pointNode = parentNode->getNode("point", 0, true); + parentReference[0][0] = pointNode->getDoubleValue("x", 0)*2/parentInfo->physicalWidth; + parentReference[0][1] = pointNode->getDoubleValue("y", 0)*2/parentInfo->physicalHeight; + pointNode = parentNode->getNode("point", 1, true); + parentReference[1][0] = pointNode->getDoubleValue("x", 0)*2/parentInfo->physicalWidth; + parentReference[1][1] = pointNode->getDoubleValue("y", 0)*2/parentInfo->physicalHeight; + + pointNode = thisNode->getNode("point", 0, true); + thisReference[0][0] = pointNode->getDoubleValue("x", 0)*2/physicalWidth; + thisReference[0][1] = pointNode->getDoubleValue("y", 0)*2/physicalHeight; + pointNode = thisNode->getNode("point", 1, true); + thisReference[1][0] = pointNode->getDoubleValue("x", 0)*2/physicalWidth; + thisReference[1][1] = pointNode->getDoubleValue("y", 0)*2/physicalHeight; + } + + pOff = osg::Matrix::perspective(45, physicalWidth/physicalHeight, 1, 20000); + cameraFlags |= PROJECTION_ABSOLUTE | ENABLE_MASTER_ZOOM; } else { // old style shear parameters double shearx = cameraNode->getDoubleValue("shear-x", 0); @@ -668,10 +858,21 @@ CameraInfo* CameraGroup::buildCamera(SGPropertyNode* cameraNode) bool useMasterSceneGraph = !psNode; CameraInfo* info = addCamera(cameraFlags, camera, vOff, pOff, useMasterSceneGraph); + info->name = cameraNode->getStringValue("name"); + info->physicalWidth = physicalWidth; + info->physicalHeight = physicalHeight; + info->bezelHeightTop = bezelHeightTop; + info->bezelHeightBottom = bezelHeightBottom; + info->bezelWidthLeft = bezelWidthLeft; + info->bezelWidthRight = bezelWidthRight; + info->relativeCameraParent = parentCameraIndex; + info->parentReference[0] = parentReference[0]; + info->parentReference[1] = parentReference[1]; + info->thisReference[0] = thisReference[0]; + info->thisReference[1] = thisReference[1]; // If a viewport isn't set on the camera, then it's hard to dig it // out of the SceneView objects in the viewer, and the coordinates // of mouse events are somewhat bizzare. - SGPropertyNode* viewportNode = cameraNode->getNode("viewport", true); buildViewport(info, viewportNode, window->gc->getTraits()); updateCameras(info); // Distortion camera needs the viewport which is created by addCamera(). diff --git a/src/Main/CameraGroup.hxx b/src/Main/CameraGroup.hxx index 9f37ce5d2..a74895e46 100644 --- a/src/Main/CameraGroup.hxx +++ b/src/Main/CameraGroup.hxx @@ -52,9 +52,15 @@ struct CameraInfo : public osg::Referenced { CameraInfo(unsigned flags_, osg::Camera* camera_ = 0) : flags(flags_), camera(camera_), slaveIndex(-1), farSlaveIndex(-1), - x(0.0), y(0.0), width(0.0), height(0.0) + x(0.0), y(0.0), width(0.0), height(0.0), + physicalWidth(0), physicalHeight(0), bezelHeightTop(0), + bezelHeightBottom(0), bezelWidthLeft(0), bezelWidthRight(0), + relativeCameraParent(~0u) { } + /** The name as given in the config file. + */ + std::string name; /** Properties of the camera. @see CameraGroup::Flags. */ unsigned flags; @@ -76,6 +82,23 @@ struct CameraInfo : public osg::Referenced double y; double width; double height; + /** Physical size parameters. + */ + double physicalWidth; + double physicalHeight; + double bezelHeightTop; + double bezelHeightBottom; + double bezelWidthLeft; + double bezelWidthRight; + /** The parent camera for relative camera configurations. + */ + unsigned relativeCameraParent; + /** The reference points in the parents projection space. + */ + osg::Vec2d parentReference[2]; + /** The reference points in the current projection space. + */ + osg::Vec2d thisReference[2]; }; /** Update the OSG cameras from the camera info. @@ -96,8 +119,9 @@ public: GUI = 0x8, /**< Camera draws the GUI. */ DO_INTERSECTION_TEST = 0x10,/**< scene intersection tests this camera. */ - FIXED_NEAR_FAR = 0x20 /**< take the near far values in the + FIXED_NEAR_FAR = 0x20, /**< take the near far values in the projection for real. */ + ENABLE_MASTER_ZOOM = 0x40 /**< Can apply the zoom algorithm. */ }; /** Create a camera group associated with an osgViewer::Viewer. * @param viewer the viewer