/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static unsigned int getRandomValueinRange(unsigned int v) { return static_cast((rand() * 1.0 * v)/(RAND_MAX-1)); } namespace osgAnimation { osg::Geometry* createBackgroundRectangle(const osg::Vec3& pos, const float width, const float height, osg::Vec4& color) { osg::StateSet *ss = new osg::StateSet; osg::Geometry* geometry = new osg::Geometry; geometry->setUseDisplayList(false); geometry->setStateSet(ss); osg::Vec3Array* vertices = new osg::Vec3Array; geometry->setVertexArray(vertices); vertices->push_back(osg::Vec3(pos.x(), pos.y(), 0)); vertices->push_back(osg::Vec3(pos.x(), pos.y()-height,0)); vertices->push_back(osg::Vec3(pos.x()+width, pos.y()-height,0)); vertices->push_back(osg::Vec3(pos.x()+width, pos.y(),0)); osg::Vec4Array* colors = new osg::Vec4Array; colors->push_back(color); geometry->setColorArray(colors); geometry->setColorBinding(osg::Geometry::BIND_OVERALL); osg::DrawElementsUInt *base = new osg::DrawElementsUInt(osg::PrimitiveSet::QUADS,0); base->push_back(0); base->push_back(1); base->push_back(2); base->push_back(3); geometry->addPrimitiveSet(base); return geometry; } struct StatsGraph : public osg::MatrixTransform { StatsGraph(osg::Vec3 pos, float width, float height) : _pos(pos), _width(width), _height(height), _statsGraphGeode(new osg::Geode) { _pos = pos - osg::Vec3(0, _height, 0.1); setMatrix(osg::Matrix::translate(_pos)); setDataVariance(osg::Object::DYNAMIC); addChild(_statsGraphGeode.get()); _statsGraphGeode->setCullingActive(false); } void changeYposition(float y) { osg::Vec3 _pos = getMatrix().getTrans(); _pos[1] = y - _height; setMatrix(osg::Matrix::translate(_pos)); } void addStatGraph(osg::Stats* viewerStats, osg::Stats* stats, const osg::Vec4& color, float max, const std::string& nameBegin, const std::string& nameEnd = "") { _statsGraphGeode->addDrawable(new Graph(_width, _height, viewerStats, stats, color, max, nameBegin, nameEnd)); } osg::Vec3 _pos; float _width; float _height; osg::ref_ptr _statsGraphGeode; struct NeverCull : public osg::Drawable::CullCallback { NeverCull() {} bool cull(osg::NodeVisitor* nv, osg::Drawable* drawable, osg::RenderInfo* renderInfo) const { return false;} }; struct Graph : public osg::Geometry { Graph(float width, float height, osg::Stats* viewerStats, osg::Stats* stats, const osg::Vec4& color, float max, const std::string& nameBegin, const std::string& nameEnd = "") { setDataVariance(osg::Object::DYNAMIC); setUseDisplayList(false); setVertexArray(new osg::Vec3Array); getVertexArray()->setDataVariance(osg::Object::DYNAMIC); setColor(color); setUpdateCallback(new GraphUpdateCallback(width, height, viewerStats, stats, max, nameBegin, nameEnd)); setCullCallback(new NeverCull); } void setColor(const osg::Vec4& color) { osg::Vec4Array* colors = new osg::Vec4Array; colors->push_back(color); setColorArray(colors); setColorBinding(osg::Geometry::BIND_OVERALL); } }; struct GraphUpdateCallback : public osg::Drawable::UpdateCallback { const unsigned int _width; const unsigned int _height; mutable unsigned int _curX; osg::Stats* _viewerStats; osg::Stats* _stats; const float _max; const std::string _nameBegin; const std::string _nameEnd; mutable unsigned int _frameNumber; GraphUpdateCallback(float width, float height, osg::Stats* viewerStats, osg::Stats* stats, float max, const std::string& nameBegin, const std::string& nameEnd = "") : _width((unsigned int)width), _height((unsigned int)height), _curX(0), _viewerStats(viewerStats), _stats(stats), _max(max), _nameBegin(nameBegin), _nameEnd(nameEnd), _frameNumber(0) { } virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable) { if (nv->getVisitorType() != osg::NodeVisitor::UPDATE_VISITOR) return; osg::Geometry* geometry = const_cast(drawable->asGeometry()); if (!geometry) return; osg::Vec3Array* vertices = dynamic_cast(geometry->getVertexArray()); if (!vertices) return; unsigned int frameNumber = nv->getFrameStamp()->getFrameNumber(); if (frameNumber == _frameNumber) return; // Get stats double value; if (_nameEnd.empty()) { if (!_stats->getAttribute(_stats->getLatestFrameNumber(), _nameBegin, value )) { value = 0.0; } } else { double beginValue, endValue; if (_stats->getAttribute( frameNumber, _nameBegin, beginValue) && _stats->getAttribute( frameNumber, _nameEnd, endValue) ) { value = endValue - beginValue; } else { value = 0.0; } } // Add new vertex for this frame. value = osg::clampTo(value, 0.0, double(_max)); if (!vertices->size()) { for (int i = 0; i < (int)_width; i++) vertices->push_back(osg::Vec3(float(_curX++), 0, 0)); // Create primitive set if none exists. if (geometry->getNumPrimitiveSets() == 0) geometry->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, 0)); osg::DrawArrays* drawArrays = dynamic_cast(geometry->getPrimitiveSet(0)); drawArrays->setFirst(0); drawArrays->setCount(vertices->size()); } vertices->push_back(osg::Vec3(float(_curX), float(_height) / _max * value, 0)); unsigned int excedent = vertices->size() - _width; vertices->erase(vertices->begin(), vertices->begin() + excedent); // Make the graph scroll when there is enough data. // Note: We check the frame number so that even if we have // many graphs, the transform is translated only once per // frame. static const float increment = -1.0; if (_frameNumber != frameNumber) { // We know the exact layout of this part of the scene // graph, so this is OK... osg::MatrixTransform* transform = geometry->getParent(0)->getParent(0)->asTransform()->asMatrixTransform(); if (transform) { transform->setMatrix(transform->getMatrix() * osg::Matrix::translate(osg::Vec3(increment, 0, 0))); } } _curX++; _frameNumber = frameNumber; geometry->dirtyBound(); } }; }; // Drawcallback to draw averaged attribute struct ValueTextDrawCallback : public virtual osg::Drawable::DrawCallback { ValueTextDrawCallback(osg::Stats* stats, const std::string& name): _stats(stats), _attributeName(name), _frameNumber(0) { } /** do customized draw code.*/ virtual void drawImplementation(osg::RenderInfo& renderInfo,const osg::Drawable* drawable) const { osgText::Text* text = (osgText::Text*)drawable; unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber(); if (frameNumber == _frameNumber) { text->drawImplementation(renderInfo); return; } double value; if (_stats->getAttribute(_stats->getLatestFrameNumber(), _attributeName, value)) { sprintf(_tmpText,"%4.2f",value); text->setText(_tmpText); } else { text->setText(""); } _frameNumber = frameNumber; text->drawImplementation(renderInfo); } osg::ref_ptr _stats; std::string _attributeName; mutable char _tmpText[128]; mutable unsigned int _frameNumber; }; struct StatAction { double _lastTime; std::string _name; osg::ref_ptr _group; osg::ref_ptr _label; osg::ref_ptr _graph; osg::ref_ptr _textLabel; osgAnimation::OutCubicMotion _fade; StatAction() { _lastTime = 0; _fade = osgAnimation::OutCubicMotion(0,5); } void init(osg::Stats* stats, const std::string& name, const osg::Vec3& pos, float width, float heigh, const osg::Vec4& color); void setPosition(const osg::Vec3& pos); #if 0 void touch() { _lastTime = osg::Timer::instance()->time_s(); float a = 1.0 - _fade.getValueAt(0.0); setAlpha(a); } bool update() { double t = osg::Timer::instance()->time_s(); float alpha = 1.0 - _fade.getValueAt(t-_lastTime); if (t - _lastTime > _fade.getDuration()) return true; setAlpha(alpha); return false; } #endif void setAlpha(float v); }; struct StatsTimeline : public osg::NodeCallback { static float _statsHeight; static float _statsWidth; osg::ref_ptr _background; osg::ref_ptr _timeline; osg::ref_ptr _group; std::map _actions; StatsTimeline() { _statsHeight = 1024; _statsWidth = 1280; } osg::MatrixTransform* createStatsForTimeline(osgAnimation::Timeline* timeline) { _timeline = timeline; std::string font("fonts/arial.ttf"); float leftPos = 10.0f; float startBlocks = 150.0f; float characterSize = 20.0f; osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); float backgroundMargin = 5; //float backgroundSpacing = 3; osg::Vec4 color(1.0, 1.0, 1.0, 1.0); _group = new osg::MatrixTransform; _group->setDataVariance(osg::Object::DYNAMIC); { osg::Vec3 pos(leftPos, _statsHeight-24.0f,0.0f); //float topOfViewerStats = pos.y() + characterSize; osg::ref_ptr stats = _timeline->getStats(); pos.y() -= characterSize + backgroundMargin; { osg::Geode* geode = new osg::Geode(); _group->addChild(geode); osg::ref_ptr timeLabel = new osgText::Text; geode->addDrawable( timeLabel.get() ); timeLabel->setColor(color); timeLabel->setFont(font); timeLabel->setCharacterSize(characterSize); timeLabel->setPosition(pos); timeLabel->setText("Time: "); osg::ref_ptr timeLabelValue = new osgText::Text; geode->addDrawable( timeLabelValue.get() ); timeLabelValue->setColor(color); timeLabelValue->setFont(font); timeLabelValue->setCharacterSize(characterSize); timeLabelValue->setPosition(pos + osg::Vec3(startBlocks, 0,0)); timeLabelValue->setText("0.0"); timeLabelValue->setDrawCallback(new ValueTextDrawCallback(stats.get(),"Timeline")); } } { osg::Vec3 pos(leftPos, _statsHeight - 24.0f ,0.0f); //float topOfViewerStats = pos.y(); osg::Geode* geode = new osg::Geode; _background = createBackgroundRectangle( pos + osg::Vec3(-backgroundMargin, backgroundMargin, 0), _statsWidth - 2 * backgroundMargin, (3 + 4.5 * 1) * characterSize + 2 * backgroundMargin, backgroundColor); geode->addDrawable(_background.get()); _group->addChild(geode); } return _group.get(); } virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) { updateGraph(); } traverse(node,nv); } void updateGraph() { osgAnimation::StatsActionVisitor* visitor = _timeline->getStatsVisitor(); if (!visitor) return; std::string font("fonts/arial.ttf"); float leftPos = 10.0f; float characterSize = 20.0f; float backgroundMargin = 5; //float backgroundSpacing = 3; float graphSpacing = 5; float width = _statsWidth - 4 * backgroundMargin; float height = characterSize; osg::Vec3 pos(leftPos, _statsHeight-24.0f,0.0f); pos.y() -= characterSize *2 + backgroundMargin; for (std::map::iterator it = _actions.begin(); it != _actions.end(); ++it) { (*it).second._group->setNodeMask(~osg::Node::NodeMask(1)); } const std::vector& channels = visitor->getChannels(); std::map size; for (int i = 0; i < (int)channels.size(); i++) { std::string name = channels[i]; if (_actions.find(name) == _actions.end()) { osg::Vec4 color(getRandomValueinRange(255)/255.0, getRandomValueinRange(255)/255.0, getRandomValueinRange(255)/255.0, 1.0); _actions[name].init(visitor->getStats(), name, pos, width, height, color); _group->addChild(_actions[name]._group.get()); //_actions[name].touch(); } else { _actions[name].setPosition(pos); //_actions[name].touch(); } _actions[name]._group->setNodeMask(~osg::Node::NodeMask(0x0)); size[name] = 0; pos.y() -= characterSize + graphSpacing; } pos.y() -= backgroundMargin; osg::Vec3Array* array = dynamic_cast(_background->getVertexArray()); float y = (*array)[0][1]; y = y - (pos.y() + backgroundMargin); //(2 * backgroundMargin + (size.size() * (characterSize + graphSpacing))); (*array)[1][1] = pos.y(); (*array)[2][1] = pos.y(); array->dirty(); _background->dirtyBound(); } }; float StatsTimeline::_statsHeight; float StatsTimeline::_statsWidth; struct FindTimelineStats : public osg::NodeVisitor { std::vector > _timelines; FindTimelineStats() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {} void apply(osg::Node& node) { osg::NodeCallback* cb = node.getUpdateCallback(); while (cb) { osgAnimation::TimelineAnimationManager* tam = dynamic_cast(cb); if (tam) _timelines.push_back(tam->getTimeline()); cb = cb->getNestedCallback(); } traverse(node); } }; StatsHandler::StatsHandler(): _keyEventTogglesOnScreenStats('a'), _keyEventPrintsOutStats('A'), _statsType(NO_STATS), _initialized(false), _statsWidth(1280.0f), _statsHeight(1024.0f) { _camera = new osg::Camera; _camera->setRenderer(new osgViewer::Renderer(_camera.get())); _camera->setProjectionResizePolicy(osg::Camera::FIXED); } bool StatsHandler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) { osgViewer::View* myview = dynamic_cast(&aa); if (!myview) return false; osgViewer::Viewer* viewer = dynamic_cast(myview->getViewerBase()); if (!viewer->getSceneData()) return false; if (ea.getHandled()) return false; switch(ea.getEventType()) { case(osgGA::GUIEventAdapter::KEYDOWN): { if (ea.getKey()==_keyEventTogglesOnScreenStats) { if (viewer->getViewerStats()) { if (!_switch.get()) { FindTimelineStats finder; viewer->getSceneData()->accept(finder); if (finder._timelines.empty()) return false; } if (!_initialized) { setUpHUDCamera(viewer); setUpScene(dynamic_cast(viewer)); } ++_statsType; if (_statsType==LAST) _statsType = NO_STATS; switch(_statsType) { case(NO_STATS): { _camera->setNodeMask(0x0); _switch->setAllChildrenOff(); break; } case(FRAME_RATE): { _camera->setNodeMask(0xffffffff); _switch->setAllChildrenOn(); break; } default: break; } } return true; } if (ea.getKey()==_keyEventPrintsOutStats) { FindTimelineStats finder; viewer->getSceneData()->accept(finder); if (!finder._timelines.empty()) { OSG_NOTICE< StatsList; StatsList statsList; for (unsigned int i = 0; i < finder._timelines.size(); i++) statsList.push_back(finder._timelines[i]->getStats()); for(unsigned int i = statsList[0]->getEarliestFrameNumber(); i< statsList[0]->getLatestFrameNumber(); ++i) { for(StatsList::iterator itr = statsList.begin(); itr != statsList.end(); ++itr) { if (itr==statsList.begin()) (*itr)->report(osg::notify(osg::NOTICE), i); else (*itr)->report(osg::notify(osg::NOTICE), i, " "); } OSG_NOTICE<setGraphicsContext(0); _camera->removeChildren( 0, _camera->getNumChildren() ); } void StatsHandler::setUpHUDCamera(osgViewer::ViewerBase* viewer) { osgViewer::GraphicsWindow* window = dynamic_cast(_camera->getGraphicsContext()); if (!window) { osgViewer::Viewer::Windows windows; viewer->getWindows(windows); if (windows.empty()) return; window = windows.front(); } _camera->setGraphicsContext(window); _camera->setViewport(0, 0, window->getTraits()->width, window->getTraits()->height); _camera->setRenderOrder(osg::Camera::POST_RENDER, 10); _camera->setProjectionMatrix(osg::Matrix::ortho2D(0.0,_statsWidth,0.0,_statsHeight)); _camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); _camera->setViewMatrix(osg::Matrix::identity()); // only clear the depth buffer _camera->setClearMask(0); //GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //-1); _camera->setAllowEventFocus(false); _camera->setCullMask(0x1); osgViewer::Viewer* v = dynamic_cast(viewer); v->getSceneData()->asGroup()->addChild(_camera.get()); _initialized = true; } void StatsHandler::setUpScene(osgViewer::Viewer* viewer) { if (!viewer->getSceneData()) return; FindTimelineStats finder; viewer->getSceneData()->accept(finder); if (finder._timelines.empty()) return; _switch = new osg::Switch; osg::StateSet* stateset = _switch->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING,osg::StateAttribute::OFF); stateset->setMode(GL_BLEND,osg::StateAttribute::ON); stateset->setMode(GL_DEPTH_TEST,osg::StateAttribute::OFF); stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); _group = new osg::Group; _camera->addChild(_switch.get()); _switch->addChild(_group.get()); for (int i = 0; i < (int)finder._timelines.size(); i++) { StatsTimeline* s = new StatsTimeline; osg::MatrixTransform* m = s->createStatsForTimeline(finder._timelines[i].get()); m->setUpdateCallback(s); m->setMatrix(osg::Matrix::translate(0, -i * 100, 0)); _group->addChild(m); } } void StatAction::init(osg::Stats* stats, const std::string& name, const osg::Vec3& pos, float width, float height, const osg::Vec4& color) { std::string font("fonts/arial.ttf"); float characterSize = 20.0f; float startBlocks = 150.0f; _name = name; _group = new osg::Group; _label = new osg::Geode; _textLabel = new osgText::Text; _label->addDrawable(_textLabel.get()); _textLabel->setDataVariance(osg::Object::DYNAMIC); _textLabel->setColor(color); _textLabel->setFont(font); _textLabel->setCharacterSize(characterSize); _textLabel->setPosition(pos - osg::Vec3(0, height, 0)); _textLabel->setText(name); StatsGraph* graph = new StatsGraph(pos + osg::Vec3(startBlocks, 0,0) , width-startBlocks, height); graph->setCullingActive(false); graph->addStatGraph(stats, stats, color, 1.0, name); _graph = graph; _group->addChild(_label.get()); _group->addChild(_graph.get()); } void StatAction::setAlpha(float v) { std::cout << this << " color alpha " << v << std::endl; StatsGraph* gfx = dynamic_cast(_graph.get()); osg::Vec4 color = _textLabel->getColor(); color[3] = v; _textLabel->setColor(color); for (int i = 0; i < (int) gfx->_statsGraphGeode->getNumDrawables(); i++) { StatsGraph::Graph* g = dynamic_cast(gfx->_statsGraphGeode->getDrawable(0)); g->setColor(color); } } void StatAction::setPosition(const osg::Vec3& pos) { float characterSize = 20.0f; StatsGraph* gfx = dynamic_cast(_graph.get()); gfx->changeYposition(pos[1]); _textLabel->setPosition(pos - osg::Vec3(0, characterSize,0)); } void StatsHandler::getUsage(osg::ApplicationUsage& usage) const { usage.addKeyboardMouseBinding("s","On screen stats."); usage.addKeyboardMouseBinding("S","Output stats to console."); } }