世界到OpenSceneGraph中的屏幕空间坐标

时间:2014-09-23 16:11:45

标签: c++ opengl openscenegraph

所以我有一个继承自Label的课程osg::Geode,我在OpenSceneGraph的世界空间中绘制。显示每个帧后,我想读取屏幕空间坐标 每个Label,所以我可以找出它们在屏幕空间中重叠的程度。为此,我创建了一个类ScreenSpace来计算它(有趣的函数是calc_screen_coords。)

我写了一个小子程序,用一些额外的信息转储每一帧,包括代表程序认为屏幕空间坐标是什么的ScreenSpace框:

Labels with ScreenSpace that matches the Label

现在在上图中,似乎没有问题;但如果我把它旋转到另一边(用我的鼠标),那么它看起来很不一样:

Labels with ScreenSpace that does not match the Label whatsoever

这就是我无法理解的。

我的世界屏幕空间计算错了吗? 或者我从Drawable得到了错误的BoundingBox? 或者它可能与我给setAutoRotateToScreen(true)对象的osgText::Text指令有关?

有更好的方法吗?我应该尝试使用广告牌吗?我怎么会这样做? (我试过,它完全没有为我工作 - 我必须遗漏一些东西......)

以下是计算Label

的屏幕空间坐标的源代码
struct Pixel {
    // elided methods...

    int x;
    int y;
}

// Forward declarations:
pair<Pixel, Pixel> calc_screen_coords(const osg::BoundingBox& box, const osg::Camera* cam);
void rearange(Pixel& left, Pixel& right);

class ScreenSpace {
public:
    ScreenSpace(const Label* label, const osg::Camera* cam)
    {
        BoundingBox box = label->getDrawable(0)->computeBound();
        tie(bottom_left_, upper_right_) = calc_screen_coords(box, cam);
        rearrange(bottom_left_, upper_right_);
    }

    // elided methods...

private:
    Pixel bottom_left_;
    Pixel upper_right_;
}

pair<Pixel, Pixel> calc_screen_coords(const osg::BoundingBox& box, const osg::Camera* cam)
{
    Vec4d vec (box.xMin(), box.yMin(), box.zMin(), 1.0);
    Vec4d veq (box.xMax(), box.yMax(), box.zMax(), 1.0);

    Matrixd transmat
        = cam->getViewMatrix()
        * cam->getProjectionMatrix()
        * cam->getViewport()->computeWindowMatrix();

    vec = vec * transmat;
    vec = vec / vec.w();

    veq = veq * transmat;
    veq = veq / veq.w();

    return make_pair(
        Pixel(static_cast<int>(vec.x()), static_cast<int>(vec.y())),
        Pixel(static_cast<int>(veq.x()), static_cast<int>(veq.y()))
    );
}

inline void swap(int& v, int& w)
{
    int temp = v;
    v = w;
    w = temp;
}

inline void rearrange(Pixel& left, Pixel& right)
{
    if (left.x > right.x) {
        swap(left.x, right.x);
    }
    if (left.y > right.y) {
        swap(left.y, right.y);
    }
}

这是Label的构造(我试图稍微缩略一下):

// Forward declaration:
Geometry* createLeader(straph::Point pos, double height, Color color);

class Label : public osg::Geode {
public:
    Label(font, fontSize, text, color, position, height, margin, bgcolor, leaderColor)
    {
        osgText::Text* txt = new osgText::Text;
        txt->setFont(font);
        txt->setColor(color.vec4());
        txt->setCharacterSize(fontSize);
        txt->setText(text);

        // Set display properties and height
        txt->setAlignment(osgText::TextBase::CENTER_BOTTOM);
        txt->setAutoRotateToScreen(true);
        txt->setPosition(toVec3(position, height));

        // Create bounding box and leader
        typedef osgText::TextBase::DrawModeMask DMM;
        unsigned drawMode = DMM::TEXT | DMM::BOUNDINGBOX;
        drawMode |= DMM::FILLEDBOUNDINGBOX;
        txt->setBoundingBoxColor(bgcolor.vec4());
        txt->setBoundingBoxMargin(margin);
        txt->setDrawMode(drawMode);
        this->addDrawable(txt);

        Geometry* leader = createLeader(position, height, leaderColor);
        this->addDrawable(leader);
    }

    // elided methods and data members...
}

Geometry* createLeader(straph::Point pos, double height, Color color)
{
    Geometry* leader = new Geometry();
    Vec3Array* array = new Vec3Array();
    array->push_back(Vec3(pos.x, pos.y, height));
    array->push_back(Vec3(pos.x, pos.y, 0.0f));
    Vec4Array* colors = new Vec4Array(1);
    (*colors)[0] = color.vec4();
    leader->setColorArray(colors);
    leader->setColorBinding(Geometry::BIND_OVERALL);
    leader->setVertexArray(array);
    leader->addPrimitiveSet(new DrawArrays(PrimitiveSet::LINES, 0, 2));
    LineWidth* lineWidth = new osg::LineWidth();
    lineWidth->setWidth(2.0f);
    leader->getOrCreateStateSet()->setAttributeAndModes(lineWidth, osg::StateAttribute::ON);
    return leader;
}

任何指针或帮助?

1 个答案:

答案 0 :(得分:2)

我找到了一个适合我的解决方案,但也不尽如人意,所以如果你有更好的解决方案,我全心全意。

基本上,我从标签中得到不同点,我知道在某些点, 我通过组合这个来计算屏幕空间。对于左侧和右侧,我采取 常规边界框的边界,对于顶部和底部,我用它计算它 边界框的中心和标签的位置。

ScreenSpace::ScreenSpace(const Label* label, const osg::Camera* cam)
{
    const Matrixd transmat
        = cam->getViewMatrix()
        * cam->getProjectionMatrix()
        * cam->getViewport()->computeWindowMatrix();

    auto topixel = [&](Vec3 v) -> Pixel {
        Vec4 vec(v.x(), v.y(), v.z(), 1.0);
        vec = vec * transmat;
        vec = vec / vec.w();
        return Pixel(static_cast<int>(vec.x()), static_cast<int>(vec.y()));
    };

    // Get left right coordinates
    vector<int> xs; xs.reserve(8);
    vector<int> ys; ys.reserve(8);
    BoundingBox box = label->getDrawable(0)->computeBound();
    for (int i=0; i < 8; i++) {
        Pixel p = topixel(box.corner(i));
        xs.push_back(p.x);
        ys.push_back(p.y);
    };
    int xmin = *min_element(xs.begin(), xs.end());
    int xmax = *max_element(xs.begin(), xs.end());

    // Get up-down coordinates
    int ymin = topixel(dynamic_cast<const osgText::Text*>(label->getDrawable(0))->getPosition()).y;
    int center = topixel(box.center()).y;
    int ymax = center + (center - ymin);

    bottom_left_ = Pixel(xmin, ymin);
    upper_right_ = Pixel(xmax, ymax);
    z_ = distance_from_camera(label, cam);
}