Creating a CAVE using Ogre3D is a snap. Thanks in no small part to the work William De Jonge on the CaveUT Unreal mod. (A project I once worked on.) The idea is that each client camera is modified by shifting and/or rotating it’s frustum. In Ogre you can apply a custom projection matrix to achieve this effect.
First, we set up the off axis camera:
Ogre::Camera *_camera = mSceneMgr->createCamera("PlayerCam");
Ogre::Camera *_dummyCam = mSceneMgr->createCamera("DummyCam");
Ogre::SceneNode *_ubernode = mSceneMgr->createSceneNode("UberNode");
Ogre::Matrix4 _offAxis;
void EnableOffAxisProjection(){
try {
double pi = 3.14159;
///Hard code the fov values for now, in your program these would be read from an .ini or something.
double dfov = 90,dfovleft=-45.0,dfovright=45.0,dfovtop=34.0f,dfovbottom=-34.0f;
double dyaw = 0.0f; ///Frustum rotation
///field of view in the y axis.
double dfovy = 2*atan( tan(/2 * pi/180) / _camera->getAspectRatio() )*180/pi;
_camera->setFOVy(Degree(dfovy));
_camera->setCustomProjectionMatrix(false);
double n = _camera->getNearClipDistance();
double f = _camera->getFarClipDistance();
double l, r, b, t;
l = n*tan(dfovleft*pi/180);
r = n*tan(dfovright*pi/180);
b = n*tan(dfovbottom*pi/180);
t = n*tan(dfovtop*pi/180);
//Small modification from original off-axis algorithm. Signs of certain vars were changed so the Y rotations aren't flipped.
//This also fixes the problem of translations moving in the incorrect direction.
_offAxis = Ogre::Matrix4( (2.0f*n)/(r-l), 0, ((r+l)/(r-l)), 0,
0, ((2.0f*n)/(t-b)), ((t+b)/(t-b)), 0,
0, 0, -(f+n)/(f-n), -(2.0f*f*n)/(f-n),
0, 0, -1, 0);
_offAxis.transpose();
_camera->setCustomProjectionMatrix(true, _offAxis);
//TODO: Support pitch and roll at some point, for now yaw is the only important value.
_camera->yaw(Degree(dyaw));
_uberNode->attachObject(_camera);
} catch (Ogre::Exception *ex){
printf("ERROR - Trouble creating off axis projection: %s\n",ex->getDescription().c_str());
}
}
As you can see, I have hardcoded the field of views. For a better understanding of where these values come from please see my link to CaveUT. Jeffrey Jacobson (the author of CaveUT) has a set of tutorials explaining how to calculate fields of view. Keep in mind these values are completely dependent on the physical CAVE setup as well as the user’s position in the environment.
We aren’t doing anything crazy here. We simply create a custom view matrix using our FOV inputs and some information about the current clip planes. We assign that new matrix to the off axis camera and we then yaw the camera (in my example yaw is 0) and we attach this camera to an “ubernode.“ Think of the uber node as the player’s body. When we rotate or translate this is what we update.(More on this after the next chunk of code.)
Next we provide a mechanism for moving the off axis camera:
void MoveCamera(Ogre::Vector3 rot, Ogre::Vector3 trans){
if(!_bStart)return;
try {
//Rotate the uber camera node which in turn rotates all sub cameras
_uberNode->pitch(Ogre::Radian(rot.y) * -1, Node::TS_LOCAL);
_uberNode->yaw(Ogre::Radian(rot.z), Node::TS_WORLD);
//Rotate dummy camera. The dummy camera does not display anything on the screen. What it does is take the same rotations that would be
//applied to the uber node and it points along the same direction. We can then use 'MoveRelative' of the dummy camera to get movement along the uber node's
//line of sight.
_dummyCam->roll(Ogre::Radian(rot.x));
_dummyCam->pitch(Ogre::Radian(rot.y));
_dummyCam->yaw(Ogre::Radian(rot.z));
_dummyCam->moveRelative(trans);
//Adjust the uber node accordingly.
_uberNode->setPosition(_dummyCam->getPosition());
} catch (Ogre::Exception *ex){
printf(" ERROR - Trouble moving off axis projection: %s\n",ex->getDescription().c_str());
}
}
Moving an off axis camera is slightly different than moving a regular camera. The off axis camera, assuming it has been yawed, is facing a different direction than the player. Remember that in a CAVE all of the cameras work together to create a single view. Moving a character “forward” for example should move all of the CAVE views “forward” relative to the player, not to their respective views.
So we create a dummy camera, set to the initial player view(which I didn’t show in my example). Instead of allowing the Ogre::FrameListener to directly update the player camera we tell it to call our MoveCamera method above.
What does MoveCamera() do?: Remember the uber node I mentioned above and how it represents the players “body”? We first update this node with any rotations that have happened since the last frame tick. Next we update our dummy camera. Think of the dummy camera is the player’s “eyes.” The dummy camera is rotated first and then translated along it’s view, this view should inherently correspond to where the player is looking in to the CAVE. We then apply this translation to the uber node and that in turn updates the actual off axis camera.
We do this for 2 reason: First, if we simply translate the off-axis camera it will move relative to it’s own view direction and not that of the player. Secondly because SceneNode’s dont’ have a move relative function. If you were so inclined you could remove my dummy camera and replace it with a class that transforms a ray and moves relative to that ray. I just wanted to make it easy by using an existing Ogre class.
That’s it, we’re done. Just make sure the Ogre::Framelistener updates MoveCamera() every tick and you’ll be good to go.
————————
*** A word of caution: If you haven’t ever set up a CAVE you are doing yourself a disservice by not reading the relevant literature. Just copying my code above is enough to get a start but without understanding the math or how to calculate FOV values you’ll be left wondering why your scene looks funny. Make sure you calculate FOV values from your player’s actual eye level (known as the sweet spot). The easiest test is to create a 3D horizontal line. If you look at that line in your CAVE from anywhere bu the sweet spot it will look bent. As you approach the sweet spot the line will gradually look straighter until it looks exactly like a straight line even though it spans multiple screens.