1
0
Fork 0

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.
This commit is contained in:
Mathias Froehlich 2011-10-23 15:40:15 +02:00
parent ad660380c2
commit fea00cc9f8
3 changed files with 482 additions and 8 deletions

View file

@ -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.
</sim>
</PropertyList>
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.
<PropertyList>
<sim>
<view n="0">
<config>
<pitch-offset-deg>0.0</pitch-offset-deg>
</config>
</view>
<rendering>
<camera-group>
<window>
<name type="string">0.0</name>
<host-name type="string"></host-name>
<display>0</display>
<screen>0</screen>
<fullscreen type="bool">true</fullscreen>
</window>
<window>
<name type="string">0.1</name>
<host-name type="string"></host-name>
<display>0</display>
<screen>1</screen>
<fullscreen type="bool">true</fullscreen>
</window>
<camera>
<name type="string">CenterCamera</name>
<window>
<name>0.0</name>
</window>
<viewport>
<x>0</x>
<y>0</y>
<width>1920</width>
<height>1080</height>
</viewport>
<view>
<heading-deg type="double">0.0</heading-deg>
<roll-deg type="double">0.0</roll-deg>
<pitch-deg type="double">0.0</pitch-deg>
</view>
<physical-dimensions>
<!-- The size of the projection plane: 533mm 300mm -->
<width>533</width>
<height>300</height>
<bezel>
<right>23</right>
<left>23</left>
<top>23</top>
<bottom>23</bottom>
</bezel>
</physical-dimensions>
<master-perspective>
<!-- Cheating, the real distance is about 800mm.
But then the screen does not show what is needed to fly.
By shortening this pictures get bigger but the view also gets
less realistic.
-->
<eye-distance>450</eye-distance>
<x-offset>0</x-offset>
<y-offset>130</y-offset>
</master-perspective>
</camera>
<camera>
<name type="string">RightCamera</name>
<window>
<name>0.0</name>
</window>
<viewport>
<x>1920</x>
<y>0</y>
<width>1920</width>
<height>1080</height>
</viewport>
<view>
<heading-deg type="double">-45</heading-deg>
<roll-deg type="double">0</roll-deg>
<pitch-deg type="double">0</pitch-deg>
</view>
<physical-dimensions>
<!-- The size of the projection plane: 533mm 300mm -->
<width>533</width>
<height>300</height>
<bezel>
<right>23</right>
<left>23</left>
<top>23</top>
<bottom>23</bottom>
</bezel>
</physical-dimensions>
<right-of-perspective>
<parent-camera type="string">CenterCamera</parent-camera>
</right-of-perspective>
<!-- <reference-points-perspective> -->
<!-- <parent-camera type="string">CenterCamera</parent-camera> -->
<!-- <parent> -->
<!-- <point n="0"> -->
<!-- <x>289.5</x> -->
<!-- <y>100</y> -->
<!-- </point> -->
<!-- <point n="1"> -->
<!-- <x>289.5</x> -->
<!-- <y>-100</y> -->
<!-- </point> -->
<!-- </parent> -->
<!-- <this> -->
<!-- <point n="0"> -->
<!-- <x>-289.5</x> -->
<!-- <y>100</y> -->
<!-- </point> -->
<!-- <point n="1"> -->
<!-- <x>-289.5</x> -->
<!-- <y>-100</y> -->
<!-- </point> -->
<!-- </this> -->
<!-- </reference-points-perspective> -->
</camera>
<camera>
<name type="string">LeftCamera</name>
<window>
<name>0.1</name>
</window>
<viewport>
<x>0</x>
<y>0</y>
<width>1920</width>
<height>1080</height>
</viewport>
<view>
<heading-deg type="double">45</heading-deg>
<roll-deg type="double">0</roll-deg>
<pitch-deg type="double">0</pitch-deg>
</view>
<physical-dimensions>
<!-- The size of the projection plane: 533mm 300mm -->
<width>533</width>
<height>300</height>
<bezel>
<right>23</right>
<left>23</left>
<top>23</top>
<bottom>23</bottom>
</bezel>
</physical-dimensions>
<left-of-perspective>
<parent-camera type="string">CenterCamera</parent-camera>
</left-of-perspective>
</camera>
<gui>
<window>
<name type="string">0.0</name>
</window>
</gui>
</camera-group>
</rendering>
</sim>
</PropertyList>

View file

@ -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 <osgViewer/GraphicsWindow>
#include <osgViewer/Renderer>
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)
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;
else
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().

View file

@ -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