2014-09-23 269 views
2

因此,我有一個類Label繼承我在OpenSceneGraph世界空間中繪製的osg::Geode。在顯示每一幀之後,我想要讀取每個標籤的屏幕空間座標 ,以便我可以找出它們在屏幕空間中的重疊程度。爲此,我創建了一個類ScreenSpace應計算該(有趣的功能是calc_screen_coords。)世界到OpenSceneGraph中的空間座標

我寫了一個小的子程序轉儲一些額外的每幀信息,包括這代表着什麼程序認爲的屏幕空間盒屏幕空間座標是:

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; 
} 

任何指針或幫助?

+0

我也使用從世界到屏幕的轉換,反之亦然,我使用類似的代碼,就像'calc_screen_coords'。唯一的區別是我沒有用同類分量來劃分座標系,並且該分量總是被設置爲0.0。另外,我直接使用'Vec3d',不需要投射任何參數,也不要直接修改世界座標。在我的功能中,我返回屏幕座標。 – 2014-09-23 16:21:43

+0

@ AdriC.S。我總是明白,你需要在最後除以同質分量來獲得實際的像素值 - 我在這裏錯了嗎?我希望看到你的功能。另外,當你說你直接使用'Vec3d'時,我假設你仍然只對x和y組件感興趣。 – cassava 2014-09-23 17:07:24

+0

@cassava:在投影之後使用'w = 1'作爲輸入點,並用'w'除以正確的方法。有一件事情是錯誤的,至少在一般情況下,你只使用邊界框的2個點。實際上是在世界空間中的邊界框(包括那個自動旋轉)?如果是這樣,它是軸對齊嗎? – derhass 2014-09-23 18:23:44

回答

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); 
} 
+0

嗨!它是如何去的?如果您的答案幫助了您,請將其標記爲已接受。 – 2014-10-29 08:33:34