From fea00cc9f87e71de710bdb074bc2fc793e7a3b12 Mon Sep 17 00:00:00 2001 From: Mathias Froehlich Date: Sun, 23 Oct 2011 15:40:15 +0200 Subject: [PATCH] Add seamless view muliscreen configuration. Add a new way to configure multi screen systems. The new version allows easier configuration of displays that need to fit at the edges as well as configurations where the screens match at reference points. This kind of screen configuration will again zoom. --- docs-mini/README.multiscreen | 249 +++++++++++++++++++++++++++++++++++ src/Main/CameraGroup.cxx | 213 +++++++++++++++++++++++++++++- src/Main/CameraGroup.hxx | 28 +++- 3 files changed, 482 insertions(+), 8 deletions(-) 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