From f623f1f9b14da6eca60bbed22dbb9a4bdc7ada4a Mon Sep 17 00:00:00 2001
From: Kitty Barnett <develop@catznip.com>
Date: Thu, 7 Jan 2021 22:26:18 +0100
Subject: [PATCH] Add additional sphere effects for the initial RFC release and
 make params tweenable for the fun of it

---
 indra/llmath/v4math.h                         | 13 +++
 .../shaders/class1/deferred/rlvF.glsl         | 87 ++++++++++++++++---
 indra/newview/rlvcommon.h                     |  2 +-
 indra/newview/rlvdefines.h                    |  4 +-
 indra/newview/rlveffects.cpp                  | 80 ++++++++++++++---
 indra/newview/rlveffects.h                    | 15 ++--
 indra/newview/rlvhelper.cpp                   | 11 +++
 7 files changed, 177 insertions(+), 35 deletions(-)

diff --git a/indra/llmath/v4math.h b/indra/llmath/v4math.h
index 623c8b20035..d1b5a9646bd 100644
--- a/indra/llmath/v4math.h
+++ b/indra/llmath/v4math.h
@@ -434,6 +434,19 @@ inline LLVector4 operator-(const LLVector4 &a)
 	return LLVector4( -a.mV[VX], -a.mV[VY], -a.mV[VZ] );
 }
 
+// [RLVa:KB] - RlvBehaviourModifierCompMin/Max
+inline bool operator<(const LLVector4& lhs, const LLVector4& rhs)
+{
+	return (lhs.mV[0] < rhs.mV[0]
+		|| (lhs.mV[0] == rhs.mV[0]
+			&& (lhs.mV[1] < rhs.mV[1]
+				|| ((lhs.mV[1] == rhs.mV[1])
+					&& lhs.mV[2] < rhs.mV[2]
+						|| ((lhs.mV[2] == rhs.mV[2])
+							&& lhs.mV[3] < rhs.mV[3])))));
+}
+// [/RLVa:KB]
+
 inline F32	dist_vec(const LLVector4 &a, const LLVector4 &b)
 {
 	LLVector4 vec = a - b;
diff --git a/indra/newview/app_settings/shaders/class1/deferred/rlvF.glsl b/indra/newview/app_settings/shaders/class1/deferred/rlvF.glsl
index 6ad884446b8..8661ffe923c 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/rlvF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/rlvF.glsl
@@ -33,7 +33,7 @@ uniform int  rlvEffectMode;     // ESphereMode
 uniform vec4 rlvEffectParam1;   // Sphere origin (in local coordinates)
 uniform vec4 rlvEffectParam2;   // Min/max dist + min/max value
 uniform bvec2 rlvEffectParam3;  // Min/max dist extend
-uniform vec4 rlvEffectParam4;   // Sphere color (not used for blur)
+uniform vec4 rlvEffectParam4;   // Sphere params (=color when using blend)
 uniform vec2 rlvEffectParam5;   // Blur direction (not used for blend)
 
 #define SPHERE_ORIGIN		rlvEffectParam1.xyz
@@ -42,7 +42,7 @@ uniform vec2 rlvEffectParam5;   // Blur direction (not used for blend)
 #define SPHERE_DISTEXTEND	rlvEffectParam3
 #define SPHERE_VALUEMIN		rlvEffectParam2.x
 #define SPHERE_VALUEMAX		rlvEffectParam2.z
-#define SPHERE_COLOUR		rlvEffectParam4.rgb
+#define SPHERE_PARAMS		rlvEffectParam4
 #define BLUR_DIRECTION		rlvEffectParam5.xy
 
 vec4 getPosition_d(vec2 pos_screen, float depth)
@@ -57,33 +57,77 @@ vec4 getPosition_d(vec2 pos_screen, float depth)
 	return pos;
 }
 
