Skip to content

Commit

Permalink
Add option to adjust HMD camera behaviour when game loses focus
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoco007 committed Dec 15, 2024
1 parent f804aba commit 047d1c1
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 26 deletions.
2 changes: 2 additions & 0 deletions Source/CustomAvatar/Configuration/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using System.Runtime.Serialization;
using CustomAvatar.Avatar;
using CustomAvatar.Player;
using CustomAvatar.Rendering;
using CustomAvatar.Utilities;
using Newtonsoft.Json;
using UnityEngine;
Expand All @@ -40,6 +41,7 @@ internal class Settings
public ObservableValue<bool> showAvatarInSmoothCamera { get; } = new ObservableValue<bool>(true);
public ObservableValue<bool> showRenderModels { get; } = new ObservableValue<bool>(true);
public bool showAvatarInMirrors { get; set; } = true;
public ObservableValue<HmdCameraBehaviour> hmdCameraBehaviour { get; } = new ObservableValue<HmdCameraBehaviour>(HmdCameraBehaviour.HmdOnly);
public ObservableValue<SkinWeights> skinWeights { get; } = new ObservableValue<SkinWeights>(SkinWeights.FourBones);
public Mirror mirror { get; } = new Mirror();

Expand Down
25 changes: 25 additions & 0 deletions Source/CustomAvatar/Rendering/HmdCameraBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Beat Saber Custom Avatars - Custom player models for body presence in Beat Saber.
// Copyright © 2018-2024 Nicolas Gnyra and Beat Saber Custom Avatars Contributors
//
// This library is free software: you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

namespace CustomAvatar.Rendering
{
internal enum HmdCameraBehaviour
{
Off,
HmdOnly,
AllCameras,
}
}
22 changes: 12 additions & 10 deletions Source/CustomAvatar/Rendering/MainCamera.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

namespace CustomAvatar.Rendering
{
[RequireComponent(typeof(Camera))]
[RequireComponent(typeof(TrackedPoseDriver))]
[DisallowMultipleComponent]
internal class MainCamera : MonoBehaviour
{
Expand Down Expand Up @@ -65,7 +67,7 @@ protected virtual int GetCameraMask(int mask)
mask |= AvatarLayers.kAlwaysVisibleMask;

// FPFC basically ends up being a 3rd person camera
if (fpfcSettings.Enabled || !beatSaberUtilities.hasFocus)
if (fpfcSettings.Enabled || (!beatSaberUtilities.hasFocus && _settings.hmdCameraBehaviour == HmdCameraBehaviour.AllCameras))
{
mask |= AvatarLayers.kOnlyInThirdPersonMask;
}
Expand Down Expand Up @@ -165,20 +167,22 @@ protected void OnDestroy()

protected void OnPreCull()
{
if (_trackedPoseDriver != null)
if (_settings.hmdCameraBehaviour == HmdCameraBehaviour.HmdOnly && !beatSaberUtilities.hasFocus)
{
_trackedPoseDriver.UseRelativeTransform = !beatSaberUtilities.hasFocus;
_trackedPoseDriver.UseRelativeTransform = true;
_trackedPoseDriver.PerformUpdate();
_camera.ResetWorldToCameraMatrix();
_camera.cullingMask |= AvatarLayers.kOnlyInThirdPersonMask;
}
}

protected void OnPostRender()
{
if (_trackedPoseDriver != null)
if (_settings.hmdCameraBehaviour == HmdCameraBehaviour.HmdOnly && !beatSaberUtilities.hasFocus)
{
// the VR camera seems to always be rendered last so we don't need to re-update the camera pose/matrix
_trackedPoseDriver.UseRelativeTransform = false;
UpdateCameraMask();
}
}

Expand All @@ -194,12 +198,10 @@ private void OnFpfcSettingsChanged(IFPFCSettings fpfcSettings)

private void OnFocusChanged(bool hasFocus)
{
if (_trackedPoseDriver != null)
{
_trackedPoseDriver.originPose = hasFocus ? Pose.identity : new Pose(
Vector3.Project(Quaternion.Euler(0, 180, 0) * -transform.localPosition * 2, Vector3.right) + new Vector3(0, 0, 1.5f),
Quaternion.Euler(0, 180, 0));
}
_trackedPoseDriver.originPose = hasFocus ? Pose.identity : new Pose(
Vector3.Project(Quaternion.Euler(0, 180, 0) * -transform.localPosition * 2, Vector3.right) + new Vector3(0, 0, 1.5f),
Quaternion.Euler(0, 180, 0));
_trackedPoseDriver.UseRelativeTransform = _settings.hmdCameraBehaviour == HmdCameraBehaviour.AllCameras;

UpdateCameraMask();
}
Expand Down
36 changes: 34 additions & 2 deletions Source/CustomAvatar/Rendering/SmoothCamera.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,35 @@
using CustomAvatar.Avatar;
using CustomAvatar.Configuration;
using CustomAvatar.Logging;
using CustomAvatar.Utilities;
using SiraUtil.Tools.FPFC;
using UnityEngine;
using Zenject;

namespace CustomAvatar.Rendering
{
[RequireComponent(typeof(SmoothCamera))]
[RequireComponent(typeof(Camera))]
[DisallowMultipleComponent]
internal class SmoothCamera : MonoBehaviour
{
private const float kCameraDefaultNearClipMask = 0.1f;

private ILogger<SmoothCamera> _logger;
private Settings _settings;
private IFPFCSettings _fpfcSettings;
private BeatSaberUtilities _beatSaberUtilities;

private global::SmoothCamera _smoothCamera;
private Camera _camera;

[Inject]
public void Construct(ILogger<SmoothCamera> logger, Settings settings)
public void Construct(ILogger<SmoothCamera> logger, Settings settings, IFPFCSettings fpfcSettings, BeatSaberUtilities beatSaberUtilities)
{
_logger = logger;
_settings = settings;
_fpfcSettings = fpfcSettings;
_beatSaberUtilities = beatSaberUtilities;

_smoothCamera = GetComponent<global::SmoothCamera>();
_camera = GetComponent<Camera>();
Expand All @@ -55,6 +63,10 @@ protected void Start()
_settings.cameraNearClipPlane.changed += OnCameraNearClipPlaneChanged;
_settings.showAvatarInSmoothCamera.changed += OnShowAvatarInSmoothCameraChanged;

_fpfcSettings.Changed += OnFpfcSettingsChanged;

_beatSaberUtilities.focusChanged += OnFocusChanged;

UpdateSmoothCamera();
}

Expand All @@ -65,6 +77,16 @@ protected void OnDestroy()
_settings.cameraNearClipPlane.changed -= OnCameraNearClipPlaneChanged;
_settings.showAvatarInSmoothCamera.changed -= OnShowAvatarInSmoothCameraChanged;
}

if (_fpfcSettings != null)
{
_fpfcSettings.Changed -= OnFpfcSettingsChanged;
}

if (_beatSaberUtilities != null)
{
_beatSaberUtilities.focusChanged -= OnFocusChanged;
}
}

private void OnCameraNearClipPlaneChanged(float value)
Expand All @@ -77,6 +99,16 @@ private void OnShowAvatarInSmoothCameraChanged(bool value)
UpdateSmoothCamera();
}

private void OnFpfcSettingsChanged(IFPFCSettings fpfcSettings)
{
UpdateSmoothCamera();
}

private void OnFocusChanged(bool focused)
{
UpdateSmoothCamera();
}

private void UpdateSmoothCamera()
{
_logger.LogInformation($"Setting avatar culling mask and near clip plane on '{_camera.name}'");
Expand All @@ -86,7 +118,7 @@ private void UpdateSmoothCamera()
_camera.cullingMask &= ~AvatarLayers.kAllLayersMask;
_camera.nearClipPlane = kCameraDefaultNearClipMask;
}
else if (_smoothCamera._thirdPersonEnabled)
else if (_smoothCamera._thirdPersonEnabled || _fpfcSettings.Enabled || (!_beatSaberUtilities.hasFocus && _settings.hmdCameraBehaviour == HmdCameraBehaviour.AllCameras)) // TODO: consolidate these conditions with the ones in MainCamera
{
_camera.cullingMask = _camera.cullingMask | AvatarLayers.kOnlyInThirdPersonMask | AvatarLayers.kAlwaysVisibleMask;
_camera.nearClipPlane = kCameraDefaultNearClipMask;
Expand Down
23 changes: 21 additions & 2 deletions Source/CustomAvatar/UI/InterfaceSettingsHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

using System.Collections.Generic;
using CustomAvatar.Configuration;
using CustomAvatar.Rendering;

namespace CustomAvatar.UI
{
Expand Down Expand Up @@ -52,7 +52,15 @@ internal bool renderInExternalCameras
set => _settings.mirror.renderInExternalCameras = value;
}

internal List<object> antiAliasingLevelOptions = new(new object[] { 1, 2, 4, 8 });
internal HmdCameraBehaviour hmdCameraBehaviour
{
get => _settings.hmdCameraBehaviour;
set => _settings.hmdCameraBehaviour.value = value;
}

internal int[] antiAliasingLevelOptions = [1, 2, 4, 8];

internal HmdCameraBehaviour[] hmdCameraBehaviourOptions = [HmdCameraBehaviour.Off, HmdCameraBehaviour.HmdOnly, HmdCameraBehaviour.AllCameras];

protected string AntiAliasingLevelFormatter(int value)
{
Expand All @@ -65,5 +73,16 @@ protected string AntiAliasingLevelFormatter(int value)
return "Off";
}
}

protected string HmdCameraBehaviourFormatter(HmdCameraBehaviour value)
{
return value switch
{
HmdCameraBehaviour.Off => "Off",
HmdCameraBehaviour.HmdOnly => "Inside HMD Only",
HmdCameraBehaviour.AllCameras => "All HMD Cameras",
_ => "?",
};
}
}
}
19 changes: 7 additions & 12 deletions Source/CustomAvatar/UI/Views/Settings.bsml
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,21 @@
<icon-button hover-hint="~measureButtonHoverHintText" on-click="OnMeasureArmSpanButtonClicked" icon="~measureButtonIcon" interactable="~isMeasureButtonEnabled" show-underline="false" anchor-min="1 0.5" anchor-max="1 0.5" pivot="1 0.5" anchored-position="0 0" size-delta="6 8" padding="1" horizontal-fit="Unconstrained" vertical-fit="Unconstrained" />
</bg>

<horizontal pref-width="90" pad-top="2">
<text color="#808080" text="Visibility" font-size="4" italics="true" align="Baseline" />
</horizontal>
<text color="#808080" text="Visibility" font-size="4" italics="true" align="Capline" anchor-min="0 0.5" anchor-max="1 0.5" size-delta="0 7" />

<toggle-setting text="Show in First Person" value="visibleInFirstPerson" bind-value="true" apply-on-change="true" />
<toggle-setting text="Show in Smooth Camera" value="showAvatarInSmoothCamera" bind-value="true" apply-on-change="true" />
<toggle-setting text="Show in Mirrors" value="showAvatarInMirrors" bind-value="true" apply-on-change="true" />
<toggle-setting text="Show Controllers/Trackers" active="~showRenderModelsOption" hover-hint="Show controllers/trackers while in this menu" value="showRenderModels" bind-value="true" apply-on-change="true" />

<horizontal pref-width="90" pad-top="2">
<text color="#808080" text="Advanced" font-size="4" italics="true" align="Baseline" />
</horizontal>
<text color="#808080" text="Advanced" font-size="4" italics="true" align="Capline" anchor-min="0 0.5" anchor-max="1 0.5" size-delta="0 7" />

<toggle-setting value="moveFloorWithRoomAdjust" bind-value="true" apply-on-change="true" text="Move Floor with Room Adjust" hover-hint="Move the floor/environment along with the game's built-in vertical room adjust." />
<toggle-setting value="enableLocomotion" bind-value="true" apply-on-change="true" text="Enable locomotion" hover-hint="Allow feet and waist to be moved automatically to follow your head/body when not using dedicated trackers. Only affects full body avatars." />
<list-setting value="cameraNearClipPlane" bind-value="true" apply-on-change="true" text="Camera Near Clip Plane" hover-hint="Distance from your eyes at which objets start being visible." options="nearClipPlaneValues" formatter="CentimeterFormatter" />
</macro.as-host>

<horizontal pref-width="90" pad-top="2">
<text color="#808080" text="Automatic Calibration (beta)" font-size="4" italics="true" align="Baseline" />
</horizontal>
<text color="#808080" text="Automatic Calibration (beta)" font-size="4" italics="true" align="Capline" anchor-min="0 0.5" anchor-max="1 0.5" size-delta="0 7" />

<macro.as-host host="automaticFbtCalibrationHost">
<macro.as-host host="trackerStatusHost">
Expand Down Expand Up @@ -89,13 +83,14 @@
<tab tags="avatar-tabs" tab-name="Interface" size-delta-y="-16" anchored-position-y="-8">
<macro.as-host host="interfaceSettingsHost">
<settings-container size-delta-x="-20" anchored-position-x="4">
<horizontal pref-width="90" pad-top="2">
<text color="#808080" text="Mirror" font-size="4" italics="true" align="Baseline" />
</horizontal>
<text color="#808080" text="Mirror" font-size="4" italics="true" align="Capline" anchor-min="0 0.5" anchor-max="1 0.5" size-delta="0 7" />
<increment-setting text="Render Scale" value="renderScale" bind-value="true" apply-on-change="true" min="0.5" max="2" increment="0.1" />
<list-setting text="Anti Aliasing" value="antiAliasingLevel" bind-value="true" apply-on-change="true" options="antiAliasingLevelOptions" formatter="AntiAliasingLevelFormatter" />
<toggle-setting text="Use Fake Mirror (beta)" value="useFakeMirror" bind-value="true" apply-on-change="true" hover-hint="Show a mirrored version of the current avatar rather than rendering a full mirror. Improves performance and allows the avatar to be closer to you, but may not accurately reflect all avatar features." />
<toggle-setting text="Show in non-VR cameras" value="renderInExternalCameras" bind-value="true" apply-on-change="true" hover-hint="Disable this setting to improve performance in this menu when multiple cameras are used (e.g. smooth camera or Camera2)." />

<text color="#808080" text="Other" font-size="4" italics="true" align="Capline" anchor-min="0 0.5" anchor-max="1 0.5" size-delta="0 7" />
<dropdown-list-setting text="Flip camera when focus is lost" value="hmdCameraBehaviour" bind-value="true" apply-on-change="true" options="hmdCameraBehaviourOptions" formatter="HmdCameraBehaviourFormatter" hover-hint="Flip the camera to face your avatar when the system (SteamVR, Oculus, etc.) dashboard is opened or when you remove your headset." />
</settings-container>
</macro.as-host>
</tab>
Expand Down

0 comments on commit 047d1c1

Please sign in to comment.