From 737e82584a98591b667b183728278baedb26d320 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Tue, 13 Mar 2018 10:35:10 -0400 Subject: [PATCH] Issue/24 (#549) * Implemented property animation mechanism * Added easing functions to Ghoul and make them usable in animation * Make sure that we don't leave properties around if SceneGraphNodes are deleted * Adding opacity settings to base renderables --- Jenkinsfile | 2 +- .../digitaluniverse/backgroundradiation.asset | 6 +- .../scene/digitaluniverse/milkyway.asset | 2 +- data/assets/scene/milkyway/milkyway/eso.asset | 2 +- ext/sgct | 2 +- .../openspace/properties/numericalproperty.h | 12 + .../properties/numericalproperty.inl | 58 ++++ include/openspace/properties/property.h | 10 + include/openspace/rendering/renderable.h | 4 + include/openspace/scene/scene.h | 58 +++- modules/base/rendering/renderablemodel.cpp | 10 + modules/base/rendering/renderablemodel.h | 4 +- modules/base/rendering/renderableplane.cpp | 5 + .../rendering/renderableplaneimagelocal.cpp | 1 - modules/base/rendering/renderablesphere.cpp | 55 ++- modules/base/rendering/renderablesphere.h | 6 +- .../rendering/renderablesphericalgrid.cpp | 5 + modules/base/rendering/renderabletrail.cpp | 5 + modules/base/rendering/renderabletrail.h | 4 +- modules/base/shaders/grid_fs.glsl | 2 + modules/base/shaders/model_fs.glsl | 3 +- modules/base/shaders/model_vs.glsl | 2 +- modules/base/shaders/plane_fs.glsl | 2 + modules/base/shaders/renderabletrail_fs.glsl | 3 +- modules/base/shaders/sphere_fs.glsl | 4 +- shaders/fragment.glsl | 2 +- src/properties/property.cpp | 5 + src/rendering/renderable.cpp | 58 +++- src/rendering/renderengine.cpp | 6 +- src/scene/scene.cpp | 173 ++++++++-- src/scene/scene_lua.inl | 317 ++++++++++-------- 31 files changed, 575 insertions(+), 253 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index fa4e137cfe..9150da5bef 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -54,7 +54,7 @@ stage('Build') { cd build cmake -G "Visual Studio 15 2017 Win64" .. ''' + flags + ''' .. - msbuild.exe OpenSpace.sln /nologo /verbosity:minimal /p:Configuration=Debug + msbuild.exe OpenSpace.sln /nologo /verbosity:minimal /p:Configuration=Debug /target:OpenSpace ''' } } diff --git a/data/assets/scene/digitaluniverse/backgroundradiation.asset b/data/assets/scene/digitaluniverse/backgroundradiation.asset index 85f10e6393..2652323ae6 100644 --- a/data/assets/scene/digitaluniverse/backgroundradiation.asset +++ b/data/assets/scene/digitaluniverse/backgroundradiation.asset @@ -23,7 +23,7 @@ local wmap = { Enabled = false, Size = 3975.41417036064E23, Segments = 80, - Alpha = 0.5, + Opacity = 0.5, Texture = textures .. "/wmap_ilc_7yr_v4_200uK_RGB_sos.png", Orientation = "Inside/Outside", FadeInThreshould = 8E26 @@ -39,7 +39,7 @@ local cbe = { Enabled = false, Size = 3975.41417036064E23, Segments = 80, - Alpha = 0.5, + Opacity = 0.5, Texture = textures .. "/COBErect.png", Orientation = "Inside/Outside", FadeInThreshould = 8E26 @@ -54,7 +54,7 @@ local planck = { Enabled = false, Size = 3975.41417036064E23, Segments = 80, - Alpha = 0.3, + Opacity = 0.3, Texture = textures .. "/cmb4k.jpg", Orientation = "Inside/Outside", FadeInThreshould = 8E26 diff --git a/data/assets/scene/digitaluniverse/milkyway.asset b/data/assets/scene/digitaluniverse/milkyway.asset index a55dca6ede..cf462f66b0 100644 --- a/data/assets/scene/digitaluniverse/milkyway.asset +++ b/data/assets/scene/digitaluniverse/milkyway.asset @@ -29,7 +29,7 @@ local sphere = { Type = "RenderableSphere", Size = 9.2E20, Segments = 40, - Alpha = 0.4, + Opacity = 0.4, Texture = sphereTextures .. "/DarkUniverse_mellinger_4k.jpg", Orientation = "Inside/Outside", FadeOutThreshould = 0.25 diff --git a/data/assets/scene/milkyway/milkyway/eso.asset b/data/assets/scene/milkyway/milkyway/eso.asset index cb77f1f655..12bab40860 100644 --- a/data/assets/scene/milkyway/milkyway/eso.asset +++ b/data/assets/scene/milkyway/milkyway/eso.asset @@ -15,7 +15,7 @@ local object = { Type = "RenderableSphere", Size = 9.2E20, Segments = 40, - Alpha = 0.4, + Opacity = 0.4, Texture = textures .. "/eso0932a_blend.png", Orientation = "Inside/Outside", FadeOutThreshould = 0.01 diff --git a/ext/sgct b/ext/sgct index 0f689a49a8..462cca02c6 160000 --- a/ext/sgct +++ b/ext/sgct @@ -1 +1 @@ -Subproject commit 0f689a49a8d0e47cdba9d57b9b9cf5e98fb7847a +Subproject commit 462cca02c68e6fe74ccc3aa18580aae673ecc5aa diff --git a/include/openspace/properties/numericalproperty.h b/include/openspace/properties/numericalproperty.h index 12cec0e4e3..d342599fae 100644 --- a/include/openspace/properties/numericalproperty.h +++ b/include/openspace/properties/numericalproperty.h @@ -64,6 +64,15 @@ public: using TemplateProperty::operator=; + + void setInterpolationTarget(ghoul::any value) override; + void setLuaInterpolationTarget(lua_State* state) override; + void setStringInterpolationTarget(std::string value) override; + + void interpolateValue(float t, + ghoul::EasingFunc easingFunc = nullptr) override; + + protected: static const std::string MinimumValueKey; static const std::string MaximumValueKey; @@ -76,6 +85,9 @@ protected: T _maximumValue; T _stepping; float _exponent; + + T _interpolationStart; + T _interpolationEnd; }; } // namespace openspace::properties diff --git a/include/openspace/properties/numericalproperty.inl b/include/openspace/properties/numericalproperty.inl index edfbcddbbd..967c700300 100644 --- a/include/openspace/properties/numericalproperty.inl +++ b/include/openspace/properties/numericalproperty.inl @@ -395,4 +395,62 @@ std::string NumericalProperty::generateAdditionalDescription() const { return result; } +template +void NumericalProperty::setInterpolationTarget(ghoul::any value) { + try { + T v = ghoul::any_cast(std::move(value)); + + _interpolationStart = TemplateProperty::_value; + _interpolationEnd = std::move(v); + } + catch (ghoul::bad_any_cast&) { + LERRORC( + "TemplateProperty", + fmt::format( + "Illegal cast from '{}' to '{}'", + value.type().name(), + typeid(T).name() + ) + ); + } +} + +template +void NumericalProperty::setLuaInterpolationTarget(lua_State* state) { + bool success = false; + T thisValue = PropertyDelegate>::template fromLuaValue( + state, + success + ); + if (success) { + _interpolationStart = TemplateProperty::_value; + _interpolationEnd = std::move(thisValue); + } +} + +template +void NumericalProperty::setStringInterpolationTarget(std::string value) { + bool success = false; + T thisValue = PropertyDelegate>::template fromString( + value, + success + ); + if (success) { + _interpolationStart = TemplateProperty::_value; + _interpolationEnd = std::move(thisValue); + } +} + +template +void NumericalProperty::interpolateValue(float t, + ghoul::EasingFunc easingFunction) +{ + if (easingFunction) { + t = easingFunction(t); + } + TemplateProperty::setValue(static_cast( + glm::mix(_interpolationStart, _interpolationEnd, t) + )); +} + } // namespace openspace::properties diff --git a/include/openspace/properties/property.h b/include/openspace/properties/property.h index e3e393a243..4031f506a0 100644 --- a/include/openspace/properties/property.h +++ b/include/openspace/properties/property.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -364,6 +365,15 @@ public: */ const ghoul::Dictionary& metaData() const; + + /// Interpolation methods + virtual void setInterpolationTarget(ghoul::any value); + virtual void setLuaInterpolationTarget(lua_State* state); + virtual void setStringInterpolationTarget(std::string value); + + virtual void interpolateValue(float t, + ghoul::EasingFunc easingFunction = nullptr); + protected: static const char* IdentifierKey; static const char* NameKey; diff --git a/include/openspace/rendering/renderable.h b/include/openspace/rendering/renderable.h index 2069bd23c3..575045be36 100644 --- a/include/openspace/rendering/renderable.h +++ b/include/openspace/rendering/renderable.h @@ -28,6 +28,7 @@ #include #include +#include namespace ghoul { class Dictionary; } namespace ghoul::opengl { @@ -99,6 +100,9 @@ public: protected: properties::BoolProperty _enabled; + properties::FloatProperty _opacity; + + void registerUpdateRenderBinFromOpacity(); private: RenderBin _renderBin; diff --git a/include/openspace/scene/scene.h b/include/openspace/scene/scene.h index 1d703bb68e..dd1ec49d46 100644 --- a/include/openspace/scene/scene.h +++ b/include/openspace/scene/scene.h @@ -25,21 +25,21 @@ #ifndef __OPENSPACE_CORE___SCENE___H__ #define __OPENSPACE_CORE___SCENE___H__ +#include + #include #include #include #include -#include #include +#include #include #include #include #include #include - -#include - +#include #include namespace ghoul { class Dictionary; } @@ -172,6 +172,47 @@ public: */ bool isInitializing() const; + /** + * Adds an interpolation request for the passed \p prop that will run for + * \p durationSeconds seconds. Every time the #updateInterpolations method is called + * the Property will be notified that it has to update itself using the stored + * interpolation values. If an interpolation record already exists for the passed + * \p prop, the previous record will be overwritten and the remaining time of the old + * interpolation is ignored. + * + * \param prop The property that should be called to update itself every frame until + * \p durationSeconds seconds have passed + * \param durationSeconds The number of seconds that the interpolation will run for + * + * \pre \p prop must not be \c nullptr + * \pre \p durationSeconds must be positive and not 0 + * \post A new interpolation record exists for \p that is not expired + */ + void addInterpolation(properties::Property* prop, float durationSeconds, + ghoul::EasingFunction easingFunction = ghoul::EasingFunction::Linear); + + /** + * Removes the passed \p prop from the list of Property%s that are update each time + * the #updateInterpolations method is called + * + * \param prop The Property that should not longer be updated + * + * \pre \prop must not be nullptr + * \post No interpolation record exists for \p prop + */ + void removeInterpolation(properties::Property* prop); + + /** + * Informs all Property%s with active interpolations about applying a new update tick + * using the Property::interpolateValue method, passing a parameter \c t which is \c 0 + * if no time has passed between the #addInterpolation method and \c 1 if an amount of + * time equal to the requested interpolation time has passed. The parameter \c t is + * updated with a resolution of 1 microsecond, which means that if this function is + * called twice within 1 microsecond, the passed parameter \c t might be the same for + * both calls + */ + void updateInterpolations(); + /** * Returns the Lua library that contains all Lua functions available to change the * scene graph. The functions contained are @@ -203,6 +244,15 @@ private: std::mutex _programUpdateLock; std::set _programsToUpdate; std::vector> _programs; + + struct InterpolationInfo { + properties::Property* prop; + std::chrono::time_point beginTime; + float durationSeconds; + ghoul::EasingFunc easingFunction; + bool isExpired = false; + }; + std::vector _interpolationInfos; }; } // namespace openspace diff --git a/modules/base/rendering/renderablemodel.cpp b/modules/base/rendering/renderablemodel.cpp index 7a335522ab..bf30e43fb2 100644 --- a/modules/base/rendering/renderablemodel.cpp +++ b/modules/base/rendering/renderablemodel.cpp @@ -123,6 +123,10 @@ RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) "RenderableModel" ); + addProperty(_opacity); + registerUpdateRenderBinFromOpacity(); + + if (dictionary.hasKey(KeyGeometry)) { std::string name = dictionary.value(SceneGraphNode::KeyName); ghoul::Dictionary dict = dictionary.value(KeyGeometry); @@ -163,6 +167,7 @@ void RenderableModel::initializeGL() { absPath("${MODULE_BASE}/shaders/model_fs.glsl") ); + _uniformCache.opacity = _programObject->uniformLocation("opacity"); _uniformCache.directionToSunViewSpace = _programObject->uniformLocation( "directionToSunViewSpace" ); @@ -176,6 +181,8 @@ void RenderableModel::initializeGL() { "performShading" ); _uniformCache.texture = _programObject->uniformLocation("texture1"); + + loadTexture(); _geometry->initialize(this); @@ -197,6 +204,8 @@ void RenderableModel::deinitializeGL() { void RenderableModel::render(const RenderData& data, RendererTasks&) { _programObject->activate(); + _programObject->setUniform(_uniformCache.opacity, _opacity); + // Model transform and view transform needs to be in double precision glm::dmat4 modelTransform = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation @@ -244,6 +253,7 @@ void RenderableModel::update(const UpdateData&) { if (_programObject->isDirty()) { _programObject->rebuildFromFile(); + _uniformCache.opacity = _programObject->uniformLocation("opacity"); _uniformCache.directionToSunViewSpace = _programObject->uniformLocation( "directionToSunViewSpace" ); diff --git a/modules/base/rendering/renderablemodel.h b/modules/base/rendering/renderablemodel.h index 433aa30b2a..37d41a86e5 100644 --- a/modules/base/rendering/renderablemodel.h +++ b/modules/base/rendering/renderablemodel.h @@ -74,8 +74,8 @@ private: properties::Mat3Property _modelTransform; std::unique_ptr _programObject; - UniformCache(directionToSunViewSpace, modelViewTransform, projectionTransform, - performShading, texture) _uniformCache; + UniformCache(opacity, directionToSunViewSpace, modelViewTransform, + projectionTransform, performShading, texture) _uniformCache; std::unique_ptr _texture; diff --git a/modules/base/rendering/renderableplane.cpp b/modules/base/rendering/renderableplane.cpp index 071f813366..91c9da3816 100644 --- a/modules/base/rendering/renderableplane.cpp +++ b/modules/base/rendering/renderableplane.cpp @@ -111,6 +111,9 @@ RenderablePlane::RenderablePlane(const ghoul::Dictionary& dictionary) "RenderablePlane" ); + addProperty(_opacity); + registerUpdateRenderBinFromOpacity(); + _size = static_cast(dictionary.value(SizeInfo.identifier)); if (dictionary.hasKey(BillboardInfo.identifier)) { @@ -185,6 +188,8 @@ void RenderablePlane::deinitializeGL() { void RenderablePlane::render(const RenderData& data, RendererTasks&) { _shader->activate(); + _shader->setUniform("opacity", _opacity); + // Model transform and view transform needs to be in double precision const glm::dmat4 rotationTransform = _billboard ? glm::inverse(glm::dmat4(data.camera.viewRotationMatrix())) : diff --git a/modules/base/rendering/renderableplaneimagelocal.cpp b/modules/base/rendering/renderableplaneimagelocal.cpp index 650ad3a3f7..f70cafedca 100644 --- a/modules/base/rendering/renderableplaneimagelocal.cpp +++ b/modules/base/rendering/renderableplaneimagelocal.cpp @@ -96,7 +96,6 @@ void RenderablePlaneImageLocal::deinitializeGL() { RenderablePlane::deinitializeGL(); } - void RenderablePlaneImageLocal::bindTexture() { _texture->bind(); } diff --git a/modules/base/rendering/renderablesphere.cpp b/modules/base/rendering/renderablesphere.cpp index 7fc9911301..e2dbe510b9 100644 --- a/modules/base/rendering/renderablesphere.cpp +++ b/modules/base/rendering/renderablesphere.cpp @@ -71,13 +71,6 @@ namespace { "This value specifies the radius of the sphere in meters." }; - static const openspace::properties::Property::PropertyInfo TransparencyInfo = { - "Alpha", - "Transparency", - "This value determines the transparency of the sphere. If this value is set to " - "1, the sphere is completely opaque. At 0, the sphere is completely transparent." - }; - static const openspace::properties::Property::PropertyInfo FadeOutThreshouldInfo = { "FadeOutThreshould", "Fade-Out Threshould", @@ -131,12 +124,6 @@ documentation::Documentation RenderableSphere::Documentation() { Optional::Yes, OrientationInfo.description }, - { - TransparencyInfo.identifier, - new DoubleInRangeVerifier(0.0, 1.0), - Optional::Yes, - TransparencyInfo.description - }, { FadeOutThreshouldInfo.identifier, new DoubleInRangeVerifier(0.0, 1.0), @@ -166,7 +153,6 @@ RenderableSphere::RenderableSphere(const ghoul::Dictionary& dictionary) , _orientation(OrientationInfo, properties::OptionProperty::DisplayType::Dropdown) , _size(SizeInfo, 1.f, 0.f, 1e35f) , _segments(SegmentsInfo, 8, 4, 1000) - , _transparency(TransparencyInfo, 1.f, 0.f, 1.f) , _disableFadeInDistance(DisableFadeInOuInfo, true) , _fadeOutThreshold(-1.0) , _fadeInThreshold(0.0) @@ -181,6 +167,9 @@ RenderableSphere::RenderableSphere(const ghoul::Dictionary& dictionary) "RenderableSphere" ); + addProperty(_opacity); + registerUpdateRenderBinFromOpacity(); + _size = static_cast(dictionary.value(SizeInfo.identifier)); _segments = static_cast(dictionary.value(SegmentsInfo.identifier)); _texturePath = absPath(dictionary.value(TextureInfo.identifier)); @@ -217,21 +206,6 @@ RenderableSphere::RenderableSphere(const ghoul::Dictionary& dictionary) addProperty(_segments); _segments.onChange([this](){ _sphereIsDirty = true; }); - _transparency.onChange([this](){ - if (_transparency > 0.f && _transparency < 1.f) { - setRenderBin(Renderable::RenderBin::Transparent); - } - else { - setRenderBin(Renderable::RenderBin::Opaque); - } - }); - if (dictionary.hasKey(TransparencyInfo.identifier)) { - _transparency = static_cast( - dictionary.value(TransparencyInfo.identifier) - ); - } - addProperty(_transparency); - addProperty(_texturePath); _texturePath.onChange([this]() { loadTexture(); }); @@ -252,7 +226,6 @@ RenderableSphere::RenderableSphere(const ghoul::Dictionary& dictionary) _disableFadeInDistance.set(false); addProperty(_disableFadeInDistance); } - } bool RenderableSphere::isReady() const { @@ -272,6 +245,11 @@ void RenderableSphere::initializeGL() { absPath("${MODULE_BASE}/shaders/sphere_fs.glsl") ); + _uniformCache.opacity = _shader->uniformLocation("opacity"); + _uniformCache.viewProjection = _shader->uniformLocation("ViewProjection"); + _uniformCache.modelTransform = _shader->uniformLocation("ModelTransform"); + _uniformCache.texture = _shader->uniformLocation("texture1"); + loadTexture(); } @@ -294,12 +272,12 @@ void RenderableSphere::render(const RenderData& data, RendererTasks&) { _shader->activate(); _shader->setIgnoreUniformLocationError(IgnoreError::Yes); - _shader->setUniform("ViewProjection", data.camera.viewProjectionMatrix()); - _shader->setUniform("ModelTransform", transform); + _shader->setUniform(_uniformCache.viewProjection, data.camera.viewProjectionMatrix()); + _shader->setUniform(_uniformCache.modelTransform, transform); setPscUniforms(*_shader.get(), data.camera, data.position); - float adjustedTransparency = _transparency; + float adjustedTransparency = _opacity; if (_fadeInThreshold > 0.0) { double distCamera = glm::length(data.camera.positionVec3()); @@ -323,16 +301,16 @@ void RenderableSphere::render(const RenderData& data, RendererTasks&) { } // Performance wise - if (adjustedTransparency < 0.01) { + if (adjustedTransparency < 0.01f) { return; } - _shader->setUniform("alpha", adjustedTransparency); + _shader->setUniform(_uniformCache.opacity, _opacity); ghoul::opengl::TextureUnit unit; unit.activate(); _texture->bind(); - _shader->setUniform("texture1", unit); + _shader->setUniform(_uniformCache.texture, unit); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); @@ -366,6 +344,11 @@ void RenderableSphere::render(const RenderData& data, RendererTasks&) { void RenderableSphere::update(const UpdateData&) { if (_shader->isDirty()) { _shader->rebuildFromFile(); + + _uniformCache.opacity = _shader->uniformLocation("opacity"); + _uniformCache.viewProjection = _shader->uniformLocation("ViewProjection"); + _uniformCache.modelTransform = _shader->uniformLocation("ModelTransform"); + _uniformCache.texture = _shader->uniformLocation("texture1"); } if (_sphereIsDirty) { diff --git a/modules/base/rendering/renderablesphere.h b/modules/base/rendering/renderablesphere.h index 002954d332..f8fbf0984b 100644 --- a/modules/base/rendering/renderablesphere.h +++ b/modules/base/rendering/renderablesphere.h @@ -31,6 +31,7 @@ #include #include #include +#include namespace ghoul::opengl { class ProgramObject; @@ -68,8 +69,6 @@ private: properties::FloatProperty _size; properties::IntProperty _segments; - properties::FloatProperty _transparency; - properties::BoolProperty _disableFadeInDistance; float _fadeOutThreshold; @@ -80,6 +79,9 @@ private: std::unique_ptr _sphere; + UniformCache(opacity, viewProjection, modelTransform, texture) _uniformCache; + + bool _sphereIsDirty; }; diff --git a/modules/base/rendering/renderablesphericalgrid.cpp b/modules/base/rendering/renderablesphericalgrid.cpp index 1ffefbbabc..e56d8eb140 100644 --- a/modules/base/rendering/renderablesphericalgrid.cpp +++ b/modules/base/rendering/renderablesphericalgrid.cpp @@ -137,6 +137,9 @@ RenderableSphericalGrid::RenderableSphericalGrid(const ghoul::Dictionary& dictio "RenderableSphericalGrid" ); + addProperty(_opacity); + registerUpdateRenderBinFromOpacity(); + if (dictionary.hasKey(GridMatrixInfo.identifier)) { _gridMatrix = dictionary.value(GridMatrixInfo.identifier); } @@ -210,6 +213,8 @@ void RenderableSphericalGrid::deinitializeGL() { void RenderableSphericalGrid::render(const RenderData& data, RendererTasks&){ _gridProgram->activate(); + _gridProgram->setUniform("opacity", _opacity); + glm::dmat4 modelTransform = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation glm::dmat4(data.modelTransform.rotation) * // Spice rotation diff --git a/modules/base/rendering/renderabletrail.cpp b/modules/base/rendering/renderabletrail.cpp index 9301f7c939..5ee7b0a2be 100644 --- a/modules/base/rendering/renderabletrail.cpp +++ b/modules/base/rendering/renderabletrail.cpp @@ -169,6 +169,9 @@ RenderableTrail::RenderableTrail(const ghoul::Dictionary& dictionary) , _renderingModes(RenderingModeInfo, properties::OptionProperty::DisplayType::Dropdown ) { + addProperty(_opacity); + registerUpdateRenderBinFromOpacity(); + _translation = Translation::createFromDictionary( dictionary.value(KeyTranslation) ); @@ -226,6 +229,7 @@ void RenderableTrail::initializeGL() { absPath("${MODULE_BASE}/shaders/renderabletrail_fs.glsl") ); + _uniformCache.opacity = _programObject->uniformLocation("opacity"); _uniformCache.modelView = _programObject->uniformLocation("modelViewTransform"); _uniformCache.projection = _programObject->uniformLocation("projectionTransform"); _uniformCache.color = _programObject->uniformLocation("color"); @@ -255,6 +259,7 @@ bool RenderableTrail::isReady() const { void RenderableTrail::render(const RenderData& data, RendererTasks&) { _programObject->activate(); + _programObject->setUniform(_uniformCache.opacity, _opacity); glm::dmat4 modelTransform = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * diff --git a/modules/base/rendering/renderabletrail.h b/modules/base/rendering/renderabletrail.h index 70ae56812f..62ea0d466d 100644 --- a/modules/base/rendering/renderabletrail.h +++ b/modules/base/rendering/renderabletrail.h @@ -160,8 +160,8 @@ private: /// Program object used to render the data stored in RenderInformation std::unique_ptr _programObject; - UniformCache(modelView, projection, color, useLineFade, lineFade, vertexSorting, - idOffset, nVertices, stride, pointSize, renderPhase) _uniformCache; + UniformCache(opacity, modelView, projection, color, useLineFade, lineFade, + vertexSorting, idOffset, nVertices, stride, pointSize, renderPhase) _uniformCache; }; } // namespace openspace diff --git a/modules/base/shaders/grid_fs.glsl b/modules/base/shaders/grid_fs.glsl index 51e0973d20..89dd564e57 100644 --- a/modules/base/shaders/grid_fs.glsl +++ b/modules/base/shaders/grid_fs.glsl @@ -29,10 +29,12 @@ in float vs_screenSpaceDepth; in vec4 vs_positionViewSpace; uniform vec4 gridColor; +uniform float opacity; Fragment getFragment() { Fragment frag; frag.color = gridColor; + frag.color.a *= opacity; frag.depth = vs_screenSpaceDepth; frag.gPosition = vs_positionViewSpace; frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0); diff --git a/modules/base/shaders/model_fs.glsl b/modules/base/shaders/model_fs.glsl index ed24bf14c1..fe5d414849 100644 --- a/modules/base/shaders/model_fs.glsl +++ b/modules/base/shaders/model_fs.glsl @@ -33,6 +33,7 @@ in vec3 vs_normalObjSpace; uniform bool performShading = true; uniform vec3 directionToSunViewSpace; uniform sampler2D texture1; +uniform float opacity = 1.0; const vec3 SpecularAlbedo = vec3(1.0); @@ -69,7 +70,7 @@ Fragment getFragment() { frag.color.rgb = diffuseAlbedo; } - frag.color.a = 1.0; + frag.color.a = opacity; frag.depth = vs_screenSpaceDepth; frag.gPosition = vs_positionCameraSpace; frag.gNormal = vec4(vs_normalObjSpace, 1.0); diff --git a/modules/base/shaders/model_vs.glsl b/modules/base/shaders/model_vs.glsl index 3a1be7e6e2..7d052b9594 100644 --- a/modules/base/shaders/model_vs.glsl +++ b/modules/base/shaders/model_vs.glsl @@ -34,7 +34,7 @@ out vec2 vs_st; out vec3 vs_normalViewSpace; out float vs_screenSpaceDepth; out vec4 vs_positionCameraSpace; -out vec3 vs_normalObjSpace; +out vec3 vs_normalObjSpace; uniform mat4 modelViewTransform; uniform mat4 projectionTransform; diff --git a/modules/base/shaders/plane_fs.glsl b/modules/base/shaders/plane_fs.glsl index d03102727a..20e48b82fd 100644 --- a/modules/base/shaders/plane_fs.glsl +++ b/modules/base/shaders/plane_fs.glsl @@ -31,6 +31,7 @@ in vec3 vs_gNormal; uniform sampler2D texture1; uniform bool additiveBlending; +uniform float opacity = 1.0; Fragment getFragment() { @@ -42,6 +43,7 @@ Fragment getFragment() { frag.color = texture(texture1, vec2(1 - vs_st.s, vs_st.t)); } + frag.color.a *= opacity; if (frag.color.a == 0.0) { discard; } diff --git a/modules/base/shaders/renderabletrail_fs.glsl b/modules/base/shaders/renderabletrail_fs.glsl index 6e005164f0..979aa32445 100644 --- a/modules/base/shaders/renderabletrail_fs.glsl +++ b/modules/base/shaders/renderabletrail_fs.glsl @@ -30,6 +30,7 @@ in float fade; uniform vec3 color; uniform int renderPhase; +uniform float opacity = 1.0; // Fragile! Keep in sync with RenderableTrail::render::RenderPhase #define RenderPhaseLines 0 @@ -40,7 +41,7 @@ uniform int renderPhase; Fragment getFragment() { Fragment frag; - frag.color = vec4(color * fade, fade); + frag.color = vec4(color * fade, fade * opacity); frag.depth = vs_positionScreenSpace.w; frag.blend = BLEND_MODE_ADDITIVE; diff --git a/modules/base/shaders/sphere_fs.glsl b/modules/base/shaders/sphere_fs.glsl index 47488304a7..e536a60184 100644 --- a/modules/base/shaders/sphere_fs.glsl +++ b/modules/base/shaders/sphere_fs.glsl @@ -31,7 +31,7 @@ in vec4 vs_gPosition; uniform float time; uniform sampler2D texture1; -uniform float alpha; +uniform float opacity; Fragment getFragment() { @@ -42,7 +42,7 @@ Fragment getFragment() { texCoord.t = 1 - texCoord.y; Fragment frag; - frag.color = texture(texture1, texCoord) * vec4(1.0, 1.0, 1.0, alpha); + frag.color = texture(texture1, texCoord) * vec4(1.0, 1.0, 1.0, opacity); frag.depth = pscDepth(position); // G-Buffer diff --git a/shaders/fragment.glsl b/shaders/fragment.glsl index 50fc9ca5dc..97755f6734 100644 --- a/shaders/fragment.glsl +++ b/shaders/fragment.glsl @@ -37,4 +37,4 @@ struct Fragment { bool forceFboRendering; }; -#endif +#endif diff --git a/src/properties/property.cpp b/src/properties/property.cpp index 5a111b3930..59fe749cbb 100644 --- a/src/properties/property.cpp +++ b/src/properties/property.cpp @@ -250,4 +250,9 @@ std::string Property::generateAdditionalDescription() const { return ""; } +void Property::setInterpolationTarget(ghoul::any) {} +void Property::setLuaInterpolationTarget(lua_State*) {} +void Property::setStringInterpolationTarget(std::string) {} +void Property::interpolateValue(float, ghoul::EasingFunc) {} + } // namespace openspace::properties diff --git a/src/rendering/renderable.cpp b/src/rendering/renderable.cpp index 1deb4cdf3c..2c00efaaa3 100644 --- a/src/rendering/renderable.cpp +++ b/src/rendering/renderable.cpp @@ -47,6 +47,12 @@ namespace { "Is Enabled", "This setting determines whether this object will be visible or not." }; + + static const openspace::properties::Property::PropertyInfo OpacityInfo = { + "Opacity", + "Transparency", + "This value determines the transparency of this object." + }; } // namespace namespace openspace { @@ -58,21 +64,27 @@ documentation::Documentation Renderable::Documentation() { "Renderable", "renderable", { - { - KeyType, - new StringAnnotationVerifier("A valid Renderable created by a factory"), - Optional::No, - "This key specifies the type of Renderable that gets created. It has to be " - "one of the valid Renderables that are available for creation (see the " - "FactoryDocumentation for a list of possible Renderables), which depends on " - "the configration of the application" - }, - { - EnabledInfo.identifier, - new BoolVerifier, - Optional::Yes, - EnabledInfo.description - } + { + KeyType, + new StringAnnotationVerifier("A valid Renderable created by a factory"), + Optional::No, + "This key specifies the type of Renderable that gets created. It has to " + "be one of the valid Renderables that are available for creation (see " + "the FactoryDocumentation for a list of possible Renderables), which " + "depends on the configration of the application" + }, + { + EnabledInfo.identifier, + new BoolVerifier, + Optional::Yes, + EnabledInfo.description + }, + { + OpacityInfo.identifier, + new DoubleInRangeVerifier(0.0, 1.0), + Optional::Yes, + OpacityInfo.description + } } }; } @@ -100,6 +112,7 @@ std::unique_ptr Renderable::createFromDictionary( Renderable::Renderable(const ghoul::Dictionary& dictionary) : properties::PropertyOwner({ "renderable" }) , _enabled(EnabledInfo, true) + , _opacity(OpacityInfo, 1.f, 0.f, 1.f) , _renderBin(RenderBin::Opaque) , _startTime("") , _endTime("") @@ -138,6 +151,10 @@ Renderable::Renderable(const ghoul::Dictionary& dictionary) _enabled = dictionary.value(EnabledInfo.identifier); } + if (dictionary.hasKey(OpacityInfo.identifier)) { + _opacity = static_cast(dictionary.value(OpacityInfo.identifier)); + } + addProperty(_enabled); } @@ -229,4 +246,15 @@ void Renderable::onEnabledChange(std::function callback) { }); } +void Renderable::registerUpdateRenderBinFromOpacity() { + _opacity.onChange([this](){ + if (_opacity > 0.f && _opacity < 1.f) { + setRenderBin(Renderable::RenderBin::Transparent); + } + else { + setRenderBin(Renderable::RenderBin::Opaque); + } + }); +} + } // namespace openspace diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index 835598b28a..fac337db8c 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -470,7 +470,11 @@ void RenderEngine::deinitializeGL() { } void RenderEngine::updateScene() { - if (!_scene) return; + if (!_scene) { + return; + } + + _scene->updateInterpolations(); const Time& currentTime = OsEng.timeManager().time(); _scene->update({ diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 07c3ca5890..5940caacdc 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -35,19 +35,18 @@ #include #include #include - #include #include -#include -#include #include #include #include #include +#include +#include #include #include #include - +#include #include #include #include @@ -120,6 +119,11 @@ void Scene::unregisterNode(SceneGraphNode* node) { _topologicallySortedNodes.end() ); _nodesByName.erase(node->name()); + // Just try to remove all properties; if the property doesn't exist, the + // removeInterpolation will not do anything + for (properties::Property* p : node->properties()) { + removeInterpolation(p); + } removePropertySubOwner(node); _dirtyNodeRegistry = true; } @@ -440,6 +444,112 @@ SceneGraphNode* Scene::loadNode(const ghoul::Dictionary& dict) { return rawNodePointer; } +void Scene::addInterpolation(properties::Property* prop, float durationSeconds, + ghoul::EasingFunction easingFunction) +{ + ghoul_precondition(prop != nullptr, "prop must not be nullptr"); + ghoul_precondition(durationSeconds > 0.0, "durationSeconds must be positive"); + ghoul_postcondition( + std::find_if( + _interpolationInfos.begin(), + _interpolationInfos.end(), + [prop](const InterpolationInfo& info) { + return info.prop == prop && !info.isExpired; + } + ) != _interpolationInfos.end(), + "A new interpolation record exists for p that is not expired" + ); + + ghoul::EasingFunc func = + easingFunction == ghoul::EasingFunction::Linear + ? + nullptr : + ghoul::easingFunction(easingFunction); + + // First check if the current property already has an interpolation information + for (std::vector::iterator it = _interpolationInfos.begin(); + it != _interpolationInfos.end(); + ++it) + { + if (it->prop == prop) { + it->beginTime = std::chrono::steady_clock::now(); + it->durationSeconds = durationSeconds; + it->easingFunction = func; + // If we found it, we can break since we make sure that each property is only + // represented once in this + return; + } + } + + InterpolationInfo i; + i.prop = prop; + i.beginTime = std::chrono::steady_clock::now(); + i.durationSeconds = durationSeconds; + i.easingFunction = func; + + _interpolationInfos.push_back(std::move(i)); +} + +void Scene::removeInterpolation(properties::Property* prop) { + ghoul_precondition(prop != nullptr, "prop must not be nullptr"); + ghoul_postcondition( + std::find_if( + _interpolationInfos.begin(), + _interpolationInfos.end(), + [prop](const InterpolationInfo& info) { + return info.prop == prop; + } + ) == _interpolationInfos.end(), + "No interpolation record exists for prop" + ); + + _interpolationInfos.erase( + std::remove_if( + _interpolationInfos.begin(), + _interpolationInfos.end(), + [prop](const InterpolationInfo& info) { return info.prop == prop; } + ), + _interpolationInfos.end() + ); +} + +void Scene::updateInterpolations() { + using namespace std::chrono; + auto now = steady_clock::now(); + + for (InterpolationInfo& i : _interpolationInfos) { + long long usPassed = duration_cast( + now - i.beginTime + ).count(); + + float t = static_cast( + static_cast(usPassed) / + static_cast(i.durationSeconds * 1000000) + ); + + // @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(glm::clamp(t, 0.f, 1.f), i.easingFunction); + + i.isExpired = (t >= 1.f); + } + + _interpolationInfos.erase( + std::remove_if( + _interpolationInfos.begin(), + _interpolationInfos.end(), + [](const InterpolationInfo& i) { + return i.isExpired; + } + ), + _interpolationInfos.end() + ); +} + void Scene::writeSceneLicenseDocumentation(const std::string& path) const { SceneLicenseWriter writer(_licenses); writer.writeDocumentation(path); @@ -453,41 +563,42 @@ scripting::LuaLibrary Scene::luaLibrary() { "setPropertyValue", &luascriptfunctions::property_setValue, {}, - "string, *", + "name, value [, duration, easing, optimization]", "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 " - "first term (separated by '.') in the uri is bracketed with { }, then " - "this term is treated as a group tag name, and the function will " - "search through all property owners to find those that are tagged with " - "this group name, and set their property values accordingly." - }, - { - "setPropertyValueRegex", - &luascriptfunctions::property_setValueRegex, - {}, - "string, *", - "Sets all property(s) 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. If the " - "first term (separated by '.') in the uri is bracketed with { }, then " - "this term is treated as a group tag name, and the function will search " - "through all property owners to find those that are tagged with this " - "group name, and set their property values accordingly." + "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 steap 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 last " + "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 third 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 third arugment is 'single' no " + "substitutions are performed and exactly 0 or 1 properties are changed." }, { "setPropertyValueSingle", &luascriptfunctions::property_setValueSingle, {}, - "string, *", - "Sets all property(s) identified by the URI in the first argument to the " - "value passed in the second argument. The type of the second argument is " - "arbitrary, but it must agree with the type the denoted Property expects." - " If the first term (separated by '.') in the uri is bracketed with { }, " - " then this term is treated as a group tag name, and the function will " - "search through all property owners to find those that are tagged with " - "this group name, and set their property values accordingly." + "URI, value [, duration, easing]", + "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 steap 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", diff --git a/src/scene/scene_lua.inl b/src/scene/scene_lua.inl index d0807b0f1f..f7b10d582e 100644 --- a/src/scene/scene_lua.inl +++ b/src/scene/scene_lua.inl @@ -23,24 +23,14 @@ ****************************************************************************************/ #include - +#include +#include #include namespace openspace { namespace { -void executePropertySet(properties::Property* prop, lua_State* L) { - prop->setLuaValue(L); - //ensure properties are synced over parallel connection - std::string value; - prop->getStringValue(value); - /*OsEng.parallelConnection().scriptMessage( - prop->fullyQualifiedIdentifier(), - value - );*/ -} - template properties::PropertyOwner* findPropertyOwnerWithMatchingGroupTag(T* prop, const std::string& tagToMatch) @@ -67,13 +57,17 @@ properties::PropertyOwner* findPropertyOwnerWithMatchingGroupTag(T* prop, } void applyRegularExpression(lua_State* L, std::regex regex, - std::vector properties, int type, - std::string groupName = "") + std::vector properties, + double interpolationDuration, + const std::string& groupName, + ghoul::EasingFunction easingFunction) { using ghoul::lua::errorLocation; using ghoul::lua::luaTypeToString; bool isGroupMode = !groupName.empty(); + const int type = lua_type(L, -1); + for (properties::Property* prop : properties) { // Check the regular expression for all properties std::string id = prop->fullyQualifiedIdentifier(); @@ -105,13 +99,24 @@ void applyRegularExpression(lua_State* L, std::regex regex, ) ); } else { - executePropertySet(prop, L); + if (interpolationDuration == 0.0) { + OsEng.renderEngine().scene()->removeInterpolation(prop); + prop->setLuaValue(L); + } + else { + prop->setLuaInterpolationTarget(L); + OsEng.renderEngine().scene()->addInterpolation( + prop, + static_cast(interpolationDuration), + easingFunction + ); + } } } } } -//Checks to see if URI contains a group tag (with { } around the first term). If so, +// Checks to see if URI contains a group tag (with { } around the first term). If so, // returns true and sets groupName with the tag bool doesUriContainGroupTag(const std::string& command, std::string& groupName) { std::string name = command.substr(0, command.find_first_of(".")); @@ -138,13 +143,15 @@ std::string extractUriWithoutGroupName(std::string uri) { namespace openspace::luascriptfunctions { -int setPropertyCall_single(properties::Property* prop, std::string uri, lua_State* L, - const int type) +int setPropertyCall_single(properties::Property& prop, const std::string& uri, + lua_State* L, double duration, + ghoul::EasingFunction eastingFunction) { using ghoul::lua::errorLocation; using ghoul::lua::luaTypeToString; - if (type != prop->typeLua()) { + const int type = lua_type(L, -1); + if (type != prop.typeLua()) { LERRORC( "property_setValue", fmt::format( @@ -153,78 +160,24 @@ int setPropertyCall_single(properties::Property* prop, std::string uri, lua_Stat errorLocation(L), uri, luaTypeToString(type), - luaTypeToString(prop->typeLua()) + luaTypeToString(prop.typeLua()) ) ); } else { - prop->setLuaValue(L); - //ensure properties are synced over parallel connection - std::string value; - prop->getStringValue(value); - } - - return 0; -} - -/** - * \ingroup LuaScripts - * setPropertyValueSingle(string, *): - * Sets all property(s) identified by the URI in the first argument to the value passed - * in the second argument. The type of the second argument is arbitrary, but it must - * agree with the type the denoted Property expects. - * If the first term (separated by '.') in the uri is bracketed with { }, then this - * term is treated as a group tag name, and the function will search through all - * property owners to find those that are tagged with this group name, and set their - * property values accordingly. - */ -int property_setValueSingle(lua_State* L) { - using ghoul::lua::errorLocation; - using ghoul::lua::luaTypeToString; - - ghoul::lua::checkArgumentsAndThrow(L, 2, "lua::property_setValueSingle"); - - std::string uri = luaL_checkstring(L, -2); - const int type = lua_type(L, -1); - std::string tagToMatch; - - if (doesUriContainGroupTag(uri, tagToMatch)) { - std::string pathRemainderToMatch = extractUriWithoutGroupName(uri); - for (properties::Property* prop : allProperties()) { - std::string propFullId = prop->fullyQualifiedIdentifier(); - //Look for a match in the uri with the group name (first term) removed - int propMatchLength = - static_cast(propFullId.length()) - - static_cast(pathRemainderToMatch.length()); - - if (propMatchLength >= 0) { - std::string thisPropMatchId = propFullId.substr(propMatchLength); - //If remainder of uri matches (with group name removed), - if (pathRemainderToMatch.compare(thisPropMatchId) == 0) { - properties::PropertyOwner* matchingTaggedOwner - = findPropertyOwnerWithMatchingGroupTag(prop, tagToMatch); - if (matchingTaggedOwner) { - setPropertyCall_single(prop, uri, L, type); - } - } - } + if (duration == 0.0) { + OsEng.renderEngine().scene()->removeInterpolation(&prop); + prop.setLuaValue(L); } - } else { - properties::Property* prop = property(uri); - if (!prop) { - LERRORC( - "property_setValue", - fmt::format( - "{}: Property with URI '{}' was not found", - errorLocation(L), - uri - ) + else { + prop.setLuaInterpolationTarget(L); + OsEng.renderEngine().scene()->addInterpolation( + &prop, + static_cast(duration), + eastingFunction ); - return 0; } - setPropertyCall_single(prop, uri, L, type); } - return 0; } @@ -244,83 +197,155 @@ int property_setValue(lua_State* L) { using ghoul::lua::errorLocation; using ghoul::lua::luaTypeToString; - ghoul::lua::checkArgumentsAndThrow(L, 2, "lua::property_setGroup"); + ghoul::lua::checkArgumentsAndThrow(L, { 2, 5 }, "lua::property_setValue"); + defer { lua_settop(L, 0); }; - std::string regex = luaL_checkstring(L, -2); - std::string groupName; + std::string uriOrRegex = luaL_checkstring(L, 1); + std::string optimization; + double interpolationDuration = 0.0; + std::string easingMethodName; + ghoul::EasingFunction easingMethod = ghoul::EasingFunction::Linear; - // Replace all wildcards * with the correct regex (.*) - size_t startPos = regex.find("*"); - while (startPos != std::string::npos) { - regex.replace(startPos, 1, "(.*)"); - startPos += 4; - startPos = regex.find("*", startPos); + if (lua_gettop(L) >= 3) { + if (lua_type(L, 3) == LUA_TNUMBER) { + interpolationDuration = luaL_checknumber(L, 3); + } + else { + optimization = luaL_checkstring(L, 3); + } + + if (lua_gettop(L) >= 4) { + if (lua_type(L, 4) == LUA_TNUMBER) { + interpolationDuration = luaL_checknumber(L, 4); + } + else { + optimization = luaL_checkstring(L, 4); + } + } + + if (lua_gettop(L) == 5) { + easingMethodName = luaL_checkstring(L, 5); + } + + // Later functions expect the value to be at the last position on the stack + lua_pushvalue(L, 2); } - if (doesUriContainGroupTag(regex, groupName)) { - std::string pathRemainderToMatch = extractUriWithoutGroupName(regex); - //Remove group name from start of regex and replace with '.*' - regex = replaceUriWithGroupName(regex, ".*"); - } - - applyRegularExpression( - L, - std::regex(regex/*, std::regex_constants::optimize*/), - allProperties(), - lua_type(L, -1), - groupName - ); - - return 0; -} - -/** - * \ingroup LuaScripts - * setPropertyValueRegex(string, *): - * Sets all property(s) 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. - * If the first term (separated by '.') in the uri is bracketed with { }, then this - * term is treated as a group tag name, and the function will search through all - * property owners to find those that are tagged with this group name, and set their - * property values accordingly. -*/ -int property_setValueRegex(lua_State* L) { - using ghoul::lua::errorLocation; - using ghoul::lua::luaTypeToString; - - ghoul::lua::checkArgumentsAndThrow(L, 2, "lua::property_setValueRegex"); - - std::string regex = luaL_checkstring(L, -2); - std::string groupName; - - if (doesUriContainGroupTag(regex, groupName)) { - std::string pathRemainderToMatch = extractUriWithoutGroupName(regex); - //Remove group name from start of regex and replace with '.*' - regex = replaceUriWithGroupName(regex, ".*"); - } - - try { - applyRegularExpression( - L, - std::regex(regex, std::regex_constants::optimize), - allProperties(), - lua_type(L, -1), - groupName + if (interpolationDuration == 0.0 && !easingMethodName.empty()) { + LWARNINGC( + "property_setValue", + "Easing method specified while interpolation duration is equal to 0" ); } - catch (const std::regex_error& e) { + + if (!easingMethodName.empty()) { + bool correctName = ghoul::isValidEasingFunctionName(easingMethodName.c_str()); + if (!correctName) { + LWARNINGC( + "property_setValue", + fmt::format("{} is not a valid easing method", easingMethodName) + ); + } + else { + easingMethod = ghoul::easingFunctionFromName(easingMethodName.c_str()); + } + } + + if (optimization.empty()) { + // Replace all wildcards * with the correct regex (.*) + size_t startPos = uriOrRegex.find("*"); + while (startPos != std::string::npos) { + uriOrRegex.replace(startPos, 1, "(.*)"); + startPos += 4; + startPos = uriOrRegex.find("*", startPos); + } + + std::string groupName; + if (doesUriContainGroupTag(uriOrRegex, groupName)) { + std::string pathRemainderToMatch = extractUriWithoutGroupName(uriOrRegex); + //Remove group name from start of regex and replace with '.*' + uriOrRegex = replaceUriWithGroupName(uriOrRegex, ".*"); + } + + try { + applyRegularExpression( + L, + std::regex(uriOrRegex), + allProperties(), + interpolationDuration, + groupName, + easingMethod + ); + } + catch (const std::regex_error& e) { + LERRORC( + "property_setValue", + fmt::format( + "Malformed regular expression: '{}': {}", uriOrRegex, e.what() + ) + ); + } + return 0; + } + else if (optimization == "regex") { + try { + applyRegularExpression( + L, + std::regex(uriOrRegex), + allProperties(), + interpolationDuration, + "", + easingMethod + ); + } + catch (const std::regex_error& e) { + LERRORC( + "property_setValueRegex", + fmt::format( + "Malformed regular expression: '{}': {}", uriOrRegex, e.what() + ) + ); + } + } + else if (optimization == "single") { + properties::Property* prop = property(uriOrRegex); + if (!prop) { + LERRORC( + "property_setValue", + fmt::format( + "{}: Property with URI '{}' was not found", + errorLocation(L), + uriOrRegex + ) + ); + return 0; + } + return setPropertyCall_single( + *prop, + uriOrRegex, + L, + interpolationDuration, + easingMethod + ); + } + else { LERRORC( - "property_setValueRegex", + "lua::property_setGroup", fmt::format( - "Malformed regular expression: '{}': {}", regex, e.what() + "{}: Unexpected optimization '{}'", + errorLocation(L), + optimization ) ); } - return 0; } +int property_setValueSingle(lua_State* L) { + lua_pushstring(L, "single"); + return property_setValue(L); +} + /** * \ingroup LuaScripts * getPropertyValue(string):