/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2017 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "scene_doc.inl" #include "scene_lua.inl" namespace { const char* _loggerCat = "Scene"; const char* _moduleExtension = ".mod"; const char* _commonModuleToken = "${COMMON_MODULE}"; const char* KeyCamera = "Camera"; const char* KeyFocusObject = "Focus"; const char* KeyPositionObject = "Position"; const char* KeyViewOffset = "Offset"; const char* MainTemplateFilename = "${OPENSPACE_DATA}/web/properties/main.hbs"; const char* PropertyOwnerTemplateFilename = "${OPENSPACE_DATA}/web/properties/propertyowner.hbs"; const char* PropertyTemplateFilename = "${OPENSPACE_DATA}/web/properties/property.hbs"; const char* HandlebarsFilename = "${OPENSPACE_DATA}/web/common/handlebars-v4.0.5.js"; const char* JsFilename = "${OPENSPACE_DATA}/web/properties/script.js"; const char* BootstrapFilename = "${OPENSPACE_DATA}/web/common/bootstrap.min.css"; const char* CssFilename = "${OPENSPACE_DATA}/web/common/style.css"; } // namespace namespace openspace { Scene::Scene() {} Scene::~Scene() {} void Scene::setRoot(std::unique_ptr root) { if (_root) { removeNode(_root.get()); } _root = std::move(root); _root->setScene(this); addNode(_root.get()); } void Scene::setCamera(std::unique_ptr camera) { _camera = std::move(camera); } Camera* Scene::camera() const { return _camera.get(); } void Scene::addNode(SceneGraphNode* node, UpdateDependencies updateDeps) { // Add the node and all its children. node->traversePreOrder([this](SceneGraphNode* n) { _topologicallySortedNodes.push_back(n); _nodesByName[n->name()] = n; }); if (updateDeps) { updateDependencies(); } } void Scene::removeNode(SceneGraphNode* node, UpdateDependencies updateDeps) { // Remove the node and all its children. node->traversePostOrder([this](SceneGraphNode* node) { _topologicallySortedNodes.erase( std::remove(_topologicallySortedNodes.begin(), _topologicallySortedNodes.end(), node), _topologicallySortedNodes.end() ); _nodesByName.erase(node->name()); }); if (updateDeps) { updateDependencies(); } } void Scene::updateDependencies() { sortTopologically(); } void Scene::sortTopologically() { _topologicallySortedNodes.insert( _topologicallySortedNodes.end(), std::make_move_iterator(_circularNodes.begin()), std::make_move_iterator(_circularNodes.end()) ); _circularNodes.clear(); ghoul_assert(_topologicallySortedNodes.size() == _nodesByName.size(), "Number of scene graph nodes is inconsistent"); if (_topologicallySortedNodes.empty()) return; // Only the Root node can have an in-degree of 0 SceneGraphNode* root = _nodesByName[SceneGraphNode::RootNodeName]; if (!root) { throw Scene::InvalidSceneError("No root node found"); } std::unordered_map inDegrees; for (SceneGraphNode* node : _topologicallySortedNodes) { size_t inDegree = node->dependencies().size(); if (node->parent() != nullptr) { inDegree++; inDegrees[node] = inDegree; } } std::stack zeroInDegreeNodes; zeroInDegreeNodes.push(root); std::vector nodes; nodes.reserve(_topologicallySortedNodes.size()); while (!zeroInDegreeNodes.empty()) { SceneGraphNode* node = zeroInDegreeNodes.top(); nodes.push_back(node); zeroInDegreeNodes.pop(); for (SceneGraphNode* n : node->dependentNodes()) { auto it = inDegrees.find(n); it->second -= 1; if (it->second == 0) { zeroInDegreeNodes.push(n); inDegrees.erase(it); } } for (SceneGraphNode* n : node->children()) { auto it = inDegrees.find(n); it->second -= 1; if (it->second == 0) { zeroInDegreeNodes.push(n); inDegrees.erase(it); } } } if (inDegrees.size() > 0) { LERROR("The scene contains circular dependencies. " << inDegrees.size() << " nodes will be disabled."); } for (auto it : inDegrees) { _circularNodes.push_back(it.first); } _topologicallySortedNodes = nodes; } void Scene::initialize() { for (SceneGraphNode* node : _topologicallySortedNodes) { try { bool success = node->initialize(); if (success) LDEBUG(node->name() << " initialized successfully!"); else LWARNING(node->name() << " not initialized."); } catch (const ghoul::RuntimeError& e) { LERRORC(std::string(_loggerCat) + "(" + e.component + ")", e.what()); } } } void Scene::update(const UpdateData& data) { for (SceneGraphNode* node : _topologicallySortedNodes) { try { LTRACE("Scene::update(begin '" + node->name() + "')"); node->update(data); LTRACE("Scene::update(end '" + node->name() + "')"); } catch (const ghoul::RuntimeError& e) { LERRORC(e.component, e.what()); } } } void Scene::evaluate(Camera* camera) { for (SceneGraphNode* node : _topologicallySortedNodes) { try { LTRACE("Scene::evaluate(begin '" + node->name() + "')"); node->evaluate(camera); LTRACE("Scene::evaluate(end '" + node->name() + "')"); } catch (const ghoul::RuntimeError& e) { LERRORC(e.component, e.what()); } } } void Scene::render(const RenderData& data, RendererTasks& tasks) { for (SceneGraphNode* node : _topologicallySortedNodes) { try { LTRACE("Scene::render(begin '" + node->name() + "')"); node->render(data, tasks); LTRACE("Scene::render(end '" + node->name() + "')"); } catch (const ghoul::RuntimeError& e) { LERRORC(e.component, e.what()); } } } void Scene::clear() { LINFO("Clearing current scene graph"); _root = nullptr; } const std::map& Scene::nodesByName() const { return _nodesByName; } SceneGraphNode* Scene::root() const { return _root.get(); } SceneGraphNode* Scene::sceneGraphNode(const std::string& name) const { auto it = _nodesByName.find(name); if (it != _nodesByName.end()) { return it->second; } return nullptr; } const std::vector& Scene::allSceneGraphNodes() const { return _topologicallySortedNodes; } void Scene::writePropertyDocumentation(const std::string& filename, const std::string& type, const std::string& sceneFilename) { LDEBUG("Writing documentation for properties"); if (type == "text") { std::ofstream file; file.exceptions(~std::ofstream::goodbit); file.open(filename); using properties::Property; for (SceneGraphNode* node : allSceneGraphNodes()) { std::vector properties = node->propertiesRecursive(); if (!properties.empty()) { file << node->name() << std::endl; for (Property* p : properties) { file << p->fullyQualifiedIdentifier() << ": " << p->guiName() << std::endl; } file << std::endl; } } } else if (type == "html") { std::ofstream file; file.exceptions(~std::ofstream::goodbit); file.open(filename); std::ifstream handlebarsInput(absPath(HandlebarsFilename)); std::ifstream jsInput(absPath(JsFilename)); std::string jsContent; std::back_insert_iterator jsInserter(jsContent); std::copy(std::istreambuf_iterator{handlebarsInput}, std::istreambuf_iterator(), jsInserter); std::copy(std::istreambuf_iterator{jsInput}, std::istreambuf_iterator(), jsInserter); std::ifstream bootstrapInput(absPath(BootstrapFilename)); std::ifstream cssInput(absPath(CssFilename)); std::string cssContent; std::back_insert_iterator cssInserter(cssContent); std::copy(std::istreambuf_iterator{bootstrapInput}, std::istreambuf_iterator(), cssInserter); std::copy(std::istreambuf_iterator{cssInput}, std::istreambuf_iterator(), cssInserter); std::ifstream mainTemplateInput(absPath(MainTemplateFilename)); std::string mainTemplateContent{ std::istreambuf_iterator{mainTemplateInput}, std::istreambuf_iterator{} }; std::ifstream propertyOwnerTemplateInput(absPath(PropertyOwnerTemplateFilename)); std::string propertyOwnerTemplateContent{ std::istreambuf_iterator{propertyOwnerTemplateInput}, std::istreambuf_iterator{} }; std::ifstream propertyTemplateInput(absPath(PropertyTemplateFilename)); std::string propertyTemplateContent{ std::istreambuf_iterator{propertyTemplateInput}, std::istreambuf_iterator{} }; // Create JSON std::function createJson = [&createJson](properties::PropertyOwner* owner) -> std::string { std::stringstream json; json << "{"; json << "\"name\": \"" << owner->name() << "\","; json << "\"properties\": ["; auto properties = owner->properties(); for (properties::Property* p : properties) { json << "{"; json << "\"id\": \"" << p->identifier() << "\","; json << "\"type\": \"" << p->className() << "\","; json << "\"fullyQualifiedId\": \"" << p->fullyQualifiedIdentifier() << "\","; json << "\"guiName\": \"" << p->guiName() << "\""; json << "}"; if (p != properties.back()) { json << ","; } } json << "],"; json << "\"propertyOwners\": ["; auto propertyOwners = owner->propertySubOwners(); for (properties::PropertyOwner* o : propertyOwners) { json << createJson(o); if (o != propertyOwners.back()) { json << ","; } } json << "]"; json << "}"; return json.str(); }; std::stringstream json; json << "["; std::vector nodes = allSceneGraphNodes(); if (!nodes.empty()) { json << std::accumulate( std::next(nodes.begin()), nodes.end(), createJson(*nodes.begin()), [createJson](std::string a, SceneGraphNode* n) { return a + "," + createJson(n); } ); } json << "]"; std::string jsonString = ""; for (const char& c : json.str()) { if (c == '\'') { jsonString += "\\'"; } else { jsonString += c; } } std::stringstream html; html << "\n" << "\n" << "\t\n" << "\t\t\n" << "\t\t\n" << "\t\t\n" << "\t\n" << "\t\n" << "\t\tDocumentation\n" << "\t\n" << "\t\n" << "\t\n" << "\n"; file << html.str(); } else LERROR("Undefined type '" << type << "' for Property documentation"); } scripting::LuaLibrary Scene::luaLibrary() { return { "", { { "setPropertyValue", &luascriptfunctions::property_setValue, "string, *", "Sets all properties identified by the URI (with potential wildcards) in " "the first argument. The second argument can be any type, but it has to " "match the type that the property (or properties) expect." }, { "setPropertyValueRegex", &luascriptfunctions::property_setValueRegex, "string, *", "Sets all properties that pass the regular expression in the first " "argument. The second argument can be any type, but it has to match the " "type of the properties that matched the regular expression. The regular " "expression has to be of the ECMAScript grammar." }, { "setPropertyValueSingle", &luascriptfunctions::property_setValueSingle, "string, *", "Sets a property identified by the URI in " "the first argument. The second argument can be any type, but it has to " "match the type that the property expects.", }, { "getPropertyValue", &luascriptfunctions::property_getValue, "string", "Returns the value the property, identified by " "the provided URI." }, { "loadScene", &luascriptfunctions::loadScene, "string", "Loads the scene found at the file passed as an " "argument. If a scene is already loaded, it is unloaded first" }, { "addSceneGraphNode", &luascriptfunctions::addSceneGraphNode, "table", "Loads the SceneGraphNode described in the table and adds it to the " "SceneGraph" }, { "removeSceneGraphNode", &luascriptfunctions::removeSceneGraphNode, "string", "Removes the SceneGraphNode identified by name" } } }; } Scene::InvalidSceneError::InvalidSceneError(const std::string& error, const std::string& comp) : ghoul::RuntimeError(error, comp) {} } // namespace openspace