-vec3 blur13(sampler2DRect image, vec2 uv, vec2 direction)
+vec3 blur13(sampler2DRect source, vec2 tc, vec2 direction)
 {
   vec4 color = vec4(0.0);
   vec2 off1 = vec2(1.411764705882353) * direction;
   vec2 off2 = vec2(3.2941176470588234) * direction;
   vec2 off3 = vec2(5.176470588235294) * direction;
 
-  color += texture2D(image, uv) * 0.1964825501511404;
+  color += texture2DRect(source, tc) * 0.1964825501511404;
 
-  color += texture2D(image, uv + off1) * 0.2969069646728344;
-  color += texture2D(image, uv - off1) * 0.2969069646728344;
+  color += texture2DRect(source, tc + off1) * 0.2969069646728344;
+  color += texture2DRect(source, tc - off1) * 0.2969069646728344;
 
-  color += texture2D(image, uv + off2) * 0.09447039785044732;
-  color += texture2D(image, uv - off2) * 0.09447039785044732;
+  color += texture2DRect(source, tc + off2) * 0.09447039785044732;
+  color += texture2DRect(source, tc - off2) * 0.09447039785044732;
 
-  color += texture2D(image, uv + off3) * 0.010381362401148057;
-  color += texture2D(image, uv - off3) * 0.010381362401148057;
+  color += texture2DRect(source, tc + off3) * 0.010381362401148057;
+  color += texture2DRect(source, tc - off3) * 0.010381362401148057;
 
   return color.xyz;
 }
 
