From 3571f35d4ad34387d5bafb55475ad2c6564cd438 Mon Sep 17 00:00:00 2001
From: XenHat <me@xenh.at>
Date: Thu, 8 Dec 2022 21:39:14 -0500
Subject: [PATCH] Implement LookAt distance clamping

---
 .../newview/app_settings/settings_alchemy.xml | 22 +++++
 indra/newview/llfloaterpreference.cpp         |  8 ++
 indra/newview/llfloaterpreference.h           |  2 +
 indra/newview/llhudeffectlookat.cpp           | 93 ++++++++++++++++---
 .../xui/en/panel_preferences_privacy.xml      | 26 ++++++
 5 files changed, 138 insertions(+), 13 deletions(-)

diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml
index 21e728cac6c..471cd754fe3 100644
--- a/indra/newview/app_settings/settings_alchemy.xml
+++ b/indra/newview/app_settings/settings_alchemy.xml
@@ -365,6 +365,28 @@
       <key>Value</key>
       <integer>0</integer>
     </map>
+    <key>AlchemyLookAtClampEnabled</key>
+    <map>
+      <key>Comment</key>
+      <string>If true, own look at distance will be clamped to a determined distance</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <integer>0</integer>
+    </map>
+    <key>AlchemyLookAtClampDistance</key>
+    <map>
+      <key>Comment</key>
+      <string>Distance at which to clamp own avatar look at</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>F32</string>
+      <key>Value</key>
+      <real>1.0</real>
+    </map>
     <key>AlchemyLookAtShow</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp
index 669d222c825..fd8bc194233 100644
--- a/indra/newview/llfloaterpreference.cpp
+++ b/indra/newview/llfloaterpreference.cpp
@@ -365,6 +365,8 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key)
 	mCommitCallbackRegistrar.add("Pref.RemoveSkin", boost::bind(&LLFloaterPreference::onRemoveSkin, this));
 	mCommitCallbackRegistrar.add("Pref.ApplySkin", boost::bind(&LLFloaterPreference::onApplySkin, this));
 	mCommitCallbackRegistrar.add("Pref.SelectSkin", boost::bind(&LLFloaterPreference::onSelectSkin, this, _2));
+
+	mCommitCallbackRegistrar.add("Pref.UpdateLookAtClampDistance", boost::bind(&LLFloaterPreference::onUpdateLookAtClampDistance, this, _2));
 }
 
 void LLFloaterPreference::processProperties( void* pData, EAvatarProcessorType type )
@@ -888,6 +890,12 @@ void LLFloaterPreference::refreshSkinInfo(const skin_t& skin)
 	getChild<LLTextBase>("skin_notes")->setText(skin.mNotes);
 }
 
