Projecting 3D position and size to 2D         Projecting 3D objects and position to a 2D plane

One way to have HUD Elements like targeting indicators or playernames positioned on 3D objects is projecting the 3D position to 2D and positioning the HUD Elements there. The following code can be used to do that.

Projecting position

using namespace Ogre;
/// returns true if in front of the cam, and fills x,y with clamped screencoords in [-1;1]
// cam->getProjectionMatrix() is cached inside ogre
bool    ProjectPos    (Camera* cam,const Ogre::Vector3& pos,Ogre::Real& x,Ogre::Real& y) {
    Vector3 eyeSpacePos = cam->getViewMatrix(true) * pos;
    // z < 0 means in front of cam
    if (eyeSpacePos.z < 0) {
        // calculate projected pos
        Vector3 screenSpacePos = cam->getProjectionMatrix() * eyeSpacePos;
        x = screenSpacePos.x;
        y = screenSpacePos.y;
        return true;
    } else {
        x = (-eyeSpacePos.x > 0) ? -1 : 1;
        y = (-eyeSpacePos.y > 0) ? -1 : 1;
        return false;
    }
}

Projecting position and radius

using namespace Ogre;
/// returns true if in front of the cam, and fills x,y with clamped screencoords in [-1;1]
/// and fills cx,cy with projected size on screen in [0;1]
// cam->getProjectionMatrix() is cached inside ogre
bool    ProjectSizeAndPos    (Camera* cam,const Ogre::Vector3& pos,const Ogre::Real rad,Ogre::Real& x,Ogre::Real& y,Ogre::Real& cx,Ogre::Real& cy) {
    Camera* cam = mCamera;
    Vector3 eyeSpacePos = cam->getViewMatrix(true) * pos;
    // z < 0 means in front of cam
    if (eyeSpacePos.z < 0) {
        // calculate projected pos
        Vector3 screenSpacePos = cam->getProjectionMatrix() * eyeSpacePos;
        x = screenSpacePos.x;
        y = screenSpacePos.y;
        // calculate projected size
        Vector3 spheresize(rad, rad, eyeSpacePos.z);
        spheresize = cam->getProjectionMatrix() * spheresize;
        cx = spheresize.x;
        cy = spheresize.y;
        return true;
    } else {
        cx = 0;
        cy = 0;
        x = (-eyeSpacePos.x > 0) ? -1 : 1;
        y = (-eyeSpacePos.y > 0) ? -1 : 1;
        return false;
    }
}

Notes

  • a return value of true just means in front of the cam, not visible on screen (e.g. it might be too far on left)
  • to check if the object is visible, check if the projected x,y are in [-1;1]
  • to check if the object is partially visible, check if the projected x is in [-1-cx/2;1+cx/2] (y analogue with cy)

Tracking a hierarchical position



When tracking the position of a scenenode pMyChildNode that is the child of another scenenode pMyParentNode,
probably SceneNode::getWorldPosition() should be used to get an absolute position.

When only the position of pMyParentNode is updated,
the pMyChildNode->getWorldPosition() will normally only be update after rendering a frame,
this leads to the projected position lagging behind one frame.
This can be prevented by either :

  • calling pMyChildNode->needUpdate() right before calculating the projected position
  • calling pMyParentNode->_update(/*bool updateChildren=*/true,/*bool parentHasChanged=*/false) when moving the parent
  • also pMyChildNode->setListener() can be used to install a listener that is called at a time where getWorldPosition() will return the correct position, so that could be a good place to recalculate the projected position, but it also depends on the camera position, it would also have to be updated every time the camera moves, so pCamera->setListener() will also be needed. The problem is that this way, the position will be recalculated twice if the cam and the parent change, if that happens every frame, it might be better to recalculate right before rendering a frame and using _update() or needUpdate() instead of the listeners.

Alias: Projecting_3D_position_and_size_to_2D