+const float pi = 3.14159265;
+
+// http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html
+vec3 blurVariable(sampler2DRect source, vec2 tc, float kernelSize, vec2 direction, float strength) {
+  float numBlurPixelsPerSide = float(kernelSize / 2);
+
+  // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
+  vec3 incrementalGaussian;
+  incrementalGaussian.x = 1.0 / (sqrt(2.0 * pi) * strength);
+  incrementalGaussian.y = exp(-0.5 / (strength * strength));
+  incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;
+
+  vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
+  float coefficientSum = 0.0;
+
+  // Take the central sample first...
+  avgValue += texture2DRect(source, tc) * incrementalGaussian.x;
+  coefficientSum += incrementalGaussian.x;
+  incrementalGaussian.xy *= incrementalGaussian.yz;
+
+  // Go through the remaining 8 vertical samples (4 on each side of the center)
+  for (float i = 1.0; i <= numBlurPixelsPerSide; i++) {
+	avgValue += texture2DRect(source, tc - i * direction) * incrementalGaussian.x;
+	avgValue += texture2DRect(source, tc + i * direction) * incrementalGaussian.x;
+	coefficientSum += 2.0 * incrementalGaussian.x;
+	incrementalGaussian.xy *= incrementalGaussian.yz;
+  }
+
+  return (avgValue / coefficientSum).rgb;
+}
+
+vec3 chromaticAberration(sampler2DRect source, vec2 tc, vec2 redDrift, vec2 blueDrift, float strength)
+{
+	vec3 sourceColor = texture2DRect(source, tc).rgb;
+
+	// Sample the color components
+	vec3 driftColor;
+	driftColor.r = texture2DRect(source, tc + redDrift).r;
+	driftColor.g = sourceColor.g;
+	driftColor.b = texture2DRect(source, tc + blueDrift).b;
+
+	// Adjust the strength of the effect
+	return mix(sourceColor, driftColor, strength);
+}
+
 void main()
 {
 	vec2 fragTC = vary_fragcoord.st;
 	float fragDepth = texture2DRect(depthMap, fragTC).x;
 	vec3 fragPosLocal = getPosition_d(fragTC, fragDepth).xyz;
-	vec3 fragColor = texture2DRect(diffuseRect, fragTC).rgb;
 	float distance = length(fragPosLocal.xyz - SPHERE_ORIGIN);
 
 	// Linear non-branching interpolation of the strength of the sphere effect (replaces if/elseif/else for x < min, min <= x <= max and x > max)
@@ -91,14 +135,31 @@ void main()
 	effectStrength = mix(effectStrength, mix(0, SPHERE_VALUEMIN, SPHERE_DISTEXTEND.x), distance < SPHERE_DISTMIN);
 	effectStrength = mix(effectStrength, mix(0, SPHERE_VALUEMAX, SPHERE_DISTEXTEND.y), distance > SPHERE_DISTMAX);
 
+	vec3 fragColor;
 	switch (rlvEffectMode)
 	{
 		case 0:		// Blend
-			fragColor = mix(fragColor, SPHERE_COLOUR, effectStrength);
+			fragColor = texture2DRect(diffuseRect, fragTC).rgb;
+			fragColor = mix(fragColor, SPHERE_PARAMS.rgb, effectStrength);
 			break;
-		case 1:		// Blur
+		case 1:		// Blur (fixed)
 			fragColor = blur13(diffuseRect, fragTC, effectStrength * BLUR_DIRECTION);
 			break;
+		case 2:		// Blur (variable)
+			fragColor = texture2DRect(diffuseRect, fragTC).rgb;
+			fragColor = mix(fragColor, blurVariable(diffuseRect, fragTC, SPHERE_PARAMS.x, BLUR_DIRECTION, effectStrength), effectStrength > 0);
+			break;
+		case 3:		// ChromaticAberration
+			fragColor = chromaticAberration(diffuseRect, fragTC, SPHERE_PARAMS.xy, SPHERE_PARAMS.zw, effectStrength);
+			break;
+		case 4:		// Pixelate
+			{
+				effectStrength = sign(effectStrength);
+				float pixelWidth = max(1, round(SPHERE_PARAMS.x * effectStrength)); float pixelHeight = max(1, round(SPHERE_PARAMS.y * effectStrength));
+				fragTC = vec2(pixelWidth * floor(fragTC.x / pixelWidth), pixelHeight * floor(fragTC.y / pixelHeight));
+				fragColor = texture2DRect(diffuseRect, fragTC).rgb;
+			}
+			break;
 	}
 
 	frag_color.rgb = fragColor;
diff --git a/indra/newview/rlvcommon.h b/indra/newview/rlvcommon.h
index 82fca44f91b..9777ff9d0b6 100644
--- a/indra/newview/rlvcommon.h
+++ b/indra/newview/rlvcommon.h
@@ -56,7 +56,7 @@ class RlvObject;
 
 struct RlvException;
 typedef boost::variant<std::string, LLUUID, S32, ERlvBehaviour> RlvExceptionOption;
-typedef boost::variant<int, float, bool, LLVector3, LLVector3d, LLUUID> RlvBehaviourModifierValue;
+typedef boost::variant<int, float, bool, LLVector3, LLVector3d, LLVector4, LLUUID> RlvBehaviourModifierValue;
 
 class RlvGCTimer;
 
diff --git a/indra/newview/rlvdefines.h b/indra/newview/rlvdefines.h
index da9716b083e..690bda54fcf 100644
--- a/indra/newview/rlvdefines.h
+++ b/indra/newview/rlvdefines.h
@@ -286,12 +286,14 @@ enum class ERlvLocalBhvrModifier
 	// @setsphere
 	SphereMode,                         // The type of effect that will apply to any pixel that intersects with the sphere (e.g. blend, blur, ...)
 	SphereOrigin,                       // The origin of the sphere can either be the avatar or the camera position
-	SphereColor,                        // [Blend only] Colour to mix with the actual pixel colour
+	SphereColor,                        // [Blend only] Colour to mix with the actual pixel colour (stored as params)
+	SphereParams,                       // Effect parameters (dependent on mode - see RlvSphereEffect)
 	SphereDistMin,                      // Distance at which the effect starts and has weight minValue; e.g. for blend this would be colour = mix(colour, sphere_colour, min_alpha)
 	SphereDistMax,                      // Distance at which the effect starts and has weight maxValue; e.g. for blend this would be colour = mix(colour, sphere_colour, max_alpha)
 	SphereDistExtend,                   // Specifies the value beyond min dist or max dist (by default the sphere extends beyond max distance at max vlaue)
 	SphereValueMin,                     // Value of the effect at minimum distance
 	SphereValueMax,                     // Value of the effect at maximum distance
+	SphereTween,                        // Amount of seconds it takes to lerp from value A to value B
 
 	Unknown,
 };
diff --git a/indra/newview/rlveffects.cpp b/indra/newview/rlveffects.cpp
index 8b303847e38..454a9db38cc 100644
--- a/indra/newview/rlveffects.cpp
+++ b/indra/newview/rlveffects.cpp
@@ -183,19 +183,20 @@ void RlvOverlayEffect::run()
 
 const int   c_SphereDefaultMode = 0;
 const int   c_SphereDefaultOrigin = 0;
-const float c_SphereDefaultColor[3] = { 0.0f, 0.0f, 0.0f };
+const float c_SphereDefaultColor[4] = { 0.0, 0.f, 0.f, 0.f };
 const float c_SphereDefaultDistance = 0.0f;
-const int   c_SphereDefaultDistanceExtend = 0;
+const int   c_SphereDefaultDistanceExtend = 1;
 const float c_SphereDefaultAlpha = 1.0f;
 
 RlvSphereEffect::RlvSphereEffect(const LLUUID& idRlvObj)
 	: LLVisualEffect(idRlvObj, EVisualEffect::RlvSphere, EVisualEffectType::PostProcessShader)
 	, m_eMode((ESphereMode)c_SphereDefaultMode)
 	, m_eOrigin((ESphereOrigin)c_SphereDefaultOrigin)
-	, m_Color(LLColor3(c_SphereDefaultColor))
+	, m_Params(LLVector4(c_SphereDefaultColor))
 	, m_nDistanceMin(c_SphereDefaultDistance), m_nDistanceMax(c_SphereDefaultDistance)
-	, m_eDistExtend((ESphereDistExtend)0)
+	, m_eDistExtend((ESphereDistExtend)c_SphereDefaultDistanceExtend)
 	, m_nValueMin(c_SphereDefaultAlpha), m_nValueMax(c_SphereDefaultAlpha)
+	, m_nTweenDuration(0.f)
 {
 	if (RlvObject* pRlvObj = gRlvHandler.getObject(idRlvObj))
 	{
@@ -207,7 +208,10 @@ RlvSphereEffect::RlvSphereEffect(const LLUUID& idRlvObj)
 
 		LLVector3 vecColor;
 		if (pRlvObj->getModifierValue<LLVector3>(ERlvLocalBhvrModifier::SphereColor, vecColor))
-			m_Color = LLColor3(vecColor.mV);
+			m_Params = LLVector4(vecColor.mV[VX], vecColor.mV[VY], vecColor.mV[VZ], 1.0f);
+		LLVector4 vecParams;
+		if (pRlvObj->getModifierValue<LLVector4>(ERlvLocalBhvrModifier::SphereParams, vecParams))
+			m_Params = vecParams;
 
 		float nFloat;
 		if (pRlvObj->getModifierValue<float>(ERlvLocalBhvrModifier::SphereDistMin, nFloat))
@@ -253,7 +257,11 @@ ERlvCmdRet RlvSphereEffect::onColorChanged(const LLUUID& idRlvObj, const boost::
 {
 	if (RlvSphereEffect* pEffect = dynamic_cast<RlvSphereEffect*>(LLVfxManager::instance().getEffect(idRlvObj)))
 	{
-		pEffect->m_Color = LLColor3((newValue) ? boost::get<LLVector3>(newValue.value()).mV : c_SphereDefaultColor);
+		LLVector4 vecColor = (newValue) ? LLVector4(boost::get<LLVector3>(newValue.value()), 1.0f) : LLVector4(c_SphereDefaultColor);
+		if (!pEffect->m_nTweenDuration)
+			pEffect->m_Params = vecColor;
+		else
+			pEffect->m_Params.start(vecColor, pEffect->m_nTweenDuration);
 	}
 	return RLV_RET_SUCCESS;
 }
@@ -263,7 +271,11 @@ ERlvCmdRet RlvSphereEffect::onDistMinChanged(const LLUUID& idRlvObj, const boost
 {
 	if (RlvSphereEffect* pEffect = dynamic_cast<RlvSphereEffect*>(LLVfxManager::instance().getEffect(idRlvObj)))
 	{
-		pEffect->m_nDistanceMin = (newValue) ? boost::get<float>(newValue.value()) : c_SphereDefaultDistance;
+		float nDistanceMin = (newValue) ? boost::get<float>(newValue.value()) : c_SphereDefaultDistance;
+		if (!pEffect->m_nTweenDuration)
+			pEffect->m_nDistanceMin = nDistanceMin;
+		else
+			pEffect->m_nDistanceMin.start(nDistanceMin, pEffect->m_nTweenDuration);
 	}
 	return RLV_RET_SUCCESS;
 }
@@ -273,7 +285,11 @@ ERlvCmdRet RlvSphereEffect::onDistMaxChanged(const LLUUID& idRlvObj, const boost
 {
 	if (RlvSphereEffect* pEffect = dynamic_cast<RlvSphereEffect*>(LLVfxManager::instance().getEffect(idRlvObj)))
 	{
-		pEffect->m_nDistanceMax = (newValue) ? boost::get<float>(newValue.value()) : c_SphereDefaultDistance;
+		float nDistanceMax = (newValue) ? boost::get<float>(newValue.value()) : c_SphereDefaultDistance;
+		if (!pEffect->m_nTweenDuration)
+			pEffect->m_nDistanceMax = nDistanceMax;
+		else
+			pEffect->m_nDistanceMax.start(nDistanceMax, pEffect->m_nTweenDuration);
 	}
 	return RLV_RET_SUCCESS;
 }
@@ -288,12 +304,40 @@ ERlvCmdRet RlvSphereEffect::onDistExtendChanged(const LLUUID& idRlvObj, const bo
 	return RLV_RET_SUCCESS;
 }
 
+// static
+ERlvCmdRet RlvSphereEffect::onParamsChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue)
+{
+	if (RlvSphereEffect* pEffect = dynamic_cast<RlvSphereEffect*>(LLVfxManager::instance().getEffect(idRlvObj)))
+	{
+		LLVector4 params = LLVector4((newValue) ? boost::get<LLVector4>(newValue.value()).mV : c_SphereDefaultColor);
+		if (!pEffect->m_nTweenDuration)
+			pEffect->m_Params = params;
+		else
+			pEffect->m_Params.start(params, pEffect->m_nTweenDuration);
+	}
+	return RLV_RET_SUCCESS;
+}
+
+// static
+ERlvCmdRet RlvSphereEffect::onTweenDurationChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue)
+{
+	if (RlvSphereEffect* pEffect = dynamic_cast<RlvSphereEffect*>(LLVfxManager::instance().getEffect(idRlvObj)))
+	{
+		pEffect->m_nTweenDuration = (newValue) ? boost::get<float>(newValue.value()) : 0;
+	}
+	return RLV_RET_SUCCESS;
+}
+
 // static
 ERlvCmdRet RlvSphereEffect::onValueMinChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue)
 {
 	if (RlvSphereEffect* pEffect = dynamic_cast<RlvSphereEffect*>(LLVfxManager::instance().getEffect(idRlvObj)))
 	{
-		pEffect->m_nValueMin = (newValue) ? boost::get<float>(newValue.value()) : c_SphereDefaultAlpha;
+		float nValueMin = (newValue) ? boost::get<float>(newValue.value()) : c_SphereDefaultAlpha;;
+		if (!pEffect->m_nTweenDuration)
+			pEffect->m_nValueMin = nValueMin;
+		else
+			pEffect->m_nValueMin.start(nValueMin, pEffect->m_nTweenDuration);
 	}
 	return RLV_RET_SUCCESS;
 }
@@ -303,7 +347,11 @@ ERlvCmdRet RlvSphereEffect::onValueMaxChanged(const LLUUID& idRlvObj, const boos
 {
 	if (RlvSphereEffect* pEffect = dynamic_cast<RlvSphereEffect*>(LLVfxManager::instance().getEffect(idRlvObj)))
 	{
-		pEffect->m_nValueMax = (newValue) ? boost::get<float>(newValue.value()) : c_SphereDefaultAlpha;
+		float nValueMax = (newValue) ? boost::get<float>(newValue.value()) : c_SphereDefaultAlpha;
+		if (!pEffect->m_nTweenDuration)
+			pEffect->m_nValueMax = nValueMax;
+		else
+			pEffect->m_nValueMax.start(nValueMax, pEffect->m_nTweenDuration);
 	}
 	return RLV_RET_SUCCESS;
 }
@@ -332,16 +380,17 @@ void RlvSphereEffect::setShaderUniforms(LLGLSLShader* pShader, LLRenderTarget* p
 	pShader->uniform4fv(LLShaderMgr::RLV_EFFECT_PARAM1, 1, posSphereOriginGl.v);
 
 	// Pack min/max distance and alpha together
-	const glh::vec4f sphereParams(m_nValueMin, m_nDistanceMin, m_nValueMax, m_nDistanceMax);
+	float nDistMin = m_nDistanceMin.get(), nDistMax = m_nDistanceMax.get();
+	const glh::vec4f sphereParams(m_nValueMin.get(), nDistMin, m_nValueMax.get(), (nDistMax >= nDistMin) ? nDistMax : nDistMin);
 	pShader->uniform4fv(LLShaderMgr::RLV_EFFECT_PARAM2, 1, sphereParams.v);
 
 	// Pass dist extend
 	int eDistExtend = (int)m_eDistExtend;
 	pShader->uniform2f(LLShaderMgr::RLV_EFFECT_PARAM3, eDistExtend & (int)ESphereDistExtend::Min, eDistExtend & (int)ESphereDistExtend::Max);
 
-	// Pass color
-	const glh::vec4f sphereColor(m_Color.mV, 1.0);
-	pShader->uniform4fv(LLShaderMgr::RLV_EFFECT_PARAM4, 1, sphereColor.v);
+	// Pass effect params
+	const glh::vec4f effectParams(m_Params.get().mV);
+	pShader->uniform4fv(LLShaderMgr::RLV_EFFECT_PARAM4, 1, effectParams.v);
 }
 
 void RlvSphereEffect::renderPass(LLGLSLShader* pShader) const
@@ -397,9 +446,12 @@ void RlvSphereEffect::run()
 	switch (m_eMode)
 	{
 		case ESphereMode::Blend:
+		case ESphereMode::ChromaticAberration:
+		case ESphereMode::Pixelate:
 			renderPass(&gRlvSphereProgram);
 			break;
 		case ESphereMode::Blur:
+		case ESphereMode::BlurVariable:
 			gRlvSphereProgram.uniform2f(LLShaderMgr::RLV_EFFECT_PARAM5, 1.f, 0.f);
 			renderPass(&gRlvSphereProgram);
 			gRlvSphereProgram.uniform2f(LLShaderMgr::RLV_EFFECT_PARAM5, 0.f, 1.f);
diff --git a/indra/newview/rlveffects.h b/indra/newview/rlveffects.h
index 4a4773d4481..283346c9f92 100644
--- a/indra/newview/rlveffects.h
+++ b/indra/newview/rlveffects.h
@@ -78,6 +78,8 @@ class RlvSphereEffect : public LLVisualEffect
 	static ERlvCmdRet onDistMinChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue);
 	static ERlvCmdRet onDistMaxChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue);
 	static ERlvCmdRet onDistExtendChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue);
+	static ERlvCmdRet onParamsChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue);
+	static ERlvCmdRet onTweenDurationChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue);
 	static ERlvCmdRet onValueMinChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue);
 	static ERlvCmdRet onValueMaxChanged(const LLUUID& idRlvObj, const boost::optional<RlvBehaviourModifierValue> newValue);
 protected:
@@ -88,17 +90,18 @@ class RlvSphereEffect : public LLVisualEffect
 	 * Member variables
 	 */
 protected:
-	enum class ESphereMode { Blend = 0, SoftBlur, Blur, Count };
+	enum class ESphereMode { Blend = 0, Blur, BlurVariable, ChromaticAberration, Pixelate, Count };
 	ESphereMode   m_eMode;
 	enum class ESphereOrigin { Avatar = 0, Camera, Count };
 	ESphereOrigin m_eOrigin;
-	LLColor3      m_Color;
-	float         m_nDistanceMin;
-	float         m_nDistanceMax;
+	LLTweenableValueLerp<LLVector4> m_Params;
+	LLTweenableValueLerp<float>     m_nDistanceMin;
+	LLTweenableValueLerp<float>     m_nDistanceMax;
 	enum class ESphereDistExtend { Max = 0x01, Min = 0x02, Both = 0x03 };
 	ESphereDistExtend m_eDistExtend;
-	float         m_nValueMin;
-	float         m_nValueMax;
+	LLTweenableValueLerp<float>     m_nValueMin;
+	LLTweenableValueLerp<float>     m_nValueMax;
+	float                           m_nTweenDuration;
 };
 
 // ====================================================================================
diff --git a/indra/newview/rlvhelper.cpp b/indra/newview/rlvhelper.cpp
index 817bce82d30..d56f0898f46 100644
--- a/indra/newview/rlvhelper.cpp
+++ b/indra/newview/rlvhelper.cpp
@@ -232,6 +232,8 @@ RlvBehaviourDictionary::RlvBehaviourDictionary()
 	pSetSphereBhvr->addModifier(ERlvLocalBhvrModifier::SphereDistMin, typeid(float), "distmin", &RlvSphereEffect::onDistMinChanged);
 	pSetSphereBhvr->addModifier(ERlvLocalBhvrModifier::SphereDistMax, typeid(float), "distmax", &RlvSphereEffect::onDistMaxChanged);
 	pSetSphereBhvr->addModifier(ERlvLocalBhvrModifier::SphereDistExtend, typeid(int), "distextend", &RlvSphereEffect::onDistExtendChanged);
+	pSetSphereBhvr->addModifier(ERlvLocalBhvrModifier::SphereParams, typeid(LLVector4), "param", &RlvSphereEffect::onParamsChanged);
+	pSetSphereBhvr->addModifier(ERlvLocalBhvrModifier::SphereTween, typeid(float), "tween", &RlvSphereEffect::onTweenDurationChanged);
 	pSetSphereBhvr->addModifier(ERlvLocalBhvrModifier::SphereValueMin, typeid(float), "valuemin", &RlvSphereEffect::onValueMinChanged);
 	pSetSphereBhvr->addModifier(ERlvLocalBhvrModifier::SphereValueMax, typeid(float), "valuemax", &RlvSphereEffect::onValueMaxChanged);
 	addEntry(pSetSphereBhvr);
@@ -655,6 +657,15 @@ bool RlvBehaviourModifier::convertOptionValue(const std::string& optionValue, co
 				return true;
 			}
 		}
+		else if (modType == typeid(LLVector4))
+		{
+			LLVector4 vecOption;
+			if (4 == sscanf(optionValue.c_str(), "%f/%f/%f/%f", vecOption.mV + 0, vecOption.mV + 1, vecOption.mV + 2, vecOption.mV + 3))
+			{
+				modValue = vecOption;
+				return true;
+			}
+		}
 		else if (modType == typeid(LLUUID))
 		{
 			LLUUID idOption;
-- 
GitLab