+void LLFloaterPreference::onUpdateLookAtClampDistance(const LLSD& data)
+{
+	F32 clamp_distance = (F32)(data.asReal());
+	gSavedSettings.setF32("AlchemyLookAtClampDistance", clamp_distance);
+}
+
 LLFloaterPreference::~LLFloaterPreference()
 {
 	
diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h
index 643d5375600..0b97c34b00f 100644
--- a/indra/newview/llfloaterpreference.h
+++ b/indra/newview/llfloaterpreference.h
@@ -220,6 +220,8 @@ class LLFloaterPreference final : public LLFloater, public LLAvatarPropertiesObs
 	void onSelectSkin(const LLSD& data);
 	void refreshSkinInfo(const skin_t& skin);
 
+	void onUpdateLookAtClampDistance(const LLSD& data);
+
 	static std::string sSkin;
 	notifications_map mNotificationOptions;
 	bool mGotPersonalInfo;
diff --git a/indra/newview/llhudeffectlookat.cpp b/indra/newview/llhudeffectlookat.cpp
index 1f49f64f2b6..d7dcba00bc6 100644
--- a/indra/newview/llhudeffectlookat.cpp
+++ b/indra/newview/llhudeffectlookat.cpp
@@ -35,6 +35,7 @@
 #include "llagent.h"
 #include "llagentcamera.h"
 #include "llvoavatar.h"
+#include "llvoavatarself.h" // for gAgentAvatarp
 #include "lldrawable.h"
 #include "llviewerobjectlist.h"
 #include "llrendersphere.h"
@@ -411,27 +412,51 @@ BOOL LLHUDEffectLookAt::setLookAt(ELookAtType target_type, LLViewerObject *objec
 		return FALSE;
 	}
 
+	auto newTargetLookat = (*mAttentions)[mTargetType];
+
 	// must be same or higher priority than existing effect
-	if ((*mAttentions)[target_type].mPriority < (*mAttentions)[mTargetType].mPriority)
+	if ((*mAttentions)[target_type].mPriority < newTargetLookat.mPriority)
 	{
 		return FALSE;
 	}
 
 	F32 current_time  = mTimer.getElapsedTimeF32();
 
-	// type of lookat behavior or target object has changed
-	BOOL lookAtChanged = (target_type != mTargetType) || (object != mTargetObject);
-
-	// lookat position has moved a certain amount and we haven't just sent an update
-	lookAtChanged = lookAtChanged || ((dist_vec_squared(position, mLastSentOffsetGlobal) > MIN_DELTAPOS_FOR_UPDATE_SQUARED) && 
-		((current_time - mLastSendTime) > (1.f / MAX_SENDS_PER_SEC)));
+	bool looking_at_self = false;
+	if (object != NULL) // Why does this crash?
+	{
+		auto objectp = static_cast<LLViewerObject*>(object);
+		if (objectp->isAvatar())
+		{
+			auto voavatarp = dynamic_cast<LLVOAvatar*>(objectp);
+			if (voavatarp->isSelf())
+			{
+				looking_at_self = true;
+			}
+		}
+	}
+	static LLCachedControl<bool> clamp_lookat_enabled(gSavedSettings, "AlchemyLookAtClampEnabled", false);
+	bool clamp_lookat = clamp_lookat_enabled && !looking_at_self && 
+						newTargetLookat.mName != "Respond" &&
+						newTargetLookat.mName != "Conversation" &&
+						newTargetLookat.mName != "AutoListen";
 
-	if (lookAtChanged)
+	if (!clamp_lookat)
 	{
-		mLastSentOffsetGlobal = position;
-		F32 timeout = (*mAttentions)[target_type].mTimeout;
-		setDuration(timeout);
-		setNeedsSendToSim(TRUE);
+		// type of lookat behavior or target object has changed
+		BOOL lookAtChanged = (target_type != mTargetType) || (object != mTargetObject);
+
+		// lookat position has moved a certain amount and we haven't just sent an update
+		lookAtChanged = lookAtChanged || ((dist_vec_squared(position, mLastSentOffsetGlobal) > MIN_DELTAPOS_FOR_UPDATE_SQUARED) && 
+			((current_time - mLastSendTime) > (1.f / MAX_SENDS_PER_SEC)));
+
+		if (lookAtChanged)
+		{
+			mLastSentOffsetGlobal = position;
+			F32 timeout = (*mAttentions)[target_type].mTimeout;
+			setDuration(timeout);
+			setNeedsSendToSim(TRUE);
+		}
 	}
  
 	if (target_type == LOOKAT_TARGET_CLEAR)
@@ -444,12 +469,54 @@ BOOL LLHUDEffectLookAt::setLookAt(ELookAtType target_type, LLViewerObject *objec
 		mTargetObject = object;
 		if (object)
 		{
-			mTargetOffsetGlobal.setVec(position);
+			if(clamp_lookat)
+			{
+				// Pretend to look at the object
+				mTargetOffsetGlobal.setVec(object->getPositionGlobal() + (LLVector3d)(position * object->getRotationRegion()));
+				mTargetObject = NULL;
+			}
+			else
+			{
+				mTargetOffsetGlobal.setVec(position);
+			}
 		}
 		else
 		{
 			mTargetOffsetGlobal = gAgent.getPosGlobalFromAgent(position);
 		}
+
+		if (clamp_lookat)
+		{
+			static LLCachedControl<F32> lookat_clamp_distance(gSavedSettings, "AlchemyLookAtClampDistance", 1.0f);
+			auto head_position = gAgent.getPosGlobalFromAgent(gAgentAvatarp->mHeadp->getWorldPosition());
+			auto distance = dist_vec(mTargetOffsetGlobal, head_position);
+
+			if (distance > lookat_clamp_distance)
+			{
+				auto distance_from_object = (mTargetOffsetGlobal - head_position) * (lookat_clamp_distance / distance);
+				mTargetOffsetGlobal.setVec(head_position + distance_from_object);
+			}
+
+			bool lookat_changed = target_type != mTargetType;
+
+			// update position
+			if (!lookat_changed)
+			{
+				auto distance_difference = dist_vec_squared(gAgent.getPosAgentFromGlobal(mTargetOffsetGlobal), mLastSentOffsetGlobal);
+				auto time_difference = (current_time - mLastSendTime);
+				if (distance_difference > MIN_DELTAPOS_FOR_UPDATE_SQUARED && time_difference > (1.f / MAX_SENDS_PER_SEC))
+				{
+					lookat_changed = true;
+				}
+			}
+			if (lookat_changed)
+			{
+				mLastSentOffsetGlobal = gAgent.getPosAgentFromGlobal(mTargetOffsetGlobal);
+				F32 timeout = (*mAttentions)[target_type].mTimeout;
+				setDuration(timeout);
+				setNeedsSendToSim(TRUE);
+			}
+		}
 		mKillTime = mTimer.getElapsedTimeF32() + mDuration;
 
 		update();
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_privacy.xml b/indra/newview/skins/default/xui/en/panel_preferences_privacy.xml
index e1fe3e0e28c..fec3ac9f4a9 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_privacy.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_privacy.xml
@@ -216,6 +216,32 @@
 		     tool_tip="Don't show me my own camera crosshairs"
          top_pad="10"
          width="350" />
+        <check_box
+         control_name="AlchemyLookAtClampEnabled"
+         height="16"
+         label="Limit camera target distance"
+         layout="topleft"
+         left="30"
+         name="privatelookat"
+	    	 tool_tip="Allows cosmetic features like eye and head movement but will point at nothing specific if beyond the set distance."
+         top_pad="4"
+         width="200" />
+         <slider
+            height="16"
+            left_pad="10"
+            top_delta="0"
+            min_val="0.5"
+            max_val="128"
+            follows="top|left"
+            control_name="AlchemyLookAtClampDistance"
+            initial_value="1.0"
+            increment="0.5"
+            label="Max Distance"
+            width="275"
+            layout="topleft">
+            <button.commit_callback
+               function="Pref.UpdateLookAtClampDistance" />
+         </slider>
         <check_box
          control_name="AlchemyLookAtPrivate"
          height="16"
-- 
GitLab