Files
OpenSpace/src/scene/scene.cpp

892 lines
30 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* 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 <openspace/scene/scene.h>
#include <openspace/camera/camera.h>
#include <openspace/documentation/documentation.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/globalscallbacks.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/events/event.h>
#include <openspace/events/eventengine.h>
#include <openspace/interaction/sessionrecording.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/query/query.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/profile.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scene/scenelicensewriter.h>
#include <openspace/scene/sceneinitializer.h>
#include <openspace/scripting/lualibrary.h>
#include <openspace/scripting/scriptengine.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/lua/luastate.h>
#include <ghoul/lua/lua_helper.h>
#include <ghoul/misc/defer.h>
#include <ghoul/misc/easing.h>
#include <ghoul/misc/misc.h>
#include <ghoul/misc/profiling.h>
#include <string>
#include <stack>
#include "scene_lua.inl"
namespace {
constexpr const char* _loggerCat = "Scene";
constexpr const char* KeyIdentifier = "Identifier";
constexpr const char* KeyParent = "Parent";
#ifdef TRACY_ENABLE
constexpr const char* renderBinToString(int renderBin) {
// Synced with Renderable::RenderBin
if (renderBin == 1) {
return "Background";
}
else if (renderBin == 2) {
return "Opaque";
}
else if (renderBin == 4) {
return "PreDeferredTransparent";
}
else if (renderBin == 8) {
return "PostDeferredTransparent";
}
else if (renderBin == 16) {
return "Overlay";
}
else {
throw ghoul::MissingCaseException();
}
}
#endif // TRACY_ENABLE
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
} // namespace
namespace openspace {
Scene::InvalidSceneError::InvalidSceneError(std::string msg, std::string comp)
: ghoul::RuntimeError(std::move(msg), std::move(comp))
{}
Scene::Scene(std::unique_ptr<SceneInitializer> initializer)
: properties::PropertyOwner({"Scene", "Scene"})
, _initializer(std::move(initializer))
{
_rootDummy.setIdentifier(SceneGraphNode::RootNodeIdentifier);
_rootDummy.setScene(this);
}
Scene::~Scene() {
clear();
_rootDummy.setScene(nullptr);
}
void Scene::attachNode(ghoul::mm_unique_ptr<SceneGraphNode> node) {
_rootDummy.attachChild(std::move(node));
}
ghoul::mm_unique_ptr<SceneGraphNode> Scene::detachNode(SceneGraphNode& node) {
return _rootDummy.detachChild(node);
}
void Scene::setCamera(std::unique_ptr<Camera> camera) {
_camera = std::move(camera);
}
Camera* Scene::camera() const {
return _camera.get();
}
void Scene::registerNode(SceneGraphNode* node) {
if (_nodesByIdentifier.count(node->identifier())) {
throw Scene::InvalidSceneError(
"Node with identifier " + node->identifier() + " already exits."
);
}
_topologicallySortedNodes.push_back(node);
_nodesByIdentifier[node->identifier()] = node;
addPropertySubOwner(node);
_dirtyNodeRegistry = true;
global::eventEngine->publishEvent<events::EventSceneGraphNodeAdded>(node);
}
void Scene::unregisterNode(SceneGraphNode* node) {
_topologicallySortedNodes.erase(
std::remove(
_topologicallySortedNodes.begin(),
_topologicallySortedNodes.end(),
node
),
_topologicallySortedNodes.end()
);
_nodesByIdentifier.erase(node->identifier());
// Just try to remove all properties; if the property doesn't exist, the
// removeInterpolation will not do anything
for (properties::Property* p : node->properties()) {
removePropertyInterpolation(p);
}
removePropertySubOwner(node);
_dirtyNodeRegistry = true;
global::eventEngine->publishEvent<events::EventSceneGraphNodeRemoved>(node);
}
void Scene::markNodeRegistryDirty() {
_dirtyNodeRegistry = true;
}
void Scene::updateNodeRegistry() {
ZoneScoped
sortTopologically();
_dirtyNodeRegistry = false;
}
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() == _nodesByIdentifier.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 = _nodesByIdentifier[SceneGraphNode::RootNodeIdentifier];
if (!root) {
throw Scene::InvalidSceneError("No root node found");
}
std::unordered_map<SceneGraphNode*, size_t> inDegrees;
for (SceneGraphNode* node : _topologicallySortedNodes) {
size_t inDegree = node->dependencies().size();
if (node->parent()) {
inDegree++;
inDegrees[node] = inDegree;
}
}
std::stack<SceneGraphNode*> zeroInDegreeNodes;
zeroInDegreeNodes.push(root);
std::vector<SceneGraphNode*> nodes;
nodes.reserve(_topologicallySortedNodes.size());
while (!zeroInDegreeNodes.empty()) {
SceneGraphNode* node = zeroInDegreeNodes.top();
nodes.push_back(node);
zeroInDegreeNodes.pop();
for (SceneGraphNode* n : node->dependentNodes()) {
const auto it = inDegrees.find(n);
it->second -= 1;
if (it->second == 0) {
zeroInDegreeNodes.push(n);
inDegrees.erase(it);
}
}
for (SceneGraphNode* n : node->children()) {
const auto it = inDegrees.find(n);
it->second -= 1;
if (it->second == 0) {
zeroInDegreeNodes.push(n);
inDegrees.erase(it);
}
}
}
if (!inDegrees.empty()) {
LERROR(fmt::format(
"The scene contains circular dependencies. {} nodes will be disabled",
inDegrees.size()
));
}
for (const std::pair<SceneGraphNode* const, size_t>& it : inDegrees) {
_circularNodes.push_back(it.first);
}
_topologicallySortedNodes = nodes;
}
void Scene::initializeNode(SceneGraphNode* node) {
_initializer->initializeNode(node);
}
bool Scene::isInitializing() const {
return _initializer->isInitializing();
}
/*
void Scene::initialize() {
bool useMultipleThreads = true;
if (OsEng.configurationManager().hasKey(
ConfigurationManager::KeyUseMultithreadedInitialization
))
{
useMultipleThreads = OsEng.configurationManager().value<bool>(
ConfigurationManager::KeyUseMultithreadedInitialization
);
}
auto initFunction = [](SceneGraphNode* node){
try {
OsEng.loadingScreen().updateItem(
node->name(),
LoadingScreen::ItemStatus::Initializing
);
node->initialize();
OsEng.loadingScreen().tickItem();
OsEng.loadingScreen().updateItem(
node->name(),
LoadingScreen::ItemStatus::Finished
);
}
catch (const ghoul::RuntimeError& e) {
LERROR(node->name() << " not initialized.");
LERRORC(std::string(_loggerCat) + "(" + e.component + ")", e.what());
OsEng.loadingScreen().updateItem(
node->name(),
LoadingScreen::ItemStatus::Failed
);
}
};
if (useMultipleThreads) {
unsigned int nThreads = std::thread::hardware_concurrency();
ghoul::ThreadPool pool(nThreads == 0 ? 2 : nThreads - 1);
OsEng.loadingScreen().postMessage("Initializing scene");
for (SceneGraphNode* node : _topologicallySortedNodes) {
pool.queue(initFunction, node);
}
pool.stop();
}
else {
for (SceneGraphNode* node : _topologicallySortedNodes) {
initFunction(node);
}
}
}
void Scene::initializeGL() {
for (SceneGraphNode* node : _topologicallySortedNodes) {
try {
node->initializeGL();
}
catch (const ghoul::RuntimeError& e) {
LERROR(node->name() << " not initialized.");
LERRORC(std::string(_loggerCat) + "(" + e.component + ")", e.what());
}
}
}
*/
void Scene::update(const UpdateData& data) {
ZoneScoped
std::vector<SceneGraphNode*> initializedNodes = _initializer->takeInitializedNodes();
for (SceneGraphNode* node : initializedNodes) {
try {
node->initializeGL();
}
catch (const ghoul::RuntimeError& e) {
LERRORC(e.component, e.message);
}
}
if (_dirtyNodeRegistry) {
updateNodeRegistry();
}
for (SceneGraphNode* node : _topologicallySortedNodes) {
try {
node->update(data);
}
catch (const ghoul::RuntimeError& e) {
LERRORC(e.component, e.what());
}
}
}
void Scene::render(const RenderData& data, RendererTasks& tasks) {
ZoneScoped
ZoneName(
renderBinToString(data.renderBinMask),
strlen(renderBinToString(data.renderBinMask))
)
for (SceneGraphNode* node : _topologicallySortedNodes) {
try {
node->render(data, tasks);
}
catch (const ghoul::RuntimeError& e) {
LERRORC(e.component, e.what());
}
if (global::callback::webBrowserPerformanceHotfix) {
(*global::callback::webBrowserPerformanceHotfix)();
}
}
{
ZoneScopedN("Get Error Hack")
// @TODO(abock 2019-08-19) This glGetError call is a hack to prevent the GPU
// thread and the CPU thread from diverging too much, particularly the uploading
// of a lot of textures for the globebrowsing planets can cause a hard stuttering
// effect. Asking for a glGetError after every rendering call will force the
// threads to implicitly synchronize and thus prevent the stuttering. The better
// solution would be to reduce the number of uploads per frame, use a staggered
// buffer, or something else like that preventing a large spike in uploads
glGetError();
}
}
void Scene::clear() {
LINFO("Clearing current scene graph");
_rootDummy.clearChildren();
}
const std::unordered_map<std::string, SceneGraphNode*>& Scene::nodesByIdentifier() const {
return _nodesByIdentifier;
}
SceneGraphNode* Scene::root() {
return &_rootDummy;
}
const SceneGraphNode* Scene::root() const {
return &_rootDummy;
}
SceneGraphNode* Scene::sceneGraphNode(const std::string& name) const {
const auto it = _nodesByIdentifier.find(name);
if (it != _nodesByIdentifier.end()) {
return it->second;
}
return nullptr;
}
const std::vector<SceneGraphNode*>& Scene::allSceneGraphNodes() const {
return _topologicallySortedNodes;
}
SceneGraphNode* Scene::loadNode(const ghoul::Dictionary& nodeDictionary) {
// First interpret the dictionary
std::vector<std::string> dependencyNames;
const std::string& nodeIdentifier = nodeDictionary.value<std::string>(KeyIdentifier);
const bool hasParent = nodeDictionary.hasKey(KeyParent);
if (_nodesByIdentifier.find(nodeIdentifier) != _nodesByIdentifier.end()) {
LERROR(fmt::format(
"Cannot add scene graph node '{}'. A node with that name already exists",
nodeIdentifier
));
return nullptr;
}
SceneGraphNode* parent = nullptr;
if (hasParent) {
const std::string parentIdentifier = nodeDictionary.value<std::string>(KeyParent);
parent = sceneGraphNode(parentIdentifier);
if (!parent) {
// TODO: Throw exception
LERROR(fmt::format(
"Could not find parent '{}' for '{}'", parentIdentifier, nodeIdentifier
));
return nullptr;
}
}
ghoul::mm_unique_ptr<SceneGraphNode> node = SceneGraphNode::createFromDictionary(
nodeDictionary
);
if (!node) {
// TODO: Throw exception
LERROR("Could not create node from dictionary: " + nodeIdentifier);
}
if (nodeDictionary.hasKey(SceneGraphNode::KeyDependencies)) {
if (!nodeDictionary.hasValue<ghoul::Dictionary>(SceneGraphNode::KeyDependencies))
{
// TODO: Throw exception
LERROR("Dependencies did not have the corrent type");
}
ghoul::Dictionary nodeDependencies =
nodeDictionary.value<ghoul::Dictionary>(SceneGraphNode::KeyDependencies);
for (std::string_view key : nodeDependencies.keys()) {
std::string value = nodeDependencies.value<std::string>(key);
dependencyNames.push_back(value);
}
}
// Make sure all dependencies are found
std::vector<SceneGraphNode*> dependencies;
bool foundAllDeps = true;
for (const std::string& depName : dependencyNames) {
SceneGraphNode* dep = sceneGraphNode(depName);
if (!dep) {
// TODO: Throw exception
LERROR(fmt::format(
"Could not find dependency '{}' for '{}'", depName, nodeIdentifier
));
foundAllDeps = false;
continue;
}
dependencies.push_back(dep);
}
if (!foundAllDeps) {
return nullptr;
}
// Now attach the node to the graph
SceneGraphNode* rawNodePointer = node.get();
if (parent) {
parent->attachChild(std::move(node));
}
else {
attachNode(std::move(node));
}
rawNodePointer->setDependencies(dependencies);
return rawNodePointer;
}
std::chrono::steady_clock::time_point Scene::currentTimeForInterpolation() {
if (global::sessionRecording->isSavingFramesDuringPlayback()) {
return global::sessionRecording->currentPlaybackInterpolationTime();
}
else {
return std::chrono::steady_clock::now();
}
}
void Scene::addPropertyInterpolation(properties::Property* prop, float durationSeconds,
ghoul::EasingFunction easingFunction)
{
ghoul_precondition(prop != nullptr, "prop must not be nullptr");
ghoul_precondition(durationSeconds > 0.f, "durationSeconds must be positive");
ghoul_postcondition(
std::find_if(
_propertyInterpolationInfos.begin(),
_propertyInterpolationInfos.end(),
[prop](const PropertyInterpolationInfo& info) {
return info.prop == prop && !info.isExpired;
}
) != _propertyInterpolationInfos.end(),
"A new interpolation record exists for p that is not expired"
);
ghoul::EasingFunc<float> func =
(easingFunction == ghoul::EasingFunction::Linear) ?
nullptr :
ghoul::easingFunction<float>(easingFunction);
// First check if the current property already has an interpolation information
std::chrono::steady_clock::time_point now = currentTimeForInterpolation();
for (PropertyInterpolationInfo& info : _propertyInterpolationInfos) {
if (info.prop == prop) {
info.beginTime = now;
info.durationSeconds = durationSeconds;
info.easingFunction = func;
// If we found it, we can break since we make sure that each property is only
// represented once in this
return;
}
}
PropertyInterpolationInfo i = {
prop,
now,
durationSeconds,
func
};
_propertyInterpolationInfos.push_back(std::move(i));
}
void Scene::removePropertyInterpolation(properties::Property* prop) {
ghoul_precondition(prop != nullptr, "prop must not be nullptr");
ghoul_postcondition(
std::find_if(
_propertyInterpolationInfos.begin(),
_propertyInterpolationInfos.end(),
[prop](const PropertyInterpolationInfo& info) {
return info.prop == prop;
}
) == _propertyInterpolationInfos.end(),
"No interpolation record exists for prop"
);
_propertyInterpolationInfos.erase(
std::remove_if(
_propertyInterpolationInfos.begin(),
_propertyInterpolationInfos.end(),
[prop](const PropertyInterpolationInfo& info) { return info.prop == prop; }
),
_propertyInterpolationInfos.end()
);
}
void Scene::updateInterpolations() {
ZoneScoped
using namespace std::chrono;
steady_clock::time_point now = currentTimeForInterpolation();
// First, let's update the properties
for (PropertyInterpolationInfo& i : _propertyInterpolationInfos) {
long long usPassed = duration_cast<std::chrono::microseconds>(
now - i.beginTime
).count();
const float t = glm::clamp(
static_cast<float>(
static_cast<double>(usPassed) /
static_cast<double>(i.durationSeconds * 1000000)
),
0.f,
1.f
);
// @FRAGILE(abock): This method might crash if someone deleted the property
// underneath us. We take care of removing entire PropertyOwners,
// but we assume that Propertys live as long as their
// SceneGraphNodes. This is true in general, but if Propertys are
// created and destroyed often by the SceneGraphNode, this might
// become a problem.
i.prop->interpolateValue(t, i.easingFunction);
i.isExpired = (t == 1.f);
if (i.isExpired) {
global::eventEngine->publishEvent<events::EventInterpolationFinished>(i.prop);
}
}
_propertyInterpolationInfos.erase(
std::remove_if(
_propertyInterpolationInfos.begin(),
_propertyInterpolationInfos.end(),
[](const PropertyInterpolationInfo& i) {
return i.isExpired;
}
),
_propertyInterpolationInfos.end()
);
}
void Scene::addInterestingTime(InterestingTime time) {
_interestingTimes.push_back(std::move(time));
}
const std::vector<Scene::InterestingTime>& Scene::interestingTimes() const {
return _interestingTimes;
}
void Scene::setPropertiesFromProfile(const Profile& p) {
ghoul::lua::LuaState L(ghoul::lua::LuaState::IncludeStandardLibrary::Yes);
for (const Profile::Property& prop : p.properties) {
std::string uriOrRegex = prop.name;
std::string groupName;
if (doesUriContainGroupTag(uriOrRegex, groupName)) {
// Remove group name from start of regex and replace with '*'
uriOrRegex = removeGroupNameFromUri(uriOrRegex);
}
_profilePropertyName = uriOrRegex;
ghoul::lua::push(L, uriOrRegex);
ghoul::lua::push(L, 0.0);
std::string workingValue = prop.value;
ghoul::trimSurroundingCharacters(workingValue, ' ');
// Later functions expect the value to be at the last position on the stack
propertyPushProfileValueToLua(L, workingValue);
applyRegularExpression(
L,
uriOrRegex,
allProperties(),
0.0,
groupName,
ghoul::EasingFunction::Linear
);
//Clear lua state stack
lua_settop(L, 0);
}
}
void Scene::propertyPushProfileValueToLua(ghoul::lua::LuaState& L,
const std::string& value)
{
_valueIsTable = false;
ProfilePropertyLua elem = propertyProcessValue(L, value);
if (!_valueIsTable) {
std::visit(overloaded{
[&L](bool v) {
ghoul::lua::push(L, v);
},
[&L](float v) {
ghoul::lua::push(L, v);
},
[&L](const std::string& v) {
ghoul::lua::push(L, v);
},
[&L](ghoul::lua::nil_t v) {
ghoul::lua::push(L, v);
}
}, elem);
}
}
ProfilePropertyLua Scene::propertyProcessValue(ghoul::lua::LuaState& L,
const std::string& value)
{
ProfilePropertyLua result;
PropertyValueType pType = propertyValueType(value);
switch (pType) {
case PropertyValueType::Boolean:
result = (value == "true") ? true : false;
break;
case PropertyValueType::Float:
result = std::stof(value);
break;
case PropertyValueType::Nil:
result = ghoul::lua::nil_t();
break;
case PropertyValueType::Table:
ghoul::trimSurroundingCharacters(const_cast<std::string&>(value), '{');
ghoul::trimSurroundingCharacters(const_cast<std::string&>(value), '}');
handlePropertyLuaTableEntry(L, value);
_valueIsTable = true;
break;
case PropertyValueType::String:
default:
ghoul::trimSurroundingCharacters(const_cast<std::string&>(value), '\"');
ghoul::trimSurroundingCharacters(const_cast<std::string&>(value), '[');
ghoul::trimSurroundingCharacters(const_cast<std::string&>(value), ']');
result = value;
break;
}
return result;
}
void Scene::handlePropertyLuaTableEntry(ghoul::lua::LuaState& L, const std::string& value)
{
PropertyValueType enclosedType;
size_t commaPos = value.find(',', 0);
if (commaPos != std::string::npos) {
enclosedType = propertyValueType(value.substr(0, commaPos));
}
else {
enclosedType = propertyValueType(value);
}
switch (enclosedType) {
case PropertyValueType::Boolean:
LERROR(fmt::format(
"A lua table of bool values is not supported. (processing property {})",
_profilePropertyName)
);
break;
case PropertyValueType::Float:
{
std::vector<float> valsF;
processPropertyValueTableEntries(L, value, valsF);
ghoul::lua::push(L, valsF);
}
break;
case PropertyValueType::String:
{
std::vector<std::string> valsS;
processPropertyValueTableEntries(L, value, valsS);
ghoul::lua::push(L, valsS);
}
break;
case PropertyValueType::Table:
default:
LERROR(fmt::format(
"Table-within-a-table values are not supported for profile a "
"property (processing property {})", _profilePropertyName
));
break;
}
}
template <typename T>
void Scene::processPropertyValueTableEntries(ghoul::lua::LuaState& L,
const std::string& value, std::vector<T>& table)
{
size_t commaPos = 0;
size_t prevPos = 0;
std::string nextValue;
while (commaPos != std::string::npos) {
commaPos = value.find(',', prevPos);
if (commaPos != std::string::npos) {
nextValue = value.substr(prevPos, commaPos - prevPos);
prevPos = commaPos + 1;
}
else {
nextValue = value.substr(prevPos);
}
ghoul::trimSurroundingCharacters(nextValue, ' ');
ProfilePropertyLua tableElement = propertyProcessValue(L, nextValue);
try {
table.push_back(std::get<T>(tableElement));
}
catch (std::bad_variant_access&) {
LERROR(fmt::format(
"Error attempting to parse profile property setting for "
"{} using value = {}", _profilePropertyName, value
));
}
}
}
PropertyValueType Scene::propertyValueType(const std::string& value) {
auto isFloatValue = [](const std::string& s) {
try {
float converted = std::numeric_limits<float>::min();
converted = std::stof(s);
return (converted != std::numeric_limits<float>::min());
}
catch (...) {
return false;
}
};
if (value == "true" || value == "false") {
return PropertyValueType::Boolean;
}
else if (isFloatValue(value)) {
return PropertyValueType::Float;
}
else if (value == "nil") {
return PropertyValueType::Nil;
}
else if ((value.front() == '{') && (value.back() == '}')) {
return PropertyValueType::Table;
}
else {
return PropertyValueType::String;
}
}
std::vector<properties::Property*> Scene::propertiesMatchingRegex(
std::string propertyString)
{
return findMatchesInAllProperties(propertyString, allProperties(), "");
}
scripting::LuaLibrary Scene::luaLibrary() {
return {
"",
{
{
"setPropertyValue",
&luascriptfunctions::propertySetValue,
{},
"",
"Sets all property(s) 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. If the "
"third is not present or is '0', the value changes instantly, otherwise "
"the change will take that many seconds and the value is interpolated at "
"each step in between. The fourth parameter is an optional easing "
"function if a 'duration' has been specified. If 'duration' is 0, this "
"parameter value is ignored. Otherwise, it can be one of many supported "
"easing functions. See easing.h for available functions. The fifth "
"argument must be either empty, 'regex', or 'single'. If the fifth"
"argument is empty (the default), the URI is interpreted using a "
"wildcard in which '*' is expanded to '(.*)' and bracketed components "
"'{ }' are interpreted as group tag names. Then, the passed value will "
"be set on all properties that fit the regex + group name combination. "
"If the fifth argument is 'regex' neither the '*' expansion, nor the "
"group tag expansion is performed and the first argument is used as an "
"ECMAScript style regular expression that matches against the fully "
"qualified IDs of properties. If the fifth argument is 'single' no "
"substitutions are performed and exactly 0 or 1 properties are changed."
},
{
"setPropertyValueSingle",
&luascriptfunctions::propertySetValueSingle,
{},
"",
"Sets the 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. If the third is not present or is '0', the value "
"changes instantly, otherwise the change will take that many seconds and "
"the value is interpolated at each step in between. The fourth "
"parameter is an optional easing function if a 'duration' has been "
"specified. If 'duration' is 0, this parameter value is ignored. "
"Otherwise, it has to be 'linear', 'easein', 'easeout', or 'easeinout'. "
"This is the same as calling the setValue method and passing 'single' as "
"the fourth argument to setPropertyValue."
},
{
"getPropertyValue",
&luascriptfunctions::propertyGetValue,
{},
"",
"Returns the value the property, identified by the provided URI."
},
codegen::lua::HasProperty,
codegen::lua::GetProperty,
codegen::lua::AddCustomProperty,
codegen::lua::RemoveCustomProperty,
codegen::lua::AddSceneGraphNode,
codegen::lua::RemoveSceneGraphNode,
codegen::lua::RemoveSceneGraphNodesFromRegex,
codegen::lua::HasSceneGraphNode,
codegen::lua::AddInterestingTime,
codegen::lua::WorldPosition,
codegen::lua::WorldRotation,
codegen::lua::SetParent,
codegen::lua::BoundingSphere,
codegen::lua::InteractionSphere
}
};
}
} // namespace openspace