diff options
-rw-r--r-- | apps/samples/miles/miles.js | 17 | ||||
-rw-r--r-- | apps/samples/miles/miles.scxml | 2 | ||||
-rw-r--r-- | apps/samples/vrml/vrml-server.scxml | 15 | ||||
-rw-r--r-- | src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp | 114 | ||||
-rw-r--r-- | src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp | 204 | ||||
-rw-r--r-- | src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.h | 14 |
6 files changed, 159 insertions, 207 deletions
diff --git a/apps/samples/miles/miles.js b/apps/samples/miles/miles.js index 4f31d68..451306b 100644 --- a/apps/samples/miles/miles.js +++ b/apps/samples/miles/miles.js @@ -68,6 +68,7 @@ function Miles(element, params) { query += "&userid=" + encodeURIComponent(email); query += "&session=" + encodeURIComponent(problemName); + self.messageElem.innerHTML += "Connecting to http://" + scxmlURL + "/miles/start" + query + "<br />"; self.xhr.get({ // The URL to request url: "http://" + scxmlURL + "/miles/start" + query, @@ -342,6 +343,10 @@ function Miles(element, params) { name: "problemName", value: problemName, style: "width: 100%", + onChange: function(){ + problemName = self.problemNameBox.get('value'); + } + }); dojo.query("div.problemName", self.connectToolTip.domNode)[0].appendChild(self.problemNameBox.domNode); @@ -349,6 +354,9 @@ function Miles(element, params) { name: "email", value: email, style: "width: 100%", + onChange: function(){ + email = self.emailBox.get('value'); + } }); dojo.query("div.email", self.connectToolTip.domNode)[0].appendChild(self.emailBox.domNode); @@ -356,6 +364,9 @@ function Miles(element, params) { name: "remoteEmail", value: remoteEmail, style: "width: 100%", + onChange: function(){ + remoteEmail = self.remoteEmailBox.get('value'); + } }); dojo.query("div.remoteEmail", self.connectToolTip.domNode)[0].appendChild(self.remoteEmailBox.domNode); @@ -363,6 +374,9 @@ function Miles(element, params) { name: "reflectorIp", value: reflectorIp, style: "width: 100%", + onChange: function(){ + reflectorIp = self.reflectorIpBox.get('value'); + } }); dojo.query("div.reflectorIp", self.connectToolTip.domNode)[0].appendChild(self.reflectorIpBox.domNode); @@ -370,6 +384,9 @@ function Miles(element, params) { name: "scxmlURL", value: scxmlURL, style: "width: 100%", + onChange: function(){ + scxmlURL = self.scxmlURLBox.get('value'); + } }); dojo.query("div.scxmlURL", self.connectToolTip.domNode)[0].appendChild(self.scxmlURLBox.domNode); diff --git a/apps/samples/miles/miles.scxml b/apps/samples/miles/miles.scxml index 22ebdda..2cad7ba 100644 --- a/apps/samples/miles/miles.scxml +++ b/apps/samples/miles/miles.scxml @@ -10,6 +10,7 @@ <if cond="_event.name === 'thumbnail.reply'"> <respond status="200" to="_event.data.origin"> <header name="Cache-Control" value="no-cache" /> <!-- force IE to actually reload --> + <header name="Access-Control-Allow-Origin" value="*" /> <header name="Content-Type" value="text/plain" /> <header name="Access-Control-Allow-Origin" value="*" /> <content expr="_event.data.image.base64()" /> @@ -17,6 +18,7 @@ <else /> <respond status="200" to="_event.data.origin"> <header name="Cache-Control" value="no-cache" /> <!-- force IE to actually reload --> + <header name="Access-Control-Allow-Origin" value="*" /> <!-- respond element will add content-type header --> <header name="Access-Control-Allow-Origin" value="*" /> <content expr="_event.data" /> diff --git a/apps/samples/vrml/vrml-server.scxml b/apps/samples/vrml/vrml-server.scxml index 0a66849..e08afd4 100644 --- a/apps/samples/vrml/vrml-server.scxml +++ b/apps/samples/vrml/vrml-server.scxml @@ -34,6 +34,21 @@ struct.key = _event.data.pathComponent.slice(1, _event.data.pathComponent.length - 1).join(pathDelim); if (struct.key.length > 0) struct.key += pathDelim; + + // support for meta file "latest.ext" + if (struct.file === "latest") { + var latestStamp = 0; + for (var key in models) { + if (key.substring(0, struct.key.length) == struct.key) { + if (models[key].ctime > latestStamp) { + var name = models[key].strippedName; + struct.file = name.substring(name.lastIndexOf(pathDelim) + 1); + latestStamp = models[key].ctime; + } + } + } + } + struct.key += struct.file; return struct; diff --git a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp index 3a99df8..25f7d45 100644 --- a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp +++ b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp @@ -1,6 +1,7 @@ /** * @file * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @author 2013 Enrico Papi (enrico.papi@ajile.it) * @copyright Simplified BSD * * @cond @@ -53,6 +54,7 @@ boost::shared_ptr<DataModelImpl> XPathDataModel::create(InterpreterImpl* interpr // dm->_xpath->setNamespaceContext(interpreter->getNSContext()); dm->_funcResolver.setInterpreter(interpreter); + dm->_xpath.setNamespaceContext(interpreter->getNSContext()); dm->_xpath.setFunctionResolver(dm->_funcResolver); dm->_xpath.setVariableResolver(dm->_varResolver); @@ -197,10 +199,29 @@ void XPathDataModel::setEvent(const Event& event) { eventDataElem.appendChild(textNode); } if (event.dom) { -// Node<std::string> importedNode = _doc.importNode(event.getFirstDOMElement(), true); Node<std::string> importedNode = _doc.importNode(event.dom, true); eventDataElem.appendChild(importedNode); } + if (event.data.array.size() == 1) { + Text<std::string> textNode = _doc.createTextNode(event.data.array.front().atom.c_str()); + eventDataElem.appendChild(textNode); + } else if (event.data.array.size() > 1) { + std::list<uscxml::Data>::const_iterator ptr; + unsigned int i; + + for( i = 0 , ptr = event.data.array.begin() ; + ((i < event.data.array.size()) && (ptr != event.data.array.end())); + i++ , ptr++ ) + { + Element<std::string> eventMESElem = _doc.createElement("data"); + Text<std::string> textNode = _doc.createTextNode(ptr->atom.c_str()); + std::stringstream ss; + ss << i; + eventMESElem.setAttribute("id", ss.str()); + eventMESElem.appendChild(textNode); + eventDataElem.appendChild(eventMESElem); + } + } eventElem.appendChild(eventDataElem); eventNodeSet.push_back(eventElem); @@ -244,8 +265,15 @@ Data XPathDataModel::getStringAsData(const std::string& content) { case NODE_SET: NodeSet<std::string> ns = result.asNodeSet(); for (int i = 0; i < ns.size(); i++) { + ss.str(""); + ss << i; + std::string idx = ss.str(); + ss.str(""); ss << ns[i]; + data.compound[idx] = Data(ss.str()); } + data.type = Data::INTERPRETED; + return data; break; } @@ -436,6 +464,9 @@ void XPathDataModel::assign(const Element<std::string>& assignElem, // test 326ff XPathValue<std::string> key = _xpath.evaluate_expr(location, _doc); +#ifdef VERBOSE + LOG(INFO) << "Key XPath : " << key.asString(); +#endif #if 0 if (key.type() == NODE_SET) { try { @@ -455,21 +486,19 @@ void XPathDataModel::assign(const Element<std::string>& assignElem, #endif NodeSet<std::string> nodeSet; if (node) { - if (node) { - Node<std::string> data = node; - while (data) { - // do not add empty text as a node - if (data.getNodeType() == Node_base::TEXT_NODE) { - std::string trimmed = data.getNodeValue(); - boost::trim(trimmed); - if (trimmed.length() == 0) { - data = data.getNextSibling(); - continue; - } - } - nodeSet.push_back(data); - data = data.getNextSibling(); + Node<std::string> data = node; + while (data) { + // do not add empty text as a node + if (data.getNodeType() == Node_base::TEXT_NODE) { + std::string trimmed = data.getNodeValue(); + boost::trim(trimmed); + if (trimmed.length() == 0) { + data = data.getNextSibling(); + continue; + } } + nodeSet.push_back(data); + data = data.getNextSibling(); } assign(key, nodeSet, assignElem); } else if (content.length() > 0) { @@ -478,6 +507,9 @@ void XPathDataModel::assign(const Element<std::string>& assignElem, assign(key, nodeSet, assignElem); } else if (HAS_ATTR(assignElem, "expr")) { XPathValue<std::string> value = _xpath.evaluate_expr(ATTR(assignElem, "expr"), _doc); +#ifdef VERBOSE + LOG(INFO) << "Value XPath : " << value.asString(); +#endif assign(key, value, assignElem); } else { LOG(ERROR) << "assign element has no content"; @@ -511,60 +543,45 @@ void XPathDataModel::init(const Element<std::string>& dataElem, location = ATTR(dataElem, "location"); } - Element<std::string> container = _doc.createElement("data"); - container.setAttribute("id", location); - - if (node) { - Node<std::string> data = node; - while (data) { - Node<std::string> dataClone = _doc.importNode(data, true); - container.appendChild(dataClone); - data = data.getNextSibling(); - } - } else if (content.length() > 0) { - Text<std::string> textNode = _doc.createTextNode(Interpreter::spaceNormalize(content)); - container.appendChild(textNode); + NodeSet<std::string> nodeSet; + if (node || (content.length() > 0)) { + _datamodel.appendChild(_doc.importNode(dataElem, true)); + nodeSet.push_back(dataElem); } else if (HAS_ATTR(dataElem, "expr")) { try { + Element<std::string> container = _doc.createElement("data"); + container.setAttribute("id", location); XPathValue<std::string> expr = _xpath.evaluate_expr(ATTR(dataElem, "expr"), _doc); switch (expr.type()) { case NODE_SET: { for (int i = 0; i < expr.asNodeSet().size(); i++) { container.appendChild(expr.asNodeSet()[i].cloneNode(true)); + nodeSet.push_back(expr.asNodeSet()[i].cloneNode(true)); } break; } case STRING: container.appendChild(_doc.createTextNode(expr.asString())); + nodeSet.push_back(_doc.createTextNode(expr.asString())); break; case NUMBER: { container.appendChild(_doc.createTextNode(toStr(expr.asNumber()))); + nodeSet.push_back(_doc.createTextNode(toStr(expr.asNumber()))); break; } case Arabica::XPath::BOOL: case ANY: throw Event("error.execution", Event::PLATFORM); } + _datamodel.appendChild(container); } catch (SyntaxException e) { throw Event("error.execution", Event::PLATFORM); } + } else { + LOG(ERROR) << "data element has no content"; } - _datamodel.appendChild(container); - // put data element into nodeset and bind to xpath variable - NodeSet<std::string> nodeSet; -#if 0 - nodeSet.push_back(container); -#else - Node<std::string> child = container.getFirstChild(); - while(child) { - nodeSet.push_back(child); - child = child.getNextSibling(); - } -#endif _varResolver.setVariable(location, nodeSet); - -// std::cout << _datamodel << std::endl; } void XPathDataModel::init(const std::string& location, const Data& data) { @@ -577,7 +594,7 @@ void XPathDataModel::assign(const XPathValue<std::string>& key, const XPathValue<std::string>& value, const Element<std::string>& assignElem) { switch (key.type()) { - case NODE_SET: { + case NODE_SET: if (key.asNodeSet().size() == 0) { throw Event("error.execution", Event::PLATFORM); } @@ -598,12 +615,12 @@ void XPathDataModel::assign(const XPathValue<std::string>& key, throw Event("error.execution", Event::PLATFORM); } break; - } case STRING: case Arabica::XPath::BOOL: case NUMBER: case ANY: throw Event("error.execution", Event::PLATFORM); + break; } } @@ -687,11 +704,10 @@ void XPathDataModel::assign(const NodeSet<std::string>& key, return; for (int i = 0; i < key.size(); i++) { - switch (key[i].getNodeType()) { + switch (key[i].getNodeType()) case Node_base::ELEMENT_NODE: { assign(Element<std::string>(key[i]), value, assignElem); break; - } default: // std::cout << key[i].getNodeType() << std::endl; throw Event("error.execution", Event::PLATFORM); @@ -765,7 +781,7 @@ void XPathDataModel::assign(const Element<std::string>& key, while(element.hasChildNodes()) element.removeChild(element.getChildNodes().item(0)); for (int i = 0; i < value.size(); i++) { - Node<std::string> importedNode = (value[i].getOwnerDocument() == _doc ? value[i].cloneNode(true) : _doc.importNode(value[i], true)); + Node<std::string> importedNode = element.getOwnerDocument().importNode(value[i], true); element.appendChild(importedNode); } } @@ -789,7 +805,7 @@ NodeSetVariableResolver::resolveVariable(const std::string& namepaceUri, } void NodeSetVariableResolver::setVariable(const std::string& name, const NodeSet<std::string>& value) { -#if 0 +#if VERBOSE std::cout << std::endl << "Setting " << name << ":" << std::endl; for (int i = 0; i < value.size(); i++) { std::cout << value[i].getNodeType() << " | " << value[i] << std::endl; @@ -849,4 +865,4 @@ bool XPathFunctionIn::doEvaluate(const Node<std::string>& context, return true; } -}
\ No newline at end of file +} diff --git a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp index f25d0b9..bace24e 100644 --- a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp +++ b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp @@ -201,15 +201,21 @@ void OSGConverter::process(const SendRequest& req) { assert(req.params.find("format") != req.params.end()); std::string source; - if (!Event::getParam(req.params, "source", source)) + if (!Event::getParam(req.params, "source", source)) { reportFailure(req); + LOG(ERROR) << "No source given for convert request"; + return; + } std::string dest; Event::getParam(req.params, "dest", dest); std::string format; - if (!Event::getParam(req.params, "format", format)) + if (!Event::getParam(req.params, "format", format)) { reportFailure(req); + LOG(ERROR) << "No format given for convert request"; + return; + } bool autoRotate = true; if (req.params.find("autorotate") != req.params.end()) { @@ -223,6 +229,7 @@ void OSGConverter::process(const SendRequest& req) { osg::ref_ptr<osg::Node> model = setupGraph(source, autoRotate); if (model->asGroup()->getNumChildren() == 0) { reportFailure(req); + LOG(ERROR) << "Could not setup scenegraph"; return; } @@ -253,76 +260,68 @@ void OSGConverter::process(const SendRequest& req) { ((osg::MatrixTransform*)model.get())->setMatrix(requestToModelPose(req)); osg::BoundingSphere bs = model->getBound(); -// osg::ref_ptr<osg::MatrixTransform> scale = new osg::MatrixTransform(); -// scale->setMatrix(osg::Matrix::scale(bs.radius() / 5, bs.radius() / 5, bs.radius() / 5)); -// scale->addChild(getOrigin()); -// sceneGraph->addChild(scale); - - osgViewer::ScreenCaptureHandler::CaptureOperation* cOp = new NameRespectingWriteToFile( - dest, - format, - osgViewer::ScreenCaptureHandler::WriteToFile::OVERWRITE, - req, this); + tthread::lock_guard<tthread::recursive_mutex> lock(_viewerMutex); + osgViewer::Viewer viewer; + osg::Camera *camera = viewer.getCamera(); + + osg::ref_ptr<osg::GraphicsContext::Traits> traits = new + osg::GraphicsContext::Traits; + traits->width = width; + traits->height = height; + traits->pbuffer = true; + traits->readDISPLAY(); + osg::GraphicsContext *gc = + osg::GraphicsContext::createGraphicsContext(traits.get()); - osgViewer::ScreenCaptureHandler* captureHandler = new osgViewer::ScreenCaptureHandler(cOp, -1); + camera->setGraphicsContext(gc); + camera->setDrawBuffer(GL_FRONT); + camera->setViewport(new osg::Viewport(0, 0, width, height)); - { - tthread::lock_guard<tthread::recursive_mutex> lock(_viewerMutex); - osgViewer::Viewer viewer; - osg::ref_ptr<osg::GraphicsContext> gc; + viewer.setSceneData(sceneGraph); - viewer.setSceneData(sceneGraph); - viewer.addEventHandler(captureHandler); - captureHandler->startCapture(); + viewer.setCameraManipulator(new osgGA::TrackballManipulator()); + viewer.getCamera()->setClearColor(osg::Vec4f(1.0f,1.0f,1.0f,1.0f)); + viewer.getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - osg::DisplaySettings* ds = osg::DisplaySettings::instance().get(); - osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits(ds); - traits->width = width; - traits->height = height; - // this fails with ubuntu in a VM in parallels - traits->pbuffer = true; + double zoom = 1; + CAST_PARAM(req.params, zoom, "zoom", double); - gc = osg::GraphicsContext::createGraphicsContext(traits.get()); + viewer.getCameraManipulator()->setByMatrix(osg::Matrix::lookAt(osg::Vec3d(0,0,bs.radius() * (-3.4 * zoom)), // eye + (osg::Vec3d)bs.center(), // center + osg::Vec3d(0,1,0))); // up - if (!gc.valid()) { - LOG(ERROR) << "Cannot create GraphicsContext!"; - return; - } + osg::Image *image = new osg::Image(); + camera->attach(osg::Camera::COLOR_BUFFER0, image); - if (!traits->width || !traits->height) { - LOG(ERROR) << "Traits returned with zero dimensions"; - return; - } + viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded); + viewer.realize(); + viewer.frame(); - GLenum pbuffer = gc->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT; - - viewer.setCameraManipulator(new osgGA::TrackballManipulator()); - viewer.getCamera()->setGraphicsContext(gc.get()); - viewer.getCamera()->setViewport(new osg::Viewport(0,0,traits->width,traits->height)); - viewer.getCamera()->setDrawBuffer(pbuffer); - viewer.getCamera()->setReadBuffer(pbuffer); - - double zoom = 1; - CAST_PARAM(req.params, zoom, "zoom", double); - - // set background color - viewer.getCamera()->setClearColor(osg::Vec4f(1.0f,1.0f,1.0f,1.0f)); - viewer.getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - viewer.getCameraManipulator()->setByMatrix(osg::Matrix::lookAt(osg::Vec3d(0,0,bs.radius() * (-3.4 * zoom)), // eye - (osg::Vec3d)bs.center(), // center - osg::Vec3d(0,1,0))); // up - - // viewer.home(); - - // perform one viewer iteration - viewer.realize(); - viewer.frame(); + std::string tempFile = URL::getTmpFilename(format); - viewer.removeEventHandler(captureHandler); + if (!osgDB::writeImageFile(*image, tempFile)) { + reportFailure(req); + LOG(ERROR) << "Could write image file at " << tempFile; + return; + } + + // read file into buffer + char* buffer = NULL; + size_t length = 0; + { + std::ifstream file(tempFile.c_str()); + + file.seekg(0, std::ios::end); + length = file.tellg(); + file.seekg(0, std::ios::beg); + buffer = (char*)malloc(length); + file.read(buffer, length); } -// delete(cOp); -// delete(captureHandler); + Data content; + content.compound[format] = Data(buffer, length, URL::getMimeType(format), false); + reportSuccess(req, content); + } void OSGConverter::reportSuccess(const SendRequest& req, const Data& content) { @@ -549,88 +548,5 @@ void OSGConverter::dumpMatrix(const osg::Matrix& m) { } } -void OSGConverter::NameRespectingWriteToFile::operator()(const osg::Image& image, const unsigned int context_id) { - -// std::cout << "NameRespectingWriteToFile" << std::endl; -// std::cout << image.s() << std::endl; -// std::cout << image.t() << std::endl; - - // write to memory first - std::string format; - if (_req.params.find("format") != _req.params.end()) { - format = _req.params.find("format")->second.atom; - } else { - _converter->reportFailure(_req); - } - - osg::ref_ptr<osgDB::ReaderWriter::Options> op = new osgDB::ReaderWriter::Options(); - //op->setOptionString("JPEG_QUALITY 75"); - //op->setOptionString("PNG_COMPRESSION 9"); - - // osgDB got confused when we write to a stringstream - std::string tempFile = URL::getTmpFilename(format); - if (!osgDB::writeImageFile(image, tempFile, op)) { - _converter->reportFailure(_req); - } - - char* buffer = NULL; - size_t length = 0; - { - std::ifstream file(tempFile.c_str()); - - file.seekg(0, std::ios::end); - length = file.tellg(); - file.seekg(0, std::ios::beg); - buffer = (char*)malloc(length); - file.read(buffer, length); - } - - std::cout << tempFile << std::endl; - -// remove(tempFile.c_str()); -// osg::ref_ptr<osgDB::ReaderWriter> writerFormat = osgDB::Registry::instance()->getReaderWriterForExtension(format); -// if(!writerFormat.valid()) -// _converter->reportFailure(_req); - - -#if 0 - std::stringstream ssFormat; - - osgDB::ReaderWriter::WriteResult res = writerFormat->writeImage(image, ssFormat, op); - - if (_filename.length() > 0) { - std::string tmpName = _filename; - size_t pathSep = _filename.find_last_of(PATH_SEPERATOR); - if (pathSep != std::string::npos) { - tmpName = _filename.substr(0, pathSep) + PATH_SEPERATOR + ".tmp" + _filename.substr(pathSep + 1, _filename.length() - pathSep - 1); - } - - { - std::ofstream outFile(tmpName.c_str()); - outFile << ssFormat.str(); - } - - if (pathSep != std::string::npos) { - int err = rename(tmpName.c_str(), _filename.c_str()); - if (err) { - _converter->reportFailure(_req); - } - } - } -#endif - - Data content; - content.compound[format] = Data(buffer, length, URL::getMimeType(format), false); - - // save image as a raw rgba as well for ffmpeg - we are using the mpb format for now -// osg::ref_ptr<osgDB::ReaderWriter> writerRGBA = osgDB::Registry::instance()->getReaderWriterForExtension("rgba"); -// if(writerRGBA.valid()) { -// std::stringstream ssRGBA; -// osgDB::ReaderWriter::WriteResult res = writerRGBA->writeImage(image, ssRGBA, op); -// content.compound["rgba"] = Data(ssRGBA.str().c_str(), ssRGBA.str().size(), false); -// } - - _converter->reportSuccess(_req, content); -} }
\ No newline at end of file diff --git a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.h b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.h index 7164bb8..a632a19 100644 --- a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.h +++ b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.h @@ -61,20 +61,6 @@ public: static void matrixToEuler(const osg::Matrix& m, double& pitch, double& roll, double& yaw); protected: - class NameRespectingWriteToFile : public osgViewer::ScreenCaptureHandler::WriteToFile { - public: - NameRespectingWriteToFile(const std::string& filename, - const std::string& extension, - SavePolicy savePolicy, - const SendRequest& req, - OSGConverter* converter) : osgViewer::ScreenCaptureHandler::WriteToFile(filename, extension, savePolicy), - _req(req), _converter(converter) { - } - - virtual void operator()(const osg::Image& image, const unsigned int context_id); - SendRequest _req; - OSGConverter* _converter; - }; uscxml::concurrency::BlockingQueue<SendRequest> _workQueue; osg::ref_ptr<osg::Node> setupGraph(const std::string filename, bool autoRotate = false); |