From 7dd6150d603203bbd2a347ef81bb159ef89eb77c Mon Sep 17 00:00:00 2001 From: jbusfield Date: Sun, 3 Sep 2023 11:06:43 -0700 Subject: [PATCH 1/9] Added controller haptics behind two new settings in Options to enable UI haptics and World haptics --- SubmersedVR/Input/SteamVrGameInput.cs | 4 +- SubmersedVR/Input/TargetingPatches.cs | 17 +- SubmersedVR/Settings.cs | 12 +- SubmersedVR/Tweaks/Haptics.cs | 825 +++++++++++++++++++ SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs | 3 +- SubmersedVR/Tweaks/RecenterInVehicles.cs | 7 + SubmersedVR/VR/VRCameraRig.cs | 16 +- SubmersedVR/VR/VRHands.cs | 14 +- SubmersedVR/VR/VRHud.cs | 14 +- 9 files changed, 893 insertions(+), 19 deletions(-) create mode 100644 SubmersedVR/Tweaks/Haptics.cs diff --git a/SubmersedVR/Input/SteamVrGameInput.cs b/SubmersedVR/Input/SteamVrGameInput.cs index eccbf2d..bf71c06 100644 --- a/SubmersedVR/Input/SteamVrGameInput.cs +++ b/SubmersedVR/Input/SteamVrGameInput.cs @@ -61,8 +61,8 @@ static bool Prefix(GameInput.Button button, ref bool __result) VRCameraRig.instance.photoRequested = false; return false; } - //Use the Sprint action as the Take Picture action when the player is not piloting a vehicle - if(actionName == "TakePicture" && !(Player.main?.currentMountedVehicle != null || (Player.main?.IsPiloting() == true))) + //Use the Sprint action as the Take Picture action when the player is not piloting a vehicle or holding a tool + if(actionName == "TakePicture" && !(Player.main?.currentMountedVehicle != null || (Player.main?.IsPiloting() == true)) && (Inventory.main.GetHeldTool() == null)) { actionName = "Sprint"; } diff --git a/SubmersedVR/Input/TargetingPatches.cs b/SubmersedVR/Input/TargetingPatches.cs index 949a6d5..d5b65a2 100644 --- a/SubmersedVR/Input/TargetingPatches.cs +++ b/SubmersedVR/Input/TargetingPatches.cs @@ -8,7 +8,6 @@ namespace SubmersedVR { - // This replaces the main camera transform in `Targeting` with the event camera from `VRCameraRig` // TODO: Rewrite with CodeMatcher [HarmonyPatch(typeof(Targeting), nameof(Targeting.GetTarget))] @@ -71,6 +70,22 @@ static IEnumerable Transpiler(IEnumerable inst new CodeMatch(opc => opc.StoresField(desiredIconField)), }).SetOpcodeAndAdvance(OpCodes.Ldc_I4_0).InstructionEnumeration(); // Replace by 0 } + } + //Catch the cases the above code doesnt handle like when in the Exosuit + [HarmonyPatch(typeof(HandReticle), nameof(HandReticle.SetIcon))] + public static class DisableDefaultHandReticleVR + { + public static void Postfix(HandReticle __instance) + { + if(__instance.desiredIconType == HandReticle.IconType.Default) + { + __instance.desiredIconType = HandReticle.IconType.None; + } + } + } + + // + } \ No newline at end of file diff --git a/SubmersedVR/Settings.cs b/SubmersedVR/Settings.cs index 5558250..c4d070e 100644 --- a/SubmersedVR/Settings.cs +++ b/SubmersedVR/Settings.cs @@ -44,6 +44,11 @@ public class Settings public static float ExosuitYOffset = 0.0f; // public static float HudDistance = 1.0f; // public static event FloatChanged HudDistanceChanged; + public static float PlayerScale = 1.0f; + + public static bool EnableGameHaptics = false; + public static bool EnableUIHaptics = false; + // Saves or loads all public static properties as settings using the given serializer internal static void Serialize(GameSettings.ISerializer serializer) @@ -99,11 +104,14 @@ internal static void AddMenu(uGUI_OptionsPanel panel) panel.AddSliderOption(tab, "SeaTruck Pilot Height Offset", SeaTruckYOffset, -0.4f, 0.4f, SeaTruckYOffset, 0.01f, (value) => { SeaTruckYOffset = value; }, SliderLabelMode.Float, "0.00"); panel.AddSliderOption(tab, "Prawn Suit Position Offset", ExosuitZOffset, -0.4f, 0.4f, ExosuitZOffset, 0.01f, (value) => { ExosuitZOffset = value; }, SliderLabelMode.Float, "0.00"); panel.AddSliderOption(tab, "Prawn Suit Height Offset", ExosuitYOffset, -0.4f, 0.4f, ExosuitYOffset, 0.01f, (value) => { ExosuitYOffset = value; }, SliderLabelMode.Float, "0.00"); - panel.AddSliderOption(tab, "Snowbike Seated Position Offset", SnowBikeZOffset, -0.4f, 0.4f, SnowBikeZOffset, 0.01f, (value) => { SnowBikeZOffset = value; }, SliderLabelMode.Float, "0.00"); - panel.AddSliderOption(tab, "Snowbike Seated Height Offset", SnowBikeYOffset, -0.2f, 0.4f, SnowBikeYOffset, 0.01f, (value) => { SnowBikeYOffset = value; }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, "Snowbike Position Offset", SnowBikeZOffset, -0.4f, 0.4f, SnowBikeZOffset, 0.01f, (value) => { SnowBikeZOffset = value; }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, "Snowbike Height Offset", SnowBikeYOffset, -0.2f, 0.4f, SnowBikeYOffset, 0.01f, (value) => { SnowBikeYOffset = value; }, SliderLabelMode.Float, "0.00"); panel.AddHeading(tab, "Experimental"); panel.AddToggleOption(tab, "Full body", FullBody, (value) => { FullBody = value; FullBodyChanged(value); }, "See the full body instead of just the hands and feet."); + panel.AddSliderOption(tab, "Body Scale", PlayerScale, 0.8f, 1.2f, PlayerScale, 0.01f, (value) => { PlayerScale = value; }, SliderLabelMode.Float, "0.00"); + panel.AddToggleOption(tab, "Enable Game Haptics", EnableGameHaptics, (value) => { EnableGameHaptics = value; }, "Enable controller vibration while interacting with world objects."); + panel.AddToggleOption(tab, "Enable UI Haptics", EnableUIHaptics, (value) => { EnableUIHaptics = value; }, "Enable controller vibration while interacting with the User Interface."); panel.AddToggleOption(tab, "Put hand reticle on laserpointer end", PutHandReticleOnLaserPointer, (value) => { PutHandReticleOnLaserPointer = value; PutHandReticleOnLaserPointerChanged(value); }); panel.AddToggleOption(tab, "Put survival meter on left wrist", PutBarsOnWrist, (value) => { PutBarsOnWrist = value; PutBarsOnWristChanged(value); }); panel.AddToggleOption(tab, "Invert Y Axis in Seamoth/Cameras", InvertYAxis, (value) => { InvertYAxis = value; InvertYAxisChanged(value); }, "Enables Y axis inversion for Seamoth and Cameras."); diff --git a/SubmersedVR/Tweaks/Haptics.cs b/SubmersedVR/Tweaks/Haptics.cs new file mode 100644 index 0000000..fa5283e --- /dev/null +++ b/SubmersedVR/Tweaks/Haptics.cs @@ -0,0 +1,825 @@ +using HarmonyLib; +using UnityEngine; +using System; +using UnityEngine.UI; +using UWE; +using System.Collections; + +namespace SubmersedVR +{ + extern alias SteamVRActions; + extern alias SteamVRRef; + using SteamVRRef.Valve.VR; + using SteamVRActions.Valve.VR; + using UnityEngine.EventSystems; + using System.Threading; + using System.CodeDom; + using rail; + + static class HapticsVR + { + public static GameObject lastHoverComponent = null; + //public static Constructable lastConstructable = null; + //public static BaseDeconstructable lastDeconstructable = null; + public static int lastBuilderCatID = -1; + public static int lastPDACatID = -1; + public static float effectsTimer = 0.0f; + public static float leftSecondaryEffectsTimer = 0.0f; + public static float rightSecondaryEffectsTimer = 0.0f; + public static float effectsTimeout = 0.0f; + public static float effectsScale = 0.0f; + + + public static void PlayHaptics(float secondsFromNow, float durationSeconds, float frequency, float amplitude, bool isUI = false, bool rightController = true, bool leftController = false, float leftAmplitude = -1.0f) + { + if((isUI && Settings.EnableUIHaptics == true) || !isUI && Settings.EnableGameHaptics == true) + { + if(leftController) + { + SteamVR_Actions.subnautica_HapticsLeft?.Execute(secondsFromNow, durationSeconds, frequency, leftAmplitude != -1.0 ? leftAmplitude : amplitude, SteamVR_Input_Sources.Any); + } + if(rightController) + { + SteamVR_Actions.subnautica_HapticsRight?.Execute(secondsFromNow, durationSeconds, frequency, amplitude, SteamVR_Input_Sources.Any); + } + } + } + + + //Fading heartbeat + public static IEnumerator PlayerDeath() + { + HapticsVR.PlayHaptics(0.0f, 0.2f, 10f, 1.0f, false, true, true); + yield return new WaitForSeconds(0.3f); + HapticsVR.PlayHaptics(0.0f, 0.3f, 10f, 0.9f, false, true, true); + yield return new WaitForSeconds(0.8f); + HapticsVR.PlayHaptics(0.0f, 0.2f, 10f, 0.5f, false, true, true); + yield return new WaitForSeconds(0.3f); + HapticsVR.PlayHaptics(0.0f, 0.3f, 10f, 0.4f, false, true, true); + yield return new WaitForSeconds(1.0f); + HapticsVR.PlayHaptics(0.0f, 0.2f, 10f, 0.3f, false, true, true); + yield return new WaitForSeconds(0.3f); + HapticsVR.PlayHaptics(0.0f, 0.3f, 10f, 0.2f, false, true, true); + yield return new WaitForSeconds(1.2f); + HapticsVR.PlayHaptics(0.0f, 0.2f, 10f, 0.1f, false, true, true); + yield return new WaitForSeconds(0.3f); + HapticsVR.PlayHaptics(0.0f, 0.3f, 10f, 0.1f, false, true, true); + } + + + public static IEnumerator PlayerHeartbeat() + { + HapticsVR.PlayHaptics(0.0f, 0.15f, 10f, 0.8f, false, true, true); + yield return new WaitForSeconds(0.2f); + HapticsVR.PlayHaptics(0.0f, 0.25f, 10f, 0.3f, false, true, true); + } + + } + + #region Patches + + //Haptics when an item is clicked (eg tapping on a stone) + //Haptics when an item is hovered over + [HarmonyPatch(typeof(GUIHand), nameof(GUIHand.Send))] + class HapticsUpdate + { + public static void Prefix( GameObject target, HandTargetEventType e, GUIHand hand) + { + //Mod.logger.LogInfo($"GUIHand.Send called"); + if (target == null || !target.activeInHierarchy || e == HandTargetEventType.None || target.GetComponent() == null) + { + HapticsVR.lastHoverComponent = null; + return; + } + if (e == HandTargetEventType.Click) + { + HapticsVR.PlayHaptics(0.0f, 0.2f, 5f, 1.0f); + } + if (e == HandTargetEventType.Hover) + { + if(target == HapticsVR.lastHoverComponent) + { + return; + } + + HapticsVR.lastHoverComponent = target; + Mod.logger.LogInfo($"GUIHand.Send Hover called"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f); + } + } + } + + //Haptics when an item is bashed (eg knife held when rock hit) + [HarmonyPatch(typeof(GUIHand), nameof(GUIHand.BashHit))] + public static class PickupableHaptics + { + public static void Postfix(GUIHand __instance) + { + Mod.logger.LogInfo($"GUIHand.BashHit called"); + HapticsVR.PlayHaptics(0.5f, 0.2f, 5f, 1.0f); + } + } + + + //Detects a Hover Out while in the exosuit so that an item can be triggered more than once + [HarmonyPatch(typeof(Exosuit), nameof(Exosuit.UpdateActiveTarget))] + public static class ExosuitHaptics + { + public static void Postfix(Exosuit __instance) + { + //Mod.logger.LogInfo($"Exosuit.UpdateActiveTarget called activeTraget is null = {__instance.activeTarget == null}"); + if (__instance.activeTarget == null) + { + HapticsVR.lastHoverComponent = null; + } + } + } + + + //Handles dual wielding drillers also + [HarmonyPatch(typeof(Exosuit), nameof(Exosuit.Update))] + public static class ExosuitDrillHaptics + { + public static void Postfix(Exosuit __instance) + { + ExosuitDrillArm left = __instance.leftArm as ExosuitDrillArm; + ExosuitDrillArm right = __instance.rightArm as ExosuitDrillArm; + if((left != null && left.drilling) || (right != null && right.drilling)) + { + //Mod.logger.LogInfo($"ExosuitDrillArm.Update {__instance.currentLeftArmType == TechType.ExosuitDrillArmModule} {__instance.currentRightArmType == TechType.ExosuitDrillArmModule}"); + HapticsVR.effectsTimer += Time.deltaTime; + if(HapticsVR.effectsTimer > HapticsVR.effectsTimeout) + { + float dur = UnityEngine.Random.Range(0.01f, 0.20f); + if(right?.drilling == true) + { + HapticsVR.PlayHaptics(0.0f, dur, 10f, UnityEngine.Random.Range(0f, 1.0f) * (right?.drillTarget == true ? 1.0f : 0.1f), false, true, false); + } + if(left?.drilling == true) + { + HapticsVR.PlayHaptics(0.0f, dur, 10f, UnityEngine.Random.Range(0f, 1.0f) * (left?.drillTarget == true ? 1.0f : 0.1f), false, false, true); + } + + HapticsVR.effectsTimer = 0.0f; + HapticsVR.effectsTimeout = UnityEngine.Random.Range(dur, dur + 0.10f); + } + } + } + } + + [HarmonyPatch(typeof(ExosuitClawArm), nameof(ExosuitClawArm.OnHit))] + public static class ExosuitClawArmHaptics + { + public static void Postfix(ExosuitClawArm __instance) + { + HapticsVR.PlayHaptics(0.0f, 0.2f, 10f, 1.0f, false, __instance.exosuit.rightArm as ExosuitClawArm == __instance, __instance.exosuit.leftArm as ExosuitClawArm == __instance); + } + } + + [HarmonyPatch(typeof(ExosuitGrapplingArm), nameof(ExosuitGrapplingArm.OnHit))] + public static class ExosuitGrapplingArmHaptics + { + public static void Postfix(ExosuitGrapplingArm __instance) + { + HapticsVR.PlayHaptics(0.0f, 0.2f, 10f, 0.5f, false, __instance.exosuit.rightArm as ExosuitGrapplingArm == __instance, __instance.exosuit.leftArm as ExosuitGrapplingArm == __instance); + } + } + + //Handles the case of dual wielded grappling hooks + [HarmonyPatch(typeof(ExosuitGrapplingArm), nameof(ExosuitGrapplingArm.FixedUpdate))] + public static class ExosuitGrapplingActiveArmHaptics + { + public static void Postfix(ExosuitGrapplingArm __instance) + { + bool rightArm = __instance.exosuit.rightArm as ExosuitGrapplingArm == __instance; + bool leftArm = __instance.exosuit.leftArm as ExosuitGrapplingArm == __instance; + if (__instance.hook.attached) + { + if(leftArm) + { + HapticsVR.leftSecondaryEffectsTimer += Time.deltaTime; + if(HapticsVR.leftSecondaryEffectsTimer > 0.8f) + { + HapticsVR.PlayHaptics(0.0f, 0.6f, 10f, 1.0f, false, false, true); + HapticsVR.leftSecondaryEffectsTimer = 0.0f; + } + } + if(rightArm) + { + HapticsVR.rightSecondaryEffectsTimer += Time.deltaTime; + if(HapticsVR.rightSecondaryEffectsTimer > 0.8f) + { + HapticsVR.PlayHaptics(0.0f, 0.6f, 10f, 1.0f, false, true, false); + HapticsVR.rightSecondaryEffectsTimer = 0.0f; + } + } + } + else if(__instance.hook.flying) + { + HapticsVR.effectsTimer += Time.deltaTime; + if(HapticsVR.effectsTimer > 0.1) + { + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, false, rightArm, leftArm); + HapticsVR.effectsTimer = 0.0f; + } + } + } + } + + [HarmonyPatch(typeof(ExosuitTorpedoArm), nameof(ExosuitTorpedoArm.Shoot))] + public static class ExosuitTorpedoArmHaptics + { + public static void Postfix(ExosuitTorpedoArm __instance) + { + //HapticsVR.PlayHaptics(0.3f, 0.4f, 10f, 0.2f, false, __instance.exosuit.rightArm as ExosuitTorpedoArm == __instance, __instance.exosuit.leftArm as ExosuitTorpedoArm == __instance); + HapticsVR.PlayHaptics(1.2f, 0.8f, 10f, 1.0f, false, __instance.exosuit.rightArm as ExosuitTorpedoArm == __instance, __instance.exosuit.leftArm as ExosuitTorpedoArm == __instance); + } + } + + //Detects a Hover Out so that an item can be triggered more than once + [HarmonyPatch(typeof(GUIHand), nameof(GUIHand.UpdateActiveTarget))] + public static class GUIHandEnable + { + public static void Postfix(GUIHand __instance) + { + //Mod.logger.LogInfo($"GUIHand.OnUpdate called activeTarget is null = {__instance.activeTarget == null}"); + if (__instance.activeTarget == null) + { + HapticsVR.lastHoverComponent = null; + } + } + } + + //Haptics when an item is picked up + [HarmonyPatch(typeof(Pickupable), nameof(Pickupable.PlayPickupSound))] + public static class PickupHaptics + { + public static void Postfix(Pickupable __instance) + { + //Mod.logger.LogInfo($"Pickupable.PlayPickupSound called"); + HapticsVR.PlayHaptics(0.0f, 0.2f, 5f, 1.0f); + } + } + + //Haptics when an item is dropped + [HarmonyPatch(typeof(Pickupable), nameof(Pickupable.PlayDropSound))] + public static class DropHaptics + { + public static void Postfix(Pickupable __instance) + { + //Mod.logger.LogInfo($"Pickupable.PlayDropSound called"); + HapticsVR.PlayHaptics(0.0f, 0.2f, 5f, 1.0f); + } + } + + //Damage from Vehicles going too deep + [HarmonyPatch(typeof(CrushDamage), nameof(CrushDamage.CrushDamageUpdate))] + public static class CrushDamagetHaptics + { + public static void Postfix(CrushDamage __instance) + { + if (__instance.GetCanTakeCrushDamage() && __instance.GetDepth() > __instance.crushDepth) + { + Mod.logger.LogInfo($"DamageFX.CrushDamageUpdate called"); + HapticsVR.PlayHaptics(0.0f, 0.5f, 10f, 1.0f, false, true, true); + } + } + } + + //Handles several damage types but triggers for some unknown things so filter them out + [HarmonyPatch(typeof(SoundOnDamage), nameof(SoundOnDamage.OnTakeDamage))] + public static class SoundOnDamageHaptics + { + public static void Postfix(SoundOnDamage __instance, DamageInfo damageInfo) + { + Mod.logger.LogInfo($"DamageFX.SoundOnDamage called {damageInfo.damage} {damageInfo.type}"); + if(!(damageInfo.type == DamageType.Normal && (damageInfo.damage == 30 || damageInfo.damage == 80))) + { + HapticsVR.PlayHaptics(0.0f, 0.4f, 10f, 1.0f, false, true, true); + } + } + } + + //Havent seen any instances of this firing + [HarmonyPatch(typeof(DamageOverTime), nameof(DamageOverTime.DoDamage))] + public static class DamageOverTimeHaptics + { + public static void Postfix(DamageOverTime __instance) + { + Mod.logger.LogInfo($"(DamageOverTime.DoDamage called "); + float dur = __instance.interval - 0.1f; + if (dur < 0) + { + dur = 0.1f; + } + HapticsVR.PlayHaptics(0.0f, dur, 10f, 1.0f, false, true, true); + } + } + + //Cyclops? + [HarmonyPatch(typeof(SubRoot), nameof(SubRoot.OnTakeDamage))] + public static class SubRootHaptics + { + public static void Postfix(SubRoot __instance, DamageInfo damageInfo) + { + Mod.logger.LogInfo($"SubRoot.OnTakeDamage called {damageInfo.damage}"); + if(damageInfo.damage > 0) + { + HapticsVR.PlayHaptics(0.0f, 0.4f, 10f, 1.0f, false, true, true); + } + } + } + + //Haptics while Seatruck is Blending attacked + [HarmonyPatch(typeof(SeaTruckSegment), nameof(SeaTruckSegment.Update))] + public static class SeaTruckAttackHaptics + { + public static void Postfix(SeaTruckSegment __instance) + { + if(__instance.underCreatureAttack) + { + //Mod.logger.LogInfo($"SeaTruckSegment.Update under creature attack = {__instance.underCreatureAttack}"); + HapticsVR.effectsTimer += Time.deltaTime; + if(HapticsVR.effectsTimer > 0.70) + { + HapticsVR.PlayHaptics(0.0f, 0.55f, 10f, 1.0f, false, true, true); + HapticsVR.effectsTimer = 0.0f; + } + } + } + } + + [HarmonyPatch(typeof(GamepadVibration), nameof(GamepadVibration.Vibrate))] + public static class GamepadVibrationHaptics + { + public static void Postfix( float duration, float leftIntesity, float rightIntensity) + { + Mod.logger.LogInfo($"GamepadVibration.Vibrate called {duration} {leftIntesity} {rightIntensity}"); + HapticsVR.PlayHaptics(0.0f, duration, 10f, rightIntensity, false, true, true, leftIntesity); + } + } + + //When health is very low + [HarmonyPatch(typeof(uGUI_HealthBar), nameof(uGUI_HealthBar.OnPulse))] + public static class PulseHaptics + { + public static void Postfix(uGUI_HealthBar __instance, float scalar) + { + if(__instance.statePulse.normalizedTime > 0 && HapticsVR.effectsTimer == 0.0f) + { + //Mod.logger.LogInfo($"uGUI_HealthBar.OnPulse called scaler = {scalar} statePulse = {__instance.statePulse.normalizedTime}"); + CoroutineHost.StartCoroutine(HapticsVR.PlayerHeartbeat()); + } + HapticsVR.effectsTimer = __instance.statePulse.normalizedTime; + } + } + + //Player taking direct damage + //This is not called if the damage is enough to kill the player + [HarmonyPatch(typeof(Player), nameof(Player.OnTakeDamage))] + public static class PlayerDamageHaptics + { + public static void Postfix(Player __instance, DamageInfo damageInfo) + { + float intensity = damageInfo.damage * 2f / 100f; + float shakeAmount = Mathf.Clamp(intensity, 0f, 5f); + Mod.logger.LogInfo($"Player.OnTakeDamage called damage = {damageInfo.damage} intensity = {intensity} duration = {shakeAmount * 2f}"); + HapticsVR.PlayHaptics(0.0f, shakeAmount * 2f, 10f, Mathf.Clamp(intensity, 0f, 1f), false, true, true); + + } + } + + //Handler that triggers even if player damage is enough to kill the player + [HarmonyPatch(typeof(LeviathanAttackTimelineManager), nameof(LeviathanAttackTimelineManager.OnPlayerAttack))] + public static class LeviathanPlayerDamageHaptics + { + public static void Postfix(LeviathanAttackTimelineManager __instance) + { + Mod.logger.LogInfo($"LeviathanAttackTimelineManager.OnPlayerAttack called"); + HapticsVR.PlayHaptics(0.0f, 4.0f, 10f, 1.0f, false, true, true); + + } + } + + //When the player dies + //Called after death, not during attack + [HarmonyPatch(typeof(Player), nameof(Player.OnKill))] + public static class DeathHaptics + { + public static void Postfix(Player __instance) + { + CoroutineHost.StartCoroutine(HapticsVR.PlayerDeath()); + } + } + + + + [HarmonyPatch(typeof(Knife), nameof(Knife.OnToolUseAnim))] + public static class KnifeHaptics + { + public static void Postfix(Knife __instance) + { + Mod.logger.LogInfo($"Knife.OnToolUseAnim"); + HapticsVR.PlayHaptics(0.0f, 0.2f, 5f, 1.0f); + } + } + + [HarmonyPatch(typeof(uGUI_PDA), nameof(uGUI_PDA.OnOpenPDA))] + class PDAOpenHaptics + { + public static void Postfix(uGUI_PDA __instance) + { + HapticsVR.PlayHaptics(0.0f, 0.2f, 20f, 0.5f, false, false, true); + } + } + + [HarmonyPatch(typeof(uGUI_PDA), nameof(uGUI_PDA.OnClosePDA))] + class PDACloseHaptics + { + public static void Postfix(uGUI_PDA __instance) + { + HapticsVR.PlayHaptics(0.0f, 0.2f, 10f, 0.5f, false, false, true); + } + } + + //Propulsion cannon when shooting objects + //Same code is used for both in and out of the exosuit + [HarmonyPatch(typeof(PropulsionCannon), nameof(PropulsionCannon.OnShoot))] + class PropulsionCannonShootHaptics + { + public static void Postfix(PropulsionCannon __instance) + { + bool rightArm = true; + bool leftArm = false; + Exosuit exosuit = VehiclesVR.PilotedExosuit(); + Mod.logger.LogInfo($"PropulsionCannon.OnShoot in vehicle exosuit = {exosuit != null}"); + if(exosuit != null) + { + rightArm = (exosuit.rightArm as ExosuitPropulsionArm).propulsionCannon == __instance; + leftArm = (exosuit.leftArm as ExosuitPropulsionArm).propulsionCannon == __instance; + } + + HapticsVR.PlayHaptics(0.0f, 0.6f, 10f, 1.0f, false, rightArm, leftArm); + } + } + + + //While actively holding an object, use a pulsating haptic + //Same code is used for both in and out of the exosuit + [HarmonyPatch(typeof(PropulsionCannon), nameof(PropulsionCannon.Update))] + class PropulsionCannonHoldHaptics + { + public static void Postfix(PropulsionCannon __instance) + { + if ((__instance.grabbedObject != null && __instance.grabbedObject.GetComponent() != null) || __instance.firstUseGrabbedObject != null) + { + Exosuit exosuit = VehiclesVR.PilotedExosuit(); + if(exosuit != null) + { + bool rightArm = rightArm = (exosuit.rightArm as ExosuitPropulsionArm).propulsionCannon == __instance; + bool leftArm = leftArm = (exosuit.leftArm as ExosuitPropulsionArm).propulsionCannon == __instance; + if(leftArm) + { + HapticsVR.leftSecondaryEffectsTimer += Time.deltaTime; + if(HapticsVR.leftSecondaryEffectsTimer > 0.9f) + { + HapticsVR.PlayHaptics(0.0f, 0.6f, 10f, 0.4f, false, false, true); + HapticsVR.leftSecondaryEffectsTimer = 0.0f; + } + } + if(rightArm) + { + HapticsVR.rightSecondaryEffectsTimer += Time.deltaTime; + if(HapticsVR.rightSecondaryEffectsTimer > 0.9f) + { + HapticsVR.PlayHaptics(0.0f, 0.6f, 10f, 0.4f, false, true, false); + HapticsVR.rightSecondaryEffectsTimer = 0.0f; + } + } + + } + else + { + HapticsVR.effectsTimer += Time.deltaTime; + if(HapticsVR.effectsTimer > 0.9) + { + HapticsVR.PlayHaptics(0.0f, 0.6f, 10f, 0.4f, false, true, __instance.firstUseGrabbedObject != null); + HapticsVR.effectsTimer = 0.0f; + } + } + } + } + } + + //Laser cutter has a randomized amplitude and on/off period + [HarmonyPatch(typeof(LaserCutter), nameof(LaserCutter.Update))] + class LaserCutterHaptics + { + public static void Postfix(LaserCutter __instance) + { + if (__instance.activeCuttingTarget != null && __instance.activeCuttingTarget.openedAmount < __instance.activeCuttingTarget.maxOpenedAmount && __instance.GetUsedToolThisFrame()) + { + HapticsVR.effectsTimer += Time.deltaTime; + if(HapticsVR.effectsTimer > HapticsVR.effectsTimeout) + { + Mod.logger.LogInfo($"LaserCutter.Update {Time.deltaTime}"); + float dur = UnityEngine.Random.Range(0.01f, 0.20f); + HapticsVR.PlayHaptics(0.0f, dur, 10f, UnityEngine.Random.Range(0f, 1.0f), false); + HapticsVR.effectsTimer = 0.0f; + HapticsVR.effectsTimeout = UnityEngine.Random.Range(dur, dur + 0.20f); + } + } + } + } + + //Welder has a kind of slow sine wave haptic + [HarmonyPatch(typeof(Welder), nameof(Welder.Update))] + class WelderHaptics + { + public static void Postfix(Welder __instance) + { + + if (__instance.activeWeldTarget != null && __instance.activeWeldTarget.GetHealthFraction() < 1f && __instance.GetUsedToolThisFrame()) + { + HapticsVR.effectsTimer += Time.deltaTime; + if(HapticsVR.effectsTimer > 0.10) + { + Mod.logger.LogInfo($"Welder.Update {Time.deltaTime}"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, HapticsVR.effectsScale, false); + HapticsVR.effectsTimer = 0.0f; + HapticsVR.effectsScale += 0.10f; + if(HapticsVR.effectsScale > 1.0f) + { + HapticsVR.effectsScale = 0.0f; + } + } + } + } + } + + //ScannerTool has a kind of quick sine wave haptic + [HarmonyPatch(typeof(ScannerTool), nameof(ScannerTool.Update))] + class ScannerToolHaptics + { + public static void Postfix(ScannerTool __instance) + { + + if (PDAScanner.isScanning && __instance.GetUsedToolThisFrame()) + { + HapticsVR.effectsTimer += Time.deltaTime; + if(HapticsVR.effectsTimer > 0.10) + { + Mod.logger.LogInfo($"ScannerTool.Update {Time.deltaTime}"); + HapticsVR.PlayHaptics(0.0f, 0.10f, 10f, HapticsVR.effectsScale, false); + HapticsVR.effectsTimer = 0.0f; + HapticsVR.effectsScale += 0.20f; + if(HapticsVR.effectsScale > 1.0f) + { + HapticsVR.effectsScale = 0.0f; + } + } + } + } + } + + //BuilderTool has a kind of quick inverse sine wave haptic + [HarmonyPatch(typeof(BuilderTool), nameof(BuilderTool.Update))] + class BuilderToolHaptics + { + public static void Postfix(BuilderTool __instance) + { + + if (__instance.isConstructing && __instance.GetUsedToolThisFrame()) + { + HapticsVR.effectsTimer += Time.deltaTime; + if(HapticsVR.effectsTimer > 0.10) + { + Mod.logger.LogInfo($"BuilderTool.Update {Time.deltaTime}"); + HapticsVR.PlayHaptics(0.0f, 0.10f, 10f, HapticsVR.effectsScale, false); + HapticsVR.effectsTimer = 0.0f; + HapticsVR.effectsScale -= 0.20f; + if(HapticsVR.effectsScale < 0.0f) + { + HapticsVR.effectsScale = 1.0f; + } + } + } + } + } +/* Doesnt handle the "hover out" so only fires once + [HarmonyPatch(typeof(BuilderTool), nameof(BuilderTool.OnHover))] + [HarmonyPatch(new Type[] { typeof(Constructable) })] + public static class ConstructableHoverHaptics + { + public static void Postfix(BuilderTool __instance, Constructable constructable) + { + //Play Haptics + if (constructable.constructed && !constructable.deconstructionAllowed) + { + HapticsVR.lastConstructable = null; + return; + } + if(constructable == HapticsVR.lastConstructable) + { + return; + } + + HapticsVR.lastConstructable = constructable; + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f); + } + } + + [HarmonyPatch(typeof(BuilderTool), nameof(BuilderTool.OnHover))] + [HarmonyPatch(new Type[] { typeof(BaseDeconstructable) })] + public static class DeconstructableHoverHaptics + { + public static void Postfix(BaseDeconstructable __instance, BaseDeconstructable deconstructable) + { + //Play Haptics + if(deconstructable == HapticsVR.lastDeconstructable) + { + return; + } + + HapticsVR.lastDeconstructable = deconstructable; + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f); + } + } +*/ + + + //UI haptics + + + [HarmonyPatch(typeof(uGUI_ButtonSound), nameof(uGUI_ButtonSound.OnPointerEnter))] + public static class uGUI_ButtonSoundHaptics + { + public static void Postfix(uGUI_ButtonSound __instance) + { + //Mod.logger.LogInfo($"uGUI_ButtonSound.OnPointerEnter"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + [HarmonyPatch(typeof(uGUI_OptionSelection), nameof(uGUI_OptionSelection.OnSelect))] + public static class uGUI_ChoiceSoundHaptics + { + public static void Postfix(uGUI_OptionSelection __instance) + { + //Mod.logger.LogInfo($"uGUI_ChoiceSound.OnValueChanged"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + [HarmonyPatch(typeof(uGUI_ToggleOptionSound), nameof(uGUI_ToggleOptionSound.OnValueChanged))] + public static class uGUI_ToggleOptionSoundHaptics + { + public static void Postfix(uGUI_ToggleOptionSound __instance) + { + //Mod.logger.LogInfo($"uGUI_ToggleOptionSound.OnValueChanged"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + [HarmonyPatch(typeof(uGUI_SliderSound), nameof(uGUI_SliderSound.OnValueChanged))] + public static class uGUI_SliderSoundHaptics + { + public static void Postfix(uGUI_SliderSound __instance) + { + //Mod.logger.LogInfo($"uGUI_ToggleOptionSound.OnValueChanged"); + HapticsVR.PlayHaptics(0.0f, 0.03f, 20f, 0.2f, true); + } + } + + //This does most of the standard UI work + [HarmonyPatch(typeof(Selectable), nameof(Selectable.OnPointerEnter))] + public static class SelectableHaptics + { + public static void Postfix(Selectable __instance) + { + //Mod.logger.LogInfo($"Selectable.OnPointerEnter"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + //PDA Encyclopedia tab items + [HarmonyPatch(typeof(uGUI_ListEntry), nameof(uGUI_ListEntry.OnPointerEnter))] + class ListEntryHaptics + { + public static void Postfix(uGUI_ListEntry __instance) + { + //Mod.logger.LogInfo($"uGUI_ListEntry.OnPointerEnter"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + //The equipents slots in the inventory section of the PDA + [HarmonyPatch(typeof(uGUI_EquipmentSlot), nameof(uGUI_EquipmentSlot.OnPointerEnter))] + class EquipmentSlotHaptics + { + public static void Postfix(uGUI_EquipmentSlot __instance) + { + //Mod.logger.LogInfo($"uGUI_EquipmentSlot.OnPointerEnter"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + [HarmonyPatch(typeof(uGUI_BlueprintsTab), nameof(uGUI_BlueprintsTab.OnPointerEnter))] + class BlueprintsHaptics + { + public static void Postfix(uGUI_BlueprintsTab __instance) + { + //Mod.logger.LogInfo($"uGUI_EquipmentSlot.OnPointerEnter"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + [HarmonyPatch(typeof(uGUI_ItemIcon), nameof(uGUI_ItemIcon.OnPointerEnter))] + [HarmonyPatch(new Type[] { })] + class ItemIconHaptics + { + public static void Postfix(uGUI_ItemIcon __instance) + { + //Mod.logger.LogInfo($"uGUI_ItemIcon.OnPointerEnter"); + HapticsVR.PlayHaptics(0.0f, 0.05f, 10f, 0.15f, true); + } + } + + +/* All of these are handled by uGUI_ItemIcon above + //Builder Menu items + [HarmonyPatch(typeof(uGUI_BuilderMenu), nameof(uGUI_BuilderMenu.OnPointerEnter))] + class BuilderMenuItemsHaptics + { + public static void Postfix(uGUI_BuilderMenu __instance, string id) + { + Mod.logger.LogInfo($"uGUI_BuilderMenu.OnPointerEnter"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + //Builder Menu Tab selector + [HarmonyPatch(typeof(uGUI_BuilderMenu), nameof(uGUI_BuilderMenu.GetToolbarTooltip))] + class BuilderMenuCategoriesHaptics + { + public static void Postfix(uGUI_BuilderMenu __instance, int index) + { + if(index == HapticsVR.lastBuilderCatID) + { + return; + } + + Mod.logger.LogInfo($"uGUI_BuilderMenu.GetToolbarTooltip"); + HapticsVR.lastBuilderCatID = index; + + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + //PDA Tab selector + [HarmonyPatch(typeof(uGUI_PDA), nameof(uGUI_PDA.GetToolbarTooltip))] + class PDACategoriesHaptics + { + public static void Postfix(uGUI_PDA __instance, int index) + { + if(index == HapticsVR.lastPDACatID) + { + return; + } + + Mod.logger.LogInfo($"uGUI_PDA.GetToolbarTooltip"); + HapticsVR.lastPDACatID = index; + + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + //PDA Inventory tab items + [HarmonyPatch(typeof(uGUI_InventoryTab), nameof(uGUI_InventoryTab.OnPointerEnter))] + class ListItemHaptics + { + public static void Postfix(uGUI_InventoryTab __instance) + { + Mod.logger.LogInfo($"uGUI_InventoryTab.OnPointerEnter"); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + + //The hover sound that is played in the PDA Photo Manager triggers this haptic + [HarmonyPatch(typeof(SoundQueue), nameof(SoundQueue.Play))] + [HarmonyPatch(new Type[] { typeof(string), typeof(SoundHost), typeof(bool), typeof(string), typeof(int), typeof(int) })] + class HoverSoundHaptics + { + public static void Postfix(SoundQueue __instance, string sound) + { + Mod.logger.LogInfo($"SoundQueue.Play {sound}"); + if(sound == "{d25126e6-061e-4bd1-bd93-934c6fd5f24c}") + { + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + } + } + } +*/ + + #endregion + +} diff --git a/SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs b/SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs index 985774f..e9917c8 100644 --- a/SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs +++ b/SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs @@ -22,6 +22,7 @@ static class MoveExosuitCameraPivot { public static void Postfix(Exosuit __instance) { + Mod.logger.LogInfo($"Exosuit.OnPilotModeBegin called"); VRCameraRig.instance.isPilotingExosuit = true; } } @@ -41,7 +42,7 @@ public static void Postfix(SeaTruckMotor __instance) { if(__instance.IsPiloted()) { - VRCameraRig.instance.isPilotingSeaTruck = true; + VRCameraRig.instance.isPilotingSeaTruck = true; } } } diff --git a/SubmersedVR/Tweaks/RecenterInVehicles.cs b/SubmersedVR/Tweaks/RecenterInVehicles.cs index abf6f6a..6b75818 100644 --- a/SubmersedVR/Tweaks/RecenterInVehicles.cs +++ b/SubmersedVR/Tweaks/RecenterInVehicles.cs @@ -2,6 +2,13 @@ namespace SubmersedVR { + static class VehiclesVR + { + public static Exosuit PilotedExosuit() + { + return Player.main?.currentMountedVehicle == null ? null : Player.main?.currentMountedVehicle as Exosuit; + } + } // [HarmonyPatch(typeof(SeaMoth), nameof(SeaMoth.OnPlayerEntered))] // static class RecenterInSeamoth diff --git a/SubmersedVR/VR/VRCameraRig.cs b/SubmersedVR/VR/VRCameraRig.cs index 2673934..abf7335 100644 --- a/SubmersedVR/VR/VRCameraRig.cs +++ b/SubmersedVR/VR/VRCameraRig.cs @@ -374,6 +374,8 @@ public void LateUpdate() //Player.main.armsController.transform.position = MainCameraControl.main.transform.position + (MainCameraControl.main.transform.forward * zOffset) + new Vector3(0f, yOffset, 0f); } } + Player.main.armsController.transform.localScale = new Vector3(Settings.PlayerScale, Settings.PlayerScale, Settings.PlayerScale); + } } @@ -421,24 +423,24 @@ public void SetWorldTarget(GameObject activeTarget, float activeHitDistance) [HarmonyPatch(typeof(MainCameraControl), nameof(MainCameraControl.OnLateUpdate))] public static class PlayerPositionFixer { - static bool Prefix(MainCameraControl __instance) + static bool Prefix(MainCameraControl __instance) { float zOffset = 0.0f; float yOffset = 0.0f; if(VRCameraRig.instance?.isPilotingSeaTruck == true) { - zOffset = 0.2f + Settings.SeaTruckZOffset; - yOffset = 0.2f + Settings.SeaTruckYOffset; + zOffset += 0.2f + Settings.SeaTruckZOffset; + yOffset += 0.2f + Settings.SeaTruckYOffset; } else if(VRCameraRig.instance?.isPilotingSnowbike == true) { - zOffset = 0.07f + Settings.SnowBikeZOffset; - yOffset = 0.2f + Settings.SnowBikeYOffset; + zOffset += 0.07f + Settings.SnowBikeZOffset; + yOffset += 0.2f + Settings.SnowBikeYOffset; } else if(VRCameraRig.instance?.isPilotingExosuit == true) { - zOffset = 0.1f + Settings.ExosuitZOffset; - yOffset = 0.2f + Settings.ExosuitYOffset; + zOffset += 0.1f + Settings.ExosuitZOffset; + yOffset += 0.2f + Settings.ExosuitYOffset; } __instance.cameraUPTransform.localPosition = new Vector3(__instance.cameraUPTransform.localPosition.x, yOffset, zOffset); diff --git a/SubmersedVR/VR/VRHands.cs b/SubmersedVR/VR/VRHands.cs index 5a3e862..3abca78 100644 --- a/SubmersedVR/VR/VRHands.cs +++ b/SubmersedVR/VR/VRHands.cs @@ -272,7 +272,7 @@ IEnumerator UpdateBodyRendering() bool retry = true; while (retry) { - Mod.logger.LogInfo($"UpdateBodyRendering {Settings.FullBody}"); + //Mod.logger.LogInfo($"UpdateBodyRendering {Settings.FullBody}"); var bodyRenderers = transform.GetComponentsInChildren().Where(r => r.name.Contains("body") || r.name.Contains("vest")); foreach (var bodyRenderer in bodyRenderers) @@ -355,6 +355,18 @@ public static void Postfix(ArmsController __instance, PlayerTool tool) } } + //Dont do the built in tool animations when first picking up a tool + [HarmonyPatch(typeof(PlayerTool), nameof(PlayerTool.Awake))] + public static class DisableFirstUseToolAnimations + { + [HarmonyPostfix] + public static void Postfix(PlayerTool __instance) + { + __instance.hasFirstUseAnimation = false; + + } + } + // This removes the animation that inspects the object/tool when equipped for the first time [HarmonyPatch(typeof(ArmsController), nameof(ArmsController.StartInspectObjectAsync))] public static class DontInspectObjectAnimation diff --git a/SubmersedVR/VR/VRHud.cs b/SubmersedVR/VR/VRHud.cs index 341f53b..adfbe3e 100644 --- a/SubmersedVR/VR/VRHud.cs +++ b/SubmersedVR/VR/VRHud.cs @@ -314,12 +314,11 @@ public static class ResetHudStaticInVehicles { public static void Postfix(Vehicle __instance) { - // TODO: How to check for SeaTruck? Mod.logger.LogInfo("Vehicle.OnPilotModeEnd {__instance is Exosuit}"); if (__instance is Exosuit) { VRHud.OnExitVehicle(); - //This moves player off of the top of the suit + //This moves player off of the top of the suit after exiting Player.main.transform.position = Player.main.transform.position + SNCameraRoot.main.transform.forward * -2.5f; Player.main.transform.localPosition += new Vector3(0.0f, 0.5f, 0.0f); } @@ -369,10 +368,15 @@ public static void Postfix() [HarmonyPatch(typeof(SeaTruckMotor), nameof(SeaTruckMotor.StopPiloting))] static class ResetHudStaticInSeaTrucker { - public static void Postfix() + public static void Prefix(SeaTruckMotor __instance, bool waitForDocking = false, bool forceStop = false, bool skipUnsubscribe = false, bool immediate = false, bool forceGetupAnimation = false) { - Mod.logger.LogInfo("SeaTruckMotor.StopPiloting"); - VRHud.OnExitVehicle(); + //Mod.logger.LogInfo($"SeaTruckMotor.StopPiloting waitForDocking = {waitForDocking} forceStop = {forceStop} skipUnsubscribe = {skipUnsubscribe} immediate = {immediate} forceGetupAnimation = {forceGetupAnimation}"); + //Doing this check because SeaTruckMotor.StopPiloting gets called many times at startup for an unknown reason + if (__instance.piloting) + { + Mod.logger.LogInfo($"SeaTruckMotor.StopPiloting"); + VRHud.OnExitVehicle(); + } } } From e5d0ee150ec4e521e120b5498c60b0c78fe4344e Mon Sep 17 00:00:00 2001 From: jbusfield Date: Tue, 5 Sep 2023 16:36:58 -0700 Subject: [PATCH 2/9] Added snap turn for exosuit and snowbike. Fixed issues with various haptics. Fixed issue where tapping Wrist Hud did not toggle main HUD. Added "HUD ON/OFF" text to wrist . --- SubmersedVR/Input/SteamVrGameInput.cs | 38 +++- SubmersedVR/Settings.cs | 35 ++- SubmersedVR/Tweaks/Exosuit.cs | 78 +++++++ SubmersedVR/Tweaks/Haptics.cs | 52 +++-- SubmersedVR/Tweaks/Hoverbike.cs | 50 +++- SubmersedVR/Tweaks/MiscTweaks.cs | 1 + SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs | 4 +- SubmersedVR/Tweaks/SeaTruck.cs | 226 ++++++++++++++++++- SubmersedVR/Utils/DebugPanel.cs | 6 +- SubmersedVR/VR/VRCameraRig.cs | 13 +- SubmersedVR/VR/VRHands.cs | 5 +- SubmersedVR/VR/VRHud.cs | 106 ++++++++- 12 files changed, 570 insertions(+), 44 deletions(-) create mode 100644 SubmersedVR/Tweaks/Exosuit.cs diff --git a/SubmersedVR/Input/SteamVrGameInput.cs b/SubmersedVR/Input/SteamVrGameInput.cs index bf71c06..768eea7 100644 --- a/SubmersedVR/Input/SteamVrGameInput.cs +++ b/SubmersedVR/Input/SteamVrGameInput.cs @@ -11,6 +11,7 @@ namespace SubmersedVR using SteamVRActions.Valve.VR; using System.Collections.Generic; using System.Reflection; + using rail; #region Patches static class SteamVrGameInput @@ -18,6 +19,7 @@ static class SteamVrGameInput public static bool InputLocked = false; public static bool IsSteamVrReady = false; public static bool SnapTurned = false; + public static bool debugSnapTurn = false; public static bool ShouldIgnore(GameInput.Button button) { @@ -151,21 +153,47 @@ public static class SnapTurning { public static void Postfix(ref Vector2 __result) { - bool isInVehicle = Player.main?.currentMountedVehicle != null || (Player.main?.IsPiloting() == true); - if (Settings.IsSnapTurningEnabled && !isInVehicle) { + //bool isInVehicle = Player.main?.currentMountedVehicle != null || (Player.main?.IsPiloting() == true); + bool isInExosuit = Player.main?.inExosuit == true; + bool isOnSnowBike = Player.main?.inHovercraft == true; + bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; + + if ((Settings.IsSnapTurningEnabled && !(isInExosuit || isOnSnowBike || isPilotingSeatruck)) || (Settings.IsExosuitSnapTurningEnabled && isInExosuit) || (Settings.IsSnowBikeSnapTurningEnabled && isOnSnowBike)) + { + float angle = Settings.SnapTurningAngle; //player + if(Settings.IsExosuitSnapTurningEnabled && isInExosuit) + { + angle = Settings.ExosuitSnapTurningAngle; + } + if(Settings.IsSnowBikeSnapTurningEnabled && isOnSnowBike) + { + angle = Settings.SnowBikeSnapTurningAngle; + } + float lookX = __result.x; float absX = Mathf.Abs(lookX); float threshold = 0.5f; - if (absX > threshold && !SteamVrGameInput.SnapTurned) { - __result.x = Settings.SnapTurningAngle * Mathf.Sign(lookX); + if(SteamVrGameInput.debugSnapTurn) + { + Mod.logger.LogInfo($"GameInput.GetLookDelta inExosuit = {isInExosuit} onSnowBike = {isOnSnowBike} angle = {angle} looX = {lookX} absX = {absX} snapTurned = {SteamVrGameInput.SnapTurned}"); + } + if (absX > threshold && !SteamVrGameInput.SnapTurned) + { + __result.x = angle * Mathf.Sign(lookX); SteamVrGameInput.SnapTurned = true; - } else { + } + else + { __result.x = 0; if (absX <= threshold) { SteamVrGameInput.SnapTurned = false; } } } + //else + //{ + // Shader.SetGlobalFloat(ShaderPropertyID._UweVrFadeAmount, 1000f); + //} } } diff --git a/SubmersedVR/Settings.cs b/SubmersedVR/Settings.cs index c4d070e..cea0c9b 100644 --- a/SubmersedVR/Settings.cs +++ b/SubmersedVR/Settings.cs @@ -15,6 +15,15 @@ public class Settings public static float SnapTurningAngle = 45.0f; public static event FloatChanged SnapTurningAngleChanged; + public static bool IsExosuitSnapTurningEnabled; + public static event BooleanChanged IsExosuitSnapTurningEnabledChanged; + public static float ExosuitSnapTurningAngle = 45.0f; + public static event FloatChanged ExosuitSnapTurningAngleChanged; + public static bool IsSnowBikeSnapTurningEnabled; + public static event BooleanChanged IsSnowBikeSnapTurningEnabledChanged; + public static float SnowBikeSnapTurningAngle = 45.0f; + public static event FloatChanged SnowBikeSnapTurningAngleChanged; + public static bool IsDebugEnabled; public static event BooleanChanged IsDebugChanged; @@ -87,17 +96,39 @@ internal static void AddMenu(uGUI_OptionsPanel panel) int tab = panel.AddTab("Submersed VR"); panel.AddHeading(tab, "Controls"); - panel.AddToggleOption(tab, "Enable Snap Turning", IsSnapTurningEnabled, (value) => { IsSnapTurningEnabled = value; + panel.AddToggleOption(tab, "Enable Player Snap Turning", IsSnapTurningEnabled, (value) => { IsSnapTurningEnabled = value; if (IsSnapTurningEnabledChanged != null) { IsSnapTurningEnabledChanged(value); } }); - panel.AddChoiceOption(tab, "Snap Turning Angle(°)", new float[] {22.5f, 45, 90}, SnapTurningAngle, (value) => { + panel.AddChoiceOption(tab, "Player Snap Turning Angle(°)", new float[] {22.5f, 30f, 45f, 90f}, SnapTurningAngle, (value) => { SnapTurningAngle = value; if (SnapTurningAngleChanged != null) { SnapTurningAngleChanged(value); } }); + panel.AddToggleOption(tab, "Enable Prawn Suit Snap Turning", IsExosuitSnapTurningEnabled, (value) => { IsExosuitSnapTurningEnabled = value; + if (IsExosuitSnapTurningEnabledChanged != null) { + IsExosuitSnapTurningEnabledChanged(value); + } + }); + panel.AddChoiceOption(tab, "Prawn Suit Snap Turning Angle(°)", new float[] {22.5f, 30f, 45f, 90f}, ExosuitSnapTurningAngle, (value) => { + ExosuitSnapTurningAngle = value; + if (ExosuitSnapTurningAngleChanged != null) { + ExosuitSnapTurningAngleChanged(value); + } + }); + panel.AddToggleOption(tab, "Enable Snowfox Snap Turning", IsSnowBikeSnapTurningEnabled, (value) => { IsSnowBikeSnapTurningEnabled = value; + if (IsSnowBikeSnapTurningEnabledChanged != null) { + IsSnowBikeSnapTurningEnabledChanged(value); + } + }); + panel.AddChoiceOption(tab, "Snowfox Snap Turning Angle(°)", new float[] {22.5f, 30f, 45f, 90f}, SnowBikeSnapTurningAngle, (value) => { + SnowBikeSnapTurningAngle = value; + if (SnowBikeSnapTurningAngleChanged != null) { + SnowBikeSnapTurningAngleChanged(value); + } + }); panel.AddHeading(tab, "Comfort"); panel.AddSliderOption(tab, "SeaTruck Pilot Position Offset", SeaTruckZOffset, -0.4f, 0.4f, SeaTruckZOffset, 0.01f, (value) => { SeaTruckZOffset = value; }, SliderLabelMode.Float, "0.00"); diff --git a/SubmersedVR/Tweaks/Exosuit.cs b/SubmersedVR/Tweaks/Exosuit.cs new file mode 100644 index 0000000..0df3b91 --- /dev/null +++ b/SubmersedVR/Tweaks/Exosuit.cs @@ -0,0 +1,78 @@ +using HarmonyLib; +using UnityEngine; + +namespace SubmersedVR +{ + // Tweaks regarding the hoverbike in VR mode of the game + static class ExosuitVR + { + + } + + #region Patches + + //Snap turning override + [HarmonyPatch(typeof(Vehicle), nameof(Vehicle.Update))] + static class VehicleSnapTurnFixer + { + public static bool Prefix(Vehicle __instance) + { + if (__instance.CanPilot()) + { + __instance.steeringWheelYaw = Mathf.Lerp(__instance.steeringWheelYaw, 0f, Time.deltaTime); + __instance.steeringWheelPitch = Mathf.Lerp(__instance.steeringWheelPitch, 0f, Time.deltaTime); + if (__instance.mainAnimator) + { + __instance.mainAnimator.SetFloat("view_yaw", __instance.steeringWheelYaw * 70f); + __instance.mainAnimator.SetFloat("view_pitch", __instance.steeringWheelPitch * 45f); + } + } + if (__instance.GetPilotingMode() && __instance.CanPilot() && (__instance.moveOnLand || __instance.transform.position.y < Ocean.GetOceanLevel())) + { + Vector2 vector = AvatarInputHandler.main.IsEnabled() ? GameInput.GetLookDelta() : Vector2.zero; + __instance.steeringWheelYaw = Mathf.Clamp(__instance.steeringWheelYaw + vector.x * __instance.steeringReponsiveness, -1f, 1f); + __instance.steeringWheelPitch = Mathf.Clamp(__instance.steeringWheelPitch + vector.y * __instance.steeringReponsiveness, -1f, 1f); + if (__instance.controlSheme == Vehicle.ControlSheme.Submersible) + { + Mod.logger.LogInfo($"Vehicle.Update called scheme = {__instance.controlSheme}"); + float d = 3f; + __instance.useRigidbody.AddTorque(__instance.transform.up * vector.x * __instance.sidewaysTorque * 0.0015f * d, ForceMode.VelocityChange); + __instance.useRigidbody.AddTorque(__instance.transform.right * -vector.y * __instance.sidewaysTorque * 0.0015f * d, ForceMode.VelocityChange); + __instance.useRigidbody.AddTorque(__instance.transform.forward * -vector.x * __instance.sidewaysTorque * 0.0002f * d, ForceMode.VelocityChange); + } + else if (__instance.controlSheme == Vehicle.ControlSheme.Submarine || __instance.controlSheme == Vehicle.ControlSheme.Mech) + { + //Exosuit + if (vector.x != 0f) + { + if(Settings.IsExosuitSnapTurningEnabled == false) + { + __instance.useRigidbody.AddTorque(__instance.transform.up * vector.x * __instance.sidewaysTorque, ForceMode.VelocityChange); + } + if(Settings.IsExosuitSnapTurningEnabled == true) + { + __instance.useRigidbody.transform.rotation = Quaternion.Euler( new Vector3( __instance.useRigidbody.transform.rotation.eulerAngles.x, __instance.useRigidbody.transform.rotation.eulerAngles.y + vector.x, __instance.useRigidbody.transform.rotation.eulerAngles.z)); + } + } + } + else if (__instance.controlSheme == Vehicle.ControlSheme.Hoverbike) + { + Mod.logger.LogInfo($"Vehicle.Update called scheme = {__instance.controlSheme}"); + __instance.useRigidbody.AddRelativeTorque(new Vector3(vector.y, 0f, 0f)); + } + } + bool flag = __instance.IsPowered(); + if (__instance.wasPowered != flag) + { + __instance.wasPowered = flag; + __instance.OnPoweredChanged(flag); + } + __instance.ReplenishOxygen(); + + return false; + } + } + + #endregion + +} diff --git a/SubmersedVR/Tweaks/Haptics.cs b/SubmersedVR/Tweaks/Haptics.cs index fa5283e..741f9b3 100644 --- a/SubmersedVR/Tweaks/Haptics.cs +++ b/SubmersedVR/Tweaks/Haptics.cs @@ -28,6 +28,7 @@ static class HapticsVR public static float rightSecondaryEffectsTimer = 0.0f; public static float effectsTimeout = 0.0f; public static float effectsScale = 0.0f; + public static float effectsDuration = 0.0f; public static void PlayHaptics(float secondsFromNow, float durationSeconds, float frequency, float amplitude, bool isUI = false, bool rightController = true, bool leftController = false, float leftAmplitude = -1.0f) @@ -69,11 +70,21 @@ public static IEnumerator PlayerDeath() public static IEnumerator PlayerHeartbeat() { - HapticsVR.PlayHaptics(0.0f, 0.15f, 10f, 0.8f, false, true, true); + HapticsVR.PlayHaptics(0.0f, 0.15f, 10f, 0.8f, false, !Settings.PutBarsOnWrist, true); yield return new WaitForSeconds(0.2f); - HapticsVR.PlayHaptics(0.0f, 0.25f, 10f, 0.3f, false, true, true); + HapticsVR.PlayHaptics(0.0f, 0.25f, 10f, 0.3f, false, !Settings.PutBarsOnWrist, true); } + public static IEnumerator PlayerFrozen() + { + HapticsVR.PlayHaptics(0.5f, 5.0f, 10f, 0.3f, false, true, true); + yield return new WaitForSeconds(5.0f); + HapticsVR.PlayHaptics(0.0f, 5.0f, 10f, 0.2f, false, true, true); + yield return new WaitForSeconds(5.0f); + HapticsVR.PlayHaptics(0.0f, 5.0f, 10f, 0.1f, false, true, true); + } + + } #region Patches @@ -293,7 +304,7 @@ public static class SoundOnDamageHaptics public static void Postfix(SoundOnDamage __instance, DamageInfo damageInfo) { Mod.logger.LogInfo($"DamageFX.SoundOnDamage called {damageInfo.damage} {damageInfo.type}"); - if(!(damageInfo.type == DamageType.Normal && (damageInfo.damage == 30 || damageInfo.damage == 80))) + if(damageInfo.damage > 0 && !(damageInfo.type == DamageType.Cold && (damageInfo.damage == 0.5)) && !(damageInfo.type == DamageType.Normal && (damageInfo.damage == 7 || damageInfo.damage == 30 || damageInfo.damage == 80))) { HapticsVR.PlayHaptics(0.0f, 0.4f, 10f, 1.0f, false, true, true); } @@ -316,6 +327,15 @@ public static void Postfix(DamageOverTime __instance) } } + [HarmonyPatch(typeof(Brinewing), nameof(Brinewing.OnFreezePlayer))] + public static class BrinewingFreezeHaptics + { + public static void Postfix() + { + CoroutineHost.StartCoroutine(HapticsVR.PlayerFrozen()); + } + } + //Cyclops? [HarmonyPatch(typeof(SubRoot), nameof(SubRoot.OnTakeDamage))] public static class SubRootHaptics @@ -365,9 +385,9 @@ public static class PulseHaptics { public static void Postfix(uGUI_HealthBar __instance, float scalar) { - if(__instance.statePulse.normalizedTime > 0 && HapticsVR.effectsTimer == 0.0f) + //Mod.logger.LogInfo($"uGUI_HealthBar.OnPulse called scaler = {scalar} statePulse = {__instance.statePulse.normalizedTime}"); + if((__instance.statePulse.normalizedTime > 0 && HapticsVR.effectsTimer == 0.0f) || (__instance.statePulse.normalizedTime < HapticsVR.effectsTimer && __instance.statePulse.normalizedTime != 0f)) { - //Mod.logger.LogInfo($"uGUI_HealthBar.OnPulse called scaler = {scalar} statePulse = {__instance.statePulse.normalizedTime}"); CoroutineHost.StartCoroutine(HapticsVR.PlayerHeartbeat()); } HapticsVR.effectsTimer = __instance.statePulse.normalizedTime; @@ -455,8 +475,8 @@ public static void Postfix(PropulsionCannon __instance) Mod.logger.LogInfo($"PropulsionCannon.OnShoot in vehicle exosuit = {exosuit != null}"); if(exosuit != null) { - rightArm = (exosuit.rightArm as ExosuitPropulsionArm).propulsionCannon == __instance; - leftArm = (exosuit.leftArm as ExosuitPropulsionArm).propulsionCannon == __instance; + rightArm = (exosuit.rightArm as ExosuitPropulsionArm)?.propulsionCannon == __instance; + leftArm = (exosuit.leftArm as ExosuitPropulsionArm)?.propulsionCannon == __instance; } HapticsVR.PlayHaptics(0.0f, 0.6f, 10f, 1.0f, false, rightArm, leftArm); @@ -476,8 +496,8 @@ public static void Postfix(PropulsionCannon __instance) Exosuit exosuit = VehiclesVR.PilotedExosuit(); if(exosuit != null) { - bool rightArm = rightArm = (exosuit.rightArm as ExosuitPropulsionArm).propulsionCannon == __instance; - bool leftArm = leftArm = (exosuit.leftArm as ExosuitPropulsionArm).propulsionCannon == __instance; + bool rightArm = rightArm = (exosuit.rightArm as ExosuitPropulsionArm)?.propulsionCannon == __instance; + bool leftArm = leftArm = (exosuit.leftArm as ExosuitPropulsionArm)?.propulsionCannon == __instance; if(leftArm) { HapticsVR.leftSecondaryEffectsTimer += Time.deltaTime; @@ -657,7 +677,7 @@ public static class uGUI_ButtonSoundHaptics public static void Postfix(uGUI_ButtonSound __instance) { //Mod.logger.LogInfo($"uGUI_ButtonSound.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); } } [HarmonyPatch(typeof(uGUI_OptionSelection), nameof(uGUI_OptionSelection.OnSelect))] @@ -666,7 +686,7 @@ public static class uGUI_ChoiceSoundHaptics public static void Postfix(uGUI_OptionSelection __instance) { //Mod.logger.LogInfo($"uGUI_ChoiceSound.OnValueChanged"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); } } @@ -676,7 +696,7 @@ public static class uGUI_ToggleOptionSoundHaptics public static void Postfix(uGUI_ToggleOptionSound __instance) { //Mod.logger.LogInfo($"uGUI_ToggleOptionSound.OnValueChanged"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); } } @@ -697,7 +717,7 @@ public static class SelectableHaptics public static void Postfix(Selectable __instance) { //Mod.logger.LogInfo($"Selectable.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); } } @@ -708,7 +728,7 @@ class ListEntryHaptics public static void Postfix(uGUI_ListEntry __instance) { //Mod.logger.LogInfo($"uGUI_ListEntry.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); } } @@ -719,7 +739,7 @@ class EquipmentSlotHaptics public static void Postfix(uGUI_EquipmentSlot __instance) { //Mod.logger.LogInfo($"uGUI_EquipmentSlot.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); } } @@ -729,7 +749,7 @@ class BlueprintsHaptics public static void Postfix(uGUI_BlueprintsTab __instance) { //Mod.logger.LogInfo($"uGUI_EquipmentSlot.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f, true); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); } } diff --git a/SubmersedVR/Tweaks/Hoverbike.cs b/SubmersedVR/Tweaks/Hoverbike.cs index 362a112..7a0a2b7 100644 --- a/SubmersedVR/Tweaks/Hoverbike.cs +++ b/SubmersedVR/Tweaks/Hoverbike.cs @@ -12,7 +12,7 @@ static class HoverbikeVR #region Patches //This makes bike rotation occur immediately when moving the rotation control instead of using the "cone of view" - [HarmonyPatch(typeof(HoverbikeHeadControlManager), nameof(HoverbikeHeadControlManager.Update))] + [HarmonyPatch(typeof(HoverbikeHeadControlManager), nameof(HoverbikeHeadControlManager.Update))] public static class HoverbikeHeadControlFixer { @@ -20,7 +20,7 @@ public static bool Prefix(HoverbikeHeadControlManager __instance) { __instance.maxViewConeAperture = 0.0f; __instance.minViewConeAperture = 0.0f; - + return true; } @@ -29,6 +29,7 @@ public static void Postfix(HoverbikeHeadControlManager __instance) { if (__instance.activeManager && !Player.main.GetPDA().isInUse && !VROptions.enableCinematics) { + Mod.logger.LogInfo($"HoverbikeHeadControlManager.Update called vector2 = { __instance.overflowInput}"); __instance.smoothedBikeRot = new Vector3(0f, __instance.smoothedBikeRot.y, 0f); Vector3 b2 = new Vector3(__instance.defaultCameraPitch, 0f, 0f); MainCameraControl.main.vehicleOverrideHeadRot = __instance.smoothedBikeRot + __instance.lookOffset + b2; @@ -60,6 +61,51 @@ public static void Postfix() } } + //Snap turning override + [HarmonyPatch(typeof(Hoverbike), nameof(Hoverbike.PhysicsMove))] + static class HoverbikeSnapTurn + { + public static bool Prefix(Hoverbike __instance) + { + __instance.isPiloting = __instance.GetPilotingCraft(); + if (__instance.dockedPad) + { + return false; + } + if (!__instance.isPiloting) + { + return false; + } + __instance.moveDirection = (AvatarInputHandler.main.IsEnabled() ? GameInput.GetMoveDirection() : Vector3.zero); + new Vector3(__instance.moveDirection.x, 0f, __instance.moveDirection.z); + Vector2 vector = __instance.overflowInput; + if (__instance.energyMixin.IsDepleted()) + { + __instance.moveDirection = Vector3.zero; + } + __instance.appliedThrottle = (__instance.moveDirection != Vector3.zero); + float d = __instance.overWater ? (__instance.horizontalDampening / __instance.waterDampening) : __instance.horizontalDampening; + __instance.rb.AddTorque(__instance.transform.right * -vector.x * __instance.sidewaysTorque * __instance.verticalDampening, ForceMode.VelocityChange); + if(Settings.IsSnowBikeSnapTurningEnabled == false) + { + __instance.rb.AddTorque(__instance.transform.up * vector.y * __instance.sidewaysTorque * d, ForceMode.VelocityChange); + } + if(Settings.IsSnowBikeSnapTurningEnabled == true) + { + __instance.rb.transform.rotation = Quaternion.Euler( new Vector3( __instance.rb.transform.rotation.eulerAngles.x, __instance.rb.transform.rotation.eulerAngles.y + vector.y, __instance.rb.transform.rotation.eulerAngles.z)); + } + Vector3 velocity = __instance.rb.velocity; + Vector3 point = __instance.moveDirection; + Mathf.Min(1f, point.magnitude); + point.y = 0f; + point.Normalize(); + __instance.horizMoveDir = MainCamera.camera.transform.rotation * point; + float d2 = __instance.overWater ? (__instance.forwardAccel / __instance.waterDampening) : __instance.forwardAccel; + __instance.rb.AddForce(__instance.horizMoveDir * d2); + + return false; + } + } #endregion diff --git a/SubmersedVR/Tweaks/MiscTweaks.cs b/SubmersedVR/Tweaks/MiscTweaks.cs index 4ba42f2..53facd6 100644 --- a/SubmersedVR/Tweaks/MiscTweaks.cs +++ b/SubmersedVR/Tweaks/MiscTweaks.cs @@ -68,6 +68,7 @@ public static void Postfix(GameInput __instance, bool useKeyboard, bool useContr } if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 106) //F5 key { + //SteamVrGameInput.debugSnapTurn = !SteamVrGameInput.debugSnapTurn; /* foreach (HideForScreenshots hideForScreenshots in UnityEngine.Object.FindObjectsOfType()) { diff --git a/SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs b/SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs index e9917c8..2c3b919 100644 --- a/SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs +++ b/SubmersedVR/Tweaks/MoveVehicleCameraPivot.cs @@ -14,7 +14,7 @@ namespace SubmersedVR // __instance.playerPosition.transform.localPosition = new Vector3(0.0f, 0.0f, 0.2f); // } // } - +/* //Set booleans in the VRCameraRig so component lookups dont need to be called during OnLateUpdate() //Actual offsets are handled in MainCameraControl.OnLateUpdate [HarmonyPatch(typeof(Exosuit), nameof(Exosuit.OnPilotModeBegin))] @@ -74,5 +74,5 @@ public static void Postfix(Hoverbike __instance) VRCameraRig.instance.isPilotingSnowbike = false; } } - +*/ } \ No newline at end of file diff --git a/SubmersedVR/Tweaks/SeaTruck.cs b/SubmersedVR/Tweaks/SeaTruck.cs index 4535688..777297c 100644 --- a/SubmersedVR/Tweaks/SeaTruck.cs +++ b/SubmersedVR/Tweaks/SeaTruck.cs @@ -1,4 +1,6 @@ using HarmonyLib; +using UnityEngine; +using UnityEngine.PostProcessing; namespace SubmersedVR { @@ -23,7 +25,229 @@ public static bool Prefix() return true; } } - + + + //Sep-2022 49371 +/* + [HarmonyPatch(typeof(PostProcessingBehaviour), "OnEnable")] + static class PostProcessingBehaviour_OnEnable_Patch + { + private static VignetteModel.Settings defaultVignetteSettings; + static void Prefix(PostProcessingBehaviour __instance) + { + Mod.logger.LogInfo($"PostProcessingBehaviour.OnEnable called"); + if(__instance.profile != null) + { + defaultVignetteSettings = __instance.profile.vignette.settings; + } + } + } + + [HarmonyPatch(typeof(PostProcessingBehaviour), "OnPreCull")] + static class PostProcessingBehaviour_OnPreCull_Patch + { + static void Postfix(PostProcessingBehaviour __instance, ref VignetteComponent ___m_Vignette, ref BloomComponent ___m_Bloom, ref EyeAdaptationComponent ___m_EyeAdaptation, ref DepthOfFieldComponent ___m_DepthOfField, ref MotionBlurComponent ___m_MotionBlur, ref ColorGradingComponent ___m_ColorGrading, ref TaaComponent ___m_Taa, ref FxaaComponent ___m_Fxaa, ref AmbientOcclusionComponent ___m_AmbientOcclusion) + { + Mod.logger.LogInfo($"PostProcessingBehaviour.OnPreCull called enabled = {__instance.enabled} profile = {__instance.profile != null} camera = {__instance.GetComponent() != null}"); + if (true) + { + + VignetteModel.Settings vSettings = new VignetteModel.Settings + { + mode = VignetteModel.Mode.Classic, + intensity = 0.8f, + color = new Color(0f, 0f, 0f, 1.0f), + center = new Vector2(0.5f,0.5f), + smoothness = 0.5f, + roundness = 1f, + rounded = true + }; + ___m_Vignette.model.settings = vSettings; + ___m_Vignette.model.enabled = true; + ___m_Vignette.model.m_Enabled = true; + } + } + } +*/ +/* + [HarmonyPatch(typeof(UwePostProcessingManager), nameof(UwePostProcessingManager.Update))] + static class PPFixer + { + public static void Postfix(UwePostProcessingManager __instance) + { + Mod.logger.LogInfo($"(UwePostProcessingManager.Update called"); + __instance.behaviour.enabled = false; + VignetteModel.Settings settings = UwePostProcessingManager.currentProfile.vignette.settings; + settings.mode = VignetteModel.Mode.Classic; + settings.intensity = 0.8f; + settings.color = new Color(1f, 0f, 0f, 1.0f); + settings.center = new Vector2(0.5f,0.5f); + settings.smoothness = 0.5f; + settings.roundness = 1f; + settings.rounded = true; + + UwePostProcessingManager.currentProfile.vignette.settings = settings; + UwePostProcessingManager.currentProfile.vignette.enabled = true; + __instance.behaviour.enabled = true; + } + } +*/ + +/* + //This correctly does snap turning but if the seatruck has many modules attached to it + //then a snap turn near fixed geometry can cause an extreme physics collision that will throw the seatruck far away. + //It will probably be better to do a rigid body turn with a tunnel vignette overlay but I cant + //get vignette post processing to work + [HarmonyPatch(typeof(SeaTruckMotor), nameof(SeaTruckMotor.Update))] + static class SeaTruckMotorSnapTurnFixer + { + public static bool Prefix(SeaTruckMotor __instance) + { + + // PostProcessingBehaviour component = VRCameraRig.instance.vrCamera.GetComponent(); + // if(component != null) + // { + // VignetteModel.Settings settings = component.profile.vignette.settings; + // settings.mode = VignetteModel.Mode.Classic; + // settings.intensity = 1.0f; + // settings.color = new Color(1f, 0f, 0f, 1.0f); + // settings.center = new Vector2(0.5f,0.5f); + // settings.smoothness = 0.5f; + // settings.roundness = 1f; + // settings.rounded = true; + // component.profile.vignette.settings = settings; + // component.profile.vignette.enabled = true; + // component.enabled = true; + // } + + + if (__instance.afterBurnerActive && Time.time > __instance.afterBurnerTime) + { + __instance.afterBurnerActive = false; + } + __instance.UpdateDrag(); + if (__instance.piloting && __instance.useRigidbody != null && !__instance.IsBusyAnimating() && !__instance.waitForDocking) + { + if (((__instance.truckSegment.isMainCab ? (AvatarInputHandler.main.IsEnabled() && !Player.main.GetPDA().isInUse) : __instance.inputStackDummy.activeInHierarchy) & (__instance.dockable == null || (!__instance.dockable.isDocked && !__instance.dockable.isInTransition))) && GameInput.GetButtonDown(GameInput.Button.Exit)) + { + __instance.StopPiloting(false, false, false, false, false, null); + } + else if (!__instance.truckSegment.isMainCab && GameInput.GetButtonDown(GameInput.Button.PDA)) + { + __instance.StopPiloting(false, false, false, false, false, null); + __instance.OpenPDADelayed(0.7f); + } + else if (!__instance.truckSegment.isMainCab && __instance.pilotPosition.position.y > -1.5f) + { + __instance.StopPiloting(false, false, false, false, false, null); + } + else if (!__instance.truckSegment.underCreatureAttack && __instance.IsPowered()) + { + if (__instance.CanTurn()) + { + Vector2 vector = (AvatarInputHandler.main.IsEnabled() || __instance.inputStackDummy.activeInHierarchy) ? GameInput.GetLookDelta() : Vector2.zero; + //vector.x = Mathf.Clamp(vector.x, -30f, 30f); + vector.y = Mathf.Clamp(vector.y, -30f, 30f); + //Mod.logger.LogInfo($"SeaTruckMotor.Update called vector = {vector} animator = {__instance.animator}"); + Int2 joystickDirection; + if (vector.x > 0f) + { + joystickDirection.x = 1; + } + else if (vector.x < 0f) + { + joystickDirection.x = -1; + } + else + { + joystickDirection.x = 0; + } + if (vector.y > 0f) + { + joystickDirection.y = -1; + } + else if (vector.y < 0f) + { + joystickDirection.y = 1; + } + else + { + joystickDirection.y = 0; + } + __instance.joystickDirection = joystickDirection; + float d = 1f / Mathf.Max(1f, __instance.GetWeight() * 0.8f) * __instance.steeringMultiplier; + if(Settings.IsVehicleSnapTurningEnabled == false) + { + __instance.useRigidbody.AddTorque(__instance.transform.up * vector.x * d, ForceMode.VelocityChange); + } + __instance.useRigidbody.AddTorque(__instance.transform.right * -vector.y * d, ForceMode.VelocityChange); + if(Settings.IsVehicleSnapTurningEnabled == false) + { + __instance.useRigidbody.AddTorque(__instance.transform.forward * -vector.x * d * 0.02f, ForceMode.VelocityChange); + } + if(Settings.IsVehicleSnapTurningEnabled == true) + { + //__instance.useRigidbody.AddTorque(__instance.transform.up * vector.x * d, ForceMode.VelocityChange); + __instance.useRigidbody.transform.rotation = Quaternion.Euler( new Vector3( __instance.useRigidbody.transform.rotation.eulerAngles.x, __instance.useRigidbody.transform.rotation.eulerAngles.y + vector.x, __instance.useRigidbody.transform.rotation.eulerAngles.z)); + __instance.useRigidbody.AddTorque(__instance.transform.forward * -vector.x * d * 0.02f, ForceMode.VelocityChange); + } + if(__instance.useRigidbody.detectCollisions) + if (__instance.animator) + { + __instance.smoothedJoystick = UWE.Utils.LerpVector(__instance.smoothedJoystick, joystickDirection.ToVector2(), Time.deltaTime * 2f); + __instance.animator.SetFloat("view_pitch", __instance.smoothedJoystick.y); + __instance.animator.SetFloat("view_yaw", __instance.smoothedJoystick.x); + __instance.animAccel = Mathf.Lerp(__instance.animAccel, (float)__instance.leverDirection.y, Time.deltaTime * 3f); + __instance.animator.SetFloat("move_speed_z", __instance.animAccel); + } + } + if (__instance.upgrades && GameInput.GetButtonDown(GameInput.Button.Sprint)) + { + __instance.upgrades.TryActivateAfterBurner(); + } + } + if (__instance.inputStackDummy.activeInHierarchy && IngameMenu.main != null) + { + if (GameInput.GetButtonDown(GameInput.Button.UIMenu)) + { + IngameMenu.main.Open(); + } + else if (!IngameMenu.main.gameObject.activeInHierarchy) + { + UWE.Utils.lockCursor = true; + } + } + } + if (__instance.engineSound) + { + if (__instance.piloting && __instance.IsPowered()) + { + __instance.engineSound.Play(); + __instance.engineSound.SetParameterValue(__instance.velocityParamIndex, __instance.useRigidbody.velocity.magnitude); + __instance.engineSound.SetParameterValue(__instance.depthParamIndex, __instance.transform.position.y); + __instance.engineSound.SetParameterValue(__instance.rpmParamIndex, (GameInput.GetMoveDirection().z + 1f) * 0.5f); + __instance.engineSound.SetParameterValue(__instance.turnParamIndex, Mathf.Clamp(GameInput.GetLookDelta().x * 0.3f, -1f, 1f)); + __instance.engineSound.SetParameterValue(__instance.upgradeParamIndex, (float)(((__instance.powerEfficiencyFactor < 1f) ? 1 : 0) + (__instance.horsePowerUpgrade ? 2 : 0))); + if (__instance.liveMixin) + { + __instance.engineSound.SetParameterValue(__instance.damagedParamIndex, 1f - __instance.liveMixin.GetHealthFraction()); + } + } + else + { + __instance.engineSound.Stop(); + } + } + if (__instance.waitForDocking && !__instance.truckSegment.IsDocking()) + { + __instance.waitForDocking = false; + Player.main.ExitLockedMode(false, false, null); + } + + return false; + } + } +*/ #endregion } diff --git a/SubmersedVR/Utils/DebugPanel.cs b/SubmersedVR/Utils/DebugPanel.cs index e853ae5..f524165 100644 --- a/SubmersedVR/Utils/DebugPanel.cs +++ b/SubmersedVR/Utils/DebugPanel.cs @@ -43,13 +43,17 @@ void OnEnable() entry.enabled = true; } - public static void Show(string message) + public static void Show(string message, bool writeToLog = false) { if (DebugPanel.main == null) { return; } DebugPanel.main.entry.text = message; + if(writeToLog) + { + Mod.logger.LogInfo($"{message}"); + } } // There might be a better hook for this diff --git a/SubmersedVR/VR/VRCameraRig.cs b/SubmersedVR/VR/VRCameraRig.cs index abf7335..88a118c 100644 --- a/SubmersedVR/VR/VRCameraRig.cs +++ b/SubmersedVR/VR/VRCameraRig.cs @@ -54,11 +54,12 @@ class VRCameraRig : MonoBehaviour float yVelocity = 0.0f; +/* //Using these booleans instead of doing a component lookup during Update() calls public bool isPilotingSeaTruck = false; public bool isPilotingSnowbike = false; public bool isPilotingExosuit = false; - +*/ public Camera UIControllerCamera { get @@ -366,8 +367,8 @@ public void LateUpdate() { RecenterBodyOnCameraOrientation(35f, 0.3f, 3.0f, 1.5f); - float zOffset = isPilotingSeaTruck || isPilotingExosuit ? -0.2f : -0.08f; - float yOffset = isPilotingSeaTruck || isPilotingExosuit ? -0.2f : -0.1f; + float zOffset = Player.main?.inSeatruckPilotingChair == true || Player.main?.inExosuit == true ? -0.2f : -0.08f; + float yOffset = Player.main?.inSeatruckPilotingChair == true || Player.main?.inExosuit == true ? -0.2f : -0.1f; if(Player.main._cinematicModeActive == false) { Player.main.armsController.transform.position = SNCameraRoot.main.mainCamera.transform.position + (SNCameraRoot.main.mainCamera.transform.forward * zOffset) + new Vector3(0f, yOffset, 0f); @@ -427,17 +428,17 @@ static bool Prefix(MainCameraControl __instance) { float zOffset = 0.0f; float yOffset = 0.0f; - if(VRCameraRig.instance?.isPilotingSeaTruck == true) + if(Player.main?.inSeatruckPilotingChair == true) { zOffset += 0.2f + Settings.SeaTruckZOffset; yOffset += 0.2f + Settings.SeaTruckYOffset; } - else if(VRCameraRig.instance?.isPilotingSnowbike == true) + else if(Player.main?.inHovercraft == true) { zOffset += 0.07f + Settings.SnowBikeZOffset; yOffset += 0.2f + Settings.SnowBikeYOffset; } - else if(VRCameraRig.instance?.isPilotingExosuit == true) + else if(Player.main?.inExosuit == true) { zOffset += 0.1f + Settings.ExosuitZOffset; yOffset += 0.2f + Settings.ExosuitYOffset; diff --git a/SubmersedVR/VR/VRHands.cs b/SubmersedVR/VR/VRHands.cs index 3abca78..fd38659 100644 --- a/SubmersedVR/VR/VRHands.cs +++ b/SubmersedVR/VR/VRHands.cs @@ -141,7 +141,6 @@ public void Setup(FullBodyBipedIK ik) Settings.FullBodyChanged -= OnFullBodyChanged; Settings.FullBodyChanged += OnFullBodyChanged; - var camRig = VRCameraRig.instance; leftTarget = camRig.leftHandTarget.transform; rightTarget = camRig.rightHandTarget.transform; @@ -246,6 +245,10 @@ void LateUpdate() leftElbow.transform.SetPositionAndRotation(leftHand.position, leftHand.rotation); rightElbow.transform.SetPositionAndRotation(rightHand.position, rightHand.rotation); + //if(WristHud.GetIndexFingerTip() is Transform indexTip) + //{ + // DebugPanel.Show($" uiIndexPos = {indexTip.transform.position} {indexTip.localPosition} "); + //} } } diff --git a/SubmersedVR/VR/VRHud.cs b/SubmersedVR/VR/VRHud.cs index adfbe3e..e3bb6cd 100644 --- a/SubmersedVR/VR/VRHud.cs +++ b/SubmersedVR/VR/VRHud.cs @@ -1,6 +1,7 @@ using System; using HarmonyLib; using UnityEngine; +using TMPro; namespace SubmersedVR { @@ -152,7 +153,10 @@ public static void OnExitVehicle() static class WristHud { - private static TransformOffset wristOffset = new TransformOffset(new Vector3(-0.079f, 0.148f, -0.158f), new Vector3(350.494f, 88.400f, 244.161f)); + //private static TransformOffset wristOffset = new TransformOffset(new Vector3(-0.079f, 0.148f, -0.158f), new Vector3(350.494f, 88.400f, 244.161f)); + //This works for Valve Index + private static TransformOffset wristOffset = new TransformOffset(new Vector3(-0.05f, 0.15f, -0.142f), new Vector3(15.494f, 88.400f, 244.161f)); + //private static TransformOffset wristOffset = new TransformOffset(new Vector3(-0.044f, 0.16f, -0.158f), new Vector3(15.494f, 88.400f, 244.161f)); private static GameObject wristTarget; private static Canvas canvas; private static CanvasGroup canvasGroup; @@ -169,6 +173,9 @@ static class WristHud private static bool touchingWrist = false; private static bool prevTouchingWrist = false; + public static GameObject pointerDot; + public static TextMeshProUGUI entry; + public static FMODAsset CreateFMODAsset(string eventPath) { FMODAsset asset = ScriptableObject.CreateInstance(); @@ -191,6 +198,48 @@ public static void Setup() canvasGroup = wristCanvasGo.AddComponent(); wristCanvasGo.transform.localScale = new Vector3(0.0004f, 0.0004f, 0.0004f); wristOffset.Apply(wristTarget.transform); + + GameObject obj = UnityEngine.Object.Instantiate(ErrorMessage.main.prefabMessage); + entry = obj.GetComponent(); + entry.rectTransform.SetParent(wristCanvasGo.transform, false); + //obj.SetActive(true); + obj.layer = LayerMask.NameToLayer("UI"); + entry.horizontalAlignment = TMPro.HorizontalAlignmentOptions.Center; + entry.enabled = false; + entry.transform.localPosition = new Vector3(0f, 0.1f, 0f); + entry.transform.localScale = new Vector3(0.66f, 1f, 1f); + UpdateWristText(); + +/* + Material newMaterial = new Material(ShaderManager.preloadedShaders.DebugDisplaySolid); + newMaterial.SetColor(ShaderPropertyID._Color, Color.cyan); + + // Setup PointerDot at the end + pointerDot = GameObject.CreatePrimitive(PrimitiveType.Sphere); + pointerDot.transform.localScale = new Vector3(0.03f, 0.03f, 0.03f); + pointerDot.transform.SetParent(wristCanvasGo.transform, false); + //pointerDot.transform.parent = wristCanvasGo.transform; + LaserPointer.Destroy(pointerDot.GetComponent()); + pointerDot.GetComponent().material = newMaterial; + pointerDot.SetActive(true); + //wristDotOffset.Apply(pointerDot.transform); +*/ + + //laserPointer = new GameObject(nameof(laserPointer)).WithParent(wristTarget).AddComponent(); + //laserPointer.gameObject.SetActive(true); + + //var cube = GameObject.CreatePrimitive(PrimitiveType.Cube).WithParent(wristTarget).ResetTransform(); + //cube.GetComponent().sharedMaterial.color = new Color(0,1,1,1); +/* + var primitive = GameObject.CreatePrimitive(PrimitiveType.Cube).WithParent(wristTarget).ResetTransform(); + //primitive.position = pos; + primitive.transform.localScale = Vector3.one * .01f; + primitive.name = "HI"; + primitive.SetActive(true); + + var renderer = primitive.GetComponent(); + renderer.material.SetColor("_Color", Color.red); +*/ } Settings.PutBarsOnWristChanged -= OnPutBarsOnHandChanged; @@ -201,6 +250,18 @@ public static void Setup() turnOffSound = CreateFMODAsset("event:/tools/flashlight/turn_off"); } + static void LogDescendants(Transform transform, int level) + { + foreach(Transform child in transform) { + Mod.logger.LogInfo($"{new String('-', level)}{child.name}"); + LogDescendants(child, level + 1); + } + } + + public static void UpdateWristText() + { + entry.text = isHudOn ? "HUD ON" : "HUD OFF"; + } public static Transform GetIndexFingerTip() { if (cachedIndexTip != null) @@ -208,10 +269,18 @@ public static Transform GetIndexFingerTip() return cachedIndexTip; } var animator = Player.main?.playerAnimator; + //animator.enabled = false; if (animator is Animator anim) { // TODO: Test this + //LogDescendants(anim.transform, 1) ; + //IK disabled var tip = anim.transform.Find("export_skeleton/head_rig/neck/chest/clav_R/clav_R_aim/shoulder_R/hand_R/hand_R_point_base/hand_R_point_mid/hand_R_point_tip_rig"); + if(tip == null) + { + //IK enabled + tip = anim.transform.Find("export_skeleton/head_rig/neck/chest/clav_R/clav_R_aim/shoulder_R/elbow_R/hand_R/hand_R_point_base/hand_R_point_mid/hand_R_point_tip_rig"); + } if (tip != null) { cachedIndexTip = tip; @@ -235,29 +304,32 @@ public static void OnUpdate() } var camPos = uiCamera.transform.position; var worldRigPos = VRCameraRig.instance.rigParentTarget.position; - var wristPos = wristTarget.transform.position; + var wristPos = wristTarget.transform.position + wristTarget.transform.right * 0.05f + wristTarget.transform.up * 0.05f ; //centered on the display rather than the corner Vector3 wristDir = wristTarget.transform.TransformDirection(Vector3.forward); Vector3 toCam = (wristPos - camPos).normalized; float wristCamDot = Vector3.Dot(wristDir, toCam); bool isFacingCamera = wristCamDot > 0.1f; - // DebugPanel.Show($"dot = {dot} <= {wristDir}, {toCam}"); + //DebugPanel.Show($"dot = {wristCamDot} <= {wristDir}, {toCam}, {camPos}"); canvasGroup.alpha = Mathf.Max(wristCamDot, 0.0f); if (isFacingCamera && GetIndexFingerTip() is Transform indexTip) { + entry.enabled = true; var uiIndexPos = indexTip.position - worldRigPos; var wristDistance = Vector3.Distance(uiIndexPos, wristPos); - // DebugPanel.Show($"wristDistance = {wristDistance} <= uiPos{uiIndexPos}, {wristPos}"); - const float threshold = 0.1f; + //DebugPanel.Show($"wristDistance = {wristDistance} <= uiPos{uiIndexPos}, {wristPos} uiIndexPos = {indexTip.transform.position} {indexTip.localPosition} {worldRigPos}", true); + const float threshold = 0.05f; touchingWrist = wristDistance < threshold; if (touchingWrist && !prevTouchingWrist) { isHudOn = !isHudOn; + UpdateWristText(); Utils.PlayFMODAsset(isHudOn ? turnOnSound : turnOffSound); + HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.8f, false, true, false); } - prevTouchingWrist = wristDistance < threshold; + prevTouchingWrist = touchingWrist; } } @@ -275,7 +347,11 @@ public static void Toggle(bool isOn) Mod.logger.LogDebug("Turning WristHud on"); barsPanel.WithParent(canvas.transform).ResetTransform(); barsPanel.GetComponent().pivot = new Vector2(0, 0); - ManagedUpdate.Subscribe(ManagedUpdate.Queue.LateUpdateLast, new ManagedUpdate.OnUpdate(OnUpdate)); + //Switched this from LateUpdateLast to CanvasFirst because the VRHands calculations occur in LateUpdate + //and were happening after the WristHud OnUpdate so finger position was being read as the default animated finger + //position, not the user overridden VRHands finger position. CanvasFirst would generally be too late for rendering updates, + //but since it's just a toggle, it should be fine + ManagedUpdate.Subscribe(ManagedUpdate.Queue.CanvasFirst, new ManagedUpdate.OnUpdate(OnUpdate)); } else { @@ -284,8 +360,10 @@ public static void Toggle(bool isOn) barsPanel.transform.SetParent(hudContent.transform, false); barsPanel.GetComponent().pivot = new Vector2(0.5f, 0.5f); barsPanel.GetComponent().anchoredPosition = new Vector2(0.0f, 0.0f); - ManagedUpdate.Unsubscribe(ManagedUpdate.Queue.LateUpdateLast, new ManagedUpdate.OnUpdate(OnUpdate)); + ManagedUpdate.Unsubscribe(ManagedUpdate.Queue.CanvasFirst, new ManagedUpdate.OnUpdate(OnUpdate)); isHudOn = true; + UpdateWristText(); + entry.enabled = false; } } @@ -448,6 +526,18 @@ public static void Postfix(ref uGUI_DepthCompass.DepthMode __result) } } + [HarmonyPatch(typeof(uGUI_DepthCompass), nameof(uGUI_DepthCompass.IsCompassEnabled))] + public static class HideDepthCompassCompassWhenHudOff + { + public static void Postfix(ref bool __result) + { + if (!WristHud.isHudOn) + { + __result = false; + } + } + } + [HarmonyPatch(typeof(uGUI_PinnedRecipes), nameof(uGUI_PinnedRecipes.GetMode))] public static class HidePinnedRecipesWhenHudOff { From 5a8062a9c736f12da53d9ee83821c8fa8b61e184 Mon Sep 17 00:00:00 2001 From: jbusfield Date: Wed, 6 Sep 2023 15:50:59 -0700 Subject: [PATCH 3/9] Minor tweaks to make haptics more comfortable for oculus. Fixed map room camera issues,Disabled quickslots while on snowbike and in SeaTruck --- SubmersedVR/Input/SteamVrGameInput.cs | 48 ++++++++++ .../SteamVR/bindings_oculus_touch.json | 24 +++++ SubmersedVR/Tweaks/Haptics.cs | 27 +++--- SubmersedVR/Tweaks/Hoverbike.cs | 2 +- SubmersedVR/Tweaks/MapRoomCamera.cs | 94 +++++++++++++++++++ SubmersedVR/Tweaks/MiscTweaks.cs | 1 + SubmersedVR/VR/VRCameraRig.cs | 4 - SubmersedVR/VR/VRHud.cs | 2 + SubmersedVR/VR/VRQuickSlots.cs | 8 ++ 9 files changed, 193 insertions(+), 17 deletions(-) diff --git a/SubmersedVR/Input/SteamVrGameInput.cs b/SubmersedVR/Input/SteamVrGameInput.cs index 768eea7..057b91e 100644 --- a/SubmersedVR/Input/SteamVrGameInput.cs +++ b/SubmersedVR/Input/SteamVrGameInput.cs @@ -63,11 +63,15 @@ static bool Prefix(GameInput.Button button, ref bool __result) VRCameraRig.instance.photoRequested = false; return false; } + //Mod.logger.LogInfo($"GameInput.GetButtonDown {actionName}"); //Use the Sprint action as the Take Picture action when the player is not piloting a vehicle or holding a tool + //Cant do this because oculus use the right stick press to show tools + /* if(actionName == "TakePicture" && !(Player.main?.currentMountedVehicle != null || (Player.main?.IsPiloting() == true)) && (Inventory.main.GetHeldTool() == null)) { actionName = "Sprint"; } + */ if(actionName == "Answer") { @@ -175,6 +179,37 @@ public static void Postfix(ref Vector2 __result) float threshold = 0.5f; if(SteamVrGameInput.debugSnapTurn) { + //When snap turning temporarily stops working the absX values are consistently way too small. + //For example + /* + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -1.278882E-05 absX = 1.278882E-05 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.006563202 absX = 0.006563202 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.01748386 absX = 0.01748386 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.01372059 absX = 0.01372059 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.007662193 absX = 0.007662193 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.00552019 absX = 0.00552019 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.001945586 absX = 0.001945586 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.001071194 absX = 0.001071194 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.0006412331 absX = 0.0006412331 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.0005760753 absX = 0.0005760753 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.001943999 absX = 0.001943999 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.003332607 absX = 0.003332607 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.002658113 absX = 0.002658113 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.003036913 absX = 0.003036913 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.003188544 absX = 0.003188544 snapTurned = False + */ + //As compared to a correct output + /* + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.008898057 absX = 0.008898057 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.3006316 absX = 0.3006316 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.6367133 absX = 0.6367133 snapTurned = False + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -1.722365 absX = 1.722365 snapTurned = True + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -2.857881 absX = 2.857881 snapTurned = True + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -2.429947 absX = 2.429947 snapTurned = True + [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -2.409516 absX = 2.409516 snapTurned = True + */ + //So Either GetAnalogValueForButton is returning the wrong value or GameInput.controllerSensitivity is getting changed somehow + //Hard coding GameInput.controllerSensitivity below in case thats the issue Mod.logger.LogInfo($"GameInput.GetLookDelta inExosuit = {isInExosuit} onSnowBike = {isOnSnowBike} angle = {angle} looX = {lookX} absX = {absX} snapTurned = {SteamVrGameInput.SnapTurned}"); } if (absX > threshold && !SteamVrGameInput.SnapTurned) @@ -186,6 +221,7 @@ public static void Postfix(ref Vector2 __result) { __result.x = 0; if (absX <= threshold) { + GameInput.controllerSensitivity.x = 0.405f; SteamVrGameInput.SnapTurned = false; } } @@ -208,6 +244,18 @@ public static bool Prefix() return false; } } + + /*This will turn on the controller tab in options but most of the settings are incompatible + [HarmonyPatch(typeof(GameInput), nameof(GameInput.IsControllerAvailable))] + public static class MakeControllerAlwaysAvailable + { + public static bool Prefix() + { + GameInput.controllerAvailable = true; + return true; + } + } + */ [HarmonyPatch(typeof(GameInput), nameof(GameInput.UpdateKeyboardAvailable))] public static class KeyboardNeverAvialable diff --git a/SubmersedVR/StreamingAssets/SteamVR/bindings_oculus_touch.json b/SubmersedVR/StreamingAssets/SteamVR/bindings_oculus_touch.json index 8408c2e..d5e91d1 100644 --- a/SubmersedVR/StreamingAssets/SteamVR/bindings_oculus_touch.json +++ b/SubmersedVR/StreamingAssets/SteamVR/bindings_oculus_touch.json @@ -39,6 +39,15 @@ "mode" : "joystick", "path" : "/user/hand/left/input/joystick" }, + { + "inputs" : { + "click" : { + "output" : "/actions/subnautica/in/sprint" + } + }, + "mode" : "joystick", + "path" : "/user/hand/right/input/joystick" + }, { "inputs" : { "click" : { @@ -207,6 +216,21 @@ }, "path" : "/user/hand/left/input/joystick" }, + { + "inputs" : { + "east" : { + "output" : "/actions/subnautica/in/cycleprev" + }, + "west" : { + "output" : "/actions/subnautica/in/cyclenext" + } + }, + "mode" : "dpad", + "parameters" : { + "sub_mode" : "click" + }, + "path" : "/user/hand/left/input/joystick" + }, { "inputs" : { "east" : { diff --git a/SubmersedVR/Tweaks/Haptics.cs b/SubmersedVR/Tweaks/Haptics.cs index 741f9b3..5562476 100644 --- a/SubmersedVR/Tweaks/Haptics.cs +++ b/SubmersedVR/Tweaks/Haptics.cs @@ -31,6 +31,9 @@ static class HapticsVR public static float effectsDuration = 0.0f; + public static float defaultUIDuration = 0.05f; + public static float defaultUIAmplitude = 0.4f; + public static void PlayHaptics(float secondsFromNow, float durationSeconds, float frequency, float amplitude, bool isUI = false, bool rightController = true, bool leftController = false, float leftAmplitude = -1.0f) { if((isUI && Settings.EnableUIHaptics == true) || !isUI && Settings.EnableGameHaptics == true) @@ -114,8 +117,8 @@ public static void Prefix( GameObject target, HandTargetEventType e, GUIHand han } HapticsVR.lastHoverComponent = target; - Mod.logger.LogInfo($"GUIHand.Send Hover called"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.5f); + //Mod.logger.LogInfo($"GUIHand.Send Hover called"); + HapticsVR.PlayHaptics(0.0f, 0.05f, 10f, 0.7f); } } } @@ -350,7 +353,7 @@ public static void Postfix(SubRoot __instance, DamageInfo damageInfo) } } - //Haptics while Seatruck is Blending attacked + //Haptics while Seatruck is being attacked [HarmonyPatch(typeof(SeaTruckSegment), nameof(SeaTruckSegment.Update))] public static class SeaTruckAttackHaptics { @@ -401,7 +404,7 @@ public static class PlayerDamageHaptics { public static void Postfix(Player __instance, DamageInfo damageInfo) { - float intensity = damageInfo.damage * 2f / 100f; + float intensity = damageInfo.damage * 3f / 100f; float shakeAmount = Mathf.Clamp(intensity, 0f, 5f); Mod.logger.LogInfo($"Player.OnTakeDamage called damage = {damageInfo.damage} intensity = {intensity} duration = {shakeAmount * 2f}"); HapticsVR.PlayHaptics(0.0f, shakeAmount * 2f, 10f, Mathf.Clamp(intensity, 0f, 1f), false, true, true); @@ -677,7 +680,7 @@ public static class uGUI_ButtonSoundHaptics public static void Postfix(uGUI_ButtonSound __instance) { //Mod.logger.LogInfo($"uGUI_ButtonSound.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); + HapticsVR.PlayHaptics(0.0f, HapticsVR.defaultUIDuration, 10f,HapticsVR.defaultUIAmplitude, true); } } [HarmonyPatch(typeof(uGUI_OptionSelection), nameof(uGUI_OptionSelection.OnSelect))] @@ -686,7 +689,7 @@ public static class uGUI_ChoiceSoundHaptics public static void Postfix(uGUI_OptionSelection __instance) { //Mod.logger.LogInfo($"uGUI_ChoiceSound.OnValueChanged"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); + HapticsVR.PlayHaptics(0.0f, HapticsVR.defaultUIDuration, 10f,HapticsVR.defaultUIAmplitude, true); } } @@ -696,7 +699,7 @@ public static class uGUI_ToggleOptionSoundHaptics public static void Postfix(uGUI_ToggleOptionSound __instance) { //Mod.logger.LogInfo($"uGUI_ToggleOptionSound.OnValueChanged"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); + HapticsVR.PlayHaptics(0.0f, HapticsVR.defaultUIDuration, 10f, HapticsVR.defaultUIAmplitude, true); } } @@ -717,7 +720,7 @@ public static class SelectableHaptics public static void Postfix(Selectable __instance) { //Mod.logger.LogInfo($"Selectable.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); + HapticsVR.PlayHaptics(0.0f, HapticsVR.defaultUIDuration, 10f, HapticsVR.defaultUIAmplitude, true); } } @@ -728,7 +731,7 @@ class ListEntryHaptics public static void Postfix(uGUI_ListEntry __instance) { //Mod.logger.LogInfo($"uGUI_ListEntry.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); + HapticsVR.PlayHaptics(0.0f, HapticsVR.defaultUIDuration, 10f, HapticsVR.defaultUIAmplitude, true); } } @@ -739,7 +742,7 @@ class EquipmentSlotHaptics public static void Postfix(uGUI_EquipmentSlot __instance) { //Mod.logger.LogInfo($"uGUI_EquipmentSlot.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); + HapticsVR.PlayHaptics(0.0f, HapticsVR.defaultUIDuration, 10f, HapticsVR.defaultUIAmplitude, true); } } @@ -749,7 +752,7 @@ class BlueprintsHaptics public static void Postfix(uGUI_BlueprintsTab __instance) { //Mod.logger.LogInfo($"uGUI_EquipmentSlot.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, true); + HapticsVR.PlayHaptics(0.0f, HapticsVR.defaultUIDuration, 10f, HapticsVR.defaultUIAmplitude, true); } } @@ -760,7 +763,7 @@ class ItemIconHaptics public static void Postfix(uGUI_ItemIcon __instance) { //Mod.logger.LogInfo($"uGUI_ItemIcon.OnPointerEnter"); - HapticsVR.PlayHaptics(0.0f, 0.05f, 10f, 0.15f, true); + HapticsVR.PlayHaptics(0.0f, HapticsVR.defaultUIDuration, 10f, HapticsVR.defaultUIAmplitude, true); } } diff --git a/SubmersedVR/Tweaks/Hoverbike.cs b/SubmersedVR/Tweaks/Hoverbike.cs index 7a0a2b7..0ca875b 100644 --- a/SubmersedVR/Tweaks/Hoverbike.cs +++ b/SubmersedVR/Tweaks/Hoverbike.cs @@ -29,7 +29,7 @@ public static void Postfix(HoverbikeHeadControlManager __instance) { if (__instance.activeManager && !Player.main.GetPDA().isInUse && !VROptions.enableCinematics) { - Mod.logger.LogInfo($"HoverbikeHeadControlManager.Update called vector2 = { __instance.overflowInput}"); + //Mod.logger.LogInfo($"HoverbikeHeadControlManager.Update called vector2 = { __instance.overflowInput}"); __instance.smoothedBikeRot = new Vector3(0f, __instance.smoothedBikeRot.y, 0f); Vector3 b2 = new Vector3(__instance.defaultCameraPitch, 0f, 0f); MainCameraControl.main.vehicleOverrideHeadRot = __instance.smoothedBikeRot + __instance.lookOffset + b2; diff --git a/SubmersedVR/Tweaks/MapRoomCamera.cs b/SubmersedVR/Tweaks/MapRoomCamera.cs index dbc1eda..c43c98f 100644 --- a/SubmersedVR/Tweaks/MapRoomCamera.cs +++ b/SubmersedVR/Tweaks/MapRoomCamera.cs @@ -46,6 +46,100 @@ public static void Postfix(uGUI_CameraDrone __instance) } } + //Snap turning override + [HarmonyPatch(typeof(MapRoomCamera), nameof(MapRoomCamera.Update))] + static class MapRoomCameraFixer + { + public static bool Prefix(MapRoomCamera __instance) + { + __instance.UpdateEnergyRecharge(); + if (__instance.IsControlled() && __instance.inputStackDummy.activeInHierarchy) + { + if (!__instance.IsReady() && LargeWorldStreamer.main.IsWorldSettled()) + { + __instance.readyForControl = true; + __instance.connectingSound.Stop(); + global::Utils.PlayFMODAsset(__instance.connectedSound, __instance.transform, 20f); + } + if (__instance.CanBeControlled(null) && __instance.readyForControl) + { + Vector2 lookDelta = GameInput.GetLookDelta(); + //__instance.rigidBody.AddTorque(__instance.transform.up * lookDelta.x * 45f * 0.0015f, ForceMode.VelocityChange); + if(Settings.IsSnapTurningEnabled == false) + { + __instance.rigidBody.AddTorque(__instance.transform.up * lookDelta.x * 45f * 0.0015f, ForceMode.VelocityChange); + } + if(!VROptions.disableInputPitch) + { + __instance.rigidBody.AddTorque(__instance.transform.right * -lookDelta.y * 45f * 0.0015f, ForceMode.VelocityChange); + } + if(Settings.IsSnapTurningEnabled == true) + { + __instance.rigidBody.transform.rotation = Quaternion.Euler( new Vector3( __instance.rigidBody.transform.rotation.eulerAngles.x, __instance.rigidBody.transform.rotation.eulerAngles.y + lookDelta.x, __instance.rigidBody.transform.rotation.eulerAngles.z)); + } + __instance.wishDir = GameInput.GetMoveDirection(); + __instance.wishDir.Normalize(); + if (__instance.dockingPoint != null && __instance.wishDir != Vector3.zero) + { + __instance.dockingPoint.UndockCamera(); + } + } + else + { + __instance.wishDir = Vector3.zero; + } + if (Input.GetKeyUp(KeyCode.Escape) || GameInput.GetButtonUp(GameInput.Button.Exit)) + { + if (__instance.routine == null) + { + __instance.FreeCamera(true); + __instance.routine = __instance.StartCoroutine(__instance.CaptureScreenAndExitLockedMode()); + } + } + else if (GameInput.GetButtonDown(GameInput.Button.CycleNext)) + { + __instance.screen.CycleCamera(1); + } + else if (GameInput.GetButtonDown(GameInput.Button.CyclePrev)) + { + __instance.screen.CycleCamera(-1); + } + else if (GameInput.GetButtonDown(GameInput.Button.RightHand)) + { + bool flag = !__instance.lightsParent.activeInHierarchy; + __instance.lightsParent.SetActive(flag); + if (flag) + { + FMODUWE.PlayOneShot(__instance.lightOnSound, __instance.transform.position, 1f); + } + else + { + FMODUWE.PlayOneShot(__instance.lightOffSound, __instance.transform.position, 1f); + } + } + if (Player.main != null && Player.main.liveMixin != null && !Player.main.liveMixin.IsAlive()) + { + __instance.FreeCamera(true); + __instance.ExitLockedMode(true); + } + float magnitude = __instance.rigidBody.velocity.magnitude; + float time = Mathf.Clamp(__instance.transform.InverseTransformDirection(__instance.rigidBody.velocity).z / 15f, 0f, 1f); + if (magnitude > 2f) + { + __instance.engineSound.Play(); + __instance.energyMixin.ConsumeEnergy(Time.deltaTime * 0.06666f); + } + else + { + __instance.engineSound.Stop(); + } + __instance.screenEffectModel.GetComponent().materials[0].SetColor(ShaderPropertyID._Color, __instance.gradientInner.Evaluate(time)); + __instance.screenEffectModel.GetComponent().materials[1].SetColor(ShaderPropertyID._Color, __instance.gradientOuter.Evaluate(time)); + } + return false; + } + } + #endregion } diff --git a/SubmersedVR/Tweaks/MiscTweaks.cs b/SubmersedVR/Tweaks/MiscTweaks.cs index 53facd6..3887eb4 100644 --- a/SubmersedVR/Tweaks/MiscTweaks.cs +++ b/SubmersedVR/Tweaks/MiscTweaks.cs @@ -69,6 +69,7 @@ public static void Postfix(GameInput __instance, bool useKeyboard, bool useContr if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 106) //F5 key { //SteamVrGameInput.debugSnapTurn = !SteamVrGameInput.debugSnapTurn; + //Mod.logger.LogInfo($"Snap turn debugging is {SteamVrGameInput.debugSnapTurn}"); /* foreach (HideForScreenshots hideForScreenshots in UnityEngine.Object.FindObjectsOfType()) { diff --git a/SubmersedVR/VR/VRCameraRig.cs b/SubmersedVR/VR/VRCameraRig.cs index 88a118c..4f54baa 100644 --- a/SubmersedVR/VR/VRCameraRig.cs +++ b/SubmersedVR/VR/VRCameraRig.cs @@ -271,10 +271,6 @@ public void StealUICamera(Camera camera, bool fromGame = false) camera.transform.localPosition = Vector3.zero; //new Vector3(0.0f, 2.0f, 0.0f); camera.transform.localRotation = Quaternion.identity; - //should work for hiding ui during screenshots but it doesnt - //var obj = camera.gameObject.AddComponent(); - //obj.type = HideForScreenshots.HideType.HUD; - // Set all canvas scalers to static, which makes UI better usable FindObjectsOfType().Where(obj => !obj.name.Contains("PDA")).ForEach(cs => cs.vrMode = uGUI_CanvasScaler.Mode.Static); SetupPDA(); diff --git a/SubmersedVR/VR/VRHud.cs b/SubmersedVR/VR/VRHud.cs index e3bb6cd..edbc4fb 100644 --- a/SubmersedVR/VR/VRHud.cs +++ b/SubmersedVR/VR/VRHud.cs @@ -204,6 +204,8 @@ public static void Setup() entry.rectTransform.SetParent(wristCanvasGo.transform, false); //obj.SetActive(true); obj.layer = LayerMask.NameToLayer("UI"); + HideForScreenshots h = obj.AddComponent(); + h.type = HideForScreenshots.HideType.HUD; entry.horizontalAlignment = TMPro.HorizontalAlignmentOptions.Center; entry.enabled = false; entry.transform.localPosition = new Vector3(0f, 0.1f, 0f); diff --git a/SubmersedVR/VR/VRQuickSlots.cs b/SubmersedVR/VR/VRQuickSlots.cs index 63fa664..a230ffd 100644 --- a/SubmersedVR/VR/VRQuickSlots.cs +++ b/SubmersedVR/VR/VRQuickSlots.cs @@ -165,6 +165,14 @@ int DetermineSlot(float angle) public void Activate(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource) { + bool isInExosuit = Player.main?.inExosuit == true; + bool isOnSnowBike = Player.main?.inHovercraft == true; + bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; + if(isOnSnowBike || isPilotingSeatruck) + { + return; + } + canvas.enabled = true; transform.position = controllerTarget.transform.position; bool isVehicleSlot = GetTarget() is Vehicle; From dac0b9b2bce994fdf761facd9cce98e0c058d90b Mon Sep 17 00:00:00 2001 From: jbusfield Date: Fri, 8 Sep 2023 11:27:45 -0700 Subject: [PATCH 4/9] Added option for hand based movement direction instead of head based. Added more haptics for health bars --- SubmersedVR/Input/SteamVrGameInput.cs | 119 ++++++++++++++++++-------- SubmersedVR/Settings.cs | 5 ++ SubmersedVR/Tweaks/Haptics.cs | 67 ++++++++++++++- SubmersedVR/VR/VRCameraRig.cs | 40 ++++++++- SubmersedVR/VR/VRHands.cs | 4 +- SubmersedVR/VR/VRHud.cs | 2 +- 6 files changed, 196 insertions(+), 41 deletions(-) diff --git a/SubmersedVR/Input/SteamVrGameInput.cs b/SubmersedVR/Input/SteamVrGameInput.cs index 057b91e..d433e30 100644 --- a/SubmersedVR/Input/SteamVrGameInput.cs +++ b/SubmersedVR/Input/SteamVrGameInput.cs @@ -152,9 +152,89 @@ public static bool Prefix() } // Implement Snap turning for the player + //Using Prefix for now while trying to figure out snap turning bug + //Can use PostFix version when issue has been resolved [HarmonyPatch(typeof(GameInput), nameof(GameInput.GetLookDelta))] public static class SnapTurning { + public static bool Prefix(GameInput __instance, ref Vector2 __result) + { + float r = 0.0f; + float l = 0.0f; + Vector2 vector = Vector2.zero; + if (!GameInput.scanningInput && !GameInput.clearInput) + { + if (GameInput.controllerEnabled) + { + Vector2 zero = Vector2.zero; + r = GameInput.GetAnalogValueForButton(GameInput.Button.LookRight); + l = GameInput.GetAnalogValueForButton(GameInput.Button.LookLeft); + float f = r - l; + float f2 = GameInput.GetAnalogValueForButton(GameInput.Button.LookUp) - GameInput.GetAnalogValueForButton(GameInput.Button.LookDown); + zero.x = Mathf.Sign(f) * Mathf.Pow(Mathf.Abs(f), 2f) * 500f * GameInput.controllerSensitivity.x * Time.deltaTime; + zero.y = Mathf.Sign(f2) * Mathf.Pow(Mathf.Abs(f2), 2f) * 500f * GameInput.controllerSensitivity.y * Time.deltaTime; + if (GameInput.invertController) + { + zero.y = -zero.y; + } + vector += zero; + } + if (GameInput.IsKeyboardAvailable()) + { + float num = GameInput.mouseSensitivity; + float num2 = GameInput.mouseSensitivity; + Vector2 zero2 = Vector2.zero; + zero2.x += GameInput.axisValues[8] * num2; + zero2.y += GameInput.axisValues[9] * num; + if (GameInput.invertMouse) + { + zero2.y = -zero2.y; + } + vector += zero2; + } + } + bool isInExosuit = Player.main?.inExosuit == true; + bool isOnSnowBike = Player.main?.inHovercraft == true; + bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; + + if ((Settings.IsSnapTurningEnabled && !(isInExosuit || isOnSnowBike || isPilotingSeatruck)) || (Settings.IsExosuitSnapTurningEnabled && isInExosuit) || (Settings.IsSnowBikeSnapTurningEnabled && isOnSnowBike)) + { + float angle = Settings.SnapTurningAngle; //player + if(Settings.IsExosuitSnapTurningEnabled && isInExosuit) + { + angle = Settings.ExosuitSnapTurningAngle; + } + if(Settings.IsSnowBikeSnapTurningEnabled && isOnSnowBike) + { + angle = Settings.SnowBikeSnapTurningAngle; + } + + float lookX = vector.x; + float absX = Mathf.Abs(lookX); + float threshold = 0.5f; + + if (absX > threshold && !SteamVrGameInput.SnapTurned) + { + vector.x = angle * Mathf.Sign(lookX); + SteamVrGameInput.SnapTurned = true; + } + else + { + vector.x = 0; + if (absX <= threshold) { + SteamVrGameInput.SnapTurned = false; + } + } + //DebugPanel.Show($"GetLookDelta: r={r} l={l} lr={r - l} delta={Time.deltaTime} lookX={lookX} absX = {absX} res={vector.x}"); + } + + __result = vector; + + return false; + } + + +#if false public static void Postfix(ref Vector2 __result) { //bool isInVehicle = Player.main?.currentMountedVehicle != null || (Player.main?.IsPiloting() == true); @@ -179,37 +259,6 @@ public static void Postfix(ref Vector2 __result) float threshold = 0.5f; if(SteamVrGameInput.debugSnapTurn) { - //When snap turning temporarily stops working the absX values are consistently way too small. - //For example - /* - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -1.278882E-05 absX = 1.278882E-05 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.006563202 absX = 0.006563202 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.01748386 absX = 0.01748386 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.01372059 absX = 0.01372059 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.007662193 absX = 0.007662193 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.00552019 absX = 0.00552019 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.001945586 absX = 0.001945586 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.001071194 absX = 0.001071194 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.0006412331 absX = 0.0006412331 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.0005760753 absX = 0.0005760753 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.001943999 absX = 0.001943999 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.003332607 absX = 0.003332607 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.002658113 absX = 0.002658113 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.003036913 absX = 0.003036913 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = 0.003188544 absX = 0.003188544 snapTurned = False - */ - //As compared to a correct output - /* - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.008898057 absX = 0.008898057 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.3006316 absX = 0.3006316 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -0.6367133 absX = 0.6367133 snapTurned = False - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -1.722365 absX = 1.722365 snapTurned = True - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -2.857881 absX = 2.857881 snapTurned = True - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -2.429947 absX = 2.429947 snapTurned = True - [Info :SubmersedVR] GameInput.GetLookDelta inExosuit = False onSnowBike = False angle = 30 looX = -2.409516 absX = 2.409516 snapTurned = True - */ - //So Either GetAnalogValueForButton is returning the wrong value or GameInput.controllerSensitivity is getting changed somehow - //Hard coding GameInput.controllerSensitivity below in case thats the issue Mod.logger.LogInfo($"GameInput.GetLookDelta inExosuit = {isInExosuit} onSnowBike = {isOnSnowBike} angle = {angle} looX = {lookX} absX = {absX} snapTurned = {SteamVrGameInput.SnapTurned}"); } if (absX > threshold && !SteamVrGameInput.SnapTurned) @@ -221,18 +270,15 @@ public static void Postfix(ref Vector2 __result) { __result.x = 0; if (absX <= threshold) { - GameInput.controllerSensitivity.x = 0.405f; SteamVrGameInput.SnapTurned = false; } } } - //else - //{ - // Shader.SetGlobalFloat(ShaderPropertyID._UweVrFadeAmount, 1000f); - //} } + #endif } + // Pretend the controlers are always available to make Subnautica not switch to Keyboard/Mouse and mess VR controls up // TODO: This should/could probably be changed though, asking SteamVR if controllers are available? [HarmonyPatch(typeof(GameInput), nameof(GameInput.UpdateControllerAvailable))] @@ -333,6 +379,7 @@ public static bool Prefix(GameInput.Button button, ref float __result) break; case GameInput.Button.LookLeft: vec = SteamVR_Actions.subnautica.Look.GetAxis(SteamVR_Input_Sources.Any); + //DebugPanel.Show($"LookLeft: {vec}"); value = vec.x < 0.0f ? -vec.x : 0.0f; break; } diff --git a/SubmersedVR/Settings.cs b/SubmersedVR/Settings.cs index cea0c9b..dc2cccb 100644 --- a/SubmersedVR/Settings.cs +++ b/SubmersedVR/Settings.cs @@ -58,6 +58,8 @@ public class Settings public static bool EnableGameHaptics = false; public static bool EnableUIHaptics = false; + public static bool HandBasedTurning = false; + // Saves or loads all public static properties as settings using the given serializer internal static void Serialize(GameSettings.ISerializer serializer) @@ -96,6 +98,9 @@ internal static void AddMenu(uGUI_OptionsPanel panel) int tab = panel.AddTab("Submersed VR"); panel.AddHeading(tab, "Controls"); + panel.AddChoiceOption(tab, "Movement Mode", new string[] {"Head Based", "Hand Based"}, HandBasedTurning ? "Hand Based" : "Head Based", (value) => { + HandBasedTurning = value == "Hand Based"; + }); panel.AddToggleOption(tab, "Enable Player Snap Turning", IsSnapTurningEnabled, (value) => { IsSnapTurningEnabled = value; if (IsSnapTurningEnabledChanged != null) { IsSnapTurningEnabledChanged(value); diff --git a/SubmersedVR/Tweaks/Haptics.cs b/SubmersedVR/Tweaks/Haptics.cs index 5562476..5a8f8cf 100644 --- a/SubmersedVR/Tweaks/Haptics.cs +++ b/SubmersedVR/Tweaks/Haptics.cs @@ -397,6 +397,71 @@ public static void Postfix(uGUI_HealthBar __instance, float scalar) } } + [HarmonyPatch(typeof(uGUI_FoodBar), nameof(uGUI_FoodBar.OnPulse))] + public static class FoodbarHaptics + { + public static void Postfix(uGUI_FoodBar __instance, float scalar) + { + if (__instance.pulseAnimationState == null) + { + return; + } + //Mod.logger.LogInfo($"uGUI_FoodBar.OnPulse called scaler = {scalar} statePulse = {__instance.pulseAnimationState.normalizedTime}"); + if((__instance.pulseAnimationState.normalizedTime > 0 && HapticsVR.effectsTimer == 0.0f) || (__instance.pulseAnimationState.normalizedTime < HapticsVR.effectsTimer && __instance.pulseAnimationState.normalizedTime != 0f)) + { + //HapticsVR.PlayHaptics(0.0f, 0.1f, 10f, 0.3f, false, !Settings.PutBarsOnWrist, true); + CoroutineHost.StartCoroutine(HapticsVR.PlayerHeartbeat()); + } + HapticsVR.effectsTimer = __instance.pulseAnimationState.normalizedTime; + } + } + + [HarmonyPatch(typeof(uGUI_OxygenBar), nameof(uGUI_OxygenBar.OnPulse))] + public static class OxygenBarHaptics + { + public static void Postfix(uGUI_OxygenBar __instance, float scalar) + { + //Mod.logger.LogInfo($"uGUI_OxygenBar.OnPulse called scaler = {scalar} statePulse = {__instance.pulseAnimationState.normalizedTime}"); + if((__instance.statePulse.normalizedTime > 0 && HapticsVR.effectsTimer == 0.0f) || (__instance.statePulse.normalizedTime < HapticsVR.effectsTimer && __instance.statePulse.normalizedTime != 0f)) + { + CoroutineHost.StartCoroutine(HapticsVR.PlayerHeartbeat()); + } + HapticsVR.effectsTimer = __instance.statePulse.normalizedTime; + } + } + + [HarmonyPatch(typeof(uGUI_WaterBar), nameof(uGUI_WaterBar.OnPulse))] + public static class WaterbarHaptics + { + public static void Postfix(uGUI_WaterBar __instance, float scalar) + { + if (__instance.pulseAnimationState == null) + { + return; + } + //Mod.logger.LogInfo($"uGUI_WaterBar.OnPulse called scaler = {scalar} statePulse = {__instance.pulseAnimationState.normalizedTime}"); + if((__instance.pulseAnimationState.normalizedTime > 0 && HapticsVR.effectsTimer == 0.0f) || (__instance.pulseAnimationState.normalizedTime < HapticsVR.effectsTimer && __instance.pulseAnimationState.normalizedTime != 0f)) + { + CoroutineHost.StartCoroutine(HapticsVR.PlayerHeartbeat()); + } + HapticsVR.effectsTimer = __instance.pulseAnimationState.normalizedTime; + } + } + + [HarmonyPatch(typeof(uGUI_BodyHeatMeter), nameof(uGUI_BodyHeatMeter.OnPulse))] + public static class BodyHeatMeterHaptics + { + public static void Postfix(uGUI_BodyHeatMeter __instance, float scalar) + { + //Mod.logger.LogInfo($"uGUI_BodyHeatMeter.OnPulse called scaler = {scalar} statePulse = {__instance.pulseAnimationState.normalizedTime}"); + if((__instance.statePulse.normalizedTime > 0 && HapticsVR.effectsTimer == 0.0f) || (__instance.statePulse.normalizedTime < HapticsVR.effectsTimer && __instance.statePulse.normalizedTime != 0f)) + { + CoroutineHost.StartCoroutine(HapticsVR.PlayerHeartbeat()); + } + HapticsVR.effectsTimer = __instance.statePulse.normalizedTime; + } + } + //Player taking direct damage //This is not called if the damage is enough to kill the player [HarmonyPatch(typeof(Player), nameof(Player.OnTakeDamage))] @@ -592,7 +657,7 @@ public static void Postfix(ScannerTool __instance) HapticsVR.effectsTimer += Time.deltaTime; if(HapticsVR.effectsTimer > 0.10) { - Mod.logger.LogInfo($"ScannerTool.Update {Time.deltaTime}"); + //Mod.logger.LogInfo($"ScannerTool.Update {Time.deltaTime}"); HapticsVR.PlayHaptics(0.0f, 0.10f, 10f, HapticsVR.effectsScale, false); HapticsVR.effectsTimer = 0.0f; HapticsVR.effectsScale += 0.20f; diff --git a/SubmersedVR/VR/VRCameraRig.cs b/SubmersedVR/VR/VRCameraRig.cs index 4f54baa..bb9bb4d 100644 --- a/SubmersedVR/VR/VRCameraRig.cs +++ b/SubmersedVR/VR/VRCameraRig.cs @@ -19,6 +19,7 @@ namespace SubmersedVR using System.Collections.Generic; using UWEXR; using System.Reflection.Emit; + using System.CodeDom; class VRCameraRig : MonoBehaviour { @@ -372,7 +373,7 @@ public void LateUpdate() } } Player.main.armsController.transform.localScale = new Vector3(Settings.PlayerScale, Settings.PlayerScale, Settings.PlayerScale); - + Player.main.playerController.standheight = 1.5f * Settings.PlayerScale; } } @@ -417,6 +418,43 @@ public void SetWorldTarget(GameObject activeTarget, float activeHitDistance) #region Patches + [HarmonyPatch(typeof(PlayerController), nameof(PlayerController.forwardReference), MethodType.Getter)] + public static class MoveDirectionOverride + { + public static Transform controllerTransform; + static bool Prefix(PlayerController __instance, ref Transform __result) + { + if(Settings.HandBasedTurning) + { + //Use the Camera's position and the laser pointer's rotation + if(controllerTransform == null) + { + controllerTransform = new GameObject().transform; + } + controllerTransform.position = MainCamera.camera.transform.position; + controllerTransform.rotation = VRCameraRig.instance.laserPointer.transform.rotation; + __result = controllerTransform; + } + else + { + __result = MainCamera.camera.transform; + } + return false; + } + } + +/* + [HarmonyPatch(typeof(UnderwaterMotor), nameof(UnderwaterMotor.UpdateMove))] + public static class MovementDirectionTest + { + static bool Prefix(UnderwaterMotor __instance) + { + //__instance.playerController.forwardReference.rotation = Quaternion.Euler(new Vector3(0f,Player.main.playerController.forwardReference.rotation.eulerAngles.y, Player.main.playerController.forwardReference.rotation.eulerAngles.z)); + return true; + } + } +*/ + [HarmonyPatch(typeof(MainCameraControl), nameof(MainCameraControl.OnLateUpdate))] public static class PlayerPositionFixer { diff --git a/SubmersedVR/VR/VRHands.cs b/SubmersedVR/VR/VRHands.cs index fd38659..61a9975 100644 --- a/SubmersedVR/VR/VRHands.cs +++ b/SubmersedVR/VR/VRHands.cs @@ -69,8 +69,8 @@ internal static TransformOffset GetHandOffset(this PlayerTool tool) case Constructor _: return new TransformOffset(new Vector3(0.042f, 0.076f, -0.166f), new Vector3(53.635f, 151.667f, 249.508f)); case LEDLight _: return new TransformOffset(new Vector3(0.051f, 0.113f, -0.122f), new Vector3(20.287f, 157.143f, 262.503f)); //case Knife _: return new TransformOffset(new Vector3(0.008f, 0.095f, -0.115f), new Vector3(17.193f, 162.033f, 250.308f)); - case HeatBlade _: return new TransformOffset(new Vector3(0.011f, 0.074f, -0.134f), new Vector3(34.705f, 171.102f, 254.320f)); - case Knife _: return new TransformOffset(new Vector3(0.011f, 0.074f, -0.134f), new Vector3(34.705f, 171.102f, 254.320f)); + case HeatBlade _: return new TransformOffset(new Vector3(0.002f, 0.070f, -0.139f), new Vector3(35.311f, 158.499f, 244.373f)); + case Knife _: return new TransformOffset(new Vector3(0.002f, 0.070f, -0.139f), new Vector3(35.311f, 158.499f, 244.373f)); case FlashLight _: return new TransformOffset(new Vector3(-0.043f, 0.077f, -0.147f), new Vector3(13.952f, 173.934f, 243.317f)); case Beacon _: return new TransformOffset(new Vector3(-0.056f, 0.065f, -0.166f), new Vector3(45.693f, 162.076f, 253.284f)); case StasisRifle _: return new TransformOffset(new Vector3(0.013f, 0.091f, -0.155f), new Vector3(32.203f, 147.266f, 237.102f)); diff --git a/SubmersedVR/VR/VRHud.cs b/SubmersedVR/VR/VRHud.cs index edbc4fb..c8e8e82 100644 --- a/SubmersedVR/VR/VRHud.cs +++ b/SubmersedVR/VR/VRHud.cs @@ -173,7 +173,7 @@ static class WristHud private static bool touchingWrist = false; private static bool prevTouchingWrist = false; - public static GameObject pointerDot; + //public static GameObject pointerDot; public static TextMeshProUGUI entry; public static FMODAsset CreateFMODAsset(string eventPath) From ae663a7b7455bffbd0088c76345bfa11f5f3a01b Mon Sep 17 00:00:00 2001 From: jbusfield Date: Sat, 16 Sep 2023 10:09:51 -0700 Subject: [PATCH 5/9] Added an option for articulated hands. Fixed issue with using both defense upgrade and boost upgrade in SeaTruck --- SubmersedVR/Input/SteamVrGameInput.cs | 40 ++- SubmersedVR/Settings.cs | 2 + .../SteamVR/bindings_oculus_touch.json | 35 ++- SubmersedVR/SubmersedVR.csproj | 2 +- SubmersedVR/Tweaks/MiscTweaks.cs | 39 +-- SubmersedVR/Utils/DebugUtilities.cs | 174 +++++++++++ SubmersedVR/VR/VRCameraRig.cs | 146 ++++++++- SubmersedVR/VR/VRHands.cs | 287 ++++++++++++++++-- SubmersedVR/VR/VRHud.cs | 8 +- SubmersedVR/VR/VRQuickSlots.cs | 111 ++++++- 10 files changed, 736 insertions(+), 108 deletions(-) create mode 100644 SubmersedVR/Utils/DebugUtilities.cs diff --git a/SubmersedVR/Input/SteamVrGameInput.cs b/SubmersedVR/Input/SteamVrGameInput.cs index d433e30..dd85b1e 100644 --- a/SubmersedVR/Input/SteamVrGameInput.cs +++ b/SubmersedVR/Input/SteamVrGameInput.cs @@ -2,6 +2,7 @@ using HarmonyLib; using UnityEngine; + #pragma warning disable Harmony003 namespace SubmersedVR { @@ -40,7 +41,8 @@ public static Vector2 GetScrollDelta() } return SteamVR_Actions.subnautica.UIScroll.GetAxis(SteamVR_Input_Sources.Any); } - } + + } // The following three patches map the steamvr actions to the button states // TODO: They could be optimized by using a switch instead of the GetStateDown(string) lookup @@ -77,8 +79,15 @@ static bool Prefix(GameInput.Button button, ref bool __result) { actionName = "Deconstruct"; } - + __result = SteamVR_Input.GetStateDown(actionName, SteamVR_Input_Sources.Any); + + //When sprint and Open Quick Slot actions conflict, dont sprint + if(Player.main?.inSeatruckPilotingChair == true && actionName == "Sprint" && SteamVR_Input.GetStateDown("OpenQuickSlotWheel", SteamVR_Input_Sources.Any)) + { + __result = false; + } + return false; } } @@ -100,6 +109,13 @@ static bool Prefix(GameInput.Button button, ref bool __result) actionName = "Deconstruct"; } __result = SteamVR_Input.GetStateUp(actionName, SteamVR_Input_Sources.Any); + + //When sprint and Open Quick Slot actions conflict, dont sprint + if(Player.main?.inSeatruckPilotingChair == true && actionName == "Sprint" && SteamVR_Input.GetStateDown("OpenQuickSlotWheel", SteamVR_Input_Sources.Any)) + { + __result = false; + } + return false; } } @@ -120,6 +136,12 @@ static bool Prefix(GameInput.Button button, ref bool __result) actionName = "Deconstruct"; } __result = SteamVR_Input.GetState(actionName, SteamVR_Input_Sources.Any); + + //When sprint and Open Quick Slot actions conflict, dont sprint + if(Player.main?.inSeatruckPilotingChair == true && actionName == "Sprint" && SteamVR_Input.GetStateDown("OpenQuickSlotWheel", SteamVR_Input_Sources.Any)) + { + __result = false; + } return false; } } @@ -225,7 +247,7 @@ public static bool Prefix(GameInput __instance, ref Vector2 __result) SteamVrGameInput.SnapTurned = false; } } - //DebugPanel.Show($"GetLookDelta: r={r} l={l} lr={r - l} delta={Time.deltaTime} lookX={lookX} absX = {absX} res={vector.x}"); + //DebugPanel.Show($"GetLookDelta: r={r} l={l} lr={r - l} delta={Time.deltaTime} lookX={lookX} absX = {absX} res={vector.x}", true); } __result = vector; @@ -407,6 +429,18 @@ static bool Prefix(GameInput __instance, bool useKeyboard, bool useController) } } + + //Why doesnt this work? + [HarmonyPatch(typeof(GameInput), nameof(GameInput.SetupDefaultControllerBindings))] + public static class SetupDefaultControllerBindingsFix + { + static void Postfix(GameInput __instance) + { + GameInput.SetBindingInternal(GameInput.Device.Controller, GameInput.Button.Sprint, GameInput.BindingSet.Primary, "ControllerButtonRightStick"); + } + } + + // This makes GameInput.AnyKeyDown() return true incase any boolean action is pressed. Is needed for the intro skip and credits. // But hmm, where is the any key on the controllers? (https://www.youtube.com/watch?v=st6-DgWeuos) [HarmonyPatch(typeof(Input), nameof(Input.anyKeyDown), MethodType.Getter)] diff --git a/SubmersedVR/Settings.cs b/SubmersedVR/Settings.cs index dc2cccb..84272ff 100644 --- a/SubmersedVR/Settings.cs +++ b/SubmersedVR/Settings.cs @@ -59,6 +59,7 @@ public class Settings public static bool EnableUIHaptics = false; public static bool HandBasedTurning = false; + public static bool ArticulatedHands = false; // Saves or loads all public static properties as settings using the given serializer @@ -144,6 +145,7 @@ internal static void AddMenu(uGUI_OptionsPanel panel) panel.AddSliderOption(tab, "Snowbike Height Offset", SnowBikeYOffset, -0.2f, 0.4f, SnowBikeYOffset, 0.01f, (value) => { SnowBikeYOffset = value; }, SliderLabelMode.Float, "0.00"); panel.AddHeading(tab, "Experimental"); + panel.AddToggleOption(tab, "Articulated Hands", ArticulatedHands, (value) => { ArticulatedHands = value; }, "Hands animate based on the movement of your physical hands."); panel.AddToggleOption(tab, "Full body", FullBody, (value) => { FullBody = value; FullBodyChanged(value); }, "See the full body instead of just the hands and feet."); panel.AddSliderOption(tab, "Body Scale", PlayerScale, 0.8f, 1.2f, PlayerScale, 0.01f, (value) => { PlayerScale = value; }, SliderLabelMode.Float, "0.00"); panel.AddToggleOption(tab, "Enable Game Haptics", EnableGameHaptics, (value) => { EnableGameHaptics = value; }, "Enable controller vibration while interacting with world objects."); diff --git a/SubmersedVR/StreamingAssets/SteamVR/bindings_oculus_touch.json b/SubmersedVR/StreamingAssets/SteamVR/bindings_oculus_touch.json index d5e91d1..93fc1d3 100644 --- a/SubmersedVR/StreamingAssets/SteamVR/bindings_oculus_touch.json +++ b/SubmersedVR/StreamingAssets/SteamVR/bindings_oculus_touch.json @@ -25,7 +25,16 @@ "path" : "/user/hand/right/pose/raw" } ], - "skeleton" : [], + "skeleton" : [ + { + "output" : "/actions/subnautica/in/lefthandskeleton", + "path" : "/user/hand/left/input/skeleton/left" + }, + { + "output" : "/actions/subnautica/in/righthandskeleton", + "path" : "/user/hand/right/input/skeleton/right" + } + ], "sources" : [ { "inputs" : { @@ -48,6 +57,18 @@ "mode" : "joystick", "path" : "/user/hand/right/input/joystick" }, + { + "inputs" : { + "click" : { + "output" : "/actions/subnautica/in/openquickslotwheel" + }, + "position" : { + "output" : "/actions/subnautica/in/look" + } + }, + "mode" : "joystick", + "path" : "/user/hand/right/input/joystick" + }, { "inputs" : { "click" : { @@ -102,18 +123,6 @@ "mode" : "button", "path" : "/user/hand/right/input/trigger" }, - { - "inputs" : { - "click" : { - "output" : "/actions/subnautica/in/openquickslotwheel" - }, - "position" : { - "output" : "/actions/subnautica/in/look" - } - }, - "mode" : "joystick", - "path" : "/user/hand/right/input/joystick" - }, { "inputs" : { "click" : { diff --git a/SubmersedVR/SubmersedVR.csproj b/SubmersedVR/SubmersedVR.csproj index 1b9bffc..2c78cc4 100644 --- a/SubmersedVR/SubmersedVR.csproj +++ b/SubmersedVR/SubmersedVR.csproj @@ -4,7 +4,7 @@ net472 SubmersedVR A VR Conversion Mod for Subnautica - 0.1.0 + 0.4.0 true latest diff --git a/SubmersedVR/Tweaks/MiscTweaks.cs b/SubmersedVR/Tweaks/MiscTweaks.cs index 3887eb4..acf6866 100644 --- a/SubmersedVR/Tweaks/MiscTweaks.cs +++ b/SubmersedVR/Tweaks/MiscTweaks.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using Story; +using BepInEx.Logging; namespace SubmersedVR { @@ -14,8 +15,8 @@ public static void BetterTextureQuality() QualitySettings.anisotropicFiltering = AnisotropicFiltering.ForceEnable; QualitySettings.masterTextureLimit = 0; } - } + } [HarmonyPatch(typeof(Bench), nameof(Bench.CanSit))] public static class CanAlwaysSit @@ -57,41 +58,5 @@ public static bool Prefix() } } - - [HarmonyPatch(typeof(GameInput), nameof(GameInput.UpdateKeyInputs))] - public class GameInputKeyTracker : MonoBehaviour - { - public static void Postfix(GameInput __instance, bool useKeyboard, bool useController) - { - if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 107) //F6 key - { - } - if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 106) //F5 key - { - //SteamVrGameInput.debugSnapTurn = !SteamVrGameInput.debugSnapTurn; - //Mod.logger.LogInfo($"Snap turn debugging is {SteamVrGameInput.debugSnapTurn}"); - /* - foreach (HideForScreenshots hideForScreenshots in UnityEngine.Object.FindObjectsOfType()) - { - Mod.logger.LogInfo($"HideForScreenshots {hideForScreenshots.type} {hideForScreenshots.name} {hideForScreenshots.tag} {hideForScreenshots.enabled}"); - } - */ - } - } - } - - -/* - //Make building with the fabricator much quicker - [HarmonyPatch(typeof(Constructable), nameof(Constructable.GetConstructInterval))] - public static class AutoBuildFix - { - public static bool Prefix(Base __instance, ref float __result) - { - __result = 0.05f; - return false; - } - } -*/ } diff --git a/SubmersedVR/Utils/DebugUtilities.cs b/SubmersedVR/Utils/DebugUtilities.cs new file mode 100644 index 0000000..bd86ef1 --- /dev/null +++ b/SubmersedVR/Utils/DebugUtilities.cs @@ -0,0 +1,174 @@ +using UnityEngine; +using HarmonyLib; + + +namespace SubmersedVR +{ + public static class DebugUtilities + { + public static bool adjustAngle = false; + public static void ManualPositionAdjustments(float x, float y, float z) + { + string result = adjustAngle ? "Adjusting angles\n" : "Adjusting positions\n"; + if(adjustAngle) + { + //result += WristHud.AdjustHUD(0, 0, 0, x, y, z); + //result += VRHands.AdjustPDA(0, 0, 0, x, y, z); + } + else + { + //result += WristHud.AdjustHUD(x, y, z, 0, 0, 0); + //result += VRHands.AdjustPDA(x, y, z, 0, 0, 0); + } + //result += VRHands.AdjustFinger(x, y, z); + + DebugPanel.Show($"{result}", true); + } + public static void ToggleAdjustmentType() + { + DebugUtilities.adjustAngle = !DebugUtilities.adjustAngle; + string result = adjustAngle ? "Adjusting angles\n" : "Adjusting positions\n"; + DebugPanel.Show($"{result}", true); + } + + } + + + [HarmonyPatch(typeof(GameInput), nameof(GameInput.UpdateKeyInputs))] + public class GameInputKeyTracker : MonoBehaviour + { + public static void Postfix(GameInput __instance, bool useKeyboard, bool useController) + { + if(Settings.IsDebugEnabled == false) { return; } + + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] != -1) + { + //DebugPanel.Show($"Key ID: {GameInput.lastInputPressed[(int)GameInput.lastDevice]}", true); + } + + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 107) //F6 key + { + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 106) //F5 key + { + //SteamVrGameInput.debugSnapTurn = !SteamVrGameInput.debugSnapTurn; + //Mod.logger.LogInfo($"Snap turn debugging is {SteamVrGameInput.debugSnapTurn}"); + /* + foreach (HideForScreenshots hideForScreenshots in UnityEngine.Object.FindObjectsOfType()) + { + Mod.logger.LogInfo($"HideForScreenshots {hideForScreenshots.type} {hideForScreenshots.name} {hideForScreenshots.tag} {hideForScreenshots.enabled}"); + } + */ + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 84) //Y+ + { + DebugUtilities.ManualPositionAdjustments(0f, 1f, 0f); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 78) //Y- + { + DebugUtilities.ManualPositionAdjustments(0f, -1f, 0f); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 82) //X+ + { + DebugUtilities.ManualPositionAdjustments(1f, 0f, 0f); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 80) //X- + { + DebugUtilities.ManualPositionAdjustments(-1f, 0f, 0f); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 83) //Z+ + { + DebugUtilities.ManualPositionAdjustments(0f, 0f, 1f); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 77) //Z- + { + DebugUtilities.ManualPositionAdjustments(0f, 0f, -1f); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 76) //0 INS + { + DebugUtilities.ToggleAdjustmentType(); + } + + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 61) //Pinky 1 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_PinkyFinger1); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 45) //Pinky 2 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_PinkyFinger2); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 70) //Pinky 3 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_PinkyFinger3); + } + + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 67) //Ring 1 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_RingFinger1); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 63) //Ring 2 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_RingFinger2); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 68) //Ring 3 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_RingFinger3); + } + + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 49) //Middle 1 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_MiddleFinger1); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 48) //Middle 2 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_MiddleFinger2); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 47) //Middle 3 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_MiddleFinger3); + } + + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 62) //Index 1 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_IndexFinger1); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 50) //Index 2 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_IndexFinger2); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 66) //Index 3 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_IndexFinger3); + } + + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 64) //Thumb 1 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_Thumb1); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 51) //Thumb 2 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_Thumb2); + } + if(GameInput.lastInputPressed[(int)GameInput.lastDevice] == 46) //Thumb 3 + { + VRHands.instance.SetCurrentEditFinger((int)VRHands.HandSkeletonBone.eBone_Thumb3); + } + + } + } + + +/* + //Make building with the fabricator much quicker + [HarmonyPatch(typeof(Constructable), nameof(Constructable.GetConstructInterval))] + public static class AutoBuildFix + { + public static bool Prefix(Base __instance, ref float __result) + { + __result = 0.05f; + return false; + } + } +*/ + +} diff --git a/SubmersedVR/VR/VRCameraRig.cs b/SubmersedVR/VR/VRCameraRig.cs index bb9bb4d..2014d3d 100644 --- a/SubmersedVR/VR/VRCameraRig.cs +++ b/SubmersedVR/VR/VRCameraRig.cs @@ -55,12 +55,11 @@ class VRCameraRig : MonoBehaviour float yVelocity = 0.0f; -/* - //Using these booleans instead of doing a component lookup during Update() calls - public bool isPilotingSeaTruck = false; - public bool isPilotingSnowbike = false; - public bool isPilotingExosuit = false; -*/ + //SetTarget can be called before the quickslots are set up. For example when a game + //first loads and you were piloting the SeaTruck in the save. This holds the target + //reference until the quickslots exist + IQuickSlots deferredTarget = null; + public Camera UIControllerCamera { get @@ -114,7 +113,120 @@ public void SetCameraTrackTarget(Transform target) { this.rigParentTarget = target; } +/* + -export_skeleton + --head_rig + ---Cam + ----cam_offset + -----cam_forward + -----cam_up + ---neck + ----chest + -----clav_L + ------clav_L_aim + -------LArm_PoleTarg + -------shoulder_L + --------elbow_L + ---------elbow_L_twist_rig + ---------hand_L + ----------attachL + -----------PlayerPDA + ------------Mesh + ------------ScreenAnchor + -----------spikeytrap_tentacle_attach + ----------hand_L_midl_base + -----------hand_L_midl_mid + ------------hand_L_midl_tip + ----------hand_L_pinky_base + -----------hand_L_pinky_mid + ------------hand_L_pinky_tip + ----------hand_L_point_base + -----------hand_L_point_mid + ------------hand_L_point_tip + ----------hand_L_ring_base + -----------hand_L_ring_mid + ------------hand_L_ring_tip + ----------hand_L_thumb_base + -----------hand_L_thumb_mid + ------------hand_L_thumb_tip + ----------metacarpal_L + ----------bleeder_attach + --------shoulder_L_twist1_rig + --------shoulder_L_twist2_rig + -----clav_R + ------clav_R_aim + -------RArm_PoleTarg + -------shoulder_R + --------elbow_R + ---------elbow_R_twist_rig + ---------hand_R + ----------attach1 + -----------attach1_forward + -----------attach1_up + ----------hand_R_midl_base + -----------hand_R_midl_mid + ------------hand_R_midl_tip_rig + ----------hand_R_pinky_base + -----------hand_R_pinky_mid + ------------hand_R_pinky_tip_rig + ----------hand_R_point_base + -----------hand_R_point_mid + ------------hand_R_point_tip_rig + ----------hand_R_ring_base + -----------hand_R_ring_mid + ------------hand_R_ring_tip_rig + ----------hand_R_thumb_base + -----------hand_R_thumb_mid + ------------hand_R_thumb_tip_rig + ----------left_hand_elbowTarget + ----------left_hand_target + ----------metacarpal_R + --------shoulder_R_twist1_rig + --------shoulder_R_twist2_rig + -----spine_3 + ------spine_2 + -------spine_1 + --------hips + ---------thigh_L + ----------calf_L + -----------ankle_L + ------------toe_L_rig + -------------Flipper1_L + ---------thigh_R + ----------calf_R + -----------ankle_R + ------------toe_R + -------------Flipper1_R + ---ponytail_base + ----ponytail_1 + -----ponytail_2 + ------ponytail_3 + -------ponytail_4 + ---robin_head_selfies + ----Robin_Head_geo + ---FlashlightHelmet + ----helmet_geo + -female_geo + --base + ---female_base_body_geo + ---female_base_flipper_geo + ---female_base_gloves_geo + ---female_base_hand_geo + ---female_base_head_geo + ---female_base_mask_geo + --coldProtective + ---female_coldProtectiveSuit_body_geo + ---female_coldProtectiveSuit_hands_geo + ---female_coldProtectiveSuit_head_geo + ---female_coldProtectiveSuit_mask_geo + --reinforced + ---female_reinforced_body_geo + ---female_reinforced_hands_geo + --stillSuit + ---female_stillSuit_body_geo + --bottom +*/ public void SetupControllers() { // TODO: Naming is inconsistent, clean this mess up, only need 1/2 pointers? @@ -277,6 +389,11 @@ public void StealUICamera(Camera camera, bool fromGame = false) SetupPDA(); VrQuickSlots = new GameObject("VRQuickSlots").ResetTransform().AddComponent(); VrQuickSlots.Setup(SteamVR_Actions.subnautica_OpenQuickSlotWheel); + if(deferredTarget != null) + { + VrQuickSlots.SetTarget(deferredTarget); + deferredTarget = null; + } } else { @@ -288,6 +405,17 @@ public void StealUICamera(Camera camera, bool fromGame = false) VRHud.Setup(uiCamera, rightControllerUI.transform); } + public void SetQuickSlotsTarget(IQuickSlots target) + { + if(VrQuickSlots == null) + { + deferredTarget = target; + } + else + { + VrQuickSlots.SetTarget(target); + } + } void SetupPDA() { // Move the quickslots to bottom of PDA bottom left and make it bigger @@ -372,6 +500,7 @@ public void LateUpdate() //Player.main.armsController.transform.position = MainCameraControl.main.transform.position + (MainCameraControl.main.transform.forward * zOffset) + new Vector3(0f, yOffset, 0f); } } + //Experimental player scaling Player.main.armsController.transform.localScale = new Vector3(Settings.PlayerScale, Settings.PlayerScale, Settings.PlayerScale); Player.main.playerController.standheight = 1.5f * Settings.PlayerScale; } @@ -418,6 +547,7 @@ public void SetWorldTarget(GameObject activeTarget, float activeHitDistance) #region Patches + //Head based vs Hand based movement [HarmonyPatch(typeof(PlayerController), nameof(PlayerController.forwardReference), MethodType.Getter)] public static class MoveDirectionOverride { @@ -427,12 +557,13 @@ static bool Prefix(PlayerController __instance, ref Transform __result) if(Settings.HandBasedTurning) { //Use the Camera's position and the laser pointer's rotation + //Use a dummy object to hold the transform if(controllerTransform == null) { controllerTransform = new GameObject().transform; } controllerTransform.position = MainCamera.camera.transform.position; - controllerTransform.rotation = VRCameraRig.instance.laserPointer.transform.rotation; + controllerTransform.rotation = VRCameraRig.GetTargetTansform().rotation; //the laser pointer transform __result = controllerTransform; } else @@ -455,6 +586,7 @@ static bool Prefix(UnderwaterMotor __instance) } */ + //Adjust the player position while piloting vehicles with vr offset positions and user overrides [HarmonyPatch(typeof(MainCameraControl), nameof(MainCameraControl.OnLateUpdate))] public static class PlayerPositionFixer { diff --git a/SubmersedVR/VR/VRHands.cs b/SubmersedVR/VR/VRHands.cs index 61a9975..df27fb9 100644 --- a/SubmersedVR/VR/VRHands.cs +++ b/SubmersedVR/VR/VRHands.cs @@ -7,13 +7,17 @@ using System; using System.Globalization; + + namespace SubmersedVR { extern alias SteamVRActions; extern alias SteamVRRef; + using SteamVRRef.Valve.VR; using SteamVRActions.Valve.VR; using System.Collections.Generic; + public struct TransformOffset { public TransformOffset(Vector3 pos, Vector3 angles) @@ -54,40 +58,44 @@ public void Apply(Transform tf) static class HandOffsets { // Default Offset when no tool is Equipped - public static TransformOffset RightHand = new TransformOffset(new Vector3(0.05f, 0.06f, -0.17f), new Vector3(40.0f, 175.0f, 270.0f)); - public static TransformOffset LeftHand = new TransformOffset(new Vector3(-0.05f, 0.06f, -0.17f), new Vector3(-40.0f, 0.0f, 90.0f)); - public static TransformOffset PDA = new TransformOffset(new Vector3(-0.05f, 0.05f, -0.14f), new Vector3(305.0f, 355.0f, 100.0f)); + //public static TransformOffset RightHand = new TransformOffset(new Vector3(0.05f, 0.06f, -0.17f), new Vector3(40.0f, 175.0f, 270.0f)); + //public static TransformOffset LeftHand = new TransformOffset(new Vector3(-0.05f, 0.06f, -0.17f), new Vector3(-40.0f, 0.0f, 90.0f)); + public static TransformOffset RightHand = new TransformOffset(new Vector3(0.008f, 0.048f, -0.153f), new Vector3(36.736f, 204.358f, 281.731f)); + public static TransformOffset LeftHand = new TransformOffset(new Vector3(-0.008f, 0.048f, -0.153f), new Vector3(-36.736f, -22.358f, 101.731f)); + public static TransformOffset PDA = new TransformOffset(new Vector3(-0.04f, -0.01f, -0.18f), new Vector3(312.0f, 339.0f, 118.0f)); internal static TransformOffset GetHandOffset(this PlayerTool tool) { switch (tool) { case FireExtinguisher _: return new TransformOffset(new Vector3(0.062f, 0.077f, -0.148f), new Vector3(40.736f, 139.849f, 249.888f)); - case Seaglide _: return new TransformOffset(new Vector3(0.032f, 0.004f, -0.122f), new Vector3(63.358f, 183.803f, 289.671f)); - case Gravsphere _: return new TransformOffset(new Vector3(-0.022f, 0.049f, -0.179f), new Vector3(46.409f, 158.708f, 255.986f)); - case DeployableStorage _: return new TransformOffset(new Vector3(0.017f, 0.099f, -0.135f), new Vector3(27.633f, 159.160f, 251.929f)); - case Constructor _: return new TransformOffset(new Vector3(0.042f, 0.076f, -0.166f), new Vector3(53.635f, 151.667f, 249.508f)); - case LEDLight _: return new TransformOffset(new Vector3(0.051f, 0.113f, -0.122f), new Vector3(20.287f, 157.143f, 262.503f)); + case Seaglide _: return new TransformOffset(new Vector3(0.055f, -0.008f, -0.170f), new Vector3(56.208f, 190.815f, 300.073f)); + case Gravsphere _: return new TransformOffset(new Vector3(0.000f, 0.026f, -0.187f), new Vector3(39.548f, 161.521f, 241.912f)); + case DeployableStorage _: return new TransformOffset(new Vector3(0.000f, -0.025f, -0.173f), new Vector3(63.504f, 123.350f, 192.938f)); + case Constructor _: return new TransformOffset(new Vector3(0.011f, 0.016f, -0.175f), new Vector3(46.927f, 159.837f, 244.111f)); + case LEDLight _: return new TransformOffset(new Vector3(-0.014f, 0.052f, -0.174f), new Vector3(25.686f, 159.462f, 230.006f)); //case Knife _: return new TransformOffset(new Vector3(0.008f, 0.095f, -0.115f), new Vector3(17.193f, 162.033f, 250.308f)); - case HeatBlade _: return new TransformOffset(new Vector3(0.002f, 0.070f, -0.139f), new Vector3(35.311f, 158.499f, 244.373f)); - case Knife _: return new TransformOffset(new Vector3(0.002f, 0.070f, -0.139f), new Vector3(35.311f, 158.499f, 244.373f)); - case FlashLight _: return new TransformOffset(new Vector3(-0.043f, 0.077f, -0.147f), new Vector3(13.952f, 173.934f, 243.317f)); - case Beacon _: return new TransformOffset(new Vector3(-0.056f, 0.065f, -0.166f), new Vector3(45.693f, 162.076f, 253.284f)); + case HeatBlade _: return new TransformOffset(new Vector3(-0.006f, 0.053f, -0.144f), new Vector3(22.605f, 169.347f, 253.672f)); + case Knife _: return new TransformOffset(new Vector3(-0.006f, 0.053f, -0.144f), new Vector3(22.605f, 169.347f, 253.672f)); + case FlashLight _: return new TransformOffset(new Vector3(-0.001f, 0.056f, -0.205f), new Vector3(42.308f, 176.628f, 250.467f)); + case Beacon _: return new TransformOffset(new Vector3(-0.007f, 0.048f, -0.208f), new Vector3(53.772f, 166.540f, 253.121f)); case StasisRifle _: return new TransformOffset(new Vector3(0.013f, 0.091f, -0.155f), new Vector3(32.203f, 147.266f, 237.102f)); - case PropulsionCannonWeapon _: return new TransformOffset(new Vector3(-0.048f, 0.108f, -0.157f), new Vector3(11.552f, 167.761f, 242.069f)); - case BuilderTool _: return new TransformOffset(new Vector3(-0.015f, 0.071f, -0.115f), new Vector3(23.094f, 171.526f, 256.598f)); - case AirBladder _: return new TransformOffset(new Vector3(-0.032f, 0.090f, -0.133f), new Vector3(7.689f, 145.798f, 224.260f)); - case Welder _: return new TransformOffset(new Vector3(0.003f, 0.061f, -0.174f), new Vector3(38.321f, 165.592f, 251.612f)); - case ScannerTool _: return new TransformOffset(new Vector3(-0.001f, -0.010f, -0.208f), new Vector3(57.830f, 141.500f, 218.863f)); + case PropulsionCannonWeapon _: return new TransformOffset(new Vector3(-0.009f, 0.064f, -0.150f), new Vector3(13.698f, 168.461f, 244.767f)); + case BuilderTool _: return new TransformOffset(new Vector3(0.025f, 0.012f, -0.198f), new Vector3(53.604f, 163.256f, 247.458f)); + case AirBladder _: return new TransformOffset(new Vector3(-0.011f, 0.032f, -0.175f), new Vector3(35.419f, 161.485f, 227.887f)); + case Welder _: return new TransformOffset(new Vector3(0.005f, 0.049f, -0.187f), new Vector3(36.966f, 165.995f, 252.229f)); + case ScannerTool _: return new TransformOffset(new Vector3(0.000f, 0.022f, -0.202f), new Vector3(52.459f, 153.129f, 231.355f)); //case ScannerTool _: return new TransformOffset(new Vector3(0.003f, 0.030f, -0.212f), new Vector3(46.895f, 169.689f, 261.732f)); - case LaserCutter _: return new TransformOffset(new Vector3(0.006f, 0.062f, -0.165f), new Vector3(36.058f, 166.251f, 252.623f)); - case Flare _: return new TransformOffset(new Vector3(0.022f, 0.011f, -0.099f), new Vector3(31.626f, 164.053f, 248.553f)); + case LaserCutter _: return new TransformOffset(new Vector3(0.006f, 0.051f, -0.178f), new Vector3(37.351f, 166.002f, 252.017f)); + case Flare _: return new TransformOffset(new Vector3(0.005f, 0.035f, -0.176f), new Vector3(30.936f, 159.593f, 233.597f)); case RepulsionCannon _: return new TransformOffset(new Vector3(-0.002f, 0.088f, -0.166f), new Vector3(33.777f, 149.093f, 232.610f)); - case SpyPenguinRemote _: return new TransformOffset(new Vector3(0.061f, 0.032f, -0.167f), new Vector3(42.475f, 164.059f, 267.806f)); - case SpyPenguinPlayerTool _: return new TransformOffset(new Vector3(0.040f, 0.046f, -0.168f), new Vector3(50.492f, 198.253f, 291.693f)); - case DiveReel _: return new TransformOffset(new Vector3(0.013f, 0.036f, -0.144f), new Vector3(49.622f, 154.604f, 231.200f)); - case TeleportationTool _: return new TransformOffset(new Vector3(0.048f, 0.016f, -0.172f), new Vector3(59.271f, 178.526f, 272.862f)); - case MetalDetector _: return new TransformOffset(new Vector3(-0.012f, 0.052f, -0.166f), new Vector3(44.017f, 172.117f, 247.519f)); + case SpyPenguinRemote _: return new TransformOffset(new Vector3(0.040f, 0.041f, -0.181f), new Vector3(36.275f, 155.571f, 251.696f)); + case SpyPenguinPlayerTool _: return new TransformOffset(new Vector3(0.030f, 0.023f, -0.193f), new Vector3(47.005f, 196.721f, 289.403f)); + case DiveReel _: return new TransformOffset(new Vector3(0.008f, 0.020f, -0.167f), new Vector3(46.825f, 153.870f, 230.479f)); + case TeleportationTool _: return new TransformOffset(new Vector3(0.011f, 0.013f, -0.199f), new Vector3(60.300f, 173.113f, 254.254f)); + case MetalDetector _: return new TransformOffset(new Vector3(0.000f, 0.042f, -0.170f), new Vector3(46.441f, 174.227f, 249.639f)); + case CreatureTool _: return new TransformOffset(new Vector3(0.003f, 0.007f, -0.179f), new Vector3(51.316f, 171.030f, 244.545f)); + case DropTool _: return new TransformOffset(new Vector3(0.020f, 0.038f, -0.172f), new Vector3(32.201f, 167.764f, 268.554f)); default: return RightHand; }; } @@ -96,7 +104,7 @@ internal static TransformOffset GetAimOffset(this PlayerTool tool) { switch (tool) { - case BuilderTool _: return new TransformOffset(new Vector3(-0.021f, -0.040f, 0.030f), new Vector3(73.312f, 2.306f, 323.270f)); + //case BuilderTool _: return new TransformOffset(new Vector3(-0.021f, -0.040f, 0.030f), new Vector3(73.312f, 2.306f, 323.270f)); //case ScannerTool _: return new TransformOffset(new Vector3(0.003f, 0.030f, -0.212f) + new Vector3(-0.014f, -0.031f, 0.008f), new Vector3(1.183f, -13.063f, -28.934f) - new Vector3(46.895f, 169.689f, 261.732f) ); case FireExtinguisher _: return new TransformOffset(new Vector3(0.003f, -0.101f, -0.054f), new Vector3(64.181f, 10.062f, 346.755f)); //case Seaglide _: return new TransformOffset(new Vector3(0.003f, -0.101f, -0.054f), new Vector3(64.181f, 10.062f, 346.755f)); @@ -108,7 +116,7 @@ internal static TransformOffset GetAimOffset(this PlayerTool tool) //case Welder _: return new TransformOffset(new Vector3(-0.028f, -0.017f, 0.056f), new Vector3(59.356f, 350.101f, 303.392f)); //case LaserCutter _: return new TransformOffset(new Vector3(-0.014f, -0.042f, 0.068f), new Vector3(60.953f, 4.267f, 316.030f)); //case HeatBlade _: return new TransformOffset(new Vector3(-0.016f, 0.033f, 0.004f), new Vector3(65.781f, 19.633f, 267.986f)); - case LEDLight _: return new TransformOffset(new Vector3(-0.021f, 0.005f, -0.008f), new Vector3(63.596f, 40.279f, 283.758f)); + //case LEDLight _: return new TransformOffset(new Vector3(-0.021f, 0.005f, -0.008f), new Vector3(63.596f, 40.279f, 283.758f)); default: return VRCameraRig.DefaultTargetTransform; }; } @@ -117,6 +125,42 @@ internal static TransformOffset GetAimOffset(this PlayerTool tool) class VRHands : MonoBehaviour { + public enum HandSkeletonBone: int + { + eBone_Root = 0, + eBone_Wrist, + eBone_Thumb0, + eBone_Thumb1, + eBone_Thumb2, + eBone_Thumb3, + eBone_IndexFinger0, + eBone_IndexFinger1, + eBone_IndexFinger2, + eBone_IndexFinger3, + eBone_IndexFinger4, + eBone_MiddleFinger0, + eBone_MiddleFinger1, + eBone_MiddleFinger2, + eBone_MiddleFinger3, + eBone_MiddleFinger4, + eBone_RingFinger0, + eBone_RingFinger1, + eBone_RingFinger2, + eBone_RingFinger3, + eBone_RingFinger4, + eBone_PinkyFinger0, + eBone_PinkyFinger1, + eBone_PinkyFinger2, + eBone_PinkyFinger3, + eBone_PinkyFinger4, + eBone_Aux_Thumb, + eBone_Aux_IndexFinger, + eBone_Aux_MiddleFinger, + eBone_Aux_RingFinger, + eBone_Aux_PinkyFinger, + eBone_Count + }; + public FullBodyBipedIK ik = null; public Transform leftTarget; @@ -129,6 +173,12 @@ class VRHands : MonoBehaviour public Transform leftElbow; public Transform rightElbow; + public Transform[] leftHandFingers; + public Transform[] rightHandFingers; + public Vector3[] minRotation; + public Vector3[] maxRotation; + public int currentEditFinger = (int)HandSkeletonBone.eBone_IndexFinger1; + public static VRHands instance; // private OffsetCalibrationTool calibrationTool; @@ -158,6 +208,8 @@ public void Setup(FullBodyBipedIK ik) UpdateBody(); + SetupFingers(); + /* var calibrationTool = new OffsetCalibrationTool(rightTarget, SteamVR_Actions.subnautica_MoveDown, SteamVR_Actions.subnautica_AltTool); calibrationTool.enabled = Settings.IsDebugEnabled; @@ -168,6 +220,121 @@ public void Setup(FullBodyBipedIK ik) */ } + public void SetCurrentEditFinger(int val) + { + currentEditFinger = val; + } + + public static string AdjustPDA(float pX, float pY, float pZ, float aX, float aY, float aZ) + { + HandOffsets.PDA = new TransformOffset(new Vector3(HandOffsets.PDA.Pos.x + (pX/1000), HandOffsets.PDA.Pos.y + (pY/1000), HandOffsets.PDA.Pos.z + (pZ/1000)), new Vector3(HandOffsets.PDA.Angles.x + aX,HandOffsets.PDA.Angles.y + aY,HandOffsets.PDA.Angles.z + aZ)); + HandOffsets.PDA.Apply(VRHands.instance.leftTarget); + return $"AdjustPDA\npX={HandOffsets.PDA.Pos.x.ToString("0.000")}\npY={HandOffsets.PDA.Pos.y.ToString("0.000")}\npZ={HandOffsets.PDA.Pos.z.ToString("0.000")}\naX={HandOffsets.PDA.Angles.x}\naY={HandOffsets.PDA.Angles.y}\naZ={HandOffsets.PDA.Angles.z}"; + } + + public static string AdjustFinger(float x, float y, float z) + { + Vector3[] rotation = VRHands.instance.maxRotation; + rotation[VRHands.instance.currentEditFinger] += new Vector3(x, y, z); + return $"AdjustFinger\nindex={VRHands.instance.currentEditFinger} x={rotation[VRHands.instance.currentEditFinger].x} y={rotation[VRHands.instance.currentEditFinger].y} z={rotation[VRHands.instance.currentEditFinger].z}"; + } + public void SetupFingers() + { + string[] boneNamesLeft = new string[(int)HandSkeletonBone.eBone_Count]; + boneNamesLeft[(int)HandSkeletonBone.eBone_Thumb1] = "/hand_L_thumb_base"; + boneNamesLeft[(int)HandSkeletonBone.eBone_Thumb2] = "/hand_L_thumb_base/hand_L_thumb_mid"; + boneNamesLeft[(int)HandSkeletonBone.eBone_Thumb3] = "/hand_L_thumb_base/hand_L_thumb_mid/hand_L_thumb_tip"; + boneNamesLeft[(int)HandSkeletonBone.eBone_IndexFinger1] = "/hand_L_point_base"; + boneNamesLeft[(int)HandSkeletonBone.eBone_IndexFinger2] = "/hand_L_point_base/hand_L_point_mid"; + boneNamesLeft[(int)HandSkeletonBone.eBone_IndexFinger3] = "/hand_L_point_base/hand_L_point_mid/hand_L_point_tip"; + boneNamesLeft[(int)HandSkeletonBone.eBone_MiddleFinger1] = "/hand_L_midl_base"; + boneNamesLeft[(int)HandSkeletonBone.eBone_MiddleFinger2] = "/hand_L_midl_base/hand_L_midl_mid"; + boneNamesLeft[(int)HandSkeletonBone.eBone_MiddleFinger3] = "/hand_L_midl_base/hand_L_midl_mid/hand_L_midl_tip"; + boneNamesLeft[(int)HandSkeletonBone.eBone_RingFinger1] = "/hand_L_ring_base"; + boneNamesLeft[(int)HandSkeletonBone.eBone_RingFinger2] = "/hand_L_ring_base/hand_L_ring_mid"; + boneNamesLeft[(int)HandSkeletonBone.eBone_RingFinger3] = "/hand_L_ring_base/hand_L_ring_mid/hand_L_ring_tip"; + boneNamesLeft[(int)HandSkeletonBone.eBone_PinkyFinger1] = "/hand_L_pinky_base"; + boneNamesLeft[(int)HandSkeletonBone.eBone_PinkyFinger2] = "/hand_L_pinky_base/hand_L_pinky_mid"; + boneNamesLeft[(int)HandSkeletonBone.eBone_PinkyFinger3] = "/hand_L_pinky_base/hand_L_pinky_mid/hand_L_pinky_tip"; + + string[] boneNamesRight = new string[(int)HandSkeletonBone.eBone_Count]; + boneNamesRight[(int)HandSkeletonBone.eBone_Thumb1] = "/hand_R_thumb_base"; + boneNamesRight[(int)HandSkeletonBone.eBone_Thumb2] = "/hand_R_thumb_base/hand_R_thumb_mid"; + boneNamesRight[(int)HandSkeletonBone.eBone_Thumb3] = "/hand_R_thumb_base/hand_R_thumb_mid/hand_R_thumb_tip_rig"; + boneNamesRight[(int)HandSkeletonBone.eBone_IndexFinger1] = "/hand_R_point_base"; + boneNamesRight[(int)HandSkeletonBone.eBone_IndexFinger2] = "/hand_R_point_base/hand_R_point_mid"; + boneNamesRight[(int)HandSkeletonBone.eBone_IndexFinger3] = "/hand_R_point_base/hand_R_point_mid/hand_R_point_tip_rig"; + boneNamesRight[(int)HandSkeletonBone.eBone_MiddleFinger1] = "/hand_R_midl_base"; + boneNamesRight[(int)HandSkeletonBone.eBone_MiddleFinger2] = "/hand_R_midl_base/hand_R_midl_mid"; + boneNamesRight[(int)HandSkeletonBone.eBone_MiddleFinger3] = "/hand_R_midl_base/hand_R_midl_mid/hand_R_midl_tip_rig"; + boneNamesRight[(int)HandSkeletonBone.eBone_RingFinger1] = "/hand_R_ring_base"; + boneNamesRight[(int)HandSkeletonBone.eBone_RingFinger2] = "/hand_R_ring_base/hand_R_ring_mid"; + boneNamesRight[(int)HandSkeletonBone.eBone_RingFinger3] = "/hand_R_ring_base/hand_R_ring_mid/hand_R_ring_tip_rig"; + boneNamesRight[(int)HandSkeletonBone.eBone_PinkyFinger1] = "/hand_R_pinky_base"; + boneNamesRight[(int)HandSkeletonBone.eBone_PinkyFinger2] = "/hand_R_pinky_base/hand_R_pinky_mid"; + boneNamesRight[(int)HandSkeletonBone.eBone_PinkyFinger3] = "/hand_R_pinky_base/hand_R_pinky_mid/hand_R_pinky_tip_rig"; + + minRotation = new Vector3[(int)HandSkeletonBone.eBone_Count]; + for(int i = 0; i < minRotation.Length; i++) + { + minRotation[i] = Vector3.zero; + } + + minRotation[(int)HandSkeletonBone.eBone_Thumb1] = new Vector3(50.2f, 65.0f, 23.1f); + minRotation[(int)HandSkeletonBone.eBone_Thumb2] = new Vector3(2.7f, -8f, 10f); + minRotation[(int)HandSkeletonBone.eBone_Thumb3] = new Vector3(0.0f, 0.0f, 2.2f); + + maxRotation = new Vector3[(int)HandSkeletonBone.eBone_Count]; + maxRotation[(int)HandSkeletonBone.eBone_Thumb1] = new Vector3(20.2f, 50.2f, 31.6f); + maxRotation[(int)HandSkeletonBone.eBone_Thumb2] = new Vector3(37.7f, -8f, 34.0f); + maxRotation[(int)HandSkeletonBone.eBone_Thumb3] = new Vector3(0.0f, 0.0f, 52.9f); + maxRotation[(int)HandSkeletonBone.eBone_IndexFinger1] = new Vector3(-10f, -16f, 79.1f); + maxRotation[(int)HandSkeletonBone.eBone_IndexFinger2] = new Vector3(30.0f, 0.0f, 109.8f); + maxRotation[(int)HandSkeletonBone.eBone_IndexFinger3] = new Vector3(0.0f, 2.7f, 76.5f); + maxRotation[(int)HandSkeletonBone.eBone_MiddleFinger1] = new Vector3(-9f, -16f, 77.1f); + maxRotation[(int)HandSkeletonBone.eBone_MiddleFinger2] = new Vector3(20.0f, 0.0f, 96.8f); + maxRotation[(int)HandSkeletonBone.eBone_MiddleFinger3] = new Vector3(7.0f, 2.7f, 78.5f); + maxRotation[(int)HandSkeletonBone.eBone_RingFinger1] = new Vector3(-10f, -20f, 74.1f); + maxRotation[(int)HandSkeletonBone.eBone_RingFinger2] = new Vector3(15.0f, 0.0f, 94.8f); + maxRotation[(int)HandSkeletonBone.eBone_RingFinger3] = new Vector3(0.0f, 2.7f, 78.5f); + maxRotation[(int)HandSkeletonBone.eBone_PinkyFinger1] = new Vector3(-7f, -15f, 71.1f); + maxRotation[(int)HandSkeletonBone.eBone_PinkyFinger2] = new Vector3(6.0f, 0.0f, 101.8f); + maxRotation[(int)HandSkeletonBone.eBone_PinkyFinger3] = new Vector3(-8f, 2.7f, 78.5f); + + leftHandFingers = new Transform[(int)HandSkeletonBone.eBone_Count]; + rightHandFingers = new Transform[(int)HandSkeletonBone.eBone_Count]; + var animator = Player.main?.playerAnimator; + if (animator is Animator anim) + { + for(int i = 0; i < boneNamesLeft.Length; i++) + { + String boneName = boneNamesLeft[i]; + if(boneName != null) + { + leftHandFingers[i] = anim.transform.Find("export_skeleton/head_rig/neck/chest/clav_L/clav_L_aim/shoulder_L/hand_L" + boneName); + if(leftHandFingers[i] == null) + { + leftHandFingers[i] = anim.transform.Find("export_skeleton/head_rig/neck/chest/clav_L/clav_L_aim/shoulder_L/elbow_L/hand_L" + boneName); + } + } + } + for(int i = 0; i < boneNamesRight.Length; i++) + { + String boneName = boneNamesRight[i]; + if(boneName != null) + { + rightHandFingers[i] = anim.transform.Find("export_skeleton/head_rig/neck/chest/clav_R/clav_R_aim/shoulder_R/hand_R" + boneName); + if(rightHandFingers[i] == null) + { + rightHandFingers[i] = anim.transform.Find("export_skeleton/head_rig/neck/chest/clav_R/clav_R_aim/shoulder_R/elbow_R/hand_R" + boneName); + } + } + } + } + + } + + public void SetHandParents(bool fullbody) { if( fullbody) @@ -244,12 +411,72 @@ void LateUpdate() // Reset Elbows leftElbow.transform.SetPositionAndRotation(leftHand.position, leftHand.rotation); rightElbow.transform.SetPositionAndRotation(rightHand.position, rightHand.rotation); + } + + if(Settings.ArticulatedHands) + { + SteamVR_Action_Skeleton rightSkeletonAction = SteamVR_Input.GetSkeletonAction("RightHandSkeleton"); + SteamVR_Action_Skeleton leftSkeletonAction = SteamVR_Input.GetSkeletonAction("LeftHandSkeleton"); + //DebugPanel.Show($"GetLookDelta: 0={skeletonAction.boneRotations[11].eulerAngles}\n 1={skeletonAction.boneRotations[12].eulerAngles}\n2={skeletonAction.boneRotations[13].eulerAngles}\n3={skeletonAction.boneRotations[14].eulerAngles}\ncurl={skeletonAction.middleCurl}", true); + //DebugPanel.Show($"Fingers: 1={rightHandFingers[(int)HandSkeletonBone.eBone_Thumb1].transform.localRotation.eulerAngles}\n2={rightHandFingers[(int)HandSkeletonBone.eBone_Thumb2].transform.localRotation.eulerAngles}\n3={rightHandFingers[(int)HandSkeletonBone.eBone_Thumb3].transform.localRotation.eulerAngles}", true); + //DebugPanel.Show($"Fingers: {leftSkeletonAction.skeletalTrackingLevel} {leftSkeletonAction.indexCurl} {leftSkeletonAction.pinkyCurl}", true); + + if(!Player.main.pda.isOpen) + { + UpdateFinger(leftHandFingers, (int)HandSkeletonBone.eBone_PinkyFinger1, leftSkeletonAction.pinkyCurl); + UpdateFinger(leftHandFingers, (int)HandSkeletonBone.eBone_RingFinger1, leftSkeletonAction.ringCurl); + UpdateFinger(leftHandFingers, (int)HandSkeletonBone.eBone_MiddleFinger1, leftSkeletonAction.middleCurl); + UpdateFinger(leftHandFingers, (int)HandSkeletonBone.eBone_IndexFinger1, leftSkeletonAction.indexCurl); + UpdateFinger(leftHandFingers, (int)HandSkeletonBone.eBone_Thumb1, leftSkeletonAction.thumbCurl); + } + + if(Inventory.main.GetHeld() == null) + { + UpdateFinger(rightHandFingers, (int)HandSkeletonBone.eBone_PinkyFinger1, rightSkeletonAction.pinkyCurl); + UpdateFinger(rightHandFingers, (int)HandSkeletonBone.eBone_RingFinger1, rightSkeletonAction.ringCurl); + UpdateFinger(rightHandFingers, (int)HandSkeletonBone.eBone_MiddleFinger1, rightSkeletonAction.middleCurl); + UpdateFinger(rightHandFingers, (int)HandSkeletonBone.eBone_IndexFinger1, rightSkeletonAction.indexCurl); + UpdateFinger(rightHandFingers, (int)HandSkeletonBone.eBone_Thumb1, rightSkeletonAction.thumbCurl); + + //SN right ring finger is deformed for some reason so this corrects it + Vector3 minRotation = new Vector3(8.0f, -5.0f, -2.2f); + Vector3 maxRotation = new Vector3(-5f, -20f, 74.1f); //this.maxRotation[(int)HandSkeletonBone.eBone_RingFinger1]; // + rightHandFingers[(int)HandSkeletonBone.eBone_RingFinger1].transform.localRotation = Quaternion.Euler(minRotation.x + ((maxRotation.x - minRotation.x) * rightSkeletonAction.ringCurl), minRotation.y + ((maxRotation.y - minRotation.y) * rightSkeletonAction.ringCurl), minRotation.z + ((maxRotation.z - minRotation.z) * rightSkeletonAction.ringCurl)); + minRotation = Vector3.zero; + maxRotation = new Vector3(28f, 0f, 94.8f); + rightHandFingers[(int)HandSkeletonBone.eBone_RingFinger2].transform.localRotation = Quaternion.Euler(minRotation.x + ((maxRotation.x - minRotation.x) * rightSkeletonAction.ringCurl), minRotation.y + ((maxRotation.y - minRotation.y) * rightSkeletonAction.ringCurl), minRotation.z + ((maxRotation.z - minRotation.z) * rightSkeletonAction.ringCurl)); + } + + } + - //if(WristHud.GetIndexFingerTip() is Transform indexTip) - //{ - // DebugPanel.Show($" uiIndexPos = {indexTip.transform.position} {indexTip.localPosition} "); - //} +/* + for(int i = 0; i < leftHandFingers.Length; i++) + { + Transform finger = leftHandFingers[i]; + if(finger != null) + { + //finger.transform.localRotation = Quaternion.Euler(-skeletonAction.boneRotations[i].eulerAngles.x, -skeletonAction.boneRotations[i].eulerAngles.y, -skeletonAction.boneRotations[i].eulerAngles.z); //skeletonAction.boneRotations[12] * new Quaternion(-1, 1, -1, 1); //Quaternion.Euler(0f,0f,0f); + finger.transform.localRotation = skeletonAction.boneRotations[i].GetInverse(); + } } +*/ + /* + leftMidBase.transform.localRotation = Quaternion.Euler(-skeletonAction.boneRotations[(int)HandSkeletonBone.eBone_MiddleFinger1].eulerAngles.x, -skeletonAction.boneRotations[12].eulerAngles.y, -skeletonAction.boneRotations[12].eulerAngles.z); //skeletonAction.boneRotations[12] * new Quaternion(-1, 1, -1, 1); //Quaternion.Euler(0f,0f,0f); + leftMidMid.transform.localRotation = Quaternion.Euler(-skeletonAction.boneRotations[13].eulerAngles.x, -skeletonAction.boneRotations[13].eulerAngles.y, -skeletonAction.boneRotations[13].eulerAngles.z); //skeletonAction.boneRotations[13] * new Quaternion(-1, 1, -1, 1); //Quaternion.Euler(0f,0f,0f); + leftMidTip.transform.localRotation = Quaternion.Euler(-skeletonAction.boneRotations[14].eulerAngles.x, -skeletonAction.boneRotations[14].eulerAngles.y, -skeletonAction.boneRotations[14].eulerAngles.z); //skeletonAction.boneRotations[14] * new Quaternion(-1, 1, -1, 1); //Quaternion.Euler(0f,0f,0f); + + rightMidBase.transform.localRotation = rightSkeletonAction.boneRotations[12].GetInverse(); + rightMidMid.transform.localRotation = rightSkeletonAction.boneRotations[13].GetInverse(); + rightMidTip.transform.localRotation = rightSkeletonAction.boneRotations[14].GetInverse(); + */ + } + + public void UpdateFinger(Transform[] fingers,int fingerID, float percent) + { + fingers[fingerID].transform.localRotation = Quaternion.Euler(minRotation[fingerID].x + ((maxRotation[fingerID].x - minRotation[fingerID].x) * percent), minRotation[fingerID].y + ((maxRotation[fingerID].y - minRotation[fingerID].y) * percent), minRotation[fingerID].z + ((maxRotation[fingerID].z - minRotation[fingerID].z) * percent)); + fingers[fingerID+1].transform.localRotation = Quaternion.Euler(minRotation[fingerID+1].x + ((maxRotation[fingerID+1].x - minRotation[fingerID+1].x) * percent), minRotation[fingerID+1].y + ((maxRotation[fingerID+1].y - minRotation[fingerID+1].y) * percent), minRotation[fingerID+1].z + ((maxRotation[fingerID+1].z - minRotation[fingerID+1].z) * percent)); + fingers[fingerID+2].transform.localRotation = Quaternion.Euler(minRotation[fingerID+2].x + ((maxRotation[fingerID+2].x - minRotation[fingerID+2].x) * percent), minRotation[fingerID+2].y + ((maxRotation[fingerID+2].y - minRotation[fingerID+2].y) * percent), minRotation[fingerID+2].z + ((maxRotation[fingerID+2].z - minRotation[fingerID+2].z) * percent)); } public void UpdateBody() diff --git a/SubmersedVR/VR/VRHud.cs b/SubmersedVR/VR/VRHud.cs index c8e8e82..0fababb 100644 --- a/SubmersedVR/VR/VRHud.cs +++ b/SubmersedVR/VR/VRHud.cs @@ -155,7 +155,7 @@ static class WristHud { //private static TransformOffset wristOffset = new TransformOffset(new Vector3(-0.079f, 0.148f, -0.158f), new Vector3(350.494f, 88.400f, 244.161f)); //This works for Valve Index - private static TransformOffset wristOffset = new TransformOffset(new Vector3(-0.05f, 0.15f, -0.142f), new Vector3(15.494f, 88.400f, 244.161f)); + private static TransformOffset wristOffset = new TransformOffset(new Vector3(-0.016f, 0.123f, -0.128f), new Vector3(15.494f, 74.4f, 245.161f)); //private static TransformOffset wristOffset = new TransformOffset(new Vector3(-0.044f, 0.16f, -0.158f), new Vector3(15.494f, 88.400f, 244.161f)); private static GameObject wristTarget; private static Canvas canvas; @@ -176,6 +176,12 @@ static class WristHud //public static GameObject pointerDot; public static TextMeshProUGUI entry; + public static string AdjustHUD(float pX, float pY, float pZ, float aX, float aY, float aZ) + { + WristHud.wristOffset = new TransformOffset(new Vector3(WristHud.wristOffset.Pos.x + (pX/1000), WristHud.wristOffset.Pos.y + (pY/1000), WristHud.wristOffset.Pos.z + (pZ/1000)), new Vector3(WristHud.wristOffset.Angles.x + aX, WristHud.wristOffset.Angles.y + aY, WristHud.wristOffset.Angles.z + aZ)); + WristHud.wristOffset.Apply(WristHud.wristTarget.transform); + return $"AdjustHUD\npX={WristHud.wristOffset.Pos.x.ToString("0.000")}\npY={WristHud.wristOffset.Pos.y.ToString("0.000")}\npZ={WristHud.wristOffset.Pos.z.ToString("0.000")}\naX={WristHud.wristOffset.Angles.x}\naY={WristHud.wristOffset.Angles.y}\naZ={WristHud.wristOffset.Angles.z}"; + } public static FMODAsset CreateFMODAsset(string eventPath) { FMODAsset asset = ScriptableObject.CreateInstance(); diff --git a/SubmersedVR/VR/VRQuickSlots.cs b/SubmersedVR/VR/VRQuickSlots.cs index a230ffd..ef6c13f 100644 --- a/SubmersedVR/VR/VRQuickSlots.cs +++ b/SubmersedVR/VR/VRQuickSlots.cs @@ -50,6 +50,7 @@ void Awake() { var qs = FindObjectsOfType().First(obj => obj.name == "QuickSlots"); Mod.logger.LogDebug($"[nameof{this.GetType()}] Start, stealing stuff from on {qs.name}"); + materialBackground = qs.materialBackground; spriteLeft = qs.spriteLeft; spriteCenter = qs.spriteCenter; @@ -58,6 +59,7 @@ void Awake() spriteHighlighted = qs.spriteHighlighted; spriteExosuitArm = qs.spriteExosuitArm; spriteSelected = qs.spriteSelected; + } new void Init(IQuickSlots newTarget) @@ -66,10 +68,12 @@ void Awake() { return; } - Mod.logger.LogDebug($"[nameof{this.GetType()}] Init on {newTarget}"); base.Init(newTarget); ArangeIconsInCircle(wheelRadius); OnSelect(this.target.GetActiveSlotID()); + + //TechType techType = base.target.GetSlotBinding()[0]; + //Mod.logger.LogInfo($"[nameof{this.GetType()}] Init on {newTarget} {newTarget is SeaTruckUpgrades} {techType}"); } new void OnSelect(int slotID) @@ -109,8 +113,43 @@ int DetermineSlot(float angle) this.target = quickSlots; this.Init(this.target); } + var targetSlots = GetTarget(); + if (targetSlots == null) + { + return; + } if (active) { + //Update the icon progress + //This affects the UI a little but doesnt perform the expected progress UI. Why? + int i = 0; + int num = this.icons.Length; + while (i < num) + { + uGUI_ItemIcon uGUI_ItemIcon = this.icons[i]; + if (!(uGUI_ItemIcon == null)) + { + float slotProgress = this.target.GetSlotProgress(i); + float slotCharge = this.target.GetSlotCharge(i); + InventoryItem slotItem = this.target.GetSlotItem(i); + uGUI_ItemIcon.SetBarValue(TooltipFactory.GetBarValue(slotItem)); + if (slotProgress < 1f) + { + uGUI_ItemIcon.SetProgress(slotProgress, FillMethod.Radial); + } + else if (slotCharge > 0f) + { + uGUI_ItemIcon.SetProgress(slotCharge, FillMethod.Vertical); + } + else + { + uGUI_ItemIcon.SetProgress(1f, FillMethod.None); + } + } + i++; + } + //End Update + var from = controllerTarget.position; var origin = transform.position; var pX = Vector3.Dot(from - origin, transform.right); @@ -132,24 +171,29 @@ int DetermineSlot(float angle) { lastSlot = currentSlot; currentSlot = DetermineSlot(angle); + + TechType techType = currentSlot >=0 ? base.target.GetSlotBinding()[currentSlot] : TechType.None; if (currentSlot != lastSlot) { - var targetSlots = GetTarget(); - if (targetSlots == null) - { - return; - } targetSlots.SlotKeyDown(currentSlot); + //Mod.logger.LogInfo($"SlotKeyDown called {targetSlots} {targetSlots is SeaTruckUpgrades} {currentSlot} {techType}"); SteamVR_Actions.subnautica_HapticsRight.Execute(0.0f, 0.1f, 10f, 0.5f, SteamVR_Input_Sources.Any); } + + if(techType == TechType.SeaTruckUpgradePerimeterDefense) + { + targetSlots.SlotLeftHeld(); + //Mod.logger.LogInfo($"SlotLeftHeld called {targetSlots} {targetSlots is SeaTruckUpgrades} {currentSlot} {techType} {targetSlots.GetSlotCharge(currentSlot)}"); + float charge = targetSlots.GetSlotCharge(currentSlot); + if(charge > 0 && charge < 1.0f) + { + SteamVR_Actions.subnautica_HapticsRight.Execute(0.0f, 0.1f, 10f, 0.5f, SteamVR_Input_Sources.Any); + } + } + } else - { - var targetSlots = GetTarget(); - if (targetSlots == null) - { - return; - } + { // NOTE: you can't deselect in vehicles if (targetSlots is QuickSlots) { @@ -164,11 +208,11 @@ int DetermineSlot(float angle) } public void Activate(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource) - { + { bool isInExosuit = Player.main?.inExosuit == true; bool isOnSnowBike = Player.main?.inHovercraft == true; bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; - if(isOnSnowBike || isPilotingSeatruck) + if(isOnSnowBike || isInExosuit) { return; } @@ -197,6 +241,22 @@ public void Activate(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fr } public void Deactivate(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource) { + var targetSlots = GetTarget(); + if(targetSlots != null && currentSlot >= 0 ) + { + TechType techType = base.target.GetSlotBinding()[currentSlot]; + if(techType == TechType.SeaTruckUpgradePerimeterDefense) + { + targetSlots.SlotLeftUp(); + //Mod.logger.LogInfo($"SlotLeftUp called {targetSlots} {targetSlots is SeaTruckUpgrades} {currentSlot} {techType}"); + } + else if(techType == TechType.SeaTruckUpgradeAfterburner) + { + targetSlots.SlotLeftDown(); + //Mod.logger.LogInfo($"SlotLeftDown called {targetSlots} {targetSlots is SeaTruckUpgrades} {currentSlot} {techType}"); + } + } + canvas.enabled = false; active = false; FPSInputModule.current.lockRotation = false; @@ -245,15 +305,34 @@ public static class QuickSlots_Patch [HarmonyPatch(typeof(Vehicle), nameof(Vehicle.OnPilotModeBegin))] public static void Postfix(Vehicle __instance) { - VRCameraRig.instance?.VrQuickSlots?.SetTarget(__instance); + VRCameraRig.instance?.SetQuickSlotsTarget(__instance); } [HarmonyPatch(typeof(Vehicle), nameof(Vehicle.OnPilotModeEnd))] public static void Postfix() { - VRCameraRig.instance?.VrQuickSlots?.SetTarget(null); + VRCameraRig.instance?.SetQuickSlotsTarget(null); } } + + [HarmonyPatch(typeof(SeaTruckUpgrades), nameof(SeaTruckUpgrades.OnPilotBegin))] + public static class SeatruckQuickslotsBegin + { + public static void Postfix(SeaTruckUpgrades __instance) + { + VRCameraRig.instance?.SetQuickSlotsTarget(__instance); + } + } + [HarmonyPatch(typeof(SeaTruckUpgrades), nameof(SeaTruckUpgrades.OnPilotEnd))] + public static class SeatruckQuickslotsEnd + { + public static void Postfix(SeaTruckUpgrades __instance) + { + VRCameraRig.instance?.SetQuickSlotsTarget(null); + } + } + + #endregion } \ No newline at end of file From 9af1b28ed91a5fe227030a9b5e80ab15f2489600 Mon Sep 17 00:00:00 2001 From: jbusfield Date: Fri, 22 Sep 2023 12:06:44 -0700 Subject: [PATCH 6/9] Add Physical Driving option. Fixed (hopefully) the issue where snap turning sometimes stops working Piloting vehicles works similar to piloting in No Mans Sky where you should rest your arms on the armrest of a chair with your wrists hanging off of the end and use small wrist movements to control the vehicle. Snow Fox While gripping the right handlebar, twist your hand backward to throttle up and move forward ( like a real motorcycle). Twist your hand the opposite way to move backwards. Push the right hand forward to turn left or backward to move right. The left hand can also be used to grip the left handlebar and the force on the two handlebars are averaged when using the left hand at the same time. To move left or right instead of turning left or right, press the A button while making the turn motions. Jump is done by pressing down on the left thumbstick and speed boost is done by pressing down on the right thumbstick. Both actions can be performed at the same time. Prawn Suit While gripping the right control, rotate your hand to the left or right keeping your fingers forward to turn left or right. While gripping the left control, rotate your left hand left or right while keeping your fingers forward to move left or right. Tilt your left hand forward or backward to move forward or backward. The A button is the Jump button while piloting the Prawn Suit SeaTruck Be sure to hold your physical hands in the same orientation that the hands appear in Subnautica. This means your physical left hand will have your knuckles facing up while your physical right hand will have your knuckles facing to the right. Tilting your right hand left or right while keeping your fingers forward will turn the SeaTruck left or right. Tilting your right hand forward or backward will tilt the SeaTruck down or up. To move the SeaTruck up or down instead of tilting up or down, press the A button while making the same motions. Tilt your left hand forward or backward while gripping the left control to move the SeaTruck forward or backward. To move the SeaTruck left or right, twist your left hand left or right while keeping you knuckles facing up. Remember to keep your arms fixed on the armrest and simply make a slight twisting motion with your wrists. --- SubmersedVR/Input/SteamVrGameInput.cs | 177 ++++++-- SubmersedVR/Settings.cs | 6 +- SubmersedVR/Tweaks/Exosuit.cs | 8 +- SubmersedVR/Tweaks/Hoverbike.cs | 49 +- SubmersedVR/Tweaks/MapRoomCamera.cs | 102 +---- SubmersedVR/Tweaks/SnapTurning.cs | 530 ++++++++++++++++++++++ SubmersedVR/VR/VRCameraRig.cs | 4 +- SubmersedVR/VR/VRHands.cs | 44 +- SubmersedVR/VR/VRPhysicalPiloting.cs | 623 ++++++++++++++++++++++++++ 9 files changed, 1351 insertions(+), 192 deletions(-) create mode 100644 SubmersedVR/Tweaks/SnapTurning.cs create mode 100644 SubmersedVR/VR/VRPhysicalPiloting.cs diff --git a/SubmersedVR/Input/SteamVrGameInput.cs b/SubmersedVR/Input/SteamVrGameInput.cs index dd85b1e..7c7755d 100644 --- a/SubmersedVR/Input/SteamVrGameInput.cs +++ b/SubmersedVR/Input/SteamVrGameInput.cs @@ -57,7 +57,7 @@ static bool Prefix(GameInput.Button button, ref bool __result) { return false; } - + String actionName = button.ToString(); if(actionName == "TakePicture" && VRCameraRig.instance.photoRequested) { @@ -65,6 +65,14 @@ static bool Prefix(GameInput.Button button, ref bool __result) VRCameraRig.instance.photoRequested = false; return false; } + //When using physical driving we use the right A button as Up/Down and Jump but the A button is mapped to the LeftHand action. + //Therefore, only return true if the real LeftHand action is occuring (default is Left Trigger) + if(actionName == "LeftHand" && Settings.PhysicalDriving && (Player.main?.inExosuit == true || Player.main?.inSeatruckPilotingChair == true)) + { + __result = SteamVR_Actions.subnautica.LeftHand.GetStateDown(SteamVR_Input_Sources.LeftHand); + return false; + } + //Mod.logger.LogInfo($"GameInput.GetButtonDown {actionName}"); //Use the Sprint action as the Take Picture action when the player is not piloting a vehicle or holding a tool //Cant do this because oculus use the right stick press to show tools @@ -104,10 +112,20 @@ static bool Prefix(GameInput.Button button, ref bool __result) } String actionName = button.ToString(); + + //When using physical driving we use the right A button as Up/Down and Jump but the A button is mapped to the LeftHand action. + //Therefore, only return true if the real LeftHand action is occuring (default is Left Trigger) + if(actionName == "LeftHand" && Settings.PhysicalDriving && (Player.main?.inExosuit == true || Player.main?.inSeatruckPilotingChair == true)) + { + __result = SteamVR_Actions.subnautica.LeftHand.GetStateUp(SteamVR_Input_Sources.LeftHand); + return false; + } + if(actionName == "Answer") { actionName = "Deconstruct"; } + __result = SteamVR_Input.GetStateUp(actionName, SteamVR_Input_Sources.Any); //When sprint and Open Quick Slot actions conflict, dont sprint @@ -131,11 +149,20 @@ static bool Prefix(GameInput.Button button, ref bool __result) } String actionName = button.ToString(); + + //When using physical driving we use the right A button as Up/Down and Jump but the A button is mapped to the LeftHand action. + //Therefore, only return true if the real LeftHand action is occuring (default is Left Trigger) + if(actionName == "LeftHand" && Settings.PhysicalDriving && (Player.main?.inExosuit == true || Player.main?.inSeatruckPilotingChair == true)) + { + __result = SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.LeftHand); + return false; + } + if(actionName == "Answer") { actionName = "Deconstruct"; } - __result = SteamVR_Input.GetState(actionName, SteamVR_Input_Sources.Any); + __result = SteamVR_Input.GetState(actionName, SteamVR_Input_Sources.Any); //When sprint and Open Quick Slot actions conflict, dont sprint if(Player.main?.inSeatruckPilotingChair == true && actionName == "Sprint" && SteamVR_Input.GetStateDown("OpenQuickSlotWheel", SteamVR_Input_Sources.Any)) @@ -172,7 +199,7 @@ public static bool Prefix() return false; } } - +/* // Implement Snap turning for the player //Using Prefix for now while trying to figure out snap turning bug //Can use PostFix version when issue has been resolved @@ -239,6 +266,7 @@ public static bool Prefix(GameInput __instance, ref Vector2 __result) { vector.x = angle * Mathf.Sign(lookX); SteamVrGameInput.SnapTurned = true; + Mod.logger.LogInfo($"Snap Turned"); } else { @@ -247,10 +275,12 @@ public static bool Prefix(GameInput __instance, ref Vector2 __result) SteamVrGameInput.SnapTurned = false; } } - //DebugPanel.Show($"GetLookDelta: r={r} l={l} lr={r - l} delta={Time.deltaTime} lookX={lookX} absX = {absX} res={vector.x}", true); + //DebugPanel.Show($"GetLookDelta: r={r} l={l} lr={r - l}\ndelta={Time.deltaTime}\nlookX={lookX}\nabsX = {absX}\nsnapTurned={SteamVrGameInput.SnapTurned}", true); } + Mod.logger.LogInfo($"Snap Turning result {vector}"); __result = vector; + //__result = Vector2.zero; return false; } @@ -299,7 +329,7 @@ public static void Postfix(ref Vector2 __result) } #endif } - +*/ // Pretend the controlers are always available to make Subnautica not switch to Keyboard/Mouse and mess VR controls up // TODO: This should/could probably be changed though, asking SteamVR if controllers are available? @@ -340,7 +370,7 @@ public static class SteamVrGetAnalogValue { public static bool Prefix(GameInput.Button button, ref float __result) { - if (SteamVrGameInput.InputLocked || !SteamVrGameInput.IsSteamVrReady) + if (SteamVrGameInput.InputLocked || !SteamVrGameInput.IsSteamVrReady || VRHands.instance == null) { __result = 0.0f; return false; @@ -348,61 +378,144 @@ public static bool Prefix(GameInput.Button button, ref float __result) Vector2 vec; bool isPressed = false; float value = 0.0f; + + bool disableUpDown = Settings.PhysicalDriving && (Player.main?.IsPilotingSeatruck() == true || Player.main?.inExosuit == true); + float? result; switch (button) { case GameInput.Button.MoveForward: - vec = SteamVR_Actions.subnautica.Move.GetAxis(SteamVR_Input_Sources.Any); - value = vec.y > 0.0f ? vec.y : 0.0f; + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.MoveForward); + if(result != null) + { + value = (float)result; + } + else + { + vec = SteamVR_Actions.subnautica.Move.GetAxis(SteamVR_Input_Sources.Any); + value = vec.y > 0.0f ? vec.y : 0.0f; + } break; case GameInput.Button.MoveBackward: - vec = SteamVR_Actions.subnautica.Move.GetAxis(SteamVR_Input_Sources.Any); - value = vec.y < 0.0f ? -vec.y : 0.0f; + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.MoveBackward); + if(result != null) + { + value = (float)result; + } + else + { + vec = SteamVR_Actions.subnautica.Move.GetAxis(SteamVR_Input_Sources.Any); + value = vec.y < 0.0f ? -vec.y : 0.0f; + } break; case GameInput.Button.MoveRight: - vec = SteamVR_Actions.subnautica.Move.GetAxis(SteamVR_Input_Sources.Any); - value = vec.x > 0.0f ? vec.x : 0.0f; + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.MoveRight); + if(result != null) + { + value = (float)result; + } + else + { + vec = SteamVR_Actions.subnautica.Move.GetAxis(SteamVR_Input_Sources.Any); + value = vec.x > 0.0f ? vec.x : 0.0f; + } break; case GameInput.Button.MoveLeft: - vec = SteamVR_Actions.subnautica.Move.GetAxis(SteamVR_Input_Sources.Any); - value = vec.x < 0.0f ? -vec.x : 0.0f; + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.MoveLeft); + if(result != null) + { + value = (float)result; + } + else + { + vec = SteamVR_Actions.subnautica.Move.GetAxis(SteamVR_Input_Sources.Any); + value = vec.x < 0.0f ? -vec.x : 0.0f; + } break; case GameInput.Button.MoveUp: - isPressed = SteamVR_Actions.subnautica.MoveUp.GetState(SteamVR_Input_Sources.Any); - value = isPressed ? 1.0f : 0.0f; + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.MoveUp); + if(result != null) + { + value = (float)result; + } + else + { + isPressed = SteamVR_Actions.subnautica.MoveUp.GetState(SteamVR_Input_Sources.Any); + value = isPressed && !disableUpDown ? 1.0f : 0.0f; + } break; case GameInput.Button.MoveDown: - isPressed = SteamVR_Actions.subnautica.MoveDown.GetState(SteamVR_Input_Sources.Any); - value = isPressed ? 1.0f : 0.0f; + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.MoveDown); + if(result != null) + { + value = (float)result; + } + else + { + isPressed = SteamVR_Actions.subnautica.MoveDown.GetState(SteamVR_Input_Sources.Any); + value = isPressed && !disableUpDown ? 1.0f : 0.0f; + } break; case GameInput.Button.LookUp: - vec = SteamVR_Actions.subnautica.Look.GetAxis(SteamVR_Input_Sources.Any); - if (Settings.InvertYAxis) + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.LookUp); + if(result != null) { - value = vec.y < 0.0f ? -vec.y : 0.0f; + value = (float)result; } else { - value = vec.y > 0.0f ? vec.y : 0.0f; + vec = SteamVR_Actions.subnautica.Look.GetAxis(SteamVR_Input_Sources.Any); + if (Settings.InvertYAxis) + { + value = vec.y < 0.0f ? -vec.y : 0.0f; + } + else + { + value = vec.y > 0.0f ? vec.y : 0.0f; + } } break; case GameInput.Button.LookDown: - vec = SteamVR_Actions.subnautica.Look.GetAxis(SteamVR_Input_Sources.Any); - if (Settings.InvertYAxis) + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.LookDown); + if(result != null) { - value = vec.y > 0.0f ? vec.y : 0.0f; + value = (float)result; } - else { - value = vec.y < 0.0f ? -vec.y : 0.0f; + else + { + vec = SteamVR_Actions.subnautica.Look.GetAxis(SteamVR_Input_Sources.Any); + if (Settings.InvertYAxis) + { + value = vec.y > 0.0f ? vec.y : 0.0f; + } + else { + value = vec.y < 0.0f ? -vec.y : 0.0f; + } } break; case GameInput.Button.LookRight: - vec = SteamVR_Actions.subnautica.Look.GetAxis(SteamVR_Input_Sources.Any); - value = vec.x > 0.0f ? vec.x : 0.0f; + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.LookRight); + if(result != null) + { + value = (float)result; + } + else + { + vec = SteamVR_Actions.subnautica.Look.GetAxis(SteamVR_Input_Sources.Any); + value = vec.x > 0.0f ? vec.x : 0.0f; + } break; case GameInput.Button.LookLeft: - vec = SteamVR_Actions.subnautica.Look.GetAxis(SteamVR_Input_Sources.Any); - //DebugPanel.Show($"LookLeft: {vec}"); - value = vec.x < 0.0f ? -vec.x : 0.0f; + result = PhysicalPilotingVR.GetValue( PhysicalPilotingVR.PhysicalPilotingDirection.LookLeft); + if(result != null) + { + value = (float)result; + } + else + { + vec = SteamVR_Actions.subnautica.Look.GetAxis(SteamVR_Input_Sources.Any); + //DebugPanel.Show($"LookLeft: {vec}"); + value = vec.x < 0.0f ? -vec.x : 0.0f; + } break; } diff --git a/SubmersedVR/Settings.cs b/SubmersedVR/Settings.cs index 84272ff..4bb8333 100644 --- a/SubmersedVR/Settings.cs +++ b/SubmersedVR/Settings.cs @@ -60,6 +60,8 @@ public class Settings public static bool HandBasedTurning = false; public static bool ArticulatedHands = false; + public static bool PhysicalDriving = false; + public static bool PhysicalLockedGrips = false; // Saves or loads all public static properties as settings using the given serializer @@ -146,7 +148,9 @@ internal static void AddMenu(uGUI_OptionsPanel panel) panel.AddHeading(tab, "Experimental"); panel.AddToggleOption(tab, "Articulated Hands", ArticulatedHands, (value) => { ArticulatedHands = value; }, "Hands animate based on the movement of your physical hands."); - panel.AddToggleOption(tab, "Full body", FullBody, (value) => { FullBody = value; FullBodyChanged(value); }, "See the full body instead of just the hands and feet."); + panel.AddToggleOption(tab, "Physical Driving", PhysicalDriving, (value) => { PhysicalDriving = value; }, "Grip Vehicle controls to steer."); + panel.AddToggleOption(tab, "Locked Steering Grips", PhysicalLockedGrips, (value) => { PhysicalLockedGrips = value; }, "Gripping the steering control locks your hands to the steering so you dont have to constantly grip. Grip again to unlock."); + panel.AddToggleOption(tab, "Full Body", FullBody, (value) => { FullBody = value; FullBodyChanged(value); }, "See the full body instead of just the hands and feet."); panel.AddSliderOption(tab, "Body Scale", PlayerScale, 0.8f, 1.2f, PlayerScale, 0.01f, (value) => { PlayerScale = value; }, SliderLabelMode.Float, "0.00"); panel.AddToggleOption(tab, "Enable Game Haptics", EnableGameHaptics, (value) => { EnableGameHaptics = value; }, "Enable controller vibration while interacting with world objects."); panel.AddToggleOption(tab, "Enable UI Haptics", EnableUIHaptics, (value) => { EnableUIHaptics = value; }, "Enable controller vibration while interacting with the User Interface."); diff --git a/SubmersedVR/Tweaks/Exosuit.cs b/SubmersedVR/Tweaks/Exosuit.cs index 0df3b91..3a9f951 100644 --- a/SubmersedVR/Tweaks/Exosuit.cs +++ b/SubmersedVR/Tweaks/Exosuit.cs @@ -10,7 +10,7 @@ static class ExosuitVR } #region Patches - +/* //Snap turning override [HarmonyPatch(typeof(Vehicle), nameof(Vehicle.Update))] static class VehicleSnapTurnFixer @@ -27,6 +27,8 @@ public static bool Prefix(Vehicle __instance) __instance.mainAnimator.SetFloat("view_pitch", __instance.steeringWheelPitch * 45f); } } + Mod.logger.LogInfo($"Vehicle.Update controlScheme = {__instance.controlSheme}"); + if (__instance.GetPilotingMode() && __instance.CanPilot() && (__instance.moveOnLand || __instance.transform.position.y < Ocean.GetOceanLevel())) { Vector2 vector = AvatarInputHandler.main.IsEnabled() ? GameInput.GetLookDelta() : Vector2.zero; @@ -42,6 +44,7 @@ public static bool Prefix(Vehicle __instance) } else if (__instance.controlSheme == Vehicle.ControlSheme.Submarine || __instance.controlSheme == Vehicle.ControlSheme.Mech) { + Mod.logger.LogInfo($"Vehicle.Update in Exosuit {vector}"); //Exosuit if (vector.x != 0f) { @@ -51,6 +54,7 @@ public static bool Prefix(Vehicle __instance) } if(Settings.IsExosuitSnapTurningEnabled == true) { + Mod.logger.LogInfo($"Vehicle.Update with snap turning called x = {vector.x}"); __instance.useRigidbody.transform.rotation = Quaternion.Euler( new Vector3( __instance.useRigidbody.transform.rotation.eulerAngles.x, __instance.useRigidbody.transform.rotation.eulerAngles.y + vector.x, __instance.useRigidbody.transform.rotation.eulerAngles.z)); } } @@ -72,7 +76,7 @@ public static bool Prefix(Vehicle __instance) return false; } } - +*/ #endregion } diff --git a/SubmersedVR/Tweaks/Hoverbike.cs b/SubmersedVR/Tweaks/Hoverbike.cs index 0ca875b..9588b4d 100644 --- a/SubmersedVR/Tweaks/Hoverbike.cs +++ b/SubmersedVR/Tweaks/Hoverbike.cs @@ -37,7 +37,7 @@ public static void Postfix(HoverbikeHeadControlManager __instance) } } - +/* Moved to SnapTurning.cs //While piloting vehicles the player's head camera gets locked into position when using XRSettings.enabled = true //While on the hoverbike we dont want to be locked in because we want to eliminate the yaw and pitch to reduce nausea //using the methods above @@ -60,52 +60,7 @@ public static void Postfix() } } } - - //Snap turning override - [HarmonyPatch(typeof(Hoverbike), nameof(Hoverbike.PhysicsMove))] - static class HoverbikeSnapTurn - { - public static bool Prefix(Hoverbike __instance) - { - __instance.isPiloting = __instance.GetPilotingCraft(); - if (__instance.dockedPad) - { - return false; - } - if (!__instance.isPiloting) - { - return false; - } - __instance.moveDirection = (AvatarInputHandler.main.IsEnabled() ? GameInput.GetMoveDirection() : Vector3.zero); - new Vector3(__instance.moveDirection.x, 0f, __instance.moveDirection.z); - Vector2 vector = __instance.overflowInput; - if (__instance.energyMixin.IsDepleted()) - { - __instance.moveDirection = Vector3.zero; - } - __instance.appliedThrottle = (__instance.moveDirection != Vector3.zero); - float d = __instance.overWater ? (__instance.horizontalDampening / __instance.waterDampening) : __instance.horizontalDampening; - __instance.rb.AddTorque(__instance.transform.right * -vector.x * __instance.sidewaysTorque * __instance.verticalDampening, ForceMode.VelocityChange); - if(Settings.IsSnowBikeSnapTurningEnabled == false) - { - __instance.rb.AddTorque(__instance.transform.up * vector.y * __instance.sidewaysTorque * d, ForceMode.VelocityChange); - } - if(Settings.IsSnowBikeSnapTurningEnabled == true) - { - __instance.rb.transform.rotation = Quaternion.Euler( new Vector3( __instance.rb.transform.rotation.eulerAngles.x, __instance.rb.transform.rotation.eulerAngles.y + vector.y, __instance.rb.transform.rotation.eulerAngles.z)); - } - Vector3 velocity = __instance.rb.velocity; - Vector3 point = __instance.moveDirection; - Mathf.Min(1f, point.magnitude); - point.y = 0f; - point.Normalize(); - __instance.horizMoveDir = MainCamera.camera.transform.rotation * point; - float d2 = __instance.overWater ? (__instance.forwardAccel / __instance.waterDampening) : __instance.forwardAccel; - __instance.rb.AddForce(__instance.horizMoveDir * d2); - - return false; - } - } +*/ #endregion diff --git a/SubmersedVR/Tweaks/MapRoomCamera.cs b/SubmersedVR/Tweaks/MapRoomCamera.cs index c43c98f..3931f69 100644 --- a/SubmersedVR/Tweaks/MapRoomCamera.cs +++ b/SubmersedVR/Tweaks/MapRoomCamera.cs @@ -22,6 +22,10 @@ public static void Postfix(MapRoomCamera __instance) { Player.main.armsController.gameObject.SetActive(false); } + else + { + VRHands.instance?.SetHandRendering(false); + } } } @@ -34,6 +38,10 @@ public static void Postfix(MapRoomCamera __instance) { Player.main.armsController.gameObject.SetActive(true); } + else + { + VRHands.instance?.SetHandRendering(true); + } } } @@ -46,100 +54,6 @@ public static void Postfix(uGUI_CameraDrone __instance) } } - //Snap turning override - [HarmonyPatch(typeof(MapRoomCamera), nameof(MapRoomCamera.Update))] - static class MapRoomCameraFixer - { - public static bool Prefix(MapRoomCamera __instance) - { - __instance.UpdateEnergyRecharge(); - if (__instance.IsControlled() && __instance.inputStackDummy.activeInHierarchy) - { - if (!__instance.IsReady() && LargeWorldStreamer.main.IsWorldSettled()) - { - __instance.readyForControl = true; - __instance.connectingSound.Stop(); - global::Utils.PlayFMODAsset(__instance.connectedSound, __instance.transform, 20f); - } - if (__instance.CanBeControlled(null) && __instance.readyForControl) - { - Vector2 lookDelta = GameInput.GetLookDelta(); - //__instance.rigidBody.AddTorque(__instance.transform.up * lookDelta.x * 45f * 0.0015f, ForceMode.VelocityChange); - if(Settings.IsSnapTurningEnabled == false) - { - __instance.rigidBody.AddTorque(__instance.transform.up * lookDelta.x * 45f * 0.0015f, ForceMode.VelocityChange); - } - if(!VROptions.disableInputPitch) - { - __instance.rigidBody.AddTorque(__instance.transform.right * -lookDelta.y * 45f * 0.0015f, ForceMode.VelocityChange); - } - if(Settings.IsSnapTurningEnabled == true) - { - __instance.rigidBody.transform.rotation = Quaternion.Euler( new Vector3( __instance.rigidBody.transform.rotation.eulerAngles.x, __instance.rigidBody.transform.rotation.eulerAngles.y + lookDelta.x, __instance.rigidBody.transform.rotation.eulerAngles.z)); - } - __instance.wishDir = GameInput.GetMoveDirection(); - __instance.wishDir.Normalize(); - if (__instance.dockingPoint != null && __instance.wishDir != Vector3.zero) - { - __instance.dockingPoint.UndockCamera(); - } - } - else - { - __instance.wishDir = Vector3.zero; - } - if (Input.GetKeyUp(KeyCode.Escape) || GameInput.GetButtonUp(GameInput.Button.Exit)) - { - if (__instance.routine == null) - { - __instance.FreeCamera(true); - __instance.routine = __instance.StartCoroutine(__instance.CaptureScreenAndExitLockedMode()); - } - } - else if (GameInput.GetButtonDown(GameInput.Button.CycleNext)) - { - __instance.screen.CycleCamera(1); - } - else if (GameInput.GetButtonDown(GameInput.Button.CyclePrev)) - { - __instance.screen.CycleCamera(-1); - } - else if (GameInput.GetButtonDown(GameInput.Button.RightHand)) - { - bool flag = !__instance.lightsParent.activeInHierarchy; - __instance.lightsParent.SetActive(flag); - if (flag) - { - FMODUWE.PlayOneShot(__instance.lightOnSound, __instance.transform.position, 1f); - } - else - { - FMODUWE.PlayOneShot(__instance.lightOffSound, __instance.transform.position, 1f); - } - } - if (Player.main != null && Player.main.liveMixin != null && !Player.main.liveMixin.IsAlive()) - { - __instance.FreeCamera(true); - __instance.ExitLockedMode(true); - } - float magnitude = __instance.rigidBody.velocity.magnitude; - float time = Mathf.Clamp(__instance.transform.InverseTransformDirection(__instance.rigidBody.velocity).z / 15f, 0f, 1f); - if (magnitude > 2f) - { - __instance.engineSound.Play(); - __instance.energyMixin.ConsumeEnergy(Time.deltaTime * 0.06666f); - } - else - { - __instance.engineSound.Stop(); - } - __instance.screenEffectModel.GetComponent().materials[0].SetColor(ShaderPropertyID._Color, __instance.gradientInner.Evaluate(time)); - __instance.screenEffectModel.GetComponent().materials[1].SetColor(ShaderPropertyID._Color, __instance.gradientOuter.Evaluate(time)); - } - return false; - } - } - #endregion } diff --git a/SubmersedVR/Tweaks/SnapTurning.cs b/SubmersedVR/Tweaks/SnapTurning.cs new file mode 100644 index 0000000..3739959 --- /dev/null +++ b/SubmersedVR/Tweaks/SnapTurning.cs @@ -0,0 +1,530 @@ +using HarmonyLib; +using UnityEngine; + +namespace SubmersedVR +{ + using UnityEngine.XR; + + //Implementation of Snap Turning + //Previous Snap Turning had a bug where occasionaly snap turning would + //simply stop working. It appears that because there are many calls + //to GetLookDelta in the SN code, occasionally something would make the + //call and capture the single snap turn result (where vector is returned as (45,0)), + //and the function that needed it would not get it. In this implementation we will + //only give snap turning results to the functions that really need it and only flip + //the flag after those functions use it + static class SnapTurning + { + public static bool hasSnapTurn = false; + public static bool disableSnapTurn = false; + public static float snapTurnAngle = 0f; + + public static void Reset() + { + hasSnapTurn = false; + snapTurnAngle = 0.0f; + } + } + + #region Patches + + [HarmonyPatch(typeof(GameInput), nameof(GameInput.GetLookDelta))] + public static class SnapTurningGetLookDelta + { + public static void Postfix(ref Vector2 __result) + { + bool isInExosuit = Player.main?.inExosuit == true; + bool isOnSnowBike = Player.main?.inHovercraft == true; + bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; + + if ((Settings.IsSnapTurningEnabled && !(isInExosuit || isOnSnowBike || isPilotingSeatruck)) || (Settings.IsExosuitSnapTurningEnabled && isInExosuit) || (Settings.IsSnowBikeSnapTurningEnabled && isOnSnowBike)) + { + float angle = Settings.SnapTurningAngle; //player + if(Settings.IsExosuitSnapTurningEnabled && isInExosuit) + { + angle = Settings.ExosuitSnapTurningAngle; + } + if(Settings.IsSnowBikeSnapTurningEnabled && isOnSnowBike) + { + angle = Settings.SnowBikeSnapTurningAngle; + } + + float lookX = __result.x; + float absX = Mathf.Abs(lookX); + float threshold = 0.5f; + + if (absX > threshold && !SnapTurning.disableSnapTurn) + { + SnapTurning.snapTurnAngle = angle * Mathf.Sign(lookX); + SnapTurning.hasSnapTurn = true; + SnapTurning.disableSnapTurn = true; + //Mod.logger.LogInfo($"Snap Turned"); + } + else + { + __result.x = 0; + if (absX <= threshold) { + SnapTurning.disableSnapTurn = false; + } + } + //DebugPanel.Show($"GetLookDelta: r={r} l={l} lr={r - l}\ndelta={Time.deltaTime}\nlookX={lookX}\nabsX = {absX}\nsnapTurned={SteamVrGameInput.SnapTurned}", true); + } + } + } + + //Snap turning override + [HarmonyPatch(typeof(Vehicle), nameof(Vehicle.Update))] + static class VehicleSnapTurnFixerVR + { + public static bool Prefix(Vehicle __instance) + { + if (__instance.CanPilot()) + { + __instance.steeringWheelYaw = Mathf.Lerp(__instance.steeringWheelYaw, 0f, Time.deltaTime); + __instance.steeringWheelPitch = Mathf.Lerp(__instance.steeringWheelPitch, 0f, Time.deltaTime); + if (__instance.mainAnimator) + { + __instance.mainAnimator.SetFloat("view_yaw", __instance.steeringWheelYaw * 70f); + __instance.mainAnimator.SetFloat("view_pitch", __instance.steeringWheelPitch * 45f); + } + } + //Mod.logger.LogInfo($"Vehicle.Update controlScheme = {__instance.controlSheme}"); + + if (__instance.GetPilotingMode() && __instance.CanPilot() && (__instance.moveOnLand || __instance.transform.position.y < Ocean.GetOceanLevel())) + { + Vector2 vector = AvatarInputHandler.main.IsEnabled() ? GameInput.GetLookDelta() : Vector2.zero; + __instance.steeringWheelYaw = Mathf.Clamp(__instance.steeringWheelYaw + vector.x * __instance.steeringReponsiveness, -1f, 1f); + __instance.steeringWheelPitch = Mathf.Clamp(__instance.steeringWheelPitch + vector.y * __instance.steeringReponsiveness, -1f, 1f); + if (__instance.controlSheme == Vehicle.ControlSheme.Submersible) + { + float d = 3f; + __instance.useRigidbody.AddTorque(__instance.transform.up * vector.x * __instance.sidewaysTorque * 0.0015f * d, ForceMode.VelocityChange); + __instance.useRigidbody.AddTorque(__instance.transform.right * -vector.y * __instance.sidewaysTorque * 0.0015f * d, ForceMode.VelocityChange); + __instance.useRigidbody.AddTorque(__instance.transform.forward * -vector.x * __instance.sidewaysTorque * 0.0002f * d, ForceMode.VelocityChange); + } + else if (__instance.controlSheme == Vehicle.ControlSheme.Submarine || __instance.controlSheme == Vehicle.ControlSheme.Mech) + { + //Exosuit + if(Settings.IsExosuitSnapTurningEnabled == true && SnapTurning.hasSnapTurn) + { + __instance.useRigidbody.transform.rotation = Quaternion.Euler( new Vector3( __instance.useRigidbody.transform.rotation.eulerAngles.x, __instance.useRigidbody.transform.rotation.eulerAngles.y + SnapTurning.snapTurnAngle, __instance.useRigidbody.transform.rotation.eulerAngles.z)); + SnapTurning.Reset(); + } + else if (Settings.IsExosuitSnapTurningEnabled == false && vector.x != 0f) + { + __instance.useRigidbody.AddTorque(__instance.transform.up * vector.x * __instance.sidewaysTorque, ForceMode.VelocityChange); + } + } + else if (__instance.controlSheme == Vehicle.ControlSheme.Hoverbike) + { + Mod.logger.LogInfo($"Vehicle.Update called scheme = {__instance.controlSheme}"); + __instance.useRigidbody.AddRelativeTorque(new Vector3(vector.y, 0f, 0f)); + } + } + bool flag = __instance.IsPowered(); + if (__instance.wasPowered != flag) + { + __instance.wasPowered = flag; + __instance.OnPoweredChanged(flag); + } + __instance.ReplenishOxygen(); + + return false; + } + } + + //Snap turning override + [HarmonyPatch(typeof(Hoverbike), nameof(Hoverbike.PhysicsMove))] + static class HoverbikeSnapTurn + { + public static bool Prefix(Hoverbike __instance) + { + __instance.isPiloting = __instance.GetPilotingCraft(); + if (__instance.dockedPad) + { + return false; + } + if (!__instance.isPiloting) + { + return false; + } + __instance.moveDirection = (AvatarInputHandler.main.IsEnabled() ? GameInput.GetMoveDirection() : Vector3.zero); + new Vector3(__instance.moveDirection.x, 0f, __instance.moveDirection.z); + Vector2 vector = __instance.overflowInput; + if (__instance.energyMixin.IsDepleted()) + { + __instance.moveDirection = Vector3.zero; + } + __instance.appliedThrottle = (__instance.moveDirection != Vector3.zero); + float d = __instance.overWater ? (__instance.horizontalDampening / __instance.waterDampening) : __instance.horizontalDampening; + __instance.rb.AddTorque(__instance.transform.right * -vector.x * __instance.sidewaysTorque * __instance.verticalDampening, ForceMode.VelocityChange); + if(Settings.IsSnowBikeSnapTurningEnabled == false) + { + __instance.rb.AddTorque(__instance.transform.up * vector.y * __instance.sidewaysTorque * d, ForceMode.VelocityChange); + } + if(Settings.IsSnowBikeSnapTurningEnabled == true && SnapTurning.hasSnapTurn) + { + __instance.rb.transform.rotation = Quaternion.Euler( new Vector3( __instance.rb.transform.rotation.eulerAngles.x, __instance.rb.transform.rotation.eulerAngles.y + SnapTurning.snapTurnAngle, __instance.rb.transform.rotation.eulerAngles.z)); + SnapTurning.Reset(); + } + Vector3 velocity = __instance.rb.velocity; + Vector3 point = __instance.moveDirection; + Mathf.Min(1f, point.magnitude); + point.y = 0f; + point.Normalize(); + __instance.horizMoveDir = MainCamera.camera.transform.rotation * point; + float d2 = __instance.overWater ? (__instance.forwardAccel / __instance.waterDampening) : __instance.forwardAccel; + __instance.rb.AddForce(__instance.horizMoveDir * d2); + + return false; + } + } + + //This does 3 things + //1 + //While piloting vehicles, the player's head camera gets locked into position when using XRSettings.enabled = true + //While on the hoverbike we dont want to be locked in because we want to eliminate the yaw and pitch to reduce nausea + //2 + //Disable the last XRSettings.enabled branch by replacing it with false + //3 + //Enable Player Snap Turning + + [HarmonyPatch(typeof(MainCameraControl), nameof(MainCameraControl.OnUpdate))] + static class MainCameraControlFixer + { + public static bool Prefix(MainCameraControl __instance) + { + //Fix 1 + bool xrenabled = true; + if(Player.main.inHovercraft) + { + xrenabled = XRSettingsEnabled.isEnabled; + XRSettingsEnabled.isEnabled = false; + } + //// + + float deltaTime = Time.deltaTime; + if (Player.main.IsUnderwater()) + { + __instance.swimCameraAnimation = Mathf.Clamp01(__instance.swimCameraAnimation + deltaTime); + } + else + { + __instance.swimCameraAnimation = Mathf.Clamp01(__instance.swimCameraAnimation - deltaTime); + } + Vector3 velocity = __instance.playerController.velocity; + bool flag = false; + bool flag2 = false; + bool flag3 = false; + bool inExosuit = Player.main.inExosuit; + bool flag4 = uGUI_BuilderMenu.IsOpen(); + bool flag5 = false; + if (Player.main != null) + { + flag = Player.main.GetPDA().isInUse; + flag3 = (Player.main.motorMode == Player.MotorMode.Vehicle); + flag2 = (flag || flag3 || __instance.cinematicMode); + flag5 = Player.main.inHovercraft; + if (XRSettings.enabled && VROptions.gazeBasedCursor) + { + flag2 = (flag2 || flag4); + } + } + if (flag2 != __instance.wasInLockedMode || __instance.lookAroundMode != __instance.wasInLookAroundMode) + { + __instance.camRotationX = 0f; + __instance.camRotationY = 0f; + __instance.wasInLockedMode = flag2; + __instance.wasInLookAroundMode = __instance.lookAroundMode; + } + bool flag6 = (!__instance.cinematicMode || __instance.lookAroundMode) && !flag && __instance.mouseLookEnabled && (flag3 || AvatarInputHandler.main == null || AvatarInputHandler.main.IsEnabled() || Builder.isPlacing); + if (flag3 && !XRSettings.enabled && !inExosuit) + { + flag6 = false; + } + if (__instance.deathSequence) + { + flag6 = false; + } + Transform transform = __instance.transform; + float num = (float)((flag || __instance.lookAroundMode || Player.main.GetMode() == Player.Mode.LockedPiloting) ? 1 : -1); + if (!flag2 || (__instance.cinematicMode && !__instance.lookAroundMode)) + { + __instance.cameraOffsetTransform.localEulerAngles = UWE.Utils.LerpEuler(__instance.cameraOffsetTransform.localEulerAngles, Vector3.zero, deltaTime * 5f); + } + else + { + transform = __instance.cameraOffsetTransform; + __instance.rotationY = Mathf.LerpAngle(__instance.rotationY, 0f, PDA.deltaTime * 15f); + __instance.transform.localEulerAngles = new Vector3(Mathf.LerpAngle(__instance.transform.localEulerAngles.x, 0f, PDA.deltaTime * 15f), __instance.transform.localEulerAngles.y, 0f); + __instance.cameraUPTransform.localEulerAngles = UWE.Utils.LerpEuler(__instance.cameraUPTransform.localEulerAngles, Vector3.zero, PDA.deltaTime * 15f); + } + if (!XRSettings.enabled) + { + float num2 = __instance.camPDAZOffset * num * PDA.deltaTime / __instance.cameraPDAZoomDuration; + Vector3 localPosition = __instance.cameraOffsetTransform.localPosition; + localPosition.z = Mathf.Clamp(localPosition.z + num2, 0f, __instance.camPDAZOffset); + __instance.cameraOffsetTransform.localPosition = localPosition; + } + else + { + __instance.animator.SetFloat(MainCameraControl.pdaDistanceParamId, VROptions.pdaDistance); + } + Vector2 vector = Vector2.zero; + if (flag6 && FPSInputModule.current.lastGroup == null) + { + vector = GameInput.GetLookDelta(); + + //Fix 3 + bool isInExosuit = Player.main?.inExosuit == true; + bool isOnSnowBike = Player.main?.inHovercraft == true; + bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; + if (Settings.IsSnapTurningEnabled && SnapTurning.hasSnapTurn && !(isInExosuit || isOnSnowBike || isPilotingSeatruck)) + { + vector.x = SnapTurning.snapTurnAngle; + SnapTurning.Reset(); + } + //// + + if (XRSettings.enabled && VROptions.disableInputPitch) + { + vector.y = 0f; + } + if (inExosuit) + { + vector.x = 0f; + } + vector *= Player.main.mesmerizedSpeedMultiplier; + if (Player.main.frozenMixin.IsFrozen()) + { + vector *= Player.main.frozenMixin.cameraSpeedMultiplier; + } + } + if (__instance.deathSequence) + { + vector = new Vector2(Mathf.Cos(Time.time * 8.5f) * 25f * Time.deltaTime, -50f * Time.deltaTime + Mathf.Cos(Time.time * 8f) * 9f * Time.deltaTime); + } + __instance.UpdateCamShake(); + if (__instance.cinematicMode && !__instance.lookAroundMode) + { + if (__instance.cinematicOverrideRotation) + { + __instance.camRotationX = __instance.transform.localEulerAngles.y; + __instance.camRotationY = -__instance.transform.localEulerAngles.x; + } + else + { + __instance.camRotationX = Mathf.LerpAngle(__instance.camRotationX, 0f, deltaTime * 2f); + __instance.camRotationY = Mathf.LerpAngle(__instance.camRotationY, 0f, deltaTime * 2f); + __instance.transform.localEulerAngles = new Vector3(-__instance.camRotationY, __instance.camRotationX, 0f); + } + } + else if (flag2) + { + if (!XRSettings.enabled) + { + bool flag7 = (!__instance.lookAroundMode && !inExosuit) || flag; + bool flag8 = !__instance.lookAroundMode || flag; + __instance.camRotationX += vector.x; + __instance.camRotationY += vector.y; + __instance.camRotationX = Mathf.Clamp(__instance.camRotationX, -60f, 60f); + __instance.camRotationY = Mathf.Clamp(__instance.camRotationY, -60f, 60f); + if (flag5) + { + __instance.cameraOffsetTransform.eulerAngles = __instance.vehicleOverrideHeadRot; + } + else + { + if (flag8) + { + __instance.camRotationX = Mathf.LerpAngle(__instance.camRotationX, 0f, PDA.deltaTime * 10f); + } + if (flag7) + { + __instance.camRotationY = Mathf.LerpAngle(__instance.camRotationY, 0f, PDA.deltaTime * 10f); + } + __instance.cameraOffsetTransform.localEulerAngles = new Vector3(-__instance.camRotationY, __instance.camRotationX, 0f); + } + } + } + else + { + __instance.rotationX += vector.x; + __instance.rotationY += vector.y; + __instance.rotationY = Mathf.Clamp(__instance.rotationY, __instance.minimumY, __instance.maximumY); + __instance.cameraUPTransform.localEulerAngles = new Vector3(Mathf.Min(0f, -__instance.rotationY), 0f, 0f); + transform.localEulerAngles = new Vector3(Mathf.Max(0f, -__instance.rotationY), __instance.rotationX, 0f); + } + __instance.UpdateStrafeTilt(); + Vector3 localEulerAngles = __instance.transform.localEulerAngles + new Vector3(0f, 0f, __instance.cameraAngleMotion.y * __instance.cameraTiltMod + __instance.strafeTilt * 0.5f); + float num3 = 0f - __instance.skin; + if (!flag2 && __instance.GetCameraBob()) + { + float target = Mathf.Min(1f, velocity.magnitude / 5f); + __instance.smoothedSpeed = Mathf.MoveTowards(__instance.smoothedSpeed, target, deltaTime); + num3 += (Mathf.Sin(Time.time * 6f) - 1f) * (0.02f + __instance.smoothedSpeed * 0.15f) * __instance.swimCameraAnimation; + } + if (__instance.impactForce > 0f) + { + __instance.impactBob = Mathf.Min(0.9f, __instance.impactBob + __instance.impactForce * deltaTime); + __instance.impactForce -= Mathf.Max(1f, __instance.impactForce) * deltaTime * 5f; + } + num3 -= __instance.impactBob; + num3 -= __instance.stepAmount; + if (__instance.impactBob > 0f) + { + __instance.impactBob = Mathf.Max(0f, __instance.impactBob - Mathf.Pow(__instance.impactBob, 0.5f) * deltaTime * 3f); + } + __instance.stepAmount = Mathf.Lerp(__instance.stepAmount, 0f, deltaTime * Mathf.Abs(__instance.stepAmount)); + float num4 = __instance.shakeAmount / 20f; + float x = UnityEngine.Random.Range(-num4, num4); + float y = UnityEngine.Random.Range(-num4, num4); + float z = UnityEngine.Random.Range(-num4, num4); + Vector3 b = __instance.initialOffset + new Vector3(x, y, z) * __instance.camShake; + __instance.shakeOffset = Vector3.Lerp(__instance.shakeOffset, b, deltaTime * 20f); + Vector3 b2 = flag5 ? __instance.vehicleOverrideHeadPos : Vector3.zero; + __instance.transform.localPosition = new Vector3(0f, num3, 0f) + b2 + __instance.shakeOffset; + __instance.transform.localEulerAngles = localEulerAngles; + if (Player.main.motorMode == Player.MotorMode.Vehicle) + { + __instance.transform.localEulerAngles = Vector3.zero; + } + Vector3 localEulerAngles2 = new Vector3(Mathf.LerpAngle(__instance.viewModel.localEulerAngles.x, 0f, deltaTime * 5f), __instance.transform.localEulerAngles.y, 0f); + Vector3 localPosition2 = __instance.transform.localPosition; + //Fix 2 + /* + if (XRSettings.enabled) + { + if (flag2 && !flag3) + { + localEulerAngles2.y = __instance.viewModelLockedYaw; + } + else + { + localEulerAngles2.y = 0f; + } + if (!flag3 && !__instance.cinematicMode) + { + if (!flag2) + { + Quaternion rotation = __instance.playerController.forwardReference.rotation; + localEulerAngles2.y = (__instance.gameObject.transform.parent.rotation.GetInverse() * rotation).eulerAngles.y; + } + localPosition2 = __instance.gameObject.transform.parent.worldToLocalMatrix.MultiplyPoint(__instance.playerController.forwardReference.position); + } + } + */ + __instance.viewModel.transform.localEulerAngles = localEulerAngles2; + __instance.viewModel.transform.localPosition = localPosition2; + + //Fix 1 + if(Player.main.inHovercraft) + { + XRSettingsEnabled.isEnabled = xrenabled; + } + //// + + return false; + } + } + + //Snap turning override + [HarmonyPatch(typeof(MapRoomCamera), nameof(MapRoomCamera.Update))] + static class MapRoomCameraFixer + { + public static bool Prefix(MapRoomCamera __instance) + { + __instance.UpdateEnergyRecharge(); + if (__instance.IsControlled() && __instance.inputStackDummy.activeInHierarchy) + { + if (!__instance.IsReady() && LargeWorldStreamer.main.IsWorldSettled()) + { + __instance.readyForControl = true; + __instance.connectingSound.Stop(); + global::Utils.PlayFMODAsset(__instance.connectedSound, __instance.transform, 20f); + } + if (__instance.CanBeControlled(null) && __instance.readyForControl) + { + Vector2 lookDelta = GameInput.GetLookDelta(); + //__instance.rigidBody.AddTorque(__instance.transform.up * lookDelta.x * 45f * 0.0015f, ForceMode.VelocityChange); + if(Settings.IsSnapTurningEnabled == false) + { + __instance.rigidBody.AddTorque(__instance.transform.up * lookDelta.x * 45f * 0.0015f, ForceMode.VelocityChange); + } + if(!VROptions.disableInputPitch) + { + __instance.rigidBody.AddTorque(__instance.transform.right * -lookDelta.y * 45f * 0.0015f, ForceMode.VelocityChange); + } + if(Settings.IsSnapTurningEnabled == true && SnapTurning.hasSnapTurn) + { + lookDelta.x = SnapTurning.snapTurnAngle; + __instance.rigidBody.transform.rotation = Quaternion.Euler( new Vector3( __instance.rigidBody.transform.rotation.eulerAngles.x, __instance.rigidBody.transform.rotation.eulerAngles.y + lookDelta.x, __instance.rigidBody.transform.rotation.eulerAngles.z)); + SnapTurning.Reset(); + } + __instance.wishDir = GameInput.GetMoveDirection(); + __instance.wishDir.Normalize(); + if (__instance.dockingPoint != null && __instance.wishDir != Vector3.zero) + { + __instance.dockingPoint.UndockCamera(); + } + } + else + { + __instance.wishDir = Vector3.zero; + } + if (Input.GetKeyUp(KeyCode.Escape) || GameInput.GetButtonUp(GameInput.Button.Exit)) + { + if (__instance.routine == null) + { + __instance.FreeCamera(true); + __instance.routine = __instance.StartCoroutine(__instance.CaptureScreenAndExitLockedMode()); + } + } + else if (GameInput.GetButtonDown(GameInput.Button.CycleNext)) + { + __instance.screen.CycleCamera(1); + } + else if (GameInput.GetButtonDown(GameInput.Button.CyclePrev)) + { + __instance.screen.CycleCamera(-1); + } + else if (GameInput.GetButtonDown(GameInput.Button.RightHand)) + { + bool flag = !__instance.lightsParent.activeInHierarchy; + __instance.lightsParent.SetActive(flag); + if (flag) + { + FMODUWE.PlayOneShot(__instance.lightOnSound, __instance.transform.position, 1f); + } + else + { + FMODUWE.PlayOneShot(__instance.lightOffSound, __instance.transform.position, 1f); + } + } + if (Player.main != null && Player.main.liveMixin != null && !Player.main.liveMixin.IsAlive()) + { + __instance.FreeCamera(true); + __instance.ExitLockedMode(true); + } + float magnitude = __instance.rigidBody.velocity.magnitude; + float time = Mathf.Clamp(__instance.transform.InverseTransformDirection(__instance.rigidBody.velocity).z / 15f, 0f, 1f); + if (magnitude > 2f) + { + __instance.engineSound.Play(); + __instance.energyMixin.ConsumeEnergy(Time.deltaTime * 0.06666f); + } + else + { + __instance.engineSound.Stop(); + } + __instance.screenEffectModel.GetComponent().materials[0].SetColor(ShaderPropertyID._Color, __instance.gradientInner.Evaluate(time)); + __instance.screenEffectModel.GetComponent().materials[1].SetColor(ShaderPropertyID._Color, __instance.gradientOuter.Evaluate(time)); + } + return false; + } + } + + #endregion + +} + diff --git a/SubmersedVR/VR/VRCameraRig.cs b/SubmersedVR/VR/VRCameraRig.cs index 2014d3d..307d59c 100644 --- a/SubmersedVR/VR/VRCameraRig.cs +++ b/SubmersedVR/VR/VRCameraRig.cs @@ -782,7 +782,7 @@ public static void Postfix(uGUI __instance) } } - +/* Moved to SnapTurning.cs // Disable the last XRSettings.enabled branch by replacing it with false/0 [HarmonyPatch(typeof(MainCameraControl), nameof(MainCameraControl.OnUpdate))] public static class DisableVRLockMechanic @@ -794,7 +794,7 @@ static IEnumerable Transpiler(IEnumerable inst }).ThrowIfNotMatch("Could not find last thingy").SetInstruction(new CodeInstruction(OpCodes.Ldc_I4_0)).InstructionEnumeration(); } } - +*/ // Disable the XRSettings.enabled to disable HandReticle Patch [HarmonyPatch(typeof(HandReticle), nameof(HandReticle.LateUpdate))] public static class DisableHandReticleVR diff --git a/SubmersedVR/VR/VRHands.cs b/SubmersedVR/VR/VRHands.cs index df27fb9..d26386c 100644 --- a/SubmersedVR/VR/VRHands.cs +++ b/SubmersedVR/VR/VRHands.cs @@ -179,6 +179,7 @@ public enum HandSkeletonBone: int public Vector3[] maxRotation; public int currentEditFinger = (int)HandSkeletonBone.eBone_IndexFinger1; + public static VRHands instance; // private OffsetCalibrationTool calibrationTool; @@ -392,12 +393,15 @@ public void OnClosePDA() ResetHandTargets(); } + void Update() { if (ik.enabled && uGUI_SpyPenguin.main.activePenguin == false) { - ik.solver.leftHandEffector.target = leftTarget; - ik.solver.rightHandEffector.target = rightTarget; + Transform t = PhysicalPilotingVR.GetCurrentPilotingTarget(PhysicalPilotingVR.PhysicalPilotingHand.Left); + ik.solver.leftHandEffector.target = t ?? leftTarget; + t = PhysicalPilotingVR.GetCurrentPilotingTarget(PhysicalPilotingVR.PhysicalPilotingHand.Right); + ik.solver.rightHandEffector.target = t ?? rightTarget; } } @@ -405,12 +409,13 @@ void LateUpdate() { if(!ik.enabled && uGUI_SpyPenguin.main.activePenguin == false) { - leftHand.transform.SetPositionAndRotation(leftTarget.position, leftTarget.rotation); - rightHand.transform.SetPositionAndRotation(rightTarget.position, rightTarget.rotation); + Transform t = PhysicalPilotingVR.GetCurrentPilotingTarget(PhysicalPilotingVR.PhysicalPilotingHand.Left); + leftHand.transform.SetPositionAndRotation((t ?? leftTarget).position, (t ?? leftTarget).rotation); + leftElbow.transform.SetPositionAndRotation(leftHand.position, leftHand.rotation); // Reset Elbows - // Reset Elbows - leftElbow.transform.SetPositionAndRotation(leftHand.position, leftHand.rotation); - rightElbow.transform.SetPositionAndRotation(rightHand.position, rightHand.rotation); + t = PhysicalPilotingVR.GetCurrentPilotingTarget(PhysicalPilotingVR.PhysicalPilotingHand.Right); + rightHand.transform.SetPositionAndRotation((t ?? rightTarget).position, (t ?? rightTarget).rotation); + rightElbow.transform.SetPositionAndRotation(rightHand.position, rightHand.rotation); // Reset Elbows } if(Settings.ArticulatedHands) @@ -480,14 +485,22 @@ public void UpdateFinger(Transform[] fingers,int fingerID, float percent) } public void UpdateBody() - { - //var bodyRenderers = transform.GetComponentsInChildren(includeInactive: true); - //foreach (var bodyRenderer in bodyRenderers) - //{ - // Mod.logger.LogInfo($"bodyRenderer {bodyRenderer.name}"); - //} + { + /* + var bodyRenderers = transform.GetComponentsInChildren(includeInactive: true); + foreach (var bodyRenderer in bodyRenderers) + { + Mod.logger.LogInfo($"bodyRenderer {bodyRenderer.name}"); + } + */ - StartCoroutine(UpdateBodyRendering()); + StartCoroutine(UpdateBodyRendering()); + } + + public void SetHandRendering(bool val) + { + var bodyRenderers = transform.GetComponentsInChildren().Where(r => r.name.Contains("hand") || r.name.Contains("glove")); + bodyRenderers.ForEach(r => r.enabled = val); } public void SetBodyRendering(bool val) @@ -577,6 +590,8 @@ public static class ChangeAimAngleForTools public static void Postfix(ArmsController __instance, PlayerTool tool) { VRHands.instance?.OnToolEquipped(tool); + + //Fix weird left arm elbow bend if(!__instance.pda.isInUse) { VRHands.instance.ik.solver.GetBendConstraint(FullBodyBipedChain.LeftArm).bendGoal = Player.main.armsController.defaultLeftArmBendGoal; @@ -585,6 +600,7 @@ public static void Postfix(ArmsController __instance, PlayerTool tool) } } + //Dont do the built in tool animations when first picking up a tool [HarmonyPatch(typeof(PlayerTool), nameof(PlayerTool.Awake))] public static class DisableFirstUseToolAnimations diff --git a/SubmersedVR/VR/VRPhysicalPiloting.cs b/SubmersedVR/VR/VRPhysicalPiloting.cs new file mode 100644 index 0000000..83861d7 --- /dev/null +++ b/SubmersedVR/VR/VRPhysicalPiloting.cs @@ -0,0 +1,623 @@ +using HarmonyLib; +using UnityEngine; + +namespace SubmersedVR +{ + extern alias SteamVRRef; + extern alias SteamVRActions; + using SteamVRRef.Valve.VR; + using SteamVRActions.Valve.VR; + using System.Xml.Schema; + using Yangrc.VolumeCloud; + using Steamworks; + + //Use hands to grip vehicle controls and pilot vehicles + static class PhysicalPilotingVR + { + public enum PhysicalPilotingDirection: int + { + MoveForward = 0, + MoveBackward, + MoveLeft, + MoveRight, + MoveUp, + MoveDown, + LookLeft, + LookRight, + LookUp, + LookDown + } + public enum PhysicalPilotingHand: int + { + Left = 0, + Right + } + + static public Transform leftPilotingTarget; //Where the hands grip when grabbing the steering controls. Moves due to animation + static public Transform rightPilotingTarget; + static public GameObject rightSeaTruckPilotingGrip; //The fixed grip location for steering. Does not move during animation + static public GameObject leftSeaTruckPilotingGrip; + static public GameObject rightExosuitPilotingGrip; //The fixed grip location for steering. Does not move during animation + static public GameObject leftExosuitPilotingGrip; + static public GameObject rightSnowBikePilotingGrip; //The fixed grip location for steering. Does not move during animation + static public GameObject leftSnowBikePilotingGrip; + static public bool leftPilotingLocked = false; + static public bool rightPilotingLocked = false; + static public bool leftGripping = false; //is the left hand currently gripping the steering control + static public bool rightGripping = false; //is the left hand currently gripping the steering control + + static float xCenterLeft_exosuit = 350f; + static float yCenterLeft_exosuit = 340f; + static float deadZone_exosuit = 15f; + + static float zCenterRight_snowbike = -5.0f; + static float deadZoneRight_snowbike = 15f; + static float zSensitivityRight_snowbike = 0.03f; + //static float xCenterLeft_snowbike = 0.0f; + static float deadZoneLeft_snowbike = 0.008f; + static float xSensitivityLeft_snowbike = 20f; + + static float xCenterRight = 335f; + static float xDeadZoneRight = 15f; + static float xSensitivityRight = 0.03f; + static float yCenterRight = 341f; + static float yDeadZoneRight = 15f; + static float ySensitivityRight = 0.03f; + static float zCenterLeft = 33; + static float zDeadZoneLeft = 15f; + static float zSensitivityLeft = 0.01f; + static float yCenterLeft = 305f; + static float yDeadZoneLeft = 10f; + static float ySensitivityLeft = 0.1f; + + static float gripDistance = 0.13f; // The distance away from a target control where the grip will recognize contact + + public static float? GetValue(PhysicalPilotingDirection direction) + { + float? value = null; + + Vector3? leftEulers = GetPilotingHandLocalRotation(PhysicalPilotingHand.Left)?.eulerAngles; + Vector3? rightEulers = GetPilotingHandLocalRotation(PhysicalPilotingHand.Right)?.eulerAngles; + Vector3? leftPosition = GetPilotingHandLocalPosition(PhysicalPilotingHand.Left); + Vector3? rightPosition = GetPilotingHandLocalPosition(PhysicalPilotingHand.Right); + //DebugPanel.Show($"left: {leftEulers}\nright: {rightEulers}\nleftPos: {leftPosition}\nrightPos: {rightPosition}\nrightHandGrip:{IsGrippingPilotingControl(PhysicalPilotingHand.Right)}\nleftHandLock:{VRHands.instance.leftHandSteeringLocked}\nvalue:{value}"); + + if(direction == PhysicalPilotingDirection.MoveForward) + { + if(Player.main?.inHovercraft == true) + { + if(rightEulers != null) + { + float z = rightEulers?.z ?? 0f; + z = z > 180 ? z - 360 : z; + value = z < (zCenterRight_snowbike-deadZoneRight_snowbike) ? (zCenterRight_snowbike-deadZoneRight_snowbike - z) * zSensitivityRight_snowbike : 0.0f; + } + } + else if(leftEulers != null) + { + if(Player.main?.inSeatruckPilotingChair == true) + { + float z = leftEulers?.z ?? 0f; + z = z > 180 ? z - 360 : z; + value = z > (zCenterLeft+zDeadZoneLeft) ? (z - (zCenterLeft+zDeadZoneLeft)) * zSensitivityLeft: 0.0f; + } + else + { + float y = leftEulers?.y ?? 0f; + y = y < 180 ? 360 + y : y; + value = y < (yCenterLeft_exosuit-deadZone_exosuit) ? (yCenterLeft_exosuit-deadZone_exosuit - y) * ySensitivityLeft : 0.0f; + } + } + + } + else if(direction == PhysicalPilotingDirection.MoveBackward) + { + if(Player.main?.inHovercraft == true) + { + if(rightEulers != null) + { + float z = rightEulers?.z ?? 0f; + z = z > 180 ? z - 360 : z; + value = z > (zCenterRight_snowbike+deadZoneRight_snowbike) ? (z - (zCenterRight_snowbike+deadZoneRight_snowbike)) * zSensitivityRight_snowbike: 0.0f; + } + } + else if(leftEulers != null) + { + if(Player.main?.inSeatruckPilotingChair == true) + { + float z = leftEulers?.z ?? 0f; + z = z > 180 ? z - 360 : z; + value = z < (zCenterLeft-zDeadZoneLeft) ? (zCenterLeft-zDeadZoneLeft - z) * zSensitivityLeft : 0.0f; + } + else + { + float y = leftEulers?.y ?? 0f; + y = y < 180 ? 360 + y : y; + value = y > (yCenterLeft_exosuit+deadZone_exosuit) ? (y - yCenterLeft_exosuit-deadZone_exosuit) * ySensitivityLeft : 0.0f; + } + } + + } + else if(direction == PhysicalPilotingDirection.MoveRight) + { + if(Player.main?.inHovercraft == true) + { + //Only Move Right is the A button is presed + if(SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + //If using both hands to steering then take the average of both hands + if(leftPosition != null) + { + float x = leftPosition?.x ?? 0f; + value = x > deadZoneLeft_snowbike ? (x - deadZoneLeft_snowbike) * xSensitivityLeft_snowbike : 0f; + } + if(rightPosition != null) + { + float x = rightPosition?.x ?? 0f; + float rvalue = x > deadZoneLeft_snowbike ? (x - deadZoneLeft_snowbike) * xSensitivityLeft_snowbike : 0f; + value = value != null ? (value + rvalue) /2 : rvalue; + } + } + else + { + value = 0f; + } + } + else if(leftEulers != null) + { + if(Player.main?.inSeatruckPilotingChair == true) + { + float y = leftEulers?.y ?? 0f; + y = y < 180 ? 360 + y : y; + value = y > (yCenterLeft+yDeadZoneLeft) ? (y - (yCenterLeft+yDeadZoneLeft)) * ySensitivityLeft : 0.0f; + } + else + { + float x = leftEulers?.x ?? 0f; + x = x < 180 ? x + 360 : x; + value = x > (xCenterLeft_exosuit+deadZone_exosuit) ? (x - xCenterLeft_exosuit-deadZone_exosuit) * xSensitivityRight : 0.0f; + } + } + } + else if(direction == PhysicalPilotingDirection.MoveLeft) + { + if(Player.main?.inHovercraft == true) + { + if(SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + //If using both hands to steering then take the average of both hands + if(leftPosition != null) + { + float x = leftPosition?.x ?? 0f; + value = x < -deadZoneLeft_snowbike ? (-deadZoneLeft_snowbike - x) * xSensitivityLeft_snowbike : 0f; + } + if(rightPosition != null) + { + float x = rightPosition?.x ?? 0f; + float rvalue = x < -deadZoneLeft_snowbike ? (-deadZoneLeft_snowbike - x) * xSensitivityLeft_snowbike : 0f; + value = value != null ? (value + rvalue) /2 : rvalue; + } + } + else + { + value = 0f; + } + } + else if(leftEulers != null) + { + if(Player.main?.inSeatruckPilotingChair == true) + { + float y = leftEulers?.y ?? 0f; + y = y < 180 ? 360 + y : y; + value = y < (yCenterLeft-yDeadZoneLeft) ? (yCenterLeft-yDeadZoneLeft - y) * ySensitivityLeft : 0.0f; + } + else + { + float x = leftEulers?.x ?? 0f; + x = x < 180 ? x + 360 : x; + value = x < (xCenterLeft_exosuit-deadZone_exosuit) ? (xCenterLeft_exosuit-deadZone_exosuit - x) * xSensitivityRight : 0.0f; //335 center + } + } + } + else if(direction == PhysicalPilotingDirection.MoveUp) + { + if(rightEulers != null) + { + //Only perform when the A button is pressed + if(SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + //if we want jump to be both A button press and up motion for exosuit then remove the if statement + if(Player.main?.inSeatruckPilotingChair == true) + { + float y = rightEulers?.y ?? 0f; + y = y < 180 ? 360 + y : y; + value = y > (yCenterRight+yDeadZoneRight) ? (y - yCenterRight-yDeadZoneRight) * ySensitivityRight : 0.0f; + } + else + { + value = 1.0f; + } + } + } + } + else if(direction == PhysicalPilotingDirection.MoveDown) + { + if(rightEulers != null) + { + //Only perform when the A button is pressed + if(SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + float y = rightEulers?.y ?? 0f; + y = y < 180 ? 360 + y : y; + value = y < (yCenterRight-yDeadZoneRight) ? (yCenterRight-yDeadZoneRight - y) * ySensitivityRight : 0.0f; + } + } + } + else if(direction == PhysicalPilotingDirection.LookUp) + { + if(rightEulers != null) + { + //Only perform when the A button is not pressed + if(!SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + float y = rightEulers?.y ?? 0f; + y = y < 180 ? 360 + y : y; + value = y > (yCenterRight+yDeadZoneRight) ? (y - yCenterRight-yDeadZoneRight) * ySensitivityRight : 0.0f; + } + } + } + else if(direction == PhysicalPilotingDirection.LookDown) + { + if(rightEulers != null) + { + //Only perform when the A button is not pressed + if(!SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + float y = rightEulers?.y ?? 0f; + y = y < 180 ? 360 + y : y; + value = y < (yCenterRight-yDeadZoneRight) ? (yCenterRight-yDeadZoneRight - y) * ySensitivityRight : 0.0f; + } + } + } + else if(direction == PhysicalPilotingDirection.LookRight) + { + if(Player.main?.inHovercraft == true) + { + //If A button is pressed then Move Left not Look Left + if(!SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + //If using both hands to steering then take the average of both hands + if(leftPosition != null) + { + float x = leftPosition?.x ?? 0f; + value = x > deadZoneLeft_snowbike ? (x - deadZoneLeft_snowbike) * xSensitivityLeft_snowbike : 0f; + } + if(rightPosition != null) + { + float x = rightPosition?.x ?? 0f; + float rvalue = x > deadZoneLeft_snowbike ? (x - deadZoneLeft_snowbike) * xSensitivityLeft_snowbike : 0f; + value = value != null ? (value + rvalue) /2 : rvalue; + } + } + else + { + value = 0f; + } + } + else if(rightEulers != null) + { + float x = rightEulers?.x ?? 0f; + x = x < 180 ? x + 360 : x; + value = x < (xCenterRight-xDeadZoneRight) ? (xCenterRight-xDeadZoneRight - x) * xSensitivityRight : 0.0f; //335 center + } + } + else if(direction == PhysicalPilotingDirection.LookLeft) + { + if(Player.main?.inHovercraft == true) + { + //If A button is pressed then Move Left not Look Left + if(!SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + if(leftPosition != null) + { + float x = leftPosition?.x ?? 0f; + value = x < -deadZoneLeft_snowbike ? (-deadZoneLeft_snowbike - x) * xSensitivityLeft_snowbike : 0f; + } + if(rightPosition != null) + { + float x = rightPosition?.x ?? 0f; + float rvalue = x < -deadZoneLeft_snowbike ? (-deadZoneLeft_snowbike - x) * xSensitivityLeft_snowbike : 0f; + value = value != null ? (value + rvalue) /2 : rvalue; + } + } + else + { + value = 0f; + } + } + else if(rightEulers != null) + { + float x = rightEulers?.x ?? 0f; + x = x < 180 ? x + 360 : x; + value = x > (xCenterRight+xDeadZoneRight) ? (x - xCenterRight-xDeadZoneRight) * xSensitivityRight : 0.0f; + } + } + return value; + } + + public static Quaternion? GetPilotingHandLocalRotation(PhysicalPilotingHand hand) + { + if(IsGrippingPilotingControl(hand)) + { + //Get hands local rotation with respect to vehicle grip + //VRhands.instance confirmed not null in IsGrippingPilotingControl + return Quaternion.Inverse(GetPilotingGrip(hand).transform.rotation) * (hand == PhysicalPilotingHand.Left ? VRHands.instance.leftTarget.rotation : VRHands.instance.rightTarget.rotation); + } + return null; + } + + public static Vector3? GetPilotingHandLocalPosition(PhysicalPilotingHand hand) + { + if(IsGrippingPilotingControl(hand)) + { + Transform gripTransform = GetPilotingGrip(hand).transform; + Vector3 gripLocal = gripTransform.InverseTransformDirection(gripTransform.position); + Vector3 handLocal = gripTransform.InverseTransformDirection(hand == PhysicalPilotingHand.Left ? VRHands.instance.leftTarget.position : VRHands.instance.rightTarget.position); + //DebugPanel.Show($"leftGrip: {GetPilotingGrip(hand).transform.position}\nleftHand: { VRHands.instance.leftTarget.position}\nleftGripLocal: {gripLocal}\nleftHandLocal: {handLocal}\ndelta:{gripLocal - handLocal}"); + + return gripLocal - handLocal; //Vector3.zero;// GetPilotingGrip(hand).transform.InverseTransformVector(GetPilotingGrip(hand).transform.position) - (hand == PhysicalPilotingHand.Left ? VRHands.instance.leftTarget.position : VRHands.instance.rightTarget.position); //Get hands right target local position with respect to seatruck right target + } + return null; + } + + public static Transform GetCurrentPilotingTarget(PhysicalPilotingHand hand) + { + if(!IsGrippingPilotingControl(hand)) + { + leftPilotingTarget = hand == PhysicalPilotingHand.Left ? leftPilotingTarget : null; + rightPilotingTarget = hand == PhysicalPilotingHand.Right ? rightPilotingTarget : null; + //leftPilotingLocked = hand == PhysicalPilotingHand.Left ? leftPilotingLocked : false; + //rightPilotingLocked = hand == PhysicalPilotingHand.Right ? rightPilotingLocked : false; + return null; + } + return GetPilotingTarget(hand); + } + + private static Transform GetPilotingTarget(PhysicalPilotingHand hand) + { + bool isInExosuit = Player.main?.inExosuit == true; + bool isOnSnowBike = Player.main?.inHovercraft == true; + bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; + //bool isPiloting = isInExosuit || isOnSnowBike || isPilotingSeatruck; + + Transform target = hand == PhysicalPilotingHand.Left ? leftPilotingTarget : rightPilotingTarget; + if(target == null) + { + if(hand == PhysicalPilotingHand.Left) + { + if(isPilotingSeatruck) + { + leftPilotingTarget = GameObject.Find("ControllerLeft_ikTarg").transform; + } + else if(isInExosuit) + { + leftPilotingTarget = Player.main?.GetVehicle()?.leftHandPlug; + } + else if(isOnSnowBike) + { + leftPilotingTarget = Player.main?.GetComponentInParent().leftHandIKTarget; + } + target = leftPilotingTarget; + } + else + { + if(isPilotingSeatruck) + { + rightPilotingTarget = GameObject.Find("ControllerRight_ikTarg").transform; + } + else if(isInExosuit) + { + rightPilotingTarget = Player.main?.GetVehicle()?.rightHandPlug; + } + else if(isOnSnowBike) + { + rightPilotingTarget = Player.main?.GetComponentInParent().rightHandIKTarget; + } + target = rightPilotingTarget; + } + } + return target; + } + + //Clean this up. Locked piloting shouldnt need to be a whole separate section + public static bool IsGrippingPilotingControl(PhysicalPilotingHand hand) + { + bool result = false; + if(VRHands.instance == null) + { + return result; + } + + bool isInExosuit = Player.main?.inExosuit == true; + bool isOnSnowBike = Player.main?.inHovercraft == true; + bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; + bool isPiloting = isInExosuit || isOnSnowBike || isPilotingSeatruck; + + if(Settings.PhysicalDriving && isPiloting) + { + if(Settings.PhysicalLockedGrips) + { + if((hand == PhysicalPilotingHand.Left && leftPilotingLocked) || (hand == PhysicalPilotingHand.Right && rightPilotingLocked)) + { + if(hand == PhysicalPilotingHand.Left && SteamVR_Actions.subnautica.MoveDown.GetStateDown(SteamVR_Input_Sources.LeftHand)) + { + leftPilotingLocked = false; + result = false; + } + else if(hand == PhysicalPilotingHand.Right && SteamVR_Actions.subnautica.MoveUp.GetStateDown(SteamVR_Input_Sources.RightHand)) + { + rightPilotingLocked = false; + result = false; + } + else + { + result = true; + } + } + else if(hand == PhysicalPilotingHand.Left && SteamVR_Actions.subnautica.MoveDown.GetStateDown(SteamVR_Input_Sources.LeftHand) && Vector3.Distance(GetPilotingGrip(hand).transform.position, VRHands.instance.leftTarget.position ) < gripDistance) + { + leftPilotingLocked = true; + result = true; + } + else if(hand == PhysicalPilotingHand.Right && SteamVR_Actions.subnautica.MoveUp.GetStateDown(SteamVR_Input_Sources.RightHand) && Vector3.Distance(GetPilotingGrip(hand).transform.position, VRHands.instance.rightTarget.position ) < gripDistance) + { + rightPilotingLocked = true; + result = true; + } + else + { + result = false; + } + } + else + { + if(hand == PhysicalPilotingHand.Left) + { + bool controlGripped = SteamVR_Actions.subnautica.MoveDown.GetState(SteamVR_Input_Sources.LeftHand); + if(!leftGripping) + { + if(controlGripped && Vector3.Distance(GetPilotingGrip(hand).transform.position, VRHands.instance.leftTarget.position ) < gripDistance ) + { + leftGripping = true; + } + } + else if(!controlGripped) + { + leftGripping = false; + } + result = leftGripping; + } + if(hand == PhysicalPilotingHand.Right) + { + bool controlGripped = SteamVR_Actions.subnautica.MoveUp.GetState(SteamVR_Input_Sources.RightHand); + if(!rightGripping) + { + if(controlGripped && Vector3.Distance(GetPilotingGrip(hand).transform.position, VRHands.instance.rightTarget.position ) < gripDistance ) + { + rightGripping = true; + } + } + else if(!controlGripped) + { + rightGripping = false; + } + result = rightGripping; + } + //result = (hand == PhysicalPilotingHand.Left ? SteamVR_Actions.subnautica.MoveDown.GetState(SteamVR_Input_Sources.LeftHand) : SteamVR_Actions.subnautica.MoveUp.GetState(SteamVR_Input_Sources.RightHand)) && ( Vector3.Distance(GetPilotingGrip(hand).transform.position, hand == PhysicalPilotingHand.Left ? VRHands.instance.leftTarget.position : VRHands.instance.rightTarget.position ) < gripDistance); + } + } + return result; + } + + //Use a new GameObject instead of the actual vehicle ikTarget because the iKTarget moves + //when the steering console is animated. This causes visual stuttering as the animation + //fights with the manual adjustment. + //The new GameObject remains in a fixed position separate from the animation. + public static GameObject GetPilotingGrip(PhysicalPilotingHand hand) + { + GameObject grip = null; + bool isInExosuit = Player.main?.inExosuit == true; + bool isOnSnowBike = Player.main?.inHovercraft == true; + bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; + if(isPilotingSeatruck) + { + grip = hand == PhysicalPilotingHand.Left ? leftSeaTruckPilotingGrip : rightSeaTruckPilotingGrip; + } + else if(isInExosuit) + { + grip = hand == PhysicalPilotingHand.Left ? leftExosuitPilotingGrip : rightExosuitPilotingGrip; + } + else if(isOnSnowBike) + { + grip = hand == PhysicalPilotingHand.Left ? leftSnowBikePilotingGrip : rightSnowBikePilotingGrip; + } + + if(grip == null) + { + Transform target = GetPilotingTarget(hand); + if(target != null) + { + grip = CreateGrip(target); + if(hand == PhysicalPilotingHand.Left) + { + if(isPilotingSeatruck) + { + leftSeaTruckPilotingGrip = grip; + } + else if(isInExosuit) + { + leftExosuitPilotingGrip = grip; + } + else if(isOnSnowBike) + { + //override with fixed positions since we dont know the orientation of the handlebars when they were first grabbed + grip.transform.localPosition = new Vector3(-0.25237f, 0.25228f, 0.01859f); + leftSnowBikePilotingGrip = grip; + } + } + else + { + if(isPilotingSeatruck) + { + rightSeaTruckPilotingGrip = grip; + } + else if(isInExosuit) + { + rightExosuitPilotingGrip = grip; + } + else if(isOnSnowBike) + { + //override with fixed positions since we dont know the orientation of the handlebars when they were first grabbed + grip.transform.localPosition = new Vector3(0.25237f, 0.25228f, 0.01859f); + rightSnowBikePilotingGrip = grip; + } + } + } + } + return grip; + } + + public static GameObject CreateGrip(Transform target) + { + GameObject grip = GameObject.CreatePrimitive(PrimitiveType.Sphere); + GameObject.Destroy(grip.GetComponent()); + grip.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f); + grip.transform.position = target.position; + grip.transform.rotation = target.rotation; + Transform parent = target.parent; + if(Player.main?.inExosuit == true) + { + parent = parent.parent; + } + else if(Player.main?.inHovercraft == true) + { + parent = Player.main?.GetComponentInParent().transform; + } + grip.transform.SetParent(parent, true); + + //Mod.logger.LogInfo($"CreateGrip localPosition x={grip.transform.localPosition.ToString("F5")}"); + Material newMaterial = new(ShaderManager.preloadedShaders.DebugDisplaySolid); + newMaterial.SetColor(ShaderPropertyID._Color, Color.cyan); + grip.GetComponent().material = newMaterial; + grip.SetActive(false); + + return grip; + } + } + + #region Patches + + + #endregion + +} From 11731df956487d9f357adf5c2f52fb2a5c5cc08c Mon Sep 17 00:00:00 2001 From: jbusfield Date: Mon, 25 Sep 2023 08:25:43 -0700 Subject: [PATCH 7/9] Updated version --- SubmersedVR/SubmersedVR.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SubmersedVR/SubmersedVR.csproj b/SubmersedVR/SubmersedVR.csproj index 2c78cc4..851fd05 100644 --- a/SubmersedVR/SubmersedVR.csproj +++ b/SubmersedVR/SubmersedVR.csproj @@ -4,7 +4,7 @@ net472 SubmersedVR A VR Conversion Mod for Subnautica - 0.4.0 + 0.5.0 true latest From 150365129c4c72f1b8497e63bf880cebc377798b Mon Sep 17 00:00:00 2001 From: jbusfield Date: Thu, 16 Nov 2023 11:05:43 -0800 Subject: [PATCH 8/9] Added adjustment settings for Physical Driving controls. Added ability to completely hide laser pointer --- SubmersedVR/Input/LaserPointer.cs | 8 +- SubmersedVR/Settings.cs | 141 ++++- SubmersedVR/SubmersedVR.csproj | 4 +- SubmersedVR/VR/VRPhysicalPiloting.cs | 735 +++++++++++++++++++-------- 4 files changed, 648 insertions(+), 240 deletions(-) diff --git a/SubmersedVR/Input/LaserPointer.cs b/SubmersedVR/Input/LaserPointer.cs index b7d5ed3..353bc71 100644 --- a/SubmersedVR/Input/LaserPointer.cs +++ b/SubmersedVR/Input/LaserPointer.cs @@ -119,8 +119,12 @@ void Update() pointerDot.SetActive(true); length = this.worldTargetDistance; } - Show(Settings.AlwaysShowLaserPointer | uiWasHit | worldWasHit, worldWasHit); - + Show(Settings.ShowLaserPointer == "Always" || (Settings.ShowLaserPointer == "Default" && (uiWasHit || worldWasHit)), worldWasHit); + if(Settings.ShowLaserPointer == "Never") + { + pointerDot.SetActive(false); + } + Vector3 endPos = transform.position + (transform.forward * length); lineRenderer.SetPosition(0, transform.position); diff --git a/SubmersedVR/Settings.cs b/SubmersedVR/Settings.cs index 4bb8333..fc402c4 100644 --- a/SubmersedVR/Settings.cs +++ b/SubmersedVR/Settings.cs @@ -33,8 +33,8 @@ public class Settings public static bool AlwaysShowControllers; public static event BooleanChanged AlwaysShowControllersChanged; - public static bool AlwaysShowLaserPointer; - public static event BooleanChanged AlwaysShowLaserPointerChanged; + public static string ShowLaserPointer = "Default"; + //public static event BooleanChanged AlwaysShowLaserPointerChanged; public static bool PutHandReticleOnLaserPointer; public static event BooleanChanged PutHandReticleOnLaserPointerChanged; @@ -55,15 +55,61 @@ public class Settings // public static event FloatChanged HudDistanceChanged; public static float PlayerScale = 1.0f; - public static bool EnableGameHaptics = false; - public static bool EnableUIHaptics = false; + public static bool EnableGameHaptics = true; + public static bool EnableUIHaptics = true; + + public static bool HandBasedTurning = false; - public static bool ArticulatedHands = false; + public static bool ArticulatedHands = true; public static bool PhysicalDriving = false; public static bool PhysicalLockedGrips = false; - + //Keep Seamoth and Cyclops defaults so VRPhysicalPiloting will compile + public static float SeamothLeftHorizontalCenterAngle = 0.0f; + public static float SeamothLeftVerticleCenterAngle = 0.0f; + public static float SeamothLeftDeadZone = 5.0f; + public static float SeamothLeftSensitivity = 0.0f; + + public static float SeamothRightHorizontalCenterAngle = 0.0f; + public static float SeamothRightVerticleCenterAngle = 0.0f; + public static float SeamothRightDeadZone = 5.0f; + + public static float CyclopsLeftHorizontalCenterAngle = 0.0f; + public static float CyclopsLeftVerticleCenterAngle = 0.0f; + public static float CyclopsLeftDeadZone = 5.0f; + + public static float CyclopsRightHorizontalCenterAngle = 0.0f; + public static float CyclopsRightVerticleCenterAngle = 0.0f; + public static float CyclopsRightDeadZone = 5.0f; + + public static float SeatruckLeftHorizontalCenterAngle = 0.0f; + public static float SeatruckLeftVerticleCenterAngle = 0.0f; + public static float SeatruckLeftDeadZone = 5.0f; + public static float SeatruckLeftSensitivity = 0.0f; + + public static float SeatruckRightHorizontalCenterAngle = 0.0f; + public static float SeatruckRightVerticleCenterAngle = 0.0f; + public static float SeatruckRightDeadZone = 5.0f; + public static bool SeatruckAltLeftGrip = false; + + public static float ExosuitLeftHorizontalCenterAngle = 0.0f; + public static float ExosuitLeftVerticleCenterAngle = 0.0f; + public static float ExosuitLeftDeadZone = 5.0f; + + public static float ExosuitRightHorizontalCenterAngle = 0.0f; + public static float ExosuitRightVerticleCenterAngle = 0.0f; + public static float ExosuitRightDeadZone = 5.0f; + + public static float SnowbikeLeftHorizontalCenterAngle = 0.0f; + public static float SnowbikeLeftVerticleCenterAngle = 0.0f; + public static float SnowbikeLeftDeadZone = 5.0f; + + public static float SnowbikeRightHorizontalCenterAngle = 0.0f; + public static float SnowbikeRightVerticleCenterAngle = 0.0f; + public static float SnowbikeRightDeadZone = 5.0f; + public static bool SnowbikeAltAccelerator = false; + // Saves or loads all public static properties as settings using the given serializer internal static void Serialize(GameSettings.ISerializer serializer) { @@ -138,24 +184,19 @@ internal static void AddMenu(uGUI_OptionsPanel panel) } }); - panel.AddHeading(tab, "Comfort"); - panel.AddSliderOption(tab, "SeaTruck Pilot Position Offset", SeaTruckZOffset, -0.4f, 0.4f, SeaTruckZOffset, 0.01f, (value) => { SeaTruckZOffset = value; }, SliderLabelMode.Float, "0.00"); - panel.AddSliderOption(tab, "SeaTruck Pilot Height Offset", SeaTruckYOffset, -0.4f, 0.4f, SeaTruckYOffset, 0.01f, (value) => { SeaTruckYOffset = value; }, SliderLabelMode.Float, "0.00"); - panel.AddSliderOption(tab, "Prawn Suit Position Offset", ExosuitZOffset, -0.4f, 0.4f, ExosuitZOffset, 0.01f, (value) => { ExosuitZOffset = value; }, SliderLabelMode.Float, "0.00"); - panel.AddSliderOption(tab, "Prawn Suit Height Offset", ExosuitYOffset, -0.4f, 0.4f, ExosuitYOffset, 0.01f, (value) => { ExosuitYOffset = value; }, SliderLabelMode.Float, "0.00"); - panel.AddSliderOption(tab, "Snowbike Position Offset", SnowBikeZOffset, -0.4f, 0.4f, SnowBikeZOffset, 0.01f, (value) => { SnowBikeZOffset = value; }, SliderLabelMode.Float, "0.00"); - panel.AddSliderOption(tab, "Snowbike Height Offset", SnowBikeYOffset, -0.2f, 0.4f, SnowBikeYOffset, 0.01f, (value) => { SnowBikeYOffset = value; }, SliderLabelMode.Float, "0.00"); - - panel.AddHeading(tab, "Experimental"); + panel.AddHeading(tab, "Immersion"); panel.AddToggleOption(tab, "Articulated Hands", ArticulatedHands, (value) => { ArticulatedHands = value; }, "Hands animate based on the movement of your physical hands."); - panel.AddToggleOption(tab, "Physical Driving", PhysicalDriving, (value) => { PhysicalDriving = value; }, "Grip Vehicle controls to steer."); - panel.AddToggleOption(tab, "Locked Steering Grips", PhysicalLockedGrips, (value) => { PhysicalLockedGrips = value; }, "Gripping the steering control locks your hands to the steering so you dont have to constantly grip. Grip again to unlock."); - panel.AddToggleOption(tab, "Full Body", FullBody, (value) => { FullBody = value; FullBodyChanged(value); }, "See the full body instead of just the hands and feet."); - panel.AddSliderOption(tab, "Body Scale", PlayerScale, 0.8f, 1.2f, PlayerScale, 0.01f, (value) => { PlayerScale = value; }, SliderLabelMode.Float, "0.00"); panel.AddToggleOption(tab, "Enable Game Haptics", EnableGameHaptics, (value) => { EnableGameHaptics = value; }, "Enable controller vibration while interacting with world objects."); panel.AddToggleOption(tab, "Enable UI Haptics", EnableUIHaptics, (value) => { EnableUIHaptics = value; }, "Enable controller vibration while interacting with the User Interface."); - panel.AddToggleOption(tab, "Put hand reticle on laserpointer end", PutHandReticleOnLaserPointer, (value) => { PutHandReticleOnLaserPointer = value; PutHandReticleOnLaserPointerChanged(value); }); panel.AddToggleOption(tab, "Put survival meter on left wrist", PutBarsOnWrist, (value) => { PutBarsOnWrist = value; PutBarsOnWristChanged(value); }); + panel.AddChoiceOption(tab, "Show Laser Pointer", new string[] {"Always", "Default", "Never"}, ShowLaserPointer , (value) => { + ShowLaserPointer = value; + }); + + panel.AddHeading(tab, "Experimental"); + panel.AddToggleOption(tab, "Full Body", FullBody, (value) => { FullBody = value; FullBodyChanged(value); }, "See the full body instead of just the hands and feet."); + panel.AddSliderOption(tab, "Body Scale", PlayerScale, 0.8f, 1.2f, PlayerScale, 0.01f, (value) => { PlayerScale = value; }, SliderLabelMode.Float, "0.00"); + panel.AddToggleOption(tab, "Put hand reticle on laserpointer end", PutHandReticleOnLaserPointer, (value) => { PutHandReticleOnLaserPointer = value; PutHandReticleOnLaserPointerChanged(value); }); panel.AddToggleOption(tab, "Invert Y Axis in Seamoth/Cameras", InvertYAxis, (value) => { InvertYAxis = value; InvertYAxisChanged(value); }, "Enables Y axis inversion for Seamoth and Cameras."); panel.AddHeading(tab, "Hidden/Advanced VR Settings(Those can cause motion sickness!)"); @@ -166,7 +207,65 @@ internal static void AddMenu(uGUI_OptionsPanel panel) panel.AddHeading(tab, "Debug Options"); panel.AddToggleOption(tab, "Debug Overlays", IsDebugEnabled, (value) => { IsDebugEnabled = value; IsDebugChanged(value); }, "Enables Debug Overlays and Logs."); panel.AddToggleOption(tab, "Always show controllers", AlwaysShowControllers, (value) => { AlwaysShowControllers = value; AlwaysShowControllersChanged(value); }, "Shows the controllers at all times."); - panel.AddToggleOption(tab, "Always show laserpointer", AlwaysShowLaserPointer, (value) => { AlwaysShowLaserPointer = value; AlwaysShowLaserPointerChanged(value); }, "Show the laserpointer at all times."); + //panel.AddToggleOption(tab, "Always show laserpointer", AlwaysShowLaserPointer, (value) => { AlwaysShowLaserPointer = value; AlwaysShowLaserPointerChanged(value); }, "Show the laserpointer at all times."); + + tab = panel.AddTab("Vehicles"); + panel.AddHeading(tab, "Comfort"); + panel.AddSliderOption(tab, "SeaTruck Pilot Position Offset", SeaTruckZOffset, -0.4f, 0.4f, SeaTruckZOffset, 0.01f, (value) => { SeaTruckZOffset = value; }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, "SeaTruck Pilot Height Offset", SeaTruckYOffset, -0.4f, 0.4f, SeaTruckYOffset, 0.01f, (value) => { SeaTruckYOffset = value; }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, "Prawn Suit Position Offset", ExosuitZOffset, -0.4f, 0.4f, ExosuitZOffset, 0.01f, (value) => { ExosuitZOffset = value; }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, "Prawn Suit Height Offset", ExosuitYOffset, -0.4f, 0.4f, ExosuitYOffset, 0.01f, (value) => { ExosuitYOffset = value; }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, "Snowbike Position Offset", SnowBikeZOffset, -0.4f, 0.4f, SnowBikeZOffset, 0.01f, (value) => { SnowBikeZOffset = value; }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, "Snowbike Height Offset", SnowBikeYOffset, -0.2f, 0.4f, SnowBikeYOffset, 0.01f, (value) => { SnowBikeYOffset = value; }, SliderLabelMode.Float, "0.00"); + + + panel.AddHeading(tab, "Options"); + panel.AddToggleOption(tab, "Physical Driving", PhysicalDriving, (value) => { PhysicalDriving = value; }, "Grip Vehicle controls to steer."); + panel.AddToggleOption(tab, "Locked Steering Grips", PhysicalLockedGrips, (value) => { PhysicalLockedGrips = value; }, "Gripping the steering control locks your hands to the steering so you dont have to constantly grip. Grip again to unlock."); + + panel.AddHeading(tab, "SeaTruck Left Hand"); + panel.AddSliderOption(tab, "Center (Left/Right)", SeatruckLeftHorizontalCenterAngle, -10f, 10f, SeatruckLeftHorizontalCenterAngle, 1f, (value) => { SeatruckLeftHorizontalCenterAngle = value; }, SliderLabelMode.Float, "0"); + panel.AddSliderOption(tab, "Center (Up/Down)", SeatruckLeftVerticleCenterAngle, -10f, 10f, SeatruckLeftVerticleCenterAngle, 1f, (value) => { SeatruckLeftVerticleCenterAngle = value; }, SliderLabelMode.Float, "0"); + //Call dead zone "Sensitivity" for users + panel.AddSliderOption(tab, "Sensitivity", SeatruckLeftDeadZone, 1f, 10f, SeatruckLeftDeadZone, 1f, (value) => { SeatruckLeftDeadZone = value; }, SliderLabelMode.Float, "0", "Higher value means turns more quickly"); + //panel.AddSliderOption(tab, "Sensitivity", SeamothLeftSensitivity, 0f, 100f, SeamothLeftSensitivity, 1f, (value) => { SeamothLeftSensitivity = value; }, SliderLabelMode.Float, "0"); + panel.AddToggleOption(tab, "Use Vertical Grip", SeatruckAltLeftGrip, (value) => { SeatruckAltLeftGrip = value; }, "Use vertical hand grip rather than horizontal."); + + panel.AddHeading(tab, "SeaTruck Right Hand"); + panel.AddSliderOption(tab, "Center (Left/Right)", SeatruckRightHorizontalCenterAngle, -10f, 10f, SeatruckRightHorizontalCenterAngle, 1f, (value) => { SeatruckRightHorizontalCenterAngle = value; }, SliderLabelMode.Float, "0"); + panel.AddSliderOption(tab, "Center (Up/Down)", SeatruckRightVerticleCenterAngle, -10f, 10f, SeatruckRightVerticleCenterAngle, 1f, (value) => { SeatruckRightVerticleCenterAngle = value; }, SliderLabelMode.Float, "0"); + //Call dead zone "Sensitivity" for users + panel.AddSliderOption(tab, "Sensitivity",SeatruckRightDeadZone, 1f, 10f, SeatruckRightDeadZone, 1f, (value) => { SeatruckRightDeadZone = value; }, SliderLabelMode.Float, "0", "Higher value means turns more quickly"); + //panel.AddSliderOption(tab, "Sensitivity", SeamothRightSensitivity, 0f, 100f, SeamothRightSensitivity, 1f, (value) => { SeamothRightSensitivity = value; }, SliderLabelMode.Float, "0"); + + panel.AddHeading(tab, "Prawn Suit Left Hand"); + panel.AddSliderOption(tab, "Center (Left/Right)", ExosuitLeftHorizontalCenterAngle, -10f, 10f, ExosuitLeftHorizontalCenterAngle, 1f, (value) => { ExosuitLeftHorizontalCenterAngle = value; }, SliderLabelMode.Float, "0"); + panel.AddSliderOption(tab, "Center (Up/Down)", ExosuitLeftVerticleCenterAngle, -10f, 10f, ExosuitLeftVerticleCenterAngle, 1f, (value) => { ExosuitLeftVerticleCenterAngle = value; }, SliderLabelMode.Float, "0"); + //Call dead zone "Sensitivity" for users + panel.AddSliderOption(tab, "Sensitivity", ExosuitLeftDeadZone, 1f, 10f, ExosuitLeftDeadZone, 1f, (value) => { ExosuitLeftDeadZone = value; }, SliderLabelMode.Float, "0", "Higher value means turns more quickly"); + //panel.AddSliderOption(tab, "Sensitivity", SeamothLeftSensitivity, 0f, 100f, SeamothLeftSensitivity, 1f, (value) => { SeamothLeftSensitivity = value; }, SliderLabelMode.Float, "0"); + + panel.AddHeading(tab, "Prawn Suit Right Hand"); + panel.AddSliderOption(tab, "Center (Left/Right)", ExosuitRightHorizontalCenterAngle, -10f, 10f, ExosuitRightHorizontalCenterAngle, 1f, (value) => { ExosuitRightHorizontalCenterAngle = value; }, SliderLabelMode.Float, "0"); + panel.AddSliderOption(tab, "Center (Up/Down)", ExosuitRightVerticleCenterAngle, -10f, 10f, ExosuitRightVerticleCenterAngle, 1f, (value) => { ExosuitRightVerticleCenterAngle = value; }, SliderLabelMode.Float, "0"); + //Call dead zone "Sensitivity" for users + panel.AddSliderOption(tab, "Sensitivity", ExosuitRightDeadZone, 1f, 10f, ExosuitRightDeadZone, 1f, (value) => { ExosuitRightDeadZone = value; }, SliderLabelMode.Float, "0", "Higher value means turns more quickly"); + //panel.AddSliderOption(tab, "Sensitivity", SeamothRightSensitivity, 0f, 100f, SeamothRightSensitivity, 1f, (value) => { SeamothRightSensitivity = value; }, SliderLabelMode.Float, "0"); + + panel.AddHeading(tab, "SnowFox Left Hand"); + //panel.AddSliderOption(tab, "Center (Left/Right)", SnowbikeLeftHorizontalCenterAngle, -10f, 10f, SnowbikeLeftHorizontalCenterAngle, 1f, (value) => { SnowbikeLeftHorizontalCenterAngle = value; }, SliderLabelMode.Float, "0"); + //panel.AddSliderOption(tab, "Center (Up/Down)", SnowbikeLeftVerticleCenterAngle, -10f, 10f, SnowbikeLeftVerticleCenterAngle, 1f, (value) => { SnowbikeLeftVerticleCenterAngle = value; }, SliderLabelMode.Float, "0"); + //Call dead zone "Sensitivity" for users + panel.AddSliderOption(tab, "Sensitivity", SnowbikeLeftDeadZone, 1f, 10f, SnowbikeLeftDeadZone, 1f, (value) => { SnowbikeLeftDeadZone = value; }, SliderLabelMode.Float, "0", "Higher value means turns more quickly"); + //panel.AddSliderOption(tab, "Sensitivity", SeamothLeftSensitivity, 0f, 100f, SeamothLeftSensitivity, 1f, (value) => { SeamothLeftSensitivity = value; }, SliderLabelMode.Float, "0"); + + panel.AddHeading(tab, "SnowFox Right Hand"); + //panel.AddSliderOption(tab, "Center (Left/Right)", SnowbikeRightHorizontalCenterAngle, -10f, 10f, SnowbikeRightHorizontalCenterAngle, 1f, (value) => { SnowbikeRightHorizontalCenterAngle = value; }, SliderLabelMode.Float, "0"); + panel.AddSliderOption(tab, "Center (Accelerator)", SnowbikeRightVerticleCenterAngle, -10f, 10f, SnowbikeRightVerticleCenterAngle, 1f, (value) => { SnowbikeRightVerticleCenterAngle = value; }, SliderLabelMode.Float, "0"); + //Call dead zone "Sensitivity" for users + panel.AddSliderOption(tab, "Sensitivity", SnowbikeRightDeadZone, 1f, 10f, SnowbikeRightDeadZone, 1f, (value) => { SnowbikeRightDeadZone = value; }, SliderLabelMode.Float, "0", "Higher value means turns more quickly"); + //panel.AddSliderOption(tab, "Sensitivity", SeamothRightSensitivity, 0f, 100f, SeamothRightSensitivity, 1f, (value) => { SeamothRightSensitivity = value; }, SliderLabelMode.Float, "0"); + panel.AddToggleOption(tab, "Invert Accelerator", SnowbikeAltAccelerator, (value) => { SnowbikeAltAccelerator = value; }, "Twist forward to accelerate."); } } diff --git a/SubmersedVR/SubmersedVR.csproj b/SubmersedVR/SubmersedVR.csproj index 851fd05..f2c0ed0 100644 --- a/SubmersedVR/SubmersedVR.csproj +++ b/SubmersedVR/SubmersedVR.csproj @@ -4,11 +4,13 @@ net472 SubmersedVR A VR Conversion Mod for Subnautica - 0.5.0 + 0.6.0 true latest c:\Program Files (x86)\Steam\steamapps\common\SubnauticaZero + + BZ $(NoWarn);CS0436 diff --git a/SubmersedVR/VR/VRPhysicalPiloting.cs b/SubmersedVR/VR/VRPhysicalPiloting.cs index 83861d7..e779a8e 100644 --- a/SubmersedVR/VR/VRPhysicalPiloting.cs +++ b/SubmersedVR/VR/VRPhysicalPiloting.cs @@ -1,6 +1,8 @@ using HarmonyLib; using UnityEngine; +using System.Collections.Generic; + namespace SubmersedVR { extern alias SteamVRRef; @@ -8,8 +10,6 @@ namespace SubmersedVR using SteamVRRef.Valve.VR; using SteamVRActions.Valve.VR; using System.Xml.Schema; - using Yangrc.VolumeCloud; - using Steamworks; //Use hands to grip vehicle controls and pilot vehicles static class PhysicalPilotingVR @@ -22,16 +22,37 @@ public enum PhysicalPilotingDirection: int MoveRight, MoveUp, MoveDown, + LookUp, + LookDown, LookLeft, LookRight, - LookUp, - LookDown } public enum PhysicalPilotingHand: int { Left = 0, Right } + public enum PhysicalVehicle: int + { + None = -1, + Exosuit, + Seamoth, + Cyclops, + SeaTruck, + SnowBike + } + public enum MovementAxis: int + { + X = 0, + Y, + Z + } + public enum MovementMagnitude: int + { + Greater = 0, + LessThan + } + static public Transform leftPilotingTarget; //Where the hands grip when grabbing the steering controls. Moves due to animation static public Transform rightPilotingTarget; @@ -41,37 +62,266 @@ public enum PhysicalPilotingHand: int static public GameObject leftExosuitPilotingGrip; static public GameObject rightSnowBikePilotingGrip; //The fixed grip location for steering. Does not move during animation static public GameObject leftSnowBikePilotingGrip; + static public GameObject rightSeamothPilotingGrip; //The fixed grip location for steering. Does not move during animation + static public GameObject leftSeamothPilotingGrip; + static public GameObject rightCyclopsPilotingGrip; //The fixed grip location for steering. Does not move during animation + static public GameObject leftCyclopsPilotingGrip; + + static public bool leftPilotingLocked = false; static public bool rightPilotingLocked = false; static public bool leftGripping = false; //is the left hand currently gripping the steering control static public bool rightGripping = false; //is the left hand currently gripping the steering control - static float xCenterLeft_exosuit = 350f; - static float yCenterLeft_exosuit = 340f; - static float deadZone_exosuit = 15f; - - static float zCenterRight_snowbike = -5.0f; - static float deadZoneRight_snowbike = 15f; - static float zSensitivityRight_snowbike = 0.03f; - //static float xCenterLeft_snowbike = 0.0f; - static float deadZoneLeft_snowbike = 0.008f; - static float xSensitivityLeft_snowbike = 20f; - - static float xCenterRight = 335f; - static float xDeadZoneRight = 15f; - static float xSensitivityRight = 0.03f; - static float yCenterRight = 341f; - static float yDeadZoneRight = 15f; - static float ySensitivityRight = 0.03f; - static float zCenterLeft = 33; - static float zDeadZoneLeft = 15f; - static float zSensitivityLeft = 0.01f; - static float yCenterLeft = 305f; - static float yDeadZoneLeft = 10f; - static float ySensitivityLeft = 0.1f; + public static float[][][] gripArray = new float[10][][] //direction, vehicle, values + { + new float[5][] //forward + { + new float[3]{340f, 15f, 1.0f}, //exosuit - center angle, deadzone, sensitivity + new float[3]{20f, 8f, 1.0f}, //seamoth + new float[3]{15f, 8f, 1.0f}, //cyclops + new float[3]{33f, 15f, 1.0f}, //seatruck + new float[3]{355f, 20f, 0.001f}, //snowbike + }, + new float[5][] //backward + { + new float[3]{340f, 15f, 1.0f}, //exosuit + new float[3]{20f, 8f, 1.0f}, //seamoth + new float[3]{15f, 8f, 1.0f}, //cyclops + new float[3]{33f, 15f, 1.0f}, //seatruck + new float[3]{355f, 20f, 0.001f}, //snowbike + }, + new float[5][] //move left + { + new float[3]{15f, 15f, 1f}, //exosuit + new float[3]{345f, 10f, 1f}, //seamoth + new float[3]{335f, 15f, 0.1f}, //cyclops + new float[3]{305f, 15f, 1.0f}, //seatruck + new float[3]{0f, 0.008f, 0.2f}, //snowbike + }, + new float[5][] //move right + { + new float[3]{15f, 15f, 1f}, //exosuit + new float[3]{345f, 10f, 1f}, //seamoth + new float[3]{335f, 15f, 0.1f}, //cyclops + new float[3]{305f, 15f, 1.0f}, //seatruck + new float[3]{0f, 0.008f, 0.2f}, //snowbike + }, + new float[5][] //move up + { + new float[3]{341f, 15f, 0.03f}, //exosuit + new float[3]{20f, 10f, 0.03f}, //seamoth + new float[3]{355f, 15f, 0.03f}, //cyclops + new float[3]{340f, 15f, 0.03f}, //seatruck + new float[3]{0f, 0f, 0.0f}, //snowbike not used + }, + new float[5][] //move down + { + new float[3]{341f, 15f, 0.03f}, //exosuit + new float[3]{20f, 10f, 0.03f}, //seamoth + new float[3]{355f, 15f, 0.03f}, //cyclops + new float[3]{340f, 15f, 0.03f}, //seatruck + new float[3]{0f, 0f, 0.0f}, //snowbike not used + }, + new float[5][] //lookup up + { + new float[3]{341f, 15f, 0.03f}, //exosuit + new float[3]{20f, 10f, 0.03f}, //seamoth + new float[3]{355f, 15f, 0.03f}, //cyclops + new float[3]{340f, 15f, 0.03f}, //seatruck + new float[3]{0f, 0f, 0.0f}, //snowbike not used + }, + new float[5][] //look down + { + new float[3]{341f, 15f, 0.03f}, //exosuit + new float[3]{20f, 10f, 0.03f}, //seamoth + new float[3]{355f, 15f, 0.03f}, //cyclops + new float[3]{340f, 15f, 0.03f}, //seatruck + new float[3]{0f, 0f, 0.0f}, //snowbike not used + }, + new float[5][] //lookup left + { + new float[3]{355f, 15f, 0.03f}, //exosuit + new float[3]{345f, 15f, 0.07f}, //seamoth + new float[3]{340f, 8f, 0.07f}, //cyclops + new float[3]{335f, 15f, 0.03f}, //seatruck + new float[3]{0f, 0.004f, 10.0f}, //snowbike + }, + new float[5][] //look right + { + new float[3]{355f, 15f, 0.03f}, //exosuit + new float[3]{345f, 15f, 0.07f}, //seamoth + new float[3]{340f, 8f, 0.07f}, //cyclops + new float[3]{335f, 15f, 0.03f}, //seatruck + new float[3]{0f, 0.004f, 10.0f}, //snowbike + }, + }; static float gripDistance = 0.13f; // The distance away from a target control where the grip will recognize contact +#if BZ + public static bool inHovercraft{get {return Player.main?.inHovercraft == true;} } + public static bool inSeaTruck{get {return Player.main?.inSeatruckPilotingChair == true;} } +#else + public static bool inHovercraft{get {return false;} } + public static bool inSeaTruck{get {return false;} } +#endif + + public static bool inSeamoth{get {return Player.main?.inSeamoth == true;} } + public static bool inExosuit{get {return Player.main?.inExosuit == true;} } + public static bool inCyclops{get {return Player.main?.currentSub?.isCyclops == true && Player.main?.isPiloting == true;} } + + public static float GetCenterAngle(PhysicalPilotingDirection direction, int vehicleType, PhysicalPilotingHand? hand = null) + { + float offset = 0.0f; + if(vehicleType == (int)PhysicalVehicle.Seamoth) + { + if(direction == PhysicalPilotingDirection.MoveForward || direction == PhysicalPilotingDirection.MoveBackward) + { + offset = 10f * Settings.SeamothLeftVerticleCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.LookUp || direction == PhysicalPilotingDirection.LookDown || direction == PhysicalPilotingDirection.MoveUp || direction == PhysicalPilotingDirection.MoveDown) + { + offset = 10f * Settings.SeamothRightVerticleCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.MoveLeft || direction == PhysicalPilotingDirection.MoveRight) + { + offset = 20f * Settings.SeamothRightHorizontalCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.LookLeft || direction == PhysicalPilotingDirection.LookRight) + { + offset = 20f * (hand == PhysicalPilotingHand.Right ? Settings.SeamothRightHorizontalCenterAngle : Settings.SeamothLeftHorizontalCenterAngle) / 10.0f; + } + } + else if(vehicleType == (int)PhysicalVehicle.Exosuit) + { + if(direction == PhysicalPilotingDirection.MoveForward || direction == PhysicalPilotingDirection.MoveBackward) + { + offset = 10f * Settings.ExosuitLeftVerticleCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.LookUp || direction == PhysicalPilotingDirection.LookDown || direction == PhysicalPilotingDirection.MoveUp || direction == PhysicalPilotingDirection.MoveDown) + { + offset = 10f * Settings.ExosuitRightVerticleCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.MoveLeft || direction == PhysicalPilotingDirection.MoveRight) + { + offset = 20f * Settings.ExosuitLeftHorizontalCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.LookLeft || direction == PhysicalPilotingDirection.LookRight) + { + offset = 20f * Settings.ExosuitRightHorizontalCenterAngle / 10.0f; + } + } + else if(vehicleType == (int)PhysicalVehicle.Cyclops) + { + if(direction == PhysicalPilotingDirection.MoveForward || direction == PhysicalPilotingDirection.MoveBackward) + { + offset = 10f * Settings.CyclopsLeftVerticleCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.LookUp || direction == PhysicalPilotingDirection.LookDown || direction == PhysicalPilotingDirection.MoveUp || direction == PhysicalPilotingDirection.MoveDown || direction == PhysicalPilotingDirection.MoveLeft || direction == PhysicalPilotingDirection.MoveRight) + { + offset = 10f * Settings.CyclopsRightVerticleCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.LookLeft || direction == PhysicalPilotingDirection.LookRight) + { + offset = 20f * (hand == PhysicalPilotingHand.Right ? Settings.CyclopsRightHorizontalCenterAngle : Settings.CyclopsLeftHorizontalCenterAngle) / 10.0f; + } + } + else if(vehicleType == (int)PhysicalVehicle.SeaTruck) + { + gripArray[(int)PhysicalPilotingDirection.MoveLeft][vehicleType][0] = Settings.SeatruckAltLeftGrip ? 220f : 305f; + gripArray[(int)PhysicalPilotingDirection.MoveRight][vehicleType][0] = Settings.SeatruckAltLeftGrip ? 220f : 305f; + gripArray[(int)PhysicalPilotingDirection.MoveForward][vehicleType][0] = Settings.SeatruckAltLeftGrip ? 315f : 15f; + gripArray[(int)PhysicalPilotingDirection.MoveBackward][vehicleType][0] = Settings.SeatruckAltLeftGrip ? 315f: 15f; + if(direction == PhysicalPilotingDirection.MoveForward || direction == PhysicalPilotingDirection.MoveBackward) + { + offset = 15f * Settings.SeatruckLeftVerticleCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.LookUp || direction == PhysicalPilotingDirection.LookDown || direction == PhysicalPilotingDirection.MoveUp || direction == PhysicalPilotingDirection.MoveDown) + { + offset = 10f * Settings.SeatruckRightVerticleCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.MoveLeft || direction == PhysicalPilotingDirection.MoveRight) + { + offset = 10f * Settings.SeatruckLeftHorizontalCenterAngle / 10.0f; + } + else if(direction == PhysicalPilotingDirection.LookLeft || direction == PhysicalPilotingDirection.LookRight) + { + offset = 20f * Settings.SeatruckRightHorizontalCenterAngle / 10.0f; + } + } + else if(vehicleType == (int)PhysicalVehicle.SnowBike) + { + if(direction == PhysicalPilotingDirection.MoveForward || direction == PhysicalPilotingDirection.MoveBackward) + { + offset = 20f * Settings.SnowbikeRightVerticleCenterAngle / 10.0f; + } + } + return gripArray[(int)direction][vehicleType][0] + offset; + } + + public static float GetDeadZone(PhysicalPilotingDirection direction, int vehicleType, PhysicalPilotingHand? hand = null) + { + float offset = 0.0f; + if(vehicleType == (int)PhysicalVehicle.Seamoth) + { + offset = gripArray[(int)direction][vehicleType][1] * (10f - ((hand == PhysicalPilotingHand.Right ? Settings.SeamothRightDeadZone : Settings.SeamothLeftDeadZone) * 2f)) / 10.0f; + } + else if(vehicleType == (int)PhysicalVehicle.Exosuit) + { + offset = gripArray[(int)direction][vehicleType][1] * (10f - ((hand == PhysicalPilotingHand.Right ? Settings.ExosuitRightDeadZone : Settings.ExosuitLeftDeadZone) * 2f)) / 10.0f; + } + else if(vehicleType == (int)PhysicalVehicle.Cyclops) + { + offset = gripArray[(int)direction][vehicleType][1] * (10f - ((hand == PhysicalPilotingHand.Right ? Settings.CyclopsRightDeadZone : Settings.CyclopsLeftDeadZone) * 2f)) / 10.0f; + } + else if(vehicleType == (int)PhysicalVehicle.SeaTruck) + { + offset = gripArray[(int)direction][vehicleType][1] * (10f - ((hand == PhysicalPilotingHand.Right ? Settings.SeatruckRightDeadZone : Settings.SeatruckLeftDeadZone) * 2f)) / 10.0f; + } + else if(vehicleType == (int)PhysicalVehicle.SnowBike) + { + //Not using deadzone for snowbike since adjusting the "Comfort" setting for the snowbike is the same as adjsting the deadzone + } + return gripArray[(int)direction][vehicleType][1] + offset; + } + + public static float GetSensitivity(PhysicalPilotingDirection direction, int vehicleType, PhysicalPilotingHand? hand = null) + { + float offset = 0.0f; + if(vehicleType == (int)PhysicalVehicle.SnowBike && (direction == PhysicalPilotingDirection.LookLeft || direction == PhysicalPilotingDirection.LookRight)) + { + offset = gripArray[(int)direction][vehicleType][2] * -(10f - ((hand == PhysicalPilotingHand.Right ? Settings.SnowbikeRightDeadZone : Settings.SnowbikeLeftDeadZone) * 2f)) / 10.0f; //8/10 - -8/10 + } + return gripArray[(int)direction][vehicleType][2] + offset; + } + + public static float? GetValue(PhysicalPilotingDirection direction, Vector3? eulers, int vehicleType, MovementAxis axis, MovementMagnitude magnitude, PhysicalPilotingHand? hand = null) + { + float? value = null; + if(eulers != null) + { + float angle = axis == MovementAxis.X ? eulers?.x ?? 0f : (axis == MovementAxis.Y ? eulers?.y ?? 0f : eulers?.z ?? 0f); + float delta = (magnitude == MovementMagnitude.LessThan ? -1 : 1) * Mathf.DeltaAngle(angle, GetCenterAngle(direction, vehicleType, hand)); + //if the grip angle exceeds the deadzone then return the sensitivity scaled movement + float deadzone = GetDeadZone(direction, vehicleType, hand); + value = delta > deadzone ? ((delta - deadzone) * GetSensitivity(direction, vehicleType, hand)) : 0.0f; + } + return value; + } + public static float? GetPositionValue(PhysicalPilotingDirection direction, Vector3? position, int vehicleType, MovementAxis axis, MovementMagnitude magnitude, PhysicalPilotingHand? hand = null) + { + float? value = null; + if(position != null) + { + float pos = axis == MovementAxis.X ? position?.x ?? 0f : (axis == MovementAxis.Y ? position?.y ?? 0f : position?.z ?? 0f); + float delta = (magnitude == MovementMagnitude.LessThan ? -1 : 1) * pos; + float deadzone = GetDeadZone(direction, vehicleType, hand); + value = delta > deadzone ? ((delta - deadzone) * GetSensitivity(direction, vehicleType, hand)) : 0.0f; + } + return value; + } + public static float? GetValue(PhysicalPilotingDirection direction) { float? value = null; @@ -80,142 +330,118 @@ public enum PhysicalPilotingHand: int Vector3? rightEulers = GetPilotingHandLocalRotation(PhysicalPilotingHand.Right)?.eulerAngles; Vector3? leftPosition = GetPilotingHandLocalPosition(PhysicalPilotingHand.Left); Vector3? rightPosition = GetPilotingHandLocalPosition(PhysicalPilotingHand.Right); - //DebugPanel.Show($"left: {leftEulers}\nright: {rightEulers}\nleftPos: {leftPosition}\nrightPos: {rightPosition}\nrightHandGrip:{IsGrippingPilotingControl(PhysicalPilotingHand.Right)}\nleftHandLock:{VRHands.instance.leftHandSteeringLocked}\nvalue:{value}"); + //DebugPanel.Show($"inHovercraft: {inHovercraft}\nleft: {leftEulers}\nright: {rightEulers}\nleftPos: {leftPosition?.ToString("F3")}\nrightPos: {rightPosition?.ToString("F3")}\nrightHandGrip:{IsGrippingPilotingControl(PhysicalPilotingHand.Right)}"); + int vehicleType = (int)(inExosuit ? PhysicalVehicle.Exosuit : (inSeamoth ? PhysicalVehicle.Seamoth : (inCyclops ? PhysicalVehicle.Cyclops : (inSeaTruck ? PhysicalVehicle.SeaTruck : (inHovercraft ? PhysicalVehicle.SnowBike : PhysicalVehicle.None))))); if(direction == PhysicalPilotingDirection.MoveForward) { - if(Player.main?.inHovercraft == true) + if(inHovercraft) { - if(rightEulers != null) - { - float z = rightEulers?.z ?? 0f; - z = z > 180 ? z - 360 : z; - value = z < (zCenterRight_snowbike-deadZoneRight_snowbike) ? (zCenterRight_snowbike-deadZoneRight_snowbike - z) * zSensitivityRight_snowbike : 0.0f; - } - } - else if(leftEulers != null) - { - if(Player.main?.inSeatruckPilotingChair == true) - { - float z = leftEulers?.z ?? 0f; - z = z > 180 ? z - 360 : z; - value = z > (zCenterLeft+zDeadZoneLeft) ? (z - (zCenterLeft+zDeadZoneLeft)) * zSensitivityLeft: 0.0f; - } - else - { - float y = leftEulers?.y ?? 0f; - y = y < 180 ? 360 + y : y; - value = y < (yCenterLeft_exosuit-deadZone_exosuit) ? (yCenterLeft_exosuit-deadZone_exosuit - y) * ySensitivityLeft : 0.0f; - } + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.Z, Settings.SnowbikeAltAccelerator ? MovementMagnitude.LessThan : MovementMagnitude.Greater); + } + else if(inSeaTruck) + { + value = GetValue(direction, leftEulers, vehicleType, Settings.SeatruckAltLeftGrip ? MovementAxis.X : MovementAxis.Z, MovementMagnitude.LessThan); + } + else + { + value = GetValue(direction, leftEulers, vehicleType, MovementAxis.Y, MovementMagnitude.Greater); } - } else if(direction == PhysicalPilotingDirection.MoveBackward) - { - if(Player.main?.inHovercraft == true) + { + if(inHovercraft) { - if(rightEulers != null) - { - float z = rightEulers?.z ?? 0f; - z = z > 180 ? z - 360 : z; - value = z > (zCenterRight_snowbike+deadZoneRight_snowbike) ? (z - (zCenterRight_snowbike+deadZoneRight_snowbike)) * zSensitivityRight_snowbike: 0.0f; - } - } - else if(leftEulers != null) - { - if(Player.main?.inSeatruckPilotingChair == true) - { - float z = leftEulers?.z ?? 0f; - z = z > 180 ? z - 360 : z; - value = z < (zCenterLeft-zDeadZoneLeft) ? (zCenterLeft-zDeadZoneLeft - z) * zSensitivityLeft : 0.0f; - } - else - { - float y = leftEulers?.y ?? 0f; - y = y < 180 ? 360 + y : y; - value = y > (yCenterLeft_exosuit+deadZone_exosuit) ? (y - yCenterLeft_exosuit-deadZone_exosuit) * ySensitivityLeft : 0.0f; - } + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.Z, Settings.SnowbikeAltAccelerator ? MovementMagnitude.Greater : MovementMagnitude.LessThan); + } + else if(inSeaTruck) + { + value = GetValue(direction, leftEulers, vehicleType, Settings.SeatruckAltLeftGrip ? MovementAxis.X : MovementAxis.Z, MovementMagnitude.Greater); + } + else + { + value = GetValue(direction, leftEulers, vehicleType, MovementAxis.Y, MovementMagnitude.LessThan); } - } - else if(direction == PhysicalPilotingDirection.MoveRight) + else if(direction == PhysicalPilotingDirection.MoveLeft) { - if(Player.main?.inHovercraft == true) + if(inHovercraft) { - //Only Move Right is the A button is presed if(SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) - { - //If using both hands to steering then take the average of both hands - if(leftPosition != null) - { - float x = leftPosition?.x ?? 0f; - value = x > deadZoneLeft_snowbike ? (x - deadZoneLeft_snowbike) * xSensitivityLeft_snowbike : 0f; - } - if(rightPosition != null) - { - float x = rightPosition?.x ?? 0f; - float rvalue = x > deadZoneLeft_snowbike ? (x - deadZoneLeft_snowbike) * xSensitivityLeft_snowbike : 0f; - value = value != null ? (value + rvalue) /2 : rvalue; - } + { + value = GetPositionValue(direction, rightPosition, vehicleType, MovementAxis.X, MovementMagnitude.LessThan, PhysicalPilotingHand.Right); + float? lvalue = GetPositionValue(direction, leftPosition, vehicleType, MovementAxis.X, MovementMagnitude.LessThan); + value = (value != null && lvalue != null) ? (value + lvalue) /2 : value ?? lvalue; } else { value = 0f; } } - else if(leftEulers != null) - { - if(Player.main?.inSeatruckPilotingChair == true) - { - float y = leftEulers?.y ?? 0f; - y = y < 180 ? 360 + y : y; - value = y > (yCenterLeft+yDeadZoneLeft) ? (y - (yCenterLeft+yDeadZoneLeft)) * ySensitivityLeft : 0.0f; - } - else + else if(inExosuit) + { + value = GetValue(direction, leftEulers, vehicleType, MovementAxis.X, MovementMagnitude.Greater); + } + else if(inCyclops) + { + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.X, MovementMagnitude.LessThan, PhysicalPilotingHand.Right); + if(leftEulers != null) { - float x = leftEulers?.x ?? 0f; - x = x < 180 ? x + 360 : x; - value = x > (xCenterLeft_exosuit+deadZone_exosuit) ? (x - xCenterLeft_exosuit-deadZone_exosuit) * xSensitivityRight : 0.0f; + //we already checked for null so we can cast to non-null + float lvalue = (float)GetValue(direction, leftEulers, vehicleType, MovementAxis.X, MovementMagnitude.Greater); + value = value != null ? (value + lvalue) /2 : lvalue; + } + } + else if(inSeaTruck) + { + value = GetValue(direction, leftEulers, vehicleType, MovementAxis.Y, MovementMagnitude.Greater); + } + else + { + if(rightEulers!= null && SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.X, MovementMagnitude.LessThan, PhysicalPilotingHand.Right); } } } - else if(direction == PhysicalPilotingDirection.MoveLeft) + else if(direction == PhysicalPilotingDirection.MoveRight) { - if(Player.main?.inHovercraft == true) + if(inHovercraft) { if(SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) - { - //If using both hands to steering then take the average of both hands - if(leftPosition != null) - { - float x = leftPosition?.x ?? 0f; - value = x < -deadZoneLeft_snowbike ? (-deadZoneLeft_snowbike - x) * xSensitivityLeft_snowbike : 0f; - } - if(rightPosition != null) - { - float x = rightPosition?.x ?? 0f; - float rvalue = x < -deadZoneLeft_snowbike ? (-deadZoneLeft_snowbike - x) * xSensitivityLeft_snowbike : 0f; - value = value != null ? (value + rvalue) /2 : rvalue; - } + { + value = GetPositionValue(direction, rightPosition, vehicleType, MovementAxis.X, MovementMagnitude.Greater, PhysicalPilotingHand.Right); + float? lvalue = GetPositionValue(direction, leftPosition, vehicleType, MovementAxis.X, MovementMagnitude.Greater); + value = (value != null && lvalue != null) ? (value + lvalue) /2 : value ?? lvalue; } else { value = 0f; } } - else if(leftEulers != null) - { - if(Player.main?.inSeatruckPilotingChair == true) - { - float y = leftEulers?.y ?? 0f; - y = y < 180 ? 360 + y : y; - value = y < (yCenterLeft-yDeadZoneLeft) ? (yCenterLeft-yDeadZoneLeft - y) * ySensitivityLeft : 0.0f; + else if(inExosuit) + { + value = GetValue(direction, leftEulers, vehicleType, MovementAxis.X, MovementMagnitude.LessThan); + } + else if(inCyclops) + { + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.X, MovementMagnitude.Greater, PhysicalPilotingHand.Right); + if(leftEulers!= null) + { + //we already checked for null so we can cast to non-null + float lvalue = (float)GetValue(direction, leftEulers, vehicleType, MovementAxis.X, MovementMagnitude.LessThan); + value = value != null ? (value + lvalue) /2 : lvalue; } - else - { - float x = leftEulers?.x ?? 0f; - x = x < 180 ? x + 360 : x; - value = x < (xCenterLeft_exosuit-deadZone_exosuit) ? (xCenterLeft_exosuit-deadZone_exosuit - x) * xSensitivityRight : 0.0f; //335 center + } + else if(inSeaTruck) + { + value = GetValue(direction, leftEulers, vehicleType, MovementAxis.Y, MovementMagnitude.LessThan); + } + else + { + if(rightEulers!= null && SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + { + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.X, MovementMagnitude.Greater, PhysicalPilotingHand.Right); } } } @@ -224,18 +450,16 @@ public enum PhysicalPilotingHand: int if(rightEulers != null) { //Only perform when the A button is pressed - if(SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + if(inCyclops || SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) { //if we want jump to be both A button press and up motion for exosuit then remove the if statement - if(Player.main?.inSeatruckPilotingChair == true) - { - float y = rightEulers?.y ?? 0f; - y = y < 180 ? 360 + y : y; - value = y > (yCenterRight+yDeadZoneRight) ? (y - yCenterRight-yDeadZoneRight) * ySensitivityRight : 0.0f; + if(inCyclops || inSeamoth || inSeaTruck) + { + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.Y, MovementMagnitude.LessThan, PhysicalPilotingHand.Right); } else { - value = 1.0f; + value = 1.0f; //exosuit } } } @@ -245,11 +469,10 @@ public enum PhysicalPilotingHand: int if(rightEulers != null) { //Only perform when the A button is pressed - if(SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) + if(inCyclops || SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) { - float y = rightEulers?.y ?? 0f; - y = y < 180 ? 360 + y : y; - value = y < (yCenterRight-yDeadZoneRight) ? (yCenterRight-yDeadZoneRight - y) * ySensitivityRight : 0.0f; + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.Y, MovementMagnitude.Greater, PhysicalPilotingHand.Right); + //DebugPanel.Show($"left: {leftEulers}\nright: {rightEulers}\ndelta: {delta}\nspeed: {value}"); } } } @@ -260,9 +483,7 @@ public enum PhysicalPilotingHand: int //Only perform when the A button is not pressed if(!SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) { - float y = rightEulers?.y ?? 0f; - y = y < 180 ? 360 + y : y; - value = y > (yCenterRight+yDeadZoneRight) ? (y - yCenterRight-yDeadZoneRight) * ySensitivityRight : 0.0f; + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.Y, MovementMagnitude.LessThan, PhysicalPilotingHand.Right); } } } @@ -273,76 +494,61 @@ public enum PhysicalPilotingHand: int //Only perform when the A button is not pressed if(!SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) { - float y = rightEulers?.y ?? 0f; - y = y < 180 ? 360 + y : y; - value = y < (yCenterRight-yDeadZoneRight) ? (yCenterRight-yDeadZoneRight - y) * ySensitivityRight : 0.0f; + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.Y, MovementMagnitude.Greater, PhysicalPilotingHand.Right); } } } - else if(direction == PhysicalPilotingDirection.LookRight) + else if(direction == PhysicalPilotingDirection.LookLeft) { - if(Player.main?.inHovercraft == true) + if(inHovercraft) { - //If A button is pressed then Move Left not Look Left + //Only turn if A button not pressed if(!SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) - { - //If using both hands to steering then take the average of both hands - if(leftPosition != null) - { - float x = leftPosition?.x ?? 0f; - value = x > deadZoneLeft_snowbike ? (x - deadZoneLeft_snowbike) * xSensitivityLeft_snowbike : 0f; - } - if(rightPosition != null) - { - float x = rightPosition?.x ?? 0f; - float rvalue = x > deadZoneLeft_snowbike ? (x - deadZoneLeft_snowbike) * xSensitivityLeft_snowbike : 0f; - value = value != null ? (value + rvalue) /2 : rvalue; - } - } - else { - value = 0f; + value = GetPositionValue(direction, rightPosition, vehicleType, MovementAxis.X, MovementMagnitude.LessThan, PhysicalPilotingHand.Right); + float? lvalue = GetPositionValue(direction, leftPosition, vehicleType, MovementAxis.X, MovementMagnitude.LessThan); + value = (value != null && lvalue != null) ? (value + lvalue) /2 : value ?? lvalue; } - } - else if(rightEulers != null) - { - float x = rightEulers?.x ?? 0f; - x = x < 180 ? x + 360 : x; - value = x < (xCenterRight-xDeadZoneRight) ? (xCenterRight-xDeadZoneRight - x) * xSensitivityRight : 0.0f; //335 center + } + else + { + if(rightEulers != null && (!(inCyclops || inSeamoth) || ((inCyclops || inSeamoth) && !SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)))) + { + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.X, MovementMagnitude.LessThan, PhysicalPilotingHand.Right); + } + if(leftEulers != null && (inCyclops || inSeamoth)) + { + float lvalue = (float)GetValue(direction, leftEulers, vehicleType, MovementAxis.X, MovementMagnitude.Greater); + value = value != null && !SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand) ? (value + lvalue) /2 : lvalue; + } } } - else if(direction == PhysicalPilotingDirection.LookLeft) + else if(direction == PhysicalPilotingDirection.LookRight) { - if(Player.main?.inHovercraft == true) + if(inHovercraft) { - //If A button is pressed then Move Left not Look Left if(!SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)) - { - if(leftPosition != null) - { - float x = leftPosition?.x ?? 0f; - value = x < -deadZoneLeft_snowbike ? (-deadZoneLeft_snowbike - x) * xSensitivityLeft_snowbike : 0f; - } - if(rightPosition != null) - { - float x = rightPosition?.x ?? 0f; - float rvalue = x < -deadZoneLeft_snowbike ? (-deadZoneLeft_snowbike - x) * xSensitivityLeft_snowbike : 0f; - value = value != null ? (value + rvalue) /2 : rvalue; - } - } - else { - value = 0f; - } - } - else if(rightEulers != null) - { - float x = rightEulers?.x ?? 0f; - x = x < 180 ? x + 360 : x; - value = x > (xCenterRight+xDeadZoneRight) ? (x - xCenterRight-xDeadZoneRight) * xSensitivityRight : 0.0f; + value = GetPositionValue(direction, rightPosition, vehicleType, MovementAxis.X, MovementMagnitude.Greater, PhysicalPilotingHand.Right); + float? lvalue = GetPositionValue(direction, leftPosition, vehicleType, MovementAxis.X, MovementMagnitude.Greater); + value = (value != null && lvalue != null) ? (value + lvalue) /2 : value ?? lvalue; + } + } + else + { + if(rightEulers != null && (!(inCyclops || inSeamoth) || ((inCyclops || inSeamoth) && !SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand)))) + { + value = GetValue(direction, rightEulers, vehicleType, MovementAxis.X, MovementMagnitude.Greater, PhysicalPilotingHand.Right); + } + if(leftEulers != null && (inCyclops || inSeamoth)) + { + float lvalue = (float)GetValue(direction, leftEulers, vehicleType, MovementAxis.X, MovementMagnitude.LessThan); + value = value != null && !SteamVR_Actions.subnautica.LeftHand.GetState(SteamVR_Input_Sources.RightHand) ? (value + lvalue) /2 : lvalue; + //DebugPanel.Show($"left: {leftEulers}\nright: {rightEulers}\ndelta: {delta}\nlvalue = {lvalue}\nspeed: {value}\nmode = {Player.main?.currentSub.GetComponent().controlMode}"); + } } } - return value; + return value; } public static Quaternion? GetPilotingHandLocalRotation(PhysicalPilotingHand hand) @@ -385,11 +591,11 @@ public static Transform GetCurrentPilotingTarget(PhysicalPilotingHand hand) private static Transform GetPilotingTarget(PhysicalPilotingHand hand) { - bool isInExosuit = Player.main?.inExosuit == true; - bool isOnSnowBike = Player.main?.inHovercraft == true; - bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; - //bool isPiloting = isInExosuit || isOnSnowBike || isPilotingSeatruck; - + bool isInSeamoth = inSeamoth; + bool isInExosuit = inExosuit; + bool isOnSnowBike = inHovercraft; + bool isInCyclops = inCyclops; + bool isPilotingSeatruck = inSeaTruck; Transform target = hand == PhysicalPilotingHand.Left ? leftPilotingTarget : rightPilotingTarget; if(target == null) { @@ -398,14 +604,23 @@ private static Transform GetPilotingTarget(PhysicalPilotingHand hand) if(isPilotingSeatruck) { leftPilotingTarget = GameObject.Find("ControllerLeft_ikTarg").transform; + //leftPilotingTarget.eulerAngles += new Vector3(-90f, 0f, 0f); } - else if(isInExosuit) + else if(isInExosuit || isInSeamoth) { leftPilotingTarget = Player.main?.GetVehicle()?.leftHandPlug; } + else if(isInCyclops) + { + leftPilotingTarget = Player.main?.GetPilotingChair().leftHandPlug; + } else if(isOnSnowBike) { +#if BZ leftPilotingTarget = Player.main?.GetComponentInParent().leftHandIKTarget; +#else + leftPilotingTarget = null; +#endif } target = leftPilotingTarget; } @@ -415,13 +630,21 @@ private static Transform GetPilotingTarget(PhysicalPilotingHand hand) { rightPilotingTarget = GameObject.Find("ControllerRight_ikTarg").transform; } - else if(isInExosuit) + else if(isInExosuit || isInSeamoth) { rightPilotingTarget = Player.main?.GetVehicle()?.rightHandPlug; } + else if(isInCyclops) + { + rightPilotingTarget = Player.main?.GetPilotingChair().rightHandPlug; + } else if(isOnSnowBike) { +#if BZ rightPilotingTarget = Player.main?.GetComponentInParent().rightHandIKTarget; +#else + rightPilotingTarget = null; +#endif } target = rightPilotingTarget; } @@ -438,10 +661,7 @@ public static bool IsGrippingPilotingControl(PhysicalPilotingHand hand) return result; } - bool isInExosuit = Player.main?.inExosuit == true; - bool isOnSnowBike = Player.main?.inHovercraft == true; - bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; - bool isPiloting = isInExosuit || isOnSnowBike || isPilotingSeatruck; + bool isPiloting = inExosuit || inHovercraft || inSeaTruck || inSeamoth || inCyclops; if(Settings.PhysicalDriving && isPiloting) { @@ -526,9 +746,11 @@ public static bool IsGrippingPilotingControl(PhysicalPilotingHand hand) public static GameObject GetPilotingGrip(PhysicalPilotingHand hand) { GameObject grip = null; - bool isInExosuit = Player.main?.inExosuit == true; - bool isOnSnowBike = Player.main?.inHovercraft == true; - bool isPilotingSeatruck = Player.main?.inSeatruckPilotingChair == true; + bool isInSeamoth = inSeamoth; + bool isInExosuit = inExosuit; + bool isInCyclops = inCyclops; + bool isOnSnowBike = inHovercraft; + bool isPilotingSeatruck = inSeaTruck; if(isPilotingSeatruck) { grip = hand == PhysicalPilotingHand.Left ? leftSeaTruckPilotingGrip : rightSeaTruckPilotingGrip; @@ -541,6 +763,14 @@ public static GameObject GetPilotingGrip(PhysicalPilotingHand hand) { grip = hand == PhysicalPilotingHand.Left ? leftSnowBikePilotingGrip : rightSnowBikePilotingGrip; } + else if(isInSeamoth) + { + grip = hand == PhysicalPilotingHand.Left ? leftSeamothPilotingGrip : rightSeamothPilotingGrip; + } + else if(isInCyclops) + { + grip = hand == PhysicalPilotingHand.Left ? leftCyclopsPilotingGrip : rightCyclopsPilotingGrip; + } if(grip == null) { @@ -558,6 +788,14 @@ public static GameObject GetPilotingGrip(PhysicalPilotingHand hand) { leftExosuitPilotingGrip = grip; } + else if(isInSeamoth) + { + leftSeamothPilotingGrip = grip; + } + else if(isInCyclops) + { + leftCyclopsPilotingGrip = grip; + } else if(isOnSnowBike) { //override with fixed positions since we dont know the orientation of the handlebars when they were first grabbed @@ -575,6 +813,14 @@ public static GameObject GetPilotingGrip(PhysicalPilotingHand hand) { rightExosuitPilotingGrip = grip; } + else if(isInSeamoth) + { + rightSeamothPilotingGrip = grip; + } + else if(isInCyclops) + { + rightCyclopsPilotingGrip = grip; + } else if(isOnSnowBike) { //override with fixed positions since we dont know the orientation of the handlebars when they were first grabbed @@ -589,27 +835,29 @@ public static GameObject GetPilotingGrip(PhysicalPilotingHand hand) public static GameObject CreateGrip(Transform target) { + //Mod.logger.LogInfo($"Creating Grip"); GameObject grip = GameObject.CreatePrimitive(PrimitiveType.Sphere); GameObject.Destroy(grip.GetComponent()); grip.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f); grip.transform.position = target.position; grip.transform.rotation = target.rotation; Transform parent = target.parent; - if(Player.main?.inExosuit == true) + if(inExosuit || inSeamoth || inCyclops) { parent = parent.parent; } - else if(Player.main?.inHovercraft == true) + else if(inHovercraft) { +#if BZ parent = Player.main?.GetComponentInParent().transform; +#endif } grip.transform.SetParent(parent, true); - //Mod.logger.LogInfo($"CreateGrip localPosition x={grip.transform.localPosition.ToString("F5")}"); Material newMaterial = new(ShaderManager.preloadedShaders.DebugDisplaySolid); newMaterial.SetColor(ShaderPropertyID._Color, Color.cyan); grip.GetComponent().material = newMaterial; - grip.SetActive(false); + grip.SetActive(false); //Set to true to see the grip models return grip; } @@ -617,7 +865,62 @@ public static GameObject CreateGrip(Transform target) #region Patches + //Make the Cyclops steering move less so that physical driving feels better + [HarmonyPatch(typeof(SubControl), nameof(SubControl.UpdateAnimation))] + static class SubControlUpdateAnimation + { + public static bool Prefix(SubControl __instance) + { + float b = 0f; + float b2 = 0f; + if ((double)Mathf.Abs(__instance.throttle.x) > 0.0001) + { + ShipSide useShipSide; + if (__instance.throttle.x > 0f) + { + useShipSide = ShipSide.Port; + b = 45f; + } + else + { + useShipSide = ShipSide.Starboard; + b = -45f; + } + if (__instance.throttle.x < -0.1f || __instance.throttle.x > 0.1f) + { + for (int i = 0; i < __instance.turnHandlers.Length; i++) + { + __instance.turnHandlers[i].OnSubTurn(useShipSide); + } + } + } + if ((double)Mathf.Abs(__instance.throttle.y) > 0.0001) + { + if (__instance.throttle.y > 0f) + { + b2 = 90f; + } + else + { + b2 = -90f; + } + } + __instance.steeringWheelYaw = Mathf.Lerp(__instance.steeringWheelYaw, b, Time.deltaTime * __instance.steeringReponsiveness); + __instance.steeringWheelPitch = Mathf.Lerp(__instance.steeringWheelPitch, b2, Time.deltaTime * __instance.steeringReponsiveness); + if (__instance.mainAnimator) + { + __instance.mainAnimator.SetFloat("view_yaw", __instance.steeringWheelYaw); + __instance.mainAnimator.SetFloat("view_pitch", __instance.steeringWheelPitch); + Player.main.playerAnimator.SetFloat("cyclops_yaw", __instance.steeringWheelYaw); + Player.main.playerAnimator.SetFloat("cyclops_pitch", __instance.steeringWheelPitch); + } + return false; + } + } #endregion } + + + From fba7465bb239abe0e86169d97395f865163c819d Mon Sep 17 00:00:00 2001 From: jbusfield Date: Wed, 29 Nov 2023 14:21:04 -0800 Subject: [PATCH 9/9] Added proper Ambient Occlusion --- SubmersedVR/AmplifyOcclusion/Packages.meta | 8 + SubmersedVR/AmplifyOcclusion/Plugins.meta | 9 + .../Plugins/AmplifyOcclusionCommon.cs | 377 +++++ .../Plugins/AmplifyOcclusionCommon.cs.meta | 11 + .../Plugins/AmplifyOcclusionEffect.cs | 1411 +++++++++++++++++ .../Plugins/AmplifyOcclusionEffect.cs.meta | 12 + .../AmplifyOcclusion/Plugins/Runtime.meta | 9 + .../Plugins/Runtime/VersionInfo.cs | 59 + .../Plugins/Runtime/VersionInfo.cs.meta | 12 + .../Plugins/amplify_occlusion.asmdef | 17 + .../Plugins/amplify_occlusion.asmdef.meta | 7 + SubmersedVR/AmplifyOcclusion/Readme.txt | 137 ++ SubmersedVR/AmplifyOcclusion/Readme.txt.meta | 8 + SubmersedVR/AmplifyOcclusion/Resources.meta | 9 + .../AmplifyOcclusion/Resources/Apply.shader | 430 +++++ .../Resources/Apply.shader.meta | 9 + .../Resources/ApplyPostEffect.cginc | 99 ++ .../Resources/ApplyPostEffect.cginc.meta | 9 + .../AmplifyOcclusion/Resources/Blur.shader | 54 + .../Resources/Blur.shader.meta | 9 + .../Resources/BlurFunctions.cginc | 192 +++ .../Resources/BlurFunctions.cginc.meta | 9 + .../AmplifyOcclusion/Resources/Common.cginc | 165 ++ .../Resources/Common.cginc.meta | 9 + .../Resources/CommonFunctions.cginc | 201 +++ .../Resources/CommonFunctions.cginc.meta | 9 + .../AmplifyOcclusion/Resources/GTAO.cginc | 366 +++++ .../Resources/GTAO.cginc.meta | 9 + .../Resources/Occlusion.shader | 93 ++ .../Resources/Occlusion.shader.meta | 9 + .../Resources/OcclusionFunctions.cginc | 195 +++ .../Resources/OcclusionFunctions.cginc.meta | 9 + .../Resources/TemporalFilter.cginc | 594 +++++++ .../Resources/TemporalFilter.cginc.meta | 9 + SubmersedVR/AmplifyOcclusion/Textures.meta | 9 + .../AmplifyOcclusion/Textures/About.png | Bin 0 -> 57164 bytes .../AmplifyOcclusion/Textures/About.png.meta | 57 + SubmersedVR/Settings.cs | 131 +- .../StreamingAssets/Amplify/amplify_resources | Bin 0 -> 55890 bytes .../Amplify/amplify_resources.manifest | 19 + SubmersedVR/SubmersedVR.csproj | 5 +- SubmersedVR/Tweaks/AmbientOcclusion.cs | 75 + SubmersedVR/VR/ShaderLoader.cs | 41 + SubmersedVR/VR/VRCameraRig.cs | 17 +- 44 files changed, 4914 insertions(+), 5 deletions(-) create mode 100644 SubmersedVR/AmplifyOcclusion/Packages.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionCommon.cs create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionCommon.cs.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionEffect.cs create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionEffect.cs.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins/Runtime.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins/Runtime/VersionInfo.cs create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins/Runtime/VersionInfo.cs.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins/amplify_occlusion.asmdef create mode 100644 SubmersedVR/AmplifyOcclusion/Plugins/amplify_occlusion.asmdef.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Readme.txt create mode 100644 SubmersedVR/AmplifyOcclusion/Readme.txt.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/Apply.shader create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/Apply.shader.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/ApplyPostEffect.cginc create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/ApplyPostEffect.cginc.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/Blur.shader create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/Blur.shader.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/BlurFunctions.cginc create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/BlurFunctions.cginc.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/Common.cginc create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/Common.cginc.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/CommonFunctions.cginc create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/CommonFunctions.cginc.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/GTAO.cginc create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/GTAO.cginc.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/Occlusion.shader create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/Occlusion.shader.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/OcclusionFunctions.cginc create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/OcclusionFunctions.cginc.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/TemporalFilter.cginc create mode 100644 SubmersedVR/AmplifyOcclusion/Resources/TemporalFilter.cginc.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Textures.meta create mode 100644 SubmersedVR/AmplifyOcclusion/Textures/About.png create mode 100644 SubmersedVR/AmplifyOcclusion/Textures/About.png.meta create mode 100644 SubmersedVR/StreamingAssets/Amplify/amplify_resources create mode 100644 SubmersedVR/StreamingAssets/Amplify/amplify_resources.manifest create mode 100644 SubmersedVR/Tweaks/AmbientOcclusion.cs create mode 100644 SubmersedVR/VR/ShaderLoader.cs diff --git a/SubmersedVR/AmplifyOcclusion/Packages.meta b/SubmersedVR/AmplifyOcclusion/Packages.meta new file mode 100644 index 0000000..06ce2c2 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Packages.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5fa49160bd4587d44a7fb25785abfa5a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Plugins.meta b/SubmersedVR/AmplifyOcclusion/Plugins.meta new file mode 100644 index 0000000..dc6e802 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b84d9762e95a6ac41ad865e2a86a83c5 +folderAsset: yes +timeCreated: 1449343891 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionCommon.cs b/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionCommon.cs new file mode 100644 index 0000000..674546b --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionCommon.cs @@ -0,0 +1,377 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Profiling; +using Valve.VR; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace AmplifyOcclusion +{ + +public static class AmplifyOcclusionCommon +{ + public static readonly int PerPixelNormalSourceCount = 4; + public static readonly float[] m_temporalRotations = { 60.0f, 300.0f, 180.0f, 240.0f, 120.0f, 0.0f }; + public static readonly float[] m_spatialOffsets = { 0.0f, 0.5f, 0.25f, 0.75f }; + + public static void CommandBuffer_TemporalFilterDirectionsOffsets( CommandBuffer cb, uint aSampleStep ) + { + float temporalRotation = AmplifyOcclusionCommon.m_temporalRotations[ aSampleStep % 6 ]; + float temporalOffset = AmplifyOcclusionCommon.m_spatialOffsets[ ( aSampleStep / 6 ) % 4 ]; + + cb.SetGlobalFloat( PropertyID._AO_TemporalDirections, temporalRotation / 360.0f ); + cb.SetGlobalFloat( PropertyID._AO_TemporalOffsets, temporalOffset ); + } + + public static Material CreateMaterialWithShaderName( string aShaderName, bool aThroughErrorMsg ) + { + var shader = ShaderLoader.GetShader(aShaderName); + + if( shader == null ) + { + if( aThroughErrorMsg == true ) + { + Debug.LogErrorFormat( "[AmplifyOcclusion] Cannot find shader: \"{0}\"" + + " Please contact support@amplify.pt", aShaderName ); + } + + return null; + } + + return new Material( shader ) { hideFlags = HideFlags.DontSave }; + } + + public static int SafeAllocateTemporaryRT( CommandBuffer cb, string propertyName, + int width, int height, + RenderTextureFormat format = RenderTextureFormat.Default, + RenderTextureReadWrite readWrite = RenderTextureReadWrite.Default, + FilterMode filterMode = FilterMode.Point ) + { + int id = Shader.PropertyToID( propertyName ); + + cb.GetTemporaryRT( id, width, height, 0, filterMode, format, readWrite ); + + return id; + } + + + public static void SafeReleaseTemporaryRT( CommandBuffer cb, int id ) + { + cb.ReleaseTemporaryRT( id ); + } + + + public static RenderTexture SafeAllocateRT( string name, + int width, int height, + RenderTextureFormat format, + RenderTextureReadWrite readWrite, + FilterMode filterMode = FilterMode.Point, + int antiAliasing = 1, + bool aUseMipMap = false ) + { + width = Mathf.Clamp( width, 1, 65536 ); + height = Mathf.Clamp( height, 1, 65536 ); + + RenderTexture rt = new RenderTexture( width, height, 0, format, readWrite ) { hideFlags = HideFlags.DontSave }; + + rt.name = name; + rt.filterMode = filterMode; + rt.wrapMode = TextureWrapMode.Clamp; + rt.antiAliasing = Mathf.Max( antiAliasing, 1 ); + rt.useMipMap = aUseMipMap; + rt.Create(); + + return rt; + } + + + public static void SafeReleaseRT( ref RenderTexture rt ) + { + if( rt != null ) + { + RenderTexture.active = null; + + rt.Release(); + RenderTexture.DestroyImmediate( rt ); + + rt = null; + } + } + + + public static bool IsStereoSinglePassEnabled( Camera aCamera ) + { + #if UNITY_EDITOR + return aCamera.stereoEnabled && ( PlayerSettings.stereoRenderingPath == StereoRenderingPath.SinglePass ); + #else + + #if UNITY_2017_2_OR_NEWER && !UNITY_SWITCH && !UNITY_XBOXONE && !UNITY_PS4 + return aCamera.stereoEnabled && ( UnityEngine.XR.XRSettings.eyeTextureDesc.vrUsage == VRTextureUsage.TwoEyes ); + #else + return false; + #endif + + #endif + } + + public static bool IsStereoMultiPassEnabled( Camera aCamera ) + { + return true; + } + + public static void UpdateGlobalShaderConstants( CommandBuffer cb, ref TargetDesc aTarget, Camera aCamera, bool isDownsample, bool isFilterDownsample ) + { + #if UNITY_2017_2_OR_NEWER && !UNITY_SWITCH && !UNITY_XBOXONE && !UNITY_PS4 + if( UnityEngine.XR.XRSettings.enabled == true ) + { + aTarget.fullWidth = (int)( UnityEngine.XR.XRSettings.eyeTextureDesc.width * UnityEngine.XR.XRSettings.eyeTextureResolutionScale ); + aTarget.fullHeight = (int)( UnityEngine.XR.XRSettings.eyeTextureDesc.height * UnityEngine.XR.XRSettings.eyeTextureResolutionScale ); + } + else + { + aTarget.fullWidth = aCamera.pixelWidth; + aTarget.fullHeight = aCamera.pixelHeight; + } + #else + aTarget.fullWidth = aCamera.pixelWidth; + aTarget.fullHeight = aCamera.pixelHeight; + #endif + + if( isFilterDownsample == true ) + { + aTarget.width = aTarget.fullWidth / 2; + aTarget.height = aTarget.fullHeight / 2; + } + else + { + aTarget.width = aTarget.fullWidth; + aTarget.height = aTarget.fullHeight; + } + + aTarget.oneOverWidth = 1.0f / (float)aTarget.width; + aTarget.oneOverHeight = 1.0f / (float)aTarget.height; + + float fovRad = aCamera.fieldOfView * Mathf.Deg2Rad; + + float invHalfTanFov = 1.0f / Mathf.Tan( fovRad * 0.5f ); + + Vector2 focalLen = new Vector2( invHalfTanFov * ( aTarget.height / (float)aTarget.width ), + invHalfTanFov ); + + Vector2 invFocalLen = new Vector2( 1.0f / focalLen.x, 1.0f / focalLen.y ); + + // Aspect Ratio + cb.SetGlobalVector( PropertyID._AO_UVToView, new Vector4( +2.0f * invFocalLen.x, + +2.0f * invFocalLen.y, + -1.0f * invFocalLen.x, + -1.0f * invFocalLen.y ) ); + + float projScale; + + if( aCamera.orthographic ) + projScale = ( (float)aTarget.fullHeight ) / aCamera.orthographicSize; + else + projScale = ( (float)aTarget.fullHeight ) / ( Mathf.Tan( fovRad * 0.5f ) * 2.0f ); + + if( ( isDownsample == true ) || ( isFilterDownsample == true ) ) + { + projScale = projScale * 0.5f * 0.5f; + } + else + { + projScale = projScale * 0.5f; + } + + cb.SetGlobalFloat( PropertyID._AO_HalfProjScale, projScale ); + } +} + + +public class AmplifyOcclusionViewProjMatrix +{ + private Matrix4x4 m_prevViewProjMatrixLeft = Matrix4x4.identity; + private Matrix4x4 m_prevInvViewProjMatrixLeft = Matrix4x4.identity; + private Matrix4x4 m_prevViewProjMatrixRight = Matrix4x4.identity; + private Matrix4x4 m_prevInvViewProjMatrixRight = Matrix4x4.identity; + + public void UpdateGlobalShaderConstants_Matrices( CommandBuffer cb, Camera aCamera, bool isUsingTemporalFilter ) + { + // Camera matrixes + if( AmplifyOcclusionCommon.IsStereoSinglePassEnabled( aCamera ) == true ) + { + Matrix4x4 viewLeft = aCamera.GetStereoViewMatrix( Camera.StereoscopicEye.Left ); + Matrix4x4 viewRight = aCamera.GetStereoViewMatrix( Camera.StereoscopicEye.Right ); + + cb.SetGlobalMatrix( PropertyID._AO_CameraViewLeft, viewLeft ); + cb.SetGlobalMatrix( PropertyID._AO_CameraViewRight, viewRight ); + + Matrix4x4 projectionMatrixLeft = aCamera.GetStereoProjectionMatrix( Camera.StereoscopicEye.Left ); + Matrix4x4 projectionMatrixRight = aCamera.GetStereoProjectionMatrix( Camera.StereoscopicEye.Right ); + + Matrix4x4 projLeft = GL.GetGPUProjectionMatrix( projectionMatrixLeft, false ); + Matrix4x4 projRight = GL.GetGPUProjectionMatrix( projectionMatrixRight, false ); + + cb.SetGlobalMatrix( PropertyID._AO_ProjMatrixLeft, projLeft ); + cb.SetGlobalMatrix( PropertyID._AO_ProjMatrixRight, projRight ); + + if( isUsingTemporalFilter ) + { + Matrix4x4 ViewProjMatrixLeft = projLeft * viewLeft; + Matrix4x4 ViewProjMatrixRight = projRight * viewRight; + + Matrix4x4 InvViewProjMatrixLeft = Matrix4x4.Inverse( ViewProjMatrixLeft ); + Matrix4x4 InvViewProjMatrixRight = Matrix4x4.Inverse( ViewProjMatrixRight ); + + cb.SetGlobalMatrix( PropertyID._AO_InvViewProjMatrixLeft, InvViewProjMatrixLeft ); + cb.SetGlobalMatrix( PropertyID._AO_PrevViewProjMatrixLeft, m_prevViewProjMatrixLeft ); + cb.SetGlobalMatrix( PropertyID._AO_PrevInvViewProjMatrixLeft, m_prevInvViewProjMatrixLeft ); + + cb.SetGlobalMatrix( PropertyID._AO_InvViewProjMatrixRight, InvViewProjMatrixRight ); + cb.SetGlobalMatrix( PropertyID._AO_PrevViewProjMatrixRight, m_prevViewProjMatrixRight ); + cb.SetGlobalMatrix( PropertyID._AO_PrevInvViewProjMatrixRight, m_prevInvViewProjMatrixRight ); + + m_prevViewProjMatrixLeft = ViewProjMatrixLeft; + m_prevInvViewProjMatrixLeft = InvViewProjMatrixLeft; + + m_prevViewProjMatrixRight = ViewProjMatrixRight; + m_prevInvViewProjMatrixRight = InvViewProjMatrixRight; + } + } + else + { + Matrix4x4 view = aCamera.worldToCameraMatrix; + + cb.SetGlobalMatrix( PropertyID._AO_CameraViewLeft, view ); + + if( isUsingTemporalFilter ) + { + Matrix4x4 proj = GL.GetGPUProjectionMatrix( aCamera.projectionMatrix, false ); + + Matrix4x4 ViewProjMatrix = proj * view; + Matrix4x4 InvViewProjMatrix = Matrix4x4.Inverse( ViewProjMatrix ); + + cb.SetGlobalMatrix( PropertyID._AO_InvViewProjMatrixLeft, InvViewProjMatrix ); + cb.SetGlobalMatrix( PropertyID._AO_PrevViewProjMatrixLeft, m_prevViewProjMatrixLeft ); + cb.SetGlobalMatrix( PropertyID._AO_PrevInvViewProjMatrixLeft, m_prevInvViewProjMatrixLeft ); + + m_prevViewProjMatrixLeft = ViewProjMatrix; + m_prevInvViewProjMatrixLeft = InvViewProjMatrix; + } + } + } + +} + +public enum SampleCountLevel +{ + Low = 0, + Medium, + High, + VeryHigh +} + +public struct TargetDesc +{ + public int fullWidth; + public int fullHeight; + public int width; + public int height; + public float oneOverWidth; + public float oneOverHeight; +} + +public static class ShaderPass +{ + public const int OcclusionLow_None_UseDynamicDepthMips = 16; + public const int CombineDownsampledOcclusionDepth = 32; + public const int NeighborMotionIntensity = 33; + public const int ClearTemporal = 34; + public const int ScaleDownCloserDepthEven = 35; + public const int ScaleDownCloserDepthEven_CameraDepthTexture = 36; + public const int Temporal = 37; + + // Blur + public const int BlurHorizontal1 = 0; + public const int BlurVertical1 = 1; + public const int BlurHorizontal2 = 2; + public const int BlurVertical2 = 3; + public const int BlurHorizontal3 = 4; + public const int BlurVertical3 = 5; + public const int BlurHorizontal4 = 6; + public const int BlurVertical4 = 7; + public const int BlurHorizontalIntensity = 8; + public const int BlurVerticalIntensity = 9; + + // Apply Occlusion + public const int ApplyDebug = 0; + public const int ApplyDebugTemporal = 1; + public const int ApplyDeferred = 3; + public const int ApplyDeferredTemporal = 4; + public const int ApplyDeferredLog = 6; + public const int ApplyDeferredLogTemporal = 7; + public const int ApplyPostEffect = 9; + public const int ApplyPostEffectTemporal = 10; + public const int ApplyPostEffectTemporalMultiply = 12; + public const int ApplyDeferredTemporalMultiply = 13; + public const int ApplyDebugCombineFromTemporal = 14; + public const int ApplyCombineFromTemporal = 15; + public const int ApplyDeferredCombineFromTemporal = 16; + public const int ApplyDeferredLogCombineFromTemporal = 17; + + // Occlusion Normal Targets + public const int OcclusionLow_None = 0; + public const int OcclusionLow_Camera = 1; + public const int OcclusionLow_GBuffer = 2; + public const int OcclusionLow_GBufferOctaEncoded = 3; +} + +public static class PropertyID +{ + public static readonly int _MainTex = Shader.PropertyToID( "_MainTex" ); + public static readonly int _AO_Radius = Shader.PropertyToID( "_AO_Radius" ); + public static readonly int _AO_PowExponent = Shader.PropertyToID( "_AO_PowExponent" ); + public static readonly int _AO_Bias = Shader.PropertyToID( "_AO_Bias" ); + public static readonly int _AO_Levels = Shader.PropertyToID( "_AO_Levels" ); + public static readonly int _AO_ThicknessDecay = Shader.PropertyToID( "_AO_ThicknessDecay" ); + public static readonly int _AO_BlurSharpness = Shader.PropertyToID( "_AO_BlurSharpness" ); + public static readonly int _AO_BufDepthToLinearEye = Shader.PropertyToID( "_AO_BufDepthToLinearEye" ); + public static readonly int _AO_CameraViewLeft = Shader.PropertyToID( "_AO_CameraViewLeft" ); + public static readonly int _AO_CameraViewRight = Shader.PropertyToID( "_AO_CameraViewRight" ); + public static readonly int _AO_ProjMatrixLeft = Shader.PropertyToID( "_AO_ProjMatrixLeft" ); + public static readonly int _AO_ProjMatrixRight = Shader.PropertyToID( "_AO_ProjMatrixRight" ); + public static readonly int _AO_InvViewProjMatrixLeft = Shader.PropertyToID( "_AO_InvViewProjMatrixLeft" ); + public static readonly int _AO_PrevViewProjMatrixLeft = Shader.PropertyToID( "_AO_PrevViewProjMatrixLeft" ); + public static readonly int _AO_PrevInvViewProjMatrixLeft = Shader.PropertyToID( "_AO_PrevInvViewProjMatrixLeft" ); + public static readonly int _AO_InvViewProjMatrixRight = Shader.PropertyToID( "_AO_InvViewProjMatrixRight" ); + public static readonly int _AO_PrevViewProjMatrixRight = Shader.PropertyToID( "_AO_PrevViewProjMatrixRight" ); + public static readonly int _AO_PrevInvViewProjMatrixRight = Shader.PropertyToID( "_AO_PrevInvViewProjMatrixRight" ); + public static readonly int _AO_GBufferNormals = Shader.PropertyToID( "_AO_GBufferNormals" ); + public static readonly int _AO_Target_TexelSize = Shader.PropertyToID( "_AO_Target_TexelSize" ); + public static readonly int _AO_TemporalCurveAdj = Shader.PropertyToID( "_AO_TemporalCurveAdj" ); + public static readonly int _AO_TemporalMotionSensibility = Shader.PropertyToID( "_AO_TemporalMotionSensibility" ); + public static readonly int _AO_CurrOcclusionDepth = Shader.PropertyToID( "_AO_CurrOcclusionDepth" ); + public static readonly int _AO_CurrOcclusionDepth_TexelSize = Shader.PropertyToID( "_AO_CurrOcclusionDepth_TexelSize" ); + public static readonly int _AO_TemporalAccumm = Shader.PropertyToID( "_AO_TemporalAccumm" ); + public static readonly int _AO_TemporalDirections = Shader.PropertyToID( "_AO_TemporalDirections" ); + public static readonly int _AO_TemporalOffsets = Shader.PropertyToID( "_AO_TemporalOffsets" ); + public static readonly int _AO_GBufferAlbedo = Shader.PropertyToID( "_AO_GBufferAlbedo" ); + public static readonly int _AO_GBufferEmission = Shader.PropertyToID( "_AO_GBufferEmission" ); + public static readonly int _AO_UVToView = Shader.PropertyToID( "_AO_UVToView" ); + public static readonly int _AO_HalfProjScale = Shader.PropertyToID( "_AO_HalfProjScale" ); + public static readonly int _AO_FadeParams = Shader.PropertyToID( "_AO_FadeParams" ); + public static readonly int _AO_FadeValues = Shader.PropertyToID( "_AO_FadeValues" ); + public static readonly int _AO_FadeToTint = Shader.PropertyToID( "_AO_FadeToTint" ); + public static readonly int _AO_CurrMotionIntensity = Shader.PropertyToID( "_AO_CurrMotionIntensity" ); + public static readonly int _AO_CurrDepthSource_TexelSize = Shader.PropertyToID( "_AO_CurrDepthSource_TexelSize" ); + public static readonly int _AO_CurrDepthSource = Shader.PropertyToID( "_AO_CurrDepthSource" ); + public static readonly int _AO_CurrMotionIntensity_TexelSize = Shader.PropertyToID( "_AO_CurrMotionIntensity_TexelSize" ); + public static readonly int _AO_SourceDepthMipmap = Shader.PropertyToID( "_AO_SourceDepthMipmap" ); + public static readonly int _AO_Source_TexelSize = Shader.PropertyToID( "_AO_Source_TexelSize" ); +} + +} \ No newline at end of file diff --git a/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionCommon.cs.meta b/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionCommon.cs.meta new file mode 100644 index 0000000..77da533 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionCommon.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ab719fca8c732f4ba57ab60bd756194 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionEffect.cs b/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionEffect.cs new file mode 100644 index 0000000..ba9c6b2 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionEffect.cs @@ -0,0 +1,1411 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +using System; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Profiling; +#if UNITY_EDITOR +using UnityEditor; +#endif +using AmplifyOcclusion; + +[ExecuteInEditMode] +[AddComponentMenu( "Image Effects/Amplify Occlusion" )] +[ImageEffectAllowedInSceneView] +[RequireComponent( typeof( Camera ) )] +public class AmplifyOcclusionEffect : MonoBehaviour +{ + private static int m_nextID = 0; + private int m_myID; + private string m_myIDstring; + + private float m_oneOverDepthScale = ( 1.0f / 65504.0f ); // 65504.0f max half float + + public enum ApplicationMethod + { + PostEffect = 0, + Deferred, + Debug + } + + public enum PerPixelNormalSource + { + None = 0, + Camera, + GBuffer, + GBufferOctaEncoded, + } + + [Header( "Ambient Occlusion" )] + [Tooltip( "How to inject the occlusion: Post Effect = Overlay, Deferred = Deferred Injection, Debug - Vizualize." )] + public ApplicationMethod ApplyMethod = ApplicationMethod.PostEffect; + + [Tooltip( "Number of samples per pass." )] + public SampleCountLevel SampleCount = SampleCountLevel.High; + + [Tooltip( "Source of per-pixel normals: None = All, Camera = Forward, GBuffer = Deferred." )] + public PerPixelNormalSource PerPixelNormals = PerPixelNormalSource.Camera; + + [Tooltip( "Final applied intensity of the occlusion effect." )] + [Range( 0, 1 )] + public float Intensity = 1.0f; + + [Tooltip( "Color tint for occlusion." )] + public Color Tint = Color.black; + + [Tooltip( "Radius spread of the occlusion." )] + public float Radius = 2.0f; + + [Tooltip( "Power exponent attenuation of the occlusion." )] + [Range( 0, 16 )] + public float PowerExponent = 1.8f; + + [Tooltip( "Controls the initial occlusion contribution offset." )] + [Range( 0, 0.99f )] + public float Bias = 0.05f; + + [Tooltip( "Controls the thickness occlusion contribution." )] + [Range( 0, 1.0f )] + public float Thickness = 1.0f; + + [Tooltip( "Compute the Occlusion and Blur at half of the resolution." )] + public bool Downsample = true; + + [Tooltip( "Cache optimization for best performance / quality tradeoff." )] + public bool CacheAware = true; + + [Header( "Distance Fade" )] + [Tooltip( "Control parameters at faraway." )] + public bool FadeEnabled = false; + + [Tooltip( "Distance in Unity unities that start to fade." )] + public float FadeStart = 100.0f; + + [Tooltip( "Length distance to performe the transition." )] + public float FadeLength = 50.0f; + + [Tooltip( "Final Intensity parameter." )] + [Range( 0, 1 )] + public float FadeToIntensity = 0.0f; + public Color FadeToTint = Color.black; + + [Tooltip( "Final Radius parameter." )] + public float FadeToRadius = 2.0f; + + [Tooltip( "Final PowerExponent parameter." )] + [Range( 0, 16 )] + public float FadeToPowerExponent = 1.0f; + + [Tooltip( "Final Thickness parameter." )] + [Range( 0, 1.0f )] + public float FadeToThickness = 1.0f; + + [Header( "Bilateral Blur" )] + public bool BlurEnabled = true; + + [Tooltip( "Radius in screen pixels." )] + [Range( 1, 4 )] + public int BlurRadius = 3; + + [Tooltip( "Number of times that the Blur will repeat." )] + [Range( 1, 4 )] + public int BlurPasses = 1; + + [Tooltip( "Sharpness of blur edge-detection: 0 = Softer Edges, 20 = Sharper Edges." )] + [Range( 0, 20 )] + public float BlurSharpness = 15.0f; + + [Header( "Temporal Filter" )] + [Tooltip( "Accumulates the effect over the time." )] + public bool FilterEnabled = true; + public bool FilterDownsample = true; + + [Tooltip( "Controls the accumulation decayment: 0 = More flicker with less ghosting, 1 = Less flicker with more ghosting." )] + [Range( 0, 1 )] + public float FilterBlending = 0.80f; + + [Tooltip( "Controls the discard sensitivity based on the motion of the scene and objects." )] + [Range( 0, 1 )] + public float FilterResponse = 0.50f; + + // Current state variables + private bool m_HDR = true; + private bool m_MSAA = true; + + // Previous state variables + private PerPixelNormalSource m_prevPerPixelNormals; + private ApplicationMethod m_prevApplyMethod; + private bool m_prevDeferredReflections = false; + private SampleCountLevel m_prevSampleCount = SampleCountLevel.Low; + private bool m_prevDownsample = false; + private bool m_prevCacheAware = false; + private bool m_prevBlurEnabled = false; + private int m_prevBlurRadius = 0; + private int m_prevBlurPasses = 0; + private bool m_prevFilterEnabled = true; + private bool m_prevFilterDownsample = true; + private bool m_prevHDR = true; + private bool m_prevMSAA = true; + +#if UNITY_EDITOR + private bool m_prevIsPlaying = false; +#endif + + private Camera m_targetCamera = null; + + private RenderTargetIdentifier[] applyDebugTargetsTemporal = new RenderTargetIdentifier[2]; + private RenderTargetIdentifier[] applyDeferredTargets_Log_Temporal = new RenderTargetIdentifier[3]; + private RenderTargetIdentifier[] applyDeferredTargetsTemporal = new RenderTargetIdentifier[3]; + private RenderTargetIdentifier[] applyOcclusionTemporal = new RenderTargetIdentifier[2]; + private RenderTargetIdentifier[] applyPostEffectTargetsTemporal = new RenderTargetIdentifier[2]; + + // NOTE: MotionVectors are not supported in Deferred Injection mode due to 1 frame delay + private bool UsingTemporalFilter { get { return ( m_sampleStep > 0 ) && ( FilterEnabled == true ) && ( m_targetCamera.cameraType != UnityEngine.CameraType.SceneView ); } } + private bool UsingMotionVectors { get { return UsingTemporalFilter && ( ApplyMethod != ApplicationMethod.Deferred ); } } + private bool UsingFilterDownsample { get { return ( Downsample == true ) && ( FilterDownsample == true ) && ( UsingTemporalFilter == true ); } } + + private bool useMRTBlendingFallback = false; + private bool checkedforMRTBlendingFallback = false; + + // Command buffer + private struct CmdBuffer + { + public CommandBuffer cmdBuffer; + public CameraEvent cmdBufferEvent; + public string cmdBufferName; + } + + CmdBuffer m_commandBuffer_Parameters; + CmdBuffer m_commandBuffer_Occlusion; + CmdBuffer m_commandBuffer_Apply; + + private void createCommandBuffer( ref CmdBuffer aCmdBuffer, string aCmdBufferName, CameraEvent aCameraEvent ) + { + if( aCmdBuffer.cmdBuffer != null ) + { + cleanupCommandBuffer( ref aCmdBuffer ); + } + + aCmdBuffer.cmdBufferName = aCmdBufferName; + + aCmdBuffer.cmdBuffer = new CommandBuffer(); + aCmdBuffer.cmdBuffer.name = aCmdBufferName; + + aCmdBuffer.cmdBufferEvent = aCameraEvent; + + m_targetCamera.AddCommandBuffer( aCameraEvent, aCmdBuffer.cmdBuffer ); + } + + private void cleanupCommandBuffer( ref CmdBuffer aCmdBuffer ) + { + CommandBuffer[] currentCBs = m_targetCamera.GetCommandBuffers( aCmdBuffer.cmdBufferEvent ); + + for( int i = 0; i < currentCBs.Length; i++ ) + { + if( currentCBs[ i ].name == aCmdBuffer.cmdBufferName ) + { + m_targetCamera.RemoveCommandBuffer( aCmdBuffer.cmdBufferEvent, currentCBs[ i ] ); + } + } + + aCmdBuffer.cmdBufferName = null; + aCmdBuffer.cmdBufferEvent = 0; + aCmdBuffer.cmdBuffer = null; + } + + // Quad Mesh + static private Mesh m_quadMesh = null; + + private void createQuadMesh() + { + if( m_quadMesh == null ) + { + m_quadMesh = new Mesh(); + m_quadMesh.vertices = new Vector3[ 4 ] { new Vector3( 0, 0, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 1, 1, 0 ), new Vector3( 1, 0, 0 ) }; + m_quadMesh.uv = new Vector2[ 4 ] { new Vector2( 0, 0 ), new Vector2( 0, 1 ), new Vector2( 1, 1 ), new Vector2( 1, 0 ) }; + m_quadMesh.triangles = new int[ 6 ] { 0, 1, 2, 0, 2, 3 }; + + m_quadMesh.normals = new Vector3[ 0 ]; + m_quadMesh.tangents = new Vector4[ 0 ]; + m_quadMesh.colors32 = new Color32[ 0 ]; + m_quadMesh.colors = new Color[ 0 ]; + } + } + + + void PerformBlit( CommandBuffer cb, Material mat, int pass ) + { + cb.DrawMesh( m_quadMesh, Matrix4x4.identity, mat, 0, pass ); + } + + // Render Materials + static private Material m_occlusionMat = null; + static private Material m_blurMat = null; + static private Material m_applyOcclusionMat = null; + + private void checkMaterials( bool aThroughErrorMsg ) + { + if( m_occlusionMat == null ) + { + m_occlusionMat = AmplifyOcclusionCommon.CreateMaterialWithShaderName( "Hidden/Amplify Occlusion/Occlusion", aThroughErrorMsg ); + } + + if( m_blurMat == null ) + { + m_blurMat = AmplifyOcclusionCommon.CreateMaterialWithShaderName( "Hidden/Amplify Occlusion/Blur", aThroughErrorMsg ); + } + + if( m_applyOcclusionMat == null ) + { + m_applyOcclusionMat = AmplifyOcclusionCommon.CreateMaterialWithShaderName( "Hidden/Amplify Occlusion/Apply", aThroughErrorMsg ); + } + + if( m_applyOcclusionMat != null ) + { + if( checkedforMRTBlendingFallback == false ) + { + checkedforMRTBlendingFallback = true; + + // some platforms still don't support MRT-blending; provide a fallback, if necessary + useMRTBlendingFallback = m_applyOcclusionMat.GetTag( "MRTBlending", false ).ToUpper() != "TRUE"; + } + } + } + + private RenderTextureFormat m_occlusionRTFormat = RenderTextureFormat.RGHalf; + private RenderTextureFormat m_accumTemporalRTFormat = RenderTextureFormat.ARGB32; + private RenderTextureFormat m_temporaryEmissionRTFormat = RenderTextureFormat.ARGB2101010; + private RenderTextureFormat m_motionIntensityRTFormat = RenderTextureFormat.R8; + + + private bool checkRenderTextureFormats() + { + // test the two fallback formats first + if( SystemInfo.SupportsRenderTextureFormat( RenderTextureFormat.ARGB32 ) && SystemInfo.SupportsRenderTextureFormat( RenderTextureFormat.ARGBHalf ) ) + { + m_occlusionRTFormat = RenderTextureFormat.RGHalf; + if( !SystemInfo.SupportsRenderTextureFormat( m_occlusionRTFormat ) ) + { + m_occlusionRTFormat = RenderTextureFormat.RGFloat; + if( !SystemInfo.SupportsRenderTextureFormat( m_occlusionRTFormat ) ) + { + // already tested above + m_occlusionRTFormat = RenderTextureFormat.ARGBHalf; + } + } + + return true; + } + return false; + } + + + void OnEnable() + { + m_myID = m_nextID; + m_myIDstring = m_myID.ToString(); + m_nextID++; + + if( !checkRenderTextureFormats() ) + { + Debug.LogError( "[AmplifyOcclusion] Target platform does not meet the minimum requirements for this effect to work properly." ); + + this.enabled = false; + + return; + } + + if( CacheAware == true ) + { + if( SystemInfo.SupportsRenderTextureFormat( RenderTextureFormat.RFloat ) == false ) + { + CacheAware = false; + UnityEngine.Debug.LogWarning( "[AmplifyOcclusion] System does not support RFloat RenderTextureFormat. CacheAware will be disabled." ); + } + else + { + if( SystemInfo.copyTextureSupport == CopyTextureSupport.None ) + { + CacheAware = false; + UnityEngine.Debug.LogWarning( "[AmplifyOcclusion] System does not support CopyTexture. CacheAware will be disabled." ); + } + else + { + // AO-62 - some OpenGLES devices actually implement RFloat buffers using RHalf format. + if( ( SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES2 ) || + ( SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3 ) ) + { + CacheAware = false; + UnityEngine.Debug.LogWarningFormat( "[AmplifyOcclusion] CacheAware is not supported on {0} devices. CacheAware will be disabled.", SystemInfo.graphicsDeviceType ); + } + } + } + } + + checkMaterials( false ); + createQuadMesh(); + + #if UNITY_2017_1_OR_NEWER + if( GraphicsSettings.HasShaderDefine( Graphics.activeTier, BuiltinShaderDefine.SHADER_API_MOBILE ) ) + { + // using 16376.0 for DepthScale for mobile due to precision issues + m_oneOverDepthScale = 1.0f / 16376.0f; + } + #else + #if UNITY_IPHONE || UNITY_ANDROID + m_oneOverDepthScale = 1.0f / 16376.0f; + #endif + #endif + } + + + void Reset() + { + if( m_commandBuffer_Parameters.cmdBuffer != null ) + { + cleanupCommandBuffer( ref m_commandBuffer_Parameters ); + } + + if( m_commandBuffer_Occlusion.cmdBuffer != null ) + { + cleanupCommandBuffer( ref m_commandBuffer_Occlusion ); + } + + if( m_commandBuffer_Apply.cmdBuffer != null ) + { + cleanupCommandBuffer( ref m_commandBuffer_Apply ); + } + + AmplifyOcclusionCommon.SafeReleaseRT( ref m_occlusionDepthRT ); + AmplifyOcclusionCommon.SafeReleaseRT( ref m_depthMipmap ); + releaseTemporalRT(); + + m_tmpMipString = null; + } + + void OnDisable() + { + Reset(); + } + + private void releaseTemporalRT() + { + if( m_temporalAccumRT != null ) + { + for( int i = 0; i < m_temporalAccumRT.Length; i++ ) + { + AmplifyOcclusionCommon.SafeReleaseRT( ref m_temporalAccumRT[ i ] ); + } + } + + m_temporalAccumRT = null; + } + + private bool m_paramsChanged = true; + private bool m_clearHistory = true; + + private void ClearHistory( CommandBuffer cb ) + { + m_clearHistory = false; + + if ( ( m_temporalAccumRT != null ) && ( m_occlusionDepthRT != null ) ) + { + for( int i = 0; i < m_temporalAccumRT.Length; i++ ) + { + cb.SetRenderTarget( m_temporalAccumRT[ i ] ); + PerformBlit( cb, m_occlusionMat, ShaderPass.ClearTemporal ); + } + } + } + + private void checkParamsChanged() + { + bool HDR = m_targetCamera.allowHDR; // && tier? + bool MSAA = m_targetCamera.allowMSAA && + m_targetCamera.actualRenderingPath != RenderingPath.DeferredLighting && + m_targetCamera.actualRenderingPath != RenderingPath.DeferredShading && + QualitySettings.antiAliasing >= 1; + + int antiAliasing = MSAA ? QualitySettings.antiAliasing : 1; + + if( m_occlusionDepthRT != null ) + { + if( ( m_occlusionDepthRT.width != m_target.width ) || + ( m_occlusionDepthRT.height != m_target.height ) || + ( m_prevMSAA != MSAA ) || + ( !m_occlusionDepthRT.IsCreated() ) || + ( m_prevFilterEnabled != FilterEnabled ) || + ( m_prevFilterDownsample != UsingFilterDownsample ) || + ( m_temporalAccumRT != null && ( !m_temporalAccumRT[ 0 ].IsCreated() || !m_temporalAccumRT[ 1 ].IsCreated() ) ) +#if UNITY_EDITOR + || ( ( m_prevIsPlaying == true ) && ( EditorApplication.isPlaying == false ) ) +#endif + ) + { + AmplifyOcclusionCommon.SafeReleaseRT( ref m_occlusionDepthRT ); + AmplifyOcclusionCommon.SafeReleaseRT( ref m_depthMipmap ); + releaseTemporalRT(); + + m_paramsChanged = true; + } + } + + if( m_temporalAccumRT != null ) + { + if( AmplifyOcclusionCommon.IsStereoMultiPassEnabled( m_targetCamera ) == true ) + { + if( m_temporalAccumRT.Length != 4 ) + { + m_temporalAccumRT = null; + } + } + else + { + if( m_temporalAccumRT.Length != 2 ) + { + m_temporalAccumRT = null; + } + } + } + + if( m_occlusionDepthRT == null ) + { + m_occlusionDepthRT = AmplifyOcclusionCommon.SafeAllocateRT( "_AO_OcclusionDepthTexture", + m_target.width, + m_target.height, + m_occlusionRTFormat, + RenderTextureReadWrite.Linear, + FilterMode.Bilinear ); + } + + if( m_temporalAccumRT == null && FilterEnabled ) + { + if( AmplifyOcclusionCommon.IsStereoMultiPassEnabled( m_targetCamera ) == true ) + { + m_temporalAccumRT = new RenderTexture[ 4 ]; + } + else + { + m_temporalAccumRT = new RenderTexture[ 2 ]; + } + + for( int i = 0; i < m_temporalAccumRT.Length; i++ ) + { + m_temporalAccumRT[ i ] = AmplifyOcclusionCommon.SafeAllocateRT( "_AO_TemporalAccum_" + i.ToString(), + m_target.width, + m_target.height, + m_accumTemporalRTFormat, + RenderTextureReadWrite.Linear, + FilterMode.Bilinear, + antiAliasing ); + } + + m_clearHistory = true; + } + + if( ( CacheAware == true ) && ( m_depthMipmap == null ) ) + { + m_depthMipmap = AmplifyOcclusionCommon.SafeAllocateRT( "_AO_DepthMipmap", + m_target.fullWidth >> 1, + m_target.fullHeight >> 1, + RenderTextureFormat.RFloat, + RenderTextureReadWrite.Linear, + FilterMode.Point, + 1, + true ); + + int minSize = (int)Mathf.Min( m_target.fullWidth, m_target.fullHeight ); + m_numberMips = (int)( Mathf.Log( (float)minSize, 2.0f ) + 1.0f ) - 1; + + m_tmpMipString = null; + m_tmpMipString = new string[m_numberMips]; + + for( int i = 0; i < m_numberMips; i++ ) + { + m_tmpMipString[i] = "_AO_TmpMip_" + i.ToString(); + } + } + else + { + if( ( CacheAware == false ) && ( m_depthMipmap != null ) ) + { + AmplifyOcclusionCommon.SafeReleaseRT( ref m_depthMipmap ); + m_tmpMipString = null; + } + } + + if( ( m_prevSampleCount != SampleCount ) || + ( m_prevDownsample != Downsample ) || + ( m_prevCacheAware != CacheAware ) || + ( m_prevBlurEnabled != BlurEnabled ) || + ( ( ( m_prevBlurPasses != BlurPasses ) || + ( m_prevBlurRadius != BlurRadius ) ) && ( BlurEnabled == true ) ) || + ( m_prevFilterEnabled != FilterEnabled ) || + ( m_prevFilterDownsample != UsingFilterDownsample ) || + ( m_prevHDR != HDR ) || + ( m_prevMSAA != MSAA ) ) + { + m_clearHistory |= ( m_prevHDR != HDR ); + m_clearHistory |= ( m_prevMSAA != MSAA ); + + m_HDR = HDR; + m_MSAA = MSAA; + + m_paramsChanged = true; + } + +#if UNITY_EDITOR + m_prevIsPlaying = EditorApplication.isPlaying; +#endif + } + + + private void updateParams() + { + m_prevSampleCount = SampleCount; + m_prevDownsample = Downsample; + m_prevCacheAware = CacheAware; + m_prevBlurEnabled = BlurEnabled; + m_prevBlurPasses = BlurPasses; + m_prevBlurRadius = BlurRadius; + m_prevFilterEnabled = FilterEnabled; + m_prevFilterDownsample = UsingFilterDownsample; + m_prevHDR = m_HDR; + m_prevMSAA = m_MSAA; + + m_paramsChanged = false; + } + + void Update() + { + if( m_targetCamera != null ) + { + if( m_targetCamera.actualRenderingPath != RenderingPath.DeferredShading ) + { + if( PerPixelNormals != PerPixelNormalSource.None && PerPixelNormals != PerPixelNormalSource.Camera ) + { + m_paramsChanged = true; + PerPixelNormals = PerPixelNormalSource.Camera; + + if( m_targetCamera.cameraType != UnityEngine.CameraType.SceneView ) + { + UnityEngine.Debug.LogWarning( "[AmplifyOcclusion] GBuffer Normals only available in Camera Deferred Shading mode. Switched to Camera source." ); + } + } + + if( ApplyMethod == ApplicationMethod.Deferred ) + { + m_paramsChanged = true; + ApplyMethod = ApplicationMethod.PostEffect; + + if( m_targetCamera.cameraType != UnityEngine.CameraType.SceneView ) + { + UnityEngine.Debug.LogWarning( "[AmplifyOcclusion] Deferred Method requires a Deferred Shading path. Switching to Post Effect Method." ); + } + } + } + else + { + if( PerPixelNormals == PerPixelNormalSource.Camera ) + { + m_paramsChanged = true; + PerPixelNormals = PerPixelNormalSource.GBuffer; + + if( m_targetCamera.cameraType != UnityEngine.CameraType.SceneView ) + { + UnityEngine.Debug.LogWarning( "[AmplifyOcclusion] Camera Normals not supported for Deferred Method. Switching to GBuffer Normals." ); + } + } + } + + if( ( m_targetCamera.depthTextureMode & DepthTextureMode.Depth ) == 0 ) + { + m_targetCamera.depthTextureMode |= DepthTextureMode.Depth; + } + + if( ( PerPixelNormals == PerPixelNormalSource.Camera ) && + ( m_targetCamera.depthTextureMode & DepthTextureMode.DepthNormals ) == 0 ) + { + m_targetCamera.depthTextureMode |= DepthTextureMode.DepthNormals; + } + + if( ( UsingMotionVectors == true ) && + ( m_targetCamera.depthTextureMode & DepthTextureMode.MotionVectors ) == 0 ) + { + m_targetCamera.depthTextureMode |= DepthTextureMode.MotionVectors; + } + + } + else + { + m_targetCamera = GetComponent(); + } + } + + + void OnPreRender() + { + Profiler.BeginSample( "AO - OnPreRender" ); + + checkMaterials( true ); + + if( m_targetCamera != null ) + { + #if UNITY_EDITOR + if( ( m_targetCamera.cameraType == UnityEngine.CameraType.SceneView ) && + ( ( ( PerPixelNormals == PerPixelNormalSource.GBuffer ) && ( m_targetCamera.orthographic == true ) ) || + ( PerPixelNormals == PerPixelNormalSource.Camera ) && + + #if UNITY_2019_1_OR_NEWER + ( SceneView.lastActiveSceneView.sceneLighting == false ) + #else + ( SceneView.lastActiveSceneView.m_SceneLighting == false ) + #endif + + ) ) + { + PerPixelNormals = PerPixelNormalSource.None; + } + #endif + + bool deferredReflections = ( GraphicsSettings.GetShaderMode( BuiltinShaderType.DeferredReflections ) != BuiltinShaderMode.Disabled ); + + if( ( m_prevPerPixelNormals != PerPixelNormals ) || + ( m_prevApplyMethod != ApplyMethod ) || + ( m_prevDeferredReflections != deferredReflections ) || + ( m_commandBuffer_Parameters.cmdBuffer == null ) || + ( m_commandBuffer_Occlusion.cmdBuffer == null ) || + ( m_commandBuffer_Apply.cmdBuffer == null ) + ) + { + CameraEvent cameraStage = CameraEvent.BeforeImageEffectsOpaque; + if( ApplyMethod == ApplicationMethod.Deferred ) + { + cameraStage = deferredReflections ? CameraEvent.BeforeReflections : CameraEvent.BeforeLighting; + } + + createCommandBuffer( ref m_commandBuffer_Parameters, "AmplifyOcclusion_Parameters_" + m_myIDstring, cameraStage ); + createCommandBuffer( ref m_commandBuffer_Occlusion, "AmplifyOcclusion_Compute_" + m_myIDstring, cameraStage ); + createCommandBuffer( ref m_commandBuffer_Apply, "AmplifyOcclusion_Apply_" + m_myIDstring, cameraStage ); + + m_prevPerPixelNormals = PerPixelNormals; + m_prevApplyMethod = ApplyMethod; + m_prevDeferredReflections = deferredReflections; + + m_paramsChanged = true; + } + + if( ( m_commandBuffer_Parameters.cmdBuffer != null ) && + ( m_commandBuffer_Occlusion.cmdBuffer != null ) && + ( m_commandBuffer_Apply.cmdBuffer != null ) ) + { + if( AmplifyOcclusionCommon.IsStereoMultiPassEnabled( m_targetCamera ) == true ) + { + uint curStepIdx = ( m_sampleStep >> 1 ) & 1; + uint curEyeIdx = ( m_sampleStep & 1 ); + m_curTemporalIdx = ( curEyeIdx * 2 ) + ( 0 + curStepIdx ); + m_prevTemporalIdx = ( curEyeIdx * 2 ) + ( 1 - curStepIdx ); + } + else + { + uint curStepIdx = m_sampleStep & 1; + m_curTemporalIdx = 0 + curStepIdx; + m_prevTemporalIdx = 1 - curStepIdx; + } + + m_commandBuffer_Parameters.cmdBuffer.Clear(); + + UpdateGlobalShaderConstants( m_commandBuffer_Parameters.cmdBuffer ); + + UpdateGlobalShaderConstants_Matrices( m_commandBuffer_Parameters.cmdBuffer ); + + UpdateGlobalShaderConstants_AmbientOcclusion( m_commandBuffer_Parameters.cmdBuffer ); + + checkParamsChanged(); + + if( m_paramsChanged ) + { + m_commandBuffer_Occlusion.cmdBuffer.Clear(); + + commandBuffer_FillComputeOcclusion( m_commandBuffer_Occlusion.cmdBuffer ); + } + + m_commandBuffer_Apply.cmdBuffer.Clear(); + + if( ApplyMethod == ApplicationMethod.Debug ) + { + commandBuffer_FillApplyDebug( m_commandBuffer_Apply.cmdBuffer ); + } + else + { + if( ApplyMethod == ApplicationMethod.PostEffect ) + { + commandBuffer_FillApplyPostEffect( m_commandBuffer_Apply.cmdBuffer ); + } + else + { + bool logTarget = !m_HDR; + + commandBuffer_FillApplyDeferred( m_commandBuffer_Apply.cmdBuffer, logTarget ); + } + } + + updateParams(); + + m_sampleStep++; // No clamp, free running counter + } + } + else + { + m_targetCamera = GetComponent(); + + Update(); + + #if UNITY_EDITOR + if( m_targetCamera.cameraType != UnityEngine.CameraType.SceneView ) + { + System.Type T = System.Type.GetType( "UnityEditor.GameView,UnityEditor" ); + UnityEngine.Object[] array = Resources.FindObjectsOfTypeAll( T ); + + if( array.Length > 0 ) + { + EditorWindow gameView = (EditorWindow)array[ 0 ]; + gameView.Focus(); + } + } + #endif + } + + Profiler.EndSample(); + } + + + void OnPostRender() + { + if( m_occlusionDepthRT != null ) + { + m_occlusionDepthRT.MarkRestoreExpected(); + } + if( m_temporalAccumRT != null ) + { + foreach ( var rt in m_temporalAccumRT ) + { + rt.MarkRestoreExpected(); + } + } + } + + + private RenderTexture m_occlusionDepthRT = null; + private RenderTexture[] m_temporalAccumRT = null; + private RenderTexture m_depthMipmap = null; + + private uint m_sampleStep = 0; + private uint m_curTemporalIdx = 0; + private uint m_prevTemporalIdx = 0; + + private string[] m_tmpMipString = null; + private int m_numberMips = 0; + private void commandBuffer_FillComputeOcclusion( CommandBuffer cb ) + { + cb.BeginSample( "AO 1 - ComputeOcclusion" ); + + if( ( PerPixelNormals == PerPixelNormalSource.GBuffer ) || + ( PerPixelNormals == PerPixelNormalSource.GBufferOctaEncoded ) ) + { + cb.SetGlobalTexture( PropertyID._AO_GBufferNormals, BuiltinRenderTextureType.GBuffer2 ); + } + + Vector4 oneOverFullSize_Size = new Vector4( 1.0f / (float)m_target.fullWidth, + 1.0f / (float)m_target.fullHeight, + m_target.fullWidth, + m_target.fullHeight ); + + int sampleCountPass = ( (int)SampleCount ) * AmplifyOcclusionCommon.PerPixelNormalSourceCount; + + int occlusionPass = ( ShaderPass.OcclusionLow_None + + sampleCountPass + + ( (int)PerPixelNormals ) ); + + if( CacheAware == true ) + { + occlusionPass += ShaderPass.OcclusionLow_None_UseDynamicDepthMips; + + // Construct Depth mipmaps + int previouslyTmpMipRT = 0; + + for( int i = 0; i < m_numberMips; i++ ) + { + int tmpMipRT; + + int width = m_target.fullWidth >> ( i + 1 ); + int height = m_target.fullHeight >> ( i + 1 ); + + tmpMipRT = AmplifyOcclusionCommon.SafeAllocateTemporaryRT( cb, m_tmpMipString[ i ], + width, height, + RenderTextureFormat.RFloat, + RenderTextureReadWrite.Linear, + FilterMode.Bilinear ); + + // _AO_CurrDepthSource was previously set + cb.SetRenderTarget( tmpMipRT ); + + PerformBlit( cb, m_occlusionMat, ( ( i == 0 )?ShaderPass.ScaleDownCloserDepthEven_CameraDepthTexture:ShaderPass.ScaleDownCloserDepthEven ) ); + + cb.CopyTexture( tmpMipRT, 0, 0, m_depthMipmap, 0, i ); + + if( previouslyTmpMipRT != 0 ) + { + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, previouslyTmpMipRT ); + } + + previouslyTmpMipRT = tmpMipRT; + + cb.SetGlobalTexture( PropertyID._AO_CurrDepthSource, tmpMipRT ); // Set next MipRT ID + } + + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, previouslyTmpMipRT ); + + cb.SetGlobalTexture( PropertyID._AO_SourceDepthMipmap, m_depthMipmap ); + } + + if( ( Downsample == true ) && ( UsingFilterDownsample == false ) ) + { + int halfWidth = m_target.fullWidth / 2; + int halfHeight = m_target.fullHeight / 2; + + int tmpSmallOcclusionRT = AmplifyOcclusionCommon.SafeAllocateTemporaryRT( cb, "_AO_SmallOcclusionTexture", + halfWidth, halfHeight, + m_occlusionRTFormat, + RenderTextureReadWrite.Linear, + FilterMode.Bilinear ); + + cb.SetGlobalVector( PropertyID._AO_Source_TexelSize, oneOverFullSize_Size ); + cb.SetGlobalVector( PropertyID._AO_Target_TexelSize, new Vector4( 1.0f / ( m_target.fullWidth / 2.0f ), + 1.0f / ( m_target.fullHeight / 2.0f ), + m_target.fullWidth / 2.0f, + m_target.fullHeight / 2.0f ) ); + + cb.SetRenderTarget( tmpSmallOcclusionRT ); + PerformBlit( cb, m_occlusionMat, occlusionPass ); + + cb.SetRenderTarget( default( RenderTexture ) ); + cb.EndSample( "AO 1 - ComputeOcclusion" ); + + if( BlurEnabled == true ) + { + commandBuffer_Blur( cb, tmpSmallOcclusionRT, halfWidth, halfHeight ); + } + + // Combine + cb.BeginSample( "AO 2b - Combine" ); + + cb.SetGlobalTexture( PropertyID._AO_CurrOcclusionDepth, tmpSmallOcclusionRT ); + + cb.SetGlobalVector( PropertyID._AO_Target_TexelSize, oneOverFullSize_Size ); + + cb.SetRenderTarget( m_occlusionDepthRT ); + + PerformBlit( cb, m_occlusionMat, ShaderPass.CombineDownsampledOcclusionDepth ); + + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, tmpSmallOcclusionRT ); + + cb.SetRenderTarget( default( RenderTexture ) ); + cb.EndSample( "AO 2b - Combine" ); + } + else + { + cb.SetGlobalVector( PropertyID._AO_Source_TexelSize, oneOverFullSize_Size ); + + if( UsingFilterDownsample == true ) + { + // Must use proper float precision 2.0 division to avoid artefacts + cb.SetGlobalVector( PropertyID._AO_Target_TexelSize, new Vector4( 1.0f / ( m_target.fullWidth / 2.0f ), + 1.0f / ( m_target.fullHeight / 2.0f ), + m_target.fullWidth / 2.0f, + m_target.fullHeight / 2.0f ) ); + } + else + { + cb.SetGlobalVector( PropertyID._AO_Target_TexelSize, new Vector4( 1.0f / (float)m_target.width, + 1.0f / (float)m_target.height, + m_target.width, + m_target.height ) ); + } + + cb.SetRenderTarget( m_occlusionDepthRT ); + PerformBlit( cb, m_occlusionMat, occlusionPass ); + + cb.SetRenderTarget( default( RenderTexture ) ); + cb.EndSample( "AO 1 - ComputeOcclusion" ); + + if( BlurEnabled == true ) + { + commandBuffer_Blur( cb, m_occlusionDepthRT, m_target.width, m_target.height ); + } + } + } + + + int commandBuffer_NeighborMotionIntensity( CommandBuffer cb, int aSourceWidth, int aSourceHeight ) + { + int tmpRT = AmplifyOcclusionCommon.SafeAllocateTemporaryRT( cb, "_AO_IntensityTmp", + aSourceWidth / 4, aSourceHeight / 4, + m_motionIntensityRTFormat, + RenderTextureReadWrite.Linear, + FilterMode.Bilinear ); + + + cb.SetRenderTarget( tmpRT ); + cb.SetGlobalVector( "_AO_Target_TexelSize", new Vector4( 1.0f / ( aSourceWidth / 4.0f ), + 1.0f / ( aSourceHeight / 4.0f ), + aSourceWidth / 4.0f, + aSourceHeight / 4.0f ) ); + + + PerformBlit( cb, m_occlusionMat, ShaderPass.NeighborMotionIntensity ); + + int tmpBlurRT = AmplifyOcclusionCommon.SafeAllocateTemporaryRT( cb, "_AO_BlurIntensityTmp", + aSourceWidth / 4, aSourceHeight / 4, + m_motionIntensityRTFormat, + RenderTextureReadWrite.Linear, + FilterMode.Bilinear ); + + // Horizontal + cb.SetGlobalTexture( PropertyID._AO_CurrMotionIntensity, tmpRT ); + cb.SetRenderTarget( tmpBlurRT ); + PerformBlit( cb, m_blurMat, ShaderPass.BlurHorizontalIntensity ); + + // Vertical + cb.SetGlobalTexture( PropertyID._AO_CurrMotionIntensity, tmpBlurRT ); + cb.SetRenderTarget( tmpRT ); + PerformBlit( cb, m_blurMat, ShaderPass.BlurVerticalIntensity ); + + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, tmpBlurRT ); + + cb.SetGlobalTexture( PropertyID._AO_CurrMotionIntensity, tmpRT ); + + return tmpRT; + } + + + void commandBuffer_Blur( CommandBuffer cb, RenderTargetIdentifier aSourceRT, int aSourceWidth, int aSourceHeight ) + { + cb.BeginSample( "AO 2 - Blur" ); + + int tmpBlurRT = AmplifyOcclusionCommon.SafeAllocateTemporaryRT( cb, "_AO_BlurTmp", + aSourceWidth, aSourceHeight, + m_occlusionRTFormat, + RenderTextureReadWrite.Linear, + FilterMode.Bilinear ); + + // Apply Cross Bilateral Blur + for( int i = 0; i < BlurPasses; i++ ) + { + // Horizontal + cb.SetGlobalTexture( PropertyID._AO_CurrOcclusionDepth, aSourceRT ); + + int blurHorizontalPass = ShaderPass.BlurHorizontal1 + ( BlurRadius - 1 ) * 2; + + cb.SetRenderTarget( tmpBlurRT ); + + PerformBlit( cb, m_blurMat, blurHorizontalPass ); + + + // Vertical + cb.SetGlobalTexture( PropertyID._AO_CurrOcclusionDepth, tmpBlurRT ); + + int blurVerticalPass = ShaderPass.BlurVertical1 + ( BlurRadius - 1 ) * 2; + + cb.SetRenderTarget( aSourceRT ); + + PerformBlit( cb, m_blurMat, blurVerticalPass ); + } + + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, tmpBlurRT ); + + cb.SetRenderTarget( default( RenderTexture ) ); + cb.EndSample( "AO 2 - Blur" ); + } + + int getTemporalPass() + { + return ( ( ( UsingMotionVectors == true ) && ( m_sampleStep > 1 ) ) ? ( 1 << 0 ) : 0 ); + } + + void commandBuffer_TemporalFilter( CommandBuffer cb ) + { + if( m_clearHistory == true ) + { + ClearHistory( cb ); + } + + // Temporal Filter + float temporalAdj = Mathf.Lerp( 0.01f, 0.99f, FilterBlending ); + + cb.SetGlobalFloat( PropertyID._AO_TemporalCurveAdj, temporalAdj ); + cb.SetGlobalFloat( PropertyID._AO_TemporalMotionSensibility, FilterResponse * FilterResponse + 0.01f ); + + cb.SetGlobalTexture( PropertyID._AO_CurrOcclusionDepth, m_occlusionDepthRT ); + cb.SetGlobalTexture( PropertyID._AO_TemporalAccumm, m_temporalAccumRT[ m_prevTemporalIdx ] ); + } + + private readonly RenderTargetIdentifier[] m_applyDeferredTargets = + { + BuiltinRenderTextureType.GBuffer0, // RGB: Albedo, A: Occ + BuiltinRenderTextureType.CameraTarget, // RGB: Emission, A: None + }; + + private readonly RenderTargetIdentifier[] m_applyDeferredTargets_Log = + { + BuiltinRenderTextureType.GBuffer0, // RGB: Albedo, A: Occ + BuiltinRenderTextureType.GBuffer3 // RGB: Emission, A: None + }; + + void commandBuffer_FillApplyDeferred( CommandBuffer cb, bool logTarget ) + { + cb.BeginSample( "AO 3 - ApplyDeferred" ); + + if( !logTarget ) + { + if( UsingTemporalFilter ) + { + commandBuffer_TemporalFilter( cb ); + + int tmpMotionIntensityRT = 0; + + if( UsingMotionVectors == true ) + { + tmpMotionIntensityRT = commandBuffer_NeighborMotionIntensity( cb, m_target.fullWidth, m_target.fullHeight ); + } + + if( UsingFilterDownsample == false ) + { + int applyOcclusionRT = 0; + if ( useMRTBlendingFallback ) + { + applyOcclusionRT = AmplifyOcclusionCommon.SafeAllocateTemporaryRT( cb, "_AO_ApplyOcclusionTexture", m_target.fullWidth, m_target.fullHeight, RenderTextureFormat.ARGB32 ); + + applyOcclusionTemporal[0] = applyOcclusionRT; + applyOcclusionTemporal[1] = new RenderTargetIdentifier( m_temporalAccumRT[ m_curTemporalIdx ] ); + + cb.SetRenderTarget( applyOcclusionTemporal, applyOcclusionTemporal[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyPostEffectTemporal + getTemporalPass() ); // re-use ApplyPostEffectTemporal pass to apply without Blend to the RT. + } + else + { + applyDeferredTargetsTemporal[0] = m_applyDeferredTargets[0]; + applyDeferredTargetsTemporal[1] = m_applyDeferredTargets[1]; + applyDeferredTargetsTemporal[2] = new RenderTargetIdentifier( m_temporalAccumRT[ m_curTemporalIdx ] ); + + cb.SetRenderTarget( applyDeferredTargetsTemporal, applyDeferredTargetsTemporal[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDeferredTemporal + getTemporalPass() ); + } + + if ( useMRTBlendingFallback ) + { + cb.SetGlobalTexture( "_AO_ApplyOcclusionTexture", applyOcclusionRT ); + + applyOcclusionTemporal[0] = m_applyDeferredTargets[0]; + applyOcclusionTemporal[1] = m_applyDeferredTargets[1]; + + cb.SetRenderTarget( applyOcclusionTemporal, applyOcclusionTemporal[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDeferredTemporalMultiply ); + + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, applyOcclusionRT ); + } + } + else + { + // UsingFilterDownsample == true + + RenderTargetIdentifier temporalRTid = new RenderTargetIdentifier( m_temporalAccumRT[ m_curTemporalIdx ] ); + + cb.SetRenderTarget( temporalRTid ); + PerformBlit( cb, m_occlusionMat, ShaderPass.Temporal + getTemporalPass() ); + + cb.SetGlobalTexture( PropertyID._AO_TemporalAccumm, temporalRTid ); + cb.SetRenderTarget( m_applyDeferredTargets, m_applyDeferredTargets[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDeferredCombineFromTemporal ); + } + + if( UsingMotionVectors == true ) + { + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, tmpMotionIntensityRT ); + } + } + else + { + cb.SetGlobalTexture( PropertyID._AO_CurrOcclusionDepth, m_occlusionDepthRT ); + + // Multiply Occlusion + cb.SetRenderTarget( m_applyDeferredTargets, m_applyDeferredTargets[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDeferred ); + } + } + else + { + // Copy Albedo and Emission to temporary buffers + int gbufferAlbedoRT = AmplifyOcclusionCommon.SafeAllocateTemporaryRT( cb, "_AO_tmpAlbedo", + m_target.fullWidth, m_target.fullHeight, + RenderTextureFormat.ARGB32 ); + + int gbufferEmissionRT = AmplifyOcclusionCommon.SafeAllocateTemporaryRT( cb, "_AO_tmpEmission", + m_target.fullWidth, m_target.fullHeight, + m_temporaryEmissionRTFormat ); + + cb.Blit( BuiltinRenderTextureType.GBuffer0, gbufferAlbedoRT ); + cb.Blit( BuiltinRenderTextureType.GBuffer3, gbufferEmissionRT ); + + cb.SetGlobalTexture( PropertyID._AO_GBufferAlbedo, gbufferAlbedoRT ); + cb.SetGlobalTexture( PropertyID._AO_GBufferEmission, gbufferEmissionRT ); + + if( UsingTemporalFilter ) + { + commandBuffer_TemporalFilter( cb ); + + int tmpMotionIntensityRT = 0; + + if( UsingMotionVectors == true ) + { + tmpMotionIntensityRT = commandBuffer_NeighborMotionIntensity( cb, m_target.fullWidth, m_target.fullHeight ); + } + + if( UsingFilterDownsample == false ) + { + applyDeferredTargets_Log_Temporal[0] = m_applyDeferredTargets_Log[0]; + applyDeferredTargets_Log_Temporal[1] = m_applyDeferredTargets_Log[1]; + applyDeferredTargets_Log_Temporal[2] = new RenderTargetIdentifier( m_temporalAccumRT[ m_curTemporalIdx ] ); + + cb.SetRenderTarget( applyDeferredTargets_Log_Temporal, applyDeferredTargets_Log_Temporal[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDeferredLogTemporal + getTemporalPass() ); + } + else + { + // UsingFilterDownsample == true + + RenderTargetIdentifier temporalRTid = new RenderTargetIdentifier( m_temporalAccumRT[ m_curTemporalIdx ] ); + + cb.SetRenderTarget( temporalRTid ); + PerformBlit( cb, m_occlusionMat, ShaderPass.Temporal + getTemporalPass() ); + + cb.SetGlobalTexture( PropertyID._AO_TemporalAccumm, temporalRTid ); + cb.SetRenderTarget( m_applyDeferredTargets_Log, m_applyDeferredTargets_Log[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDeferredLogCombineFromTemporal ); + } + + if( UsingMotionVectors == true ) + { + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, tmpMotionIntensityRT ); + } + } + else + { + cb.SetGlobalTexture( PropertyID._AO_CurrOcclusionDepth, m_occlusionDepthRT ); + + cb.SetRenderTarget( m_applyDeferredTargets_Log, m_applyDeferredTargets_Log[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDeferredLog ); + } + + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, gbufferAlbedoRT ); + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, gbufferEmissionRT ); + } + + cb.SetRenderTarget( default( RenderTexture ) ); + cb.EndSample( "AO 3 - ApplyDeferred" ); + } + + + void commandBuffer_FillApplyPostEffect( CommandBuffer cb ) + { + cb.BeginSample( "AO 3 - ApplyPostEffect" ); + + if( UsingTemporalFilter ) + { + commandBuffer_TemporalFilter( cb ); + + int tmpMotionIntensityRT = 0; + + if( UsingMotionVectors == true ) + { + tmpMotionIntensityRT = commandBuffer_NeighborMotionIntensity( cb, m_target.fullWidth, m_target.fullHeight ); + } + + if( UsingFilterDownsample == false ) + { + int applyOcclusionRT = 0; + if ( useMRTBlendingFallback ) + { + applyOcclusionRT = AmplifyOcclusionCommon.SafeAllocateTemporaryRT( cb, "_AO_ApplyOcclusionTexture", m_target.fullWidth, m_target.fullHeight, RenderTextureFormat.ARGB32 ); + applyPostEffectTargetsTemporal[ 0 ] = applyOcclusionRT; + } + else + { + applyPostEffectTargetsTemporal[ 0 ] = BuiltinRenderTextureType.CameraTarget; + } + + applyPostEffectTargetsTemporal[1] = new RenderTargetIdentifier( m_temporalAccumRT[ m_curTemporalIdx ] ); + + cb.SetRenderTarget( applyPostEffectTargetsTemporal, applyPostEffectTargetsTemporal[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyPostEffectTemporal + getTemporalPass() ); + + if ( useMRTBlendingFallback ) + { + cb.SetGlobalTexture( "_AO_ApplyOcclusionTexture", applyOcclusionRT ); + + cb.SetRenderTarget( BuiltinRenderTextureType.CameraTarget ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyPostEffectTemporalMultiply ); + + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, applyOcclusionRT ); + } + } + else + { + // UsingFilterDownsample == true + + RenderTargetIdentifier temporalRTid = new RenderTargetIdentifier( m_temporalAccumRT[ m_curTemporalIdx ] ); + + cb.SetRenderTarget( temporalRTid ); + PerformBlit( cb, m_occlusionMat, ShaderPass.Temporal + getTemporalPass() ); + + cb.SetGlobalTexture( PropertyID._AO_TemporalAccumm, temporalRTid ); + cb.SetRenderTarget( BuiltinRenderTextureType.CameraTarget ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyCombineFromTemporal ); + } + + if( UsingMotionVectors == true ) + { + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, tmpMotionIntensityRT ); + } + } + else + { + cb.SetGlobalTexture( PropertyID._AO_CurrOcclusionDepth, m_occlusionDepthRT ); + + cb.SetRenderTarget( BuiltinRenderTextureType.CameraTarget ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyPostEffect ); + } + + cb.SetRenderTarget( default( RenderTexture ) ); + cb.EndSample( "AO 3 - ApplyPostEffect" ); + } + + + void commandBuffer_FillApplyDebug( CommandBuffer cb ) + { + cb.BeginSample( "AO 3 - ApplyDebug" ); + + if( UsingTemporalFilter ) + { + commandBuffer_TemporalFilter( cb ); + + int tmpMotionIntensityRT = 0; + + if( UsingMotionVectors == true ) + { + tmpMotionIntensityRT = commandBuffer_NeighborMotionIntensity( cb, m_target.fullWidth, m_target.fullHeight ); + } + + if( UsingFilterDownsample == false ) + { + applyDebugTargetsTemporal[0] = BuiltinRenderTextureType.CameraTarget; + applyDebugTargetsTemporal[1] = new RenderTargetIdentifier( m_temporalAccumRT[ m_curTemporalIdx ] ); + + cb.SetRenderTarget( applyDebugTargetsTemporal, applyDebugTargetsTemporal[ 0 ] /* Not used, just to make Unity happy */ ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDebugTemporal + getTemporalPass() ); + } + else + { + // UsingFilterDownsample == true + + RenderTargetIdentifier temporalRTid = new RenderTargetIdentifier( m_temporalAccumRT[ m_curTemporalIdx ] ); + + cb.SetRenderTarget( temporalRTid ); + PerformBlit( cb, m_occlusionMat, ShaderPass.Temporal + getTemporalPass() ); + + cb.SetGlobalTexture( PropertyID._AO_TemporalAccumm, temporalRTid ); + cb.SetRenderTarget( BuiltinRenderTextureType.CameraTarget ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDebugCombineFromTemporal ); + } + + if( UsingMotionVectors == true ) + { + AmplifyOcclusionCommon.SafeReleaseTemporaryRT( cb, tmpMotionIntensityRT ); + } + } + else + { + cb.SetGlobalTexture( PropertyID._AO_CurrOcclusionDepth, m_occlusionDepthRT ); + + cb.SetRenderTarget( BuiltinRenderTextureType.CameraTarget ); + PerformBlit( cb, m_applyOcclusionMat, ShaderPass.ApplyDebug ); + } + + cb.SetRenderTarget( default( RenderTexture ) ); + cb.EndSample( "AO 3 - ApplyDebug" ); + } + + private TargetDesc m_target = new TargetDesc(); + + void UpdateGlobalShaderConstants( CommandBuffer cb ) + { + AmplifyOcclusionCommon.UpdateGlobalShaderConstants( cb, ref m_target, m_targetCamera, Downsample, UsingFilterDownsample ); + } + + void UpdateGlobalShaderConstants_AmbientOcclusion( CommandBuffer cb ) + { + // Ambient Occlusion + cb.SetGlobalFloat( PropertyID._AO_Radius, Radius ); + cb.SetGlobalFloat( PropertyID._AO_PowExponent, PowerExponent ); + cb.SetGlobalFloat( PropertyID._AO_Bias, Bias * Bias ); + cb.SetGlobalColor( PropertyID._AO_Levels, new Color( Tint.r, Tint.g, Tint.b, Intensity ) ); + + float invThickness = ( 1.0f - Thickness ); + cb.SetGlobalFloat( PropertyID._AO_ThicknessDecay, ( 1.0f - invThickness * invThickness ) * 0.98f ); + + float AO_BufDepthToLinearEye = m_targetCamera.farClipPlane * m_oneOverDepthScale; + cb.SetGlobalFloat( PropertyID._AO_BufDepthToLinearEye, AO_BufDepthToLinearEye ); + + if( BlurEnabled == true ) + { + float AO_BlurSharpness = BlurSharpness * 100.0f * AO_BufDepthToLinearEye; + + cb.SetGlobalFloat( PropertyID._AO_BlurSharpness, AO_BlurSharpness ); + } + + // Distance Fade + if( FadeEnabled == true ) + { + FadeStart = Mathf.Max( 0.0f, FadeStart ); + FadeLength = Mathf.Max( 0.01f, FadeLength ); + + float rcpFadeLength = 1.0f / FadeLength; + + cb.SetGlobalVector( PropertyID._AO_FadeParams, new Vector2( FadeStart, rcpFadeLength ) ); + float invFadeThickness = ( 1.0f - FadeToThickness ); + cb.SetGlobalVector( PropertyID._AO_FadeValues, new Vector4( FadeToIntensity, FadeToRadius, FadeToPowerExponent, ( 1.0f - invFadeThickness * invFadeThickness ) * 0.98f ) ); + cb.SetGlobalColor( PropertyID._AO_FadeToTint, new Color( FadeToTint.r, FadeToTint.g, FadeToTint.b, 0.0f ) ); + } + else + { + cb.SetGlobalVector( PropertyID._AO_FadeParams, new Vector2( 0.0f, 0.0f ) ); + } + + if( UsingTemporalFilter == true ) + { + AmplifyOcclusionCommon.CommandBuffer_TemporalFilterDirectionsOffsets( cb, m_sampleStep ); + } + else + { + cb.SetGlobalFloat( PropertyID._AO_TemporalDirections, 0 ); + cb.SetGlobalFloat( PropertyID._AO_TemporalOffsets, 0 ); + } + } + + AmplifyOcclusionViewProjMatrix m_viewProjMatrix = new AmplifyOcclusionViewProjMatrix(); + void UpdateGlobalShaderConstants_Matrices( CommandBuffer cb ) + { + m_viewProjMatrix.UpdateGlobalShaderConstants_Matrices( cb, m_targetCamera, UsingTemporalFilter ); + } +} diff --git a/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionEffect.cs.meta b/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionEffect.cs.meta new file mode 100644 index 0000000..72809b1 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins/AmplifyOcclusionEffect.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6a879e317c20b0d41a9a56f1a97f4cf8 +timeCreated: 1452773390 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 02279d0f85b95124a85f8cbf24716a1b, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Plugins/Runtime.meta b/SubmersedVR/AmplifyOcclusion/Plugins/Runtime.meta new file mode 100644 index 0000000..18df770 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins/Runtime.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 0ea144e64248f8e4eb2f89a54207d5a7 +folderAsset: yes +timeCreated: 1455193882 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Plugins/Runtime/VersionInfo.cs b/SubmersedVR/AmplifyOcclusion/Plugins/Runtime/VersionInfo.cs new file mode 100644 index 0000000..de640f7 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins/Runtime/VersionInfo.cs @@ -0,0 +1,59 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +using System; +using UnityEngine; + +namespace AmplifyOcclusion +{ +#pragma warning disable 429 +[Serializable] +public class VersionInfo +{ + public const byte Major = 2; + public const byte Minor = 0; + public const byte Release = 8; + public const byte Revision = 0; + + public static string StaticToString() + { + return string.Format( "{0}.{1}.{2}", Major, Minor, Release ) + ( Revision > 0 ? "r" + Revision.ToString() : "" ); + } + + public override string ToString() + { + return string.Format( "{0}.{1}.{2}", m_major, m_minor, m_release ) + ( Revision > 0 ? "r" + Revision.ToString() : "" ); + } + + public static int FullNumber { get { return Major * 100 + Minor * 10 + Release; } } + public int Number { get { return m_major * 100 + m_minor * 10 + m_release; } } + + [SerializeField] private int m_major; + [SerializeField] private int m_minor; + [SerializeField] private int m_release; + + VersionInfo() + { + m_major = Major; + m_minor = Minor; + m_release = Release; + } + + VersionInfo( byte major, byte minor, byte release ) + { + m_major = major; + m_minor = minor; + m_release = release; + } + + public static VersionInfo Current() + { + return new VersionInfo( Major, Minor, Release ); + } + + public static bool Matches( VersionInfo version ) + { + return ( Major == version.m_major ) && ( Minor == version.m_minor ) && ( Release == version.m_release ); + } +} +} diff --git a/SubmersedVR/AmplifyOcclusion/Plugins/Runtime/VersionInfo.cs.meta b/SubmersedVR/AmplifyOcclusion/Plugins/Runtime/VersionInfo.cs.meta new file mode 100644 index 0000000..6a254e0 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins/Runtime/VersionInfo.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b7a571d9c0060e44f97b9daf344c1924 +timeCreated: 1455193887 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Plugins/amplify_occlusion.asmdef b/SubmersedVR/AmplifyOcclusion/Plugins/amplify_occlusion.asmdef new file mode 100644 index 0000000..7dd1a52 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins/amplify_occlusion.asmdef @@ -0,0 +1,17 @@ +{ + "name": "amplify_occlusion", + "references": [ + "GUID:1fbf338ef6b952a448262a7ff7df5fe4" + ], + "includePlatforms": [ + "WindowsStandalone64" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/SubmersedVR/AmplifyOcclusion/Plugins/amplify_occlusion.asmdef.meta b/SubmersedVR/AmplifyOcclusion/Plugins/amplify_occlusion.asmdef.meta new file mode 100644 index 0000000..8adf374 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Plugins/amplify_occlusion.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c02a3cbccf3446d4db2acf71c2153e3c +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Readme.txt b/SubmersedVR/AmplifyOcclusion/Readme.txt new file mode 100644 index 0000000..593cde3 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Readme.txt @@ -0,0 +1,137 @@ +# Amplify Unity Products + + While this package is provided, as is, for free, we develop and maintain professionally created + solutions used by thousands of developers. We invite you to check our current projects, and to + join our growing [Discord Community](https://discord.gg/SbNs7zK). + + [Amplify Shader Editor](https://assetstore.unity.com/packages/tools/visual-scripting/amplify-shader-editor-68570?aid=1011lPwI&pubref=GitHub) - Award-winning node-based shader creation tool + + [Amplify Impostors](https://assetstore.unity.com/packages/tools/utilities/amplify-impostors-beta-119877?aid=1011lPwI&pubref=GitHub) - 1-Click Impostor Creator + + [Amplify LUT Pack](https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/amplify-lut-pack-50070?aid=1011lPwI&pubref=GitHub) - 200+ LUTs for Amplify Color and Unity PPS + +# Amplify Occlusion + + Amplify Occlusion was the first industry-grade, full-featured screen-space ambient occlusion + solution to be released on the Asset Store in 2016, at a time when Unity itself lacked a decent + SSAO implementation. It managed to remain the fastest SSAO solution for Unity released a built-in + implementation of "Multi-scale Volumetric Occlusion" as part of their Post-processing Stack. + +

+ + The first version of this plugin was using a technique known as HBAO, or "Horizon-Based + Ambient Occlusion", based on a 2008 paper titled "Image-Space Horizon-Based Ambient Occlusion" + by Louis Bavoil, Miguel Sainz and Rouslan Dimitrov. + + The second version, which improved upon the first iteration on both quality and performance was + using a technique known as GTAO, or "Ground-Truth Ambient Occlusion", based on a 2016 paper titled + "Practical Realtime Strategies for Accurate Indirect Occlusion" by Jorge Jimenez, Xian-Chun Wu, + Angelo Pesce and Adrian Jarabo. + +

+ + This package was for sale on the Unity Asset Store between 2016 and 2019 with an + average rating of 5 stars. It is now deprecated and we no longer support it, so we + are releasing it to open-source world under the MIT License. + +# Description + + Amplify Occlusion sets out to deliver a new industry standard for fast, high-quality + Screen-Space Ambient Occlusion in Unity; delivering state of the art Ground Truth Ambient + Occlusion (GTAO) and bringing quality and accuracy closer to traditional raytracing. A true + all-in-one package, providing a highly-robust and efficient way to simulate ambient occlusion + and contact shadowing. Now you can attenuate reflections in occluded areas, make objects + actually connect to the world and add real depth to your scenes with minimal effort. + +# Features + + * Ground Truth Ambient Occlusion + * PS4, Xbox One and Switch compatible + * Single and Multi-pass VR support + * Up to 2X faster than Amplify Occlusion 1.0 + * Revamped Spatial and Temporal Filters + * Dramatically Higher-Quality + * Higher Flexibility + * Under 1 ms on a mid-range GPU at Full HD + * Accurate and fast-performing + * Deferred and Forward Rendering + * PBR compatible injection mode + * Superior occlusion approximation + * Extensive blur and intensity controls + +# Supported Platforms + + All platforms + +# Software Requirements + + Minimum + + Unity 5.6.0+ + +# Quick Guide + + Standard How-to + + 1) Select and apply “Image Effects/Amplify Occlusion” to your main camera. + + 2) Adjust the Intensity and Radius. + + 3) Adjust the blur values until you are satisfied with the results. + + Scriptable Render Pipeline How-to + + 1) Install packages dependencies: + Window -> Package Manager, Advanced -> Show preview packages + Select and install: + + Render-Pipelines.Core + Render-Pipelines.High-Definition + Render-Pipelines.Lightweight + Post Processing + + 2) Go to "Assets/Import Package/Custom Package..." and select + "Assets/AmplifyOcclusion/Packages/PostProcessingSRP_XXX.unitypackage" + + 3) How to set up an SRP project example: + + 3.a) Create SRP asset via Assets menu: + + Create/Rendering/High Definition Render Pipeline Asset + + OR + + Create/Rendering/Lightweight Render Pipeline Asset + + 3.b) Set Edit->ProjectSettings/Player/Other settings/ColorSpace to Linear (necessary for HD SRP) + + 3.c) Edit->ProjectSettings/Graphics/Scriptable Render Pipeline Settings: select the RenderPipelineAsset + created in 3.a) + + 3.d) On Camera, using Lightweight Render Pipeline, disable MSAA + + 3.e) Camera->Add Component->Post-Process Layer + + 3.f) Camera->Post-Process Layer->Layer: Everything (as example) + + 3.g) Camera->Add Component->Post-Process Volume + + 3.h) Camera->Post-Process Volume->Is Global: check (as example) + + 3.i) Camera->Post-Process Volume->Profile: New + + 3.j) Camera->Post-Process Volume->Add effect... AmplifyCreations->AmplifyOcclusion + +# Documentation + + Please refer to the following website for an up-to-date online manual: + + http://amplify.pt/unity/amplify-occlusion/manual + +# Acknowledgements + + AO v2.0 was developed by the talented Mário Luzeiro: + + https://pt.linkedin.com/in/mluzeiro + https://twitter.com/mluzeiro + diff --git a/SubmersedVR/AmplifyOcclusion/Readme.txt.meta b/SubmersedVR/AmplifyOcclusion/Readme.txt.meta new file mode 100644 index 0000000..4b8419f --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Readme.txt.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 77317bbba8f48fc44ab8ce181ec0a1cc +timeCreated: 1455194262 +licenseType: Store +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources.meta b/SubmersedVR/AmplifyOcclusion/Resources.meta new file mode 100644 index 0000000..8650028 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b93186d740e83604b9c1a073b45f4059 +folderAsset: yes +timeCreated: 1449343868 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/Apply.shader b/SubmersedVR/AmplifyOcclusion/Resources/Apply.shader new file mode 100644 index 0000000..bf76fac --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/Apply.shader @@ -0,0 +1,430 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +Shader "Hidden/Amplify Occlusion/Apply" +{ + CGINCLUDE + #pragma vertex vert + #pragma fragment frag + #pragma target 3.0 + #pragma exclude_renderers gles d3d11_9x n3ds + #pragma multi_compile_instancing + + #include "Common.cginc" + #include "TemporalFilter.cginc" + + UNITY_DECLARE_SCREENSPACE_TEXTURE( _AO_GBufferAlbedo ); + UNITY_DECLARE_SCREENSPACE_TEXTURE( _AO_GBufferEmission ); + UNITY_DECLARE_SCREENSPACE_TEXTURE( _AO_ApplyOcclusionTexture ); + + + struct DeferredOutput + { + half4 albedo : SV_Target0; + half4 emission : SV_Target1; + }; + + struct DeferredOutputTemporal + { + half4 albedo : SV_Target0; + half4 emission : SV_Target1; + half4 temporalAcc : SV_Target2; + }; + + + #include "ApplyPostEffect.cginc" + + + PostEffectOutputTemporal ApplyPostEffectTemporal( v2f_in IN, const bool aUseMotionVectors ) + { + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const float2 screenPos = IN.uv.xy; + + const half2 occlusionDepth = FetchOcclusionDepth( screenPos ); + + PostEffectOutputTemporal OUT; + + if( occlusionDepth.y < HALF_MAX ) + { + const half linear01Depth = occlusionDepth.y * ONE_OVER_DEPTH_SCALE; + const half sampledDepth = Linear01ToSampledDepth( linear01Depth ); + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + half occlusion; + const half4 temporalAcc = TemporalFilter( screenPos, occlusionDepth.x, sampledDepth, linearEyeDepth, linear01Depth, aUseMotionVectors, occlusion ); + + const half4 occlusionRGBA = CalcOcclusion( occlusion, linearEyeDepth ); + + OUT.occlusionColor = occlusionRGBA; + OUT.temporalAcc = temporalAcc; + } + else + { + OUT.occlusionColor = half4( (1).xxxx ); + OUT.temporalAcc = half4( (1).xxxx ); + } + + return OUT; + } + + + half4 ApplyPostEffect( v2f_in IN ) + { + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 screenPos = IN.uv.xy; + + const half2 occlusionDepth = FetchOcclusionDepth( screenPos ); + + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + const half4 occlusionRGBA = CalcOcclusion( occlusionDepth.x, linearEyeDepth ); + + return half4( occlusionRGBA.rgb, 1 ); + } + + + DeferredOutput ApplyDeferred( v2f_in IN, const bool log ) + { + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 screenPos = IN.uv.xy; + + const half2 occlusionDepth = FetchOcclusionDepth( screenPos ); + + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + const half4 occlusionRGBA = CalcOcclusion( occlusionDepth.x, linearEyeDepth ); + + half4 emission, albedo; + + if ( log ) + { + emission = UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_GBufferEmission, UnityStereoTransformScreenSpaceTex( screenPos ) ); + albedo = UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_GBufferAlbedo, UnityStereoTransformScreenSpaceTex( screenPos ) ); + + emission.rgb = -log2( emission.rgb ); + emission.rgb *= occlusionRGBA.rgb; + + albedo.a *= occlusionRGBA.a; + + emission.rgb = exp2( -emission.rgb ); + } + else + { + albedo = half4( 1, 1, 1, occlusionRGBA.a ); + emission = half4( occlusionRGBA.rgb, 1 ); + } + + DeferredOutput OUT; + OUT.albedo = albedo; + OUT.emission = emission; + return OUT; + } + + + DeferredOutput ApplyDeferredCombineFromTemporal( v2f_in IN, const bool log ) + { + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 screenPos = IN.uv.xy; + + const half depthSample = SampleDepth0( screenPos ); + + const half2 occlusionLinearEyeDepth = ComputeCombineDownsampledOcclusionFromTemporal( screenPos, depthSample ); + + const half4 occlusionRGBA = CalcOcclusion( occlusionLinearEyeDepth.x, occlusionLinearEyeDepth.y ); + + half4 emission, albedo; + + if ( log ) + { + emission = UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_GBufferEmission, UnityStereoTransformScreenSpaceTex( screenPos ) ); + albedo = UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_GBufferAlbedo, UnityStereoTransformScreenSpaceTex( screenPos ) ); + + emission.rgb = -log2( emission.rgb ); + emission.rgb *= occlusionRGBA.rgb; + + albedo.a *= occlusionRGBA.a; + + emission.rgb = exp2( -emission.rgb ); + } + else + { + albedo = half4( 1, 1, 1, occlusionRGBA.a ); + emission = half4( occlusionRGBA.rgb, 1 ); + } + + DeferredOutput OUT; + OUT.albedo = albedo; + OUT.emission = emission; + return OUT; + } + + + DeferredOutputTemporal ApplyDeferredTemporal( v2f_in IN, const bool log, const bool aUseMotionVectors ) + { + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const float2 screenPos = IN.uv.xy; + + const half2 occlusionDepth = FetchOcclusionDepth( screenPos ); + + DeferredOutputTemporal OUT; + + if( occlusionDepth.y < HALF_MAX ) + { + const half linear01Depth = occlusionDepth.y * ONE_OVER_DEPTH_SCALE; + const half sampledDepth = Linear01ToSampledDepth( linear01Depth ); + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + half occlusion; + const half4 temporalAcc = TemporalFilter( screenPos, occlusionDepth.x, sampledDepth, linearEyeDepth, linear01Depth, aUseMotionVectors, occlusion ); + + const half4 occlusionRGBA = CalcOcclusion( occlusion, linearEyeDepth ); + + half4 emission, albedo; + + if ( log ) + { + emission = UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_GBufferEmission, UnityStereoTransformScreenSpaceTex( screenPos ) ); + albedo = UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_GBufferAlbedo, UnityStereoTransformScreenSpaceTex( screenPos ) ); + + emission.rgb = -log2( emission.rgb ); + emission.rgb *= occlusionRGBA.rgb; + + albedo.a *= occlusionRGBA.a; + + emission.rgb = exp2( -emission.rgb ); + } + else + { + albedo = half4( 1, 1, 1, occlusionRGBA.a ); + emission = half4( occlusionRGBA.rgb, 1 ); + } + + OUT.albedo = albedo; + OUT.emission = emission; + OUT.temporalAcc = temporalAcc; + } + else + { + half4 emission, albedo; + + if ( log ) + { + emission = UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_GBufferEmission, UnityStereoTransformScreenSpaceTex( screenPos ) ); + albedo = UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_GBufferAlbedo, UnityStereoTransformScreenSpaceTex( screenPos ) ); + } + else + { + albedo = half4( (1).xxxx ); + emission = half4( (1).xxxx ); + } + + OUT.albedo = albedo; + OUT.emission = emission; + OUT.temporalAcc = half4( (1).xxxx ); + } + + return OUT; + } + + + half4 ApplyPostEffectTemporalMultiply( v2f_in IN ) + { + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + return UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_ApplyOcclusionTexture, UnityStereoTransformScreenSpaceTex( IN.uv.xy ) ); + } + + DeferredOutput ApplyDeferredTemporalMultiply( v2f_in IN ) + { + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half4 occlusionRGBA = UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_ApplyOcclusionTexture, UnityStereoTransformScreenSpaceTex( IN.uv.xy ) ); + + DeferredOutput OUT; + OUT.albedo = half4( (1.0).xxx, occlusionRGBA.a ); + OUT.emission = half4( occlusionRGBA.rgb, 1.0 ); + + return OUT; + } + + ENDCG + + /////////////////////////////////////////////////////////////////////////////////////// + // MRT BLENDING PATH + /////////////////////////////////////////////////////////////////////////////////////// + + SubShader + { + Tags { "MRTBlending" = "True" } + ZTest Always Cull Off ZWrite Off + + // -- APPLICATION METHODS -------------------------------------------------------------- + // 0 => APPLY DEBUG + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return ApplyDebug( IN ); } ENDCG } + // 1 => APPLY DEBUG Temporal + Pass { CGPROGRAM PostEffectOutputTemporal frag( v2f_in IN ) { return ApplyDebugTemporal( IN, false); } ENDCG } + Pass { CGPROGRAM PostEffectOutputTemporal frag( v2f_in IN ) { return ApplyDebugTemporal( IN, true ); } ENDCG } + + // 3 => APPLY DEFERRED + Pass + { + Blend DstColor Zero, DstAlpha Zero + CGPROGRAM DeferredOutput frag( v2f_in IN ) { return ApplyDeferred( IN, false ); } ENDCG + } + // 4 => APPLY DEFERRED Temporal + Pass + { + Blend 0 DstColor Zero, DstAlpha Zero + Blend 1 DstColor Zero, DstAlpha Zero + Blend 2 Off + CGPROGRAM DeferredOutputTemporal frag( v2f_in IN ) { return ApplyDeferredTemporal( IN, false, false ); } ENDCG + } + Pass + { + Blend 0 DstColor Zero, DstAlpha Zero + Blend 1 DstColor Zero, DstAlpha Zero + Blend 2 Off + CGPROGRAM DeferredOutputTemporal frag( v2f_in IN ) { return ApplyDeferredTemporal( IN, false, true ); } ENDCG + } + + // 6 => APPLY DEFERRED (LOG) + Pass { CGPROGRAM DeferredOutput frag( v2f_in IN ) { return ApplyDeferred( IN, true ); } ENDCG } + // 7 => APPLY DEFERRED (LOG) Temporal + Pass { CGPROGRAM DeferredOutputTemporal frag( v2f_in IN ) { return ApplyDeferredTemporal( IN, true, false ); } ENDCG } + Pass { CGPROGRAM DeferredOutputTemporal frag( v2f_in IN ) { return ApplyDeferredTemporal( IN, true, true ); } ENDCG } + + + // 9 => APPLY POST-EFFECT + Pass + { + Blend DstColor Zero + CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return ApplyPostEffect( IN ); } ENDCG + } + // 10 => APPLY POST-EFFECT Temporal + Pass + { + Blend 0 DstColor Zero + Blend 1 Off + CGPROGRAM PostEffectOutputTemporal frag( v2f_in IN ) { return ApplyPostEffectTemporal( IN, false ); } ENDCG + } + Pass + { + Blend 0 DstColor Zero + Blend 1 Off + CGPROGRAM PostEffectOutputTemporal frag( v2f_in IN ) { return ApplyPostEffectTemporal( IN, true ); } ENDCG + } + + // 12 => dummy + Pass { } + + // 13 => dummy + Pass { } + + // 14 => APPLY DEBUG Combine from Temporal + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return ApplyDebugCombineFromTemporal( IN ); } ENDCG } + + // 15 => APPLY POST-EFFECT Combine from Temporal + Pass + { + Blend DstColor Zero + CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return ApplyCombineFromTemporal( IN ); } ENDCG + } + + // 16 => APPLY DEFERRED Combine from Temporal + Pass + { + Blend DstColor Zero, DstAlpha Zero + CGPROGRAM DeferredOutput frag( v2f_in IN ) { return ApplyDeferredCombineFromTemporal( IN, false ); } ENDCG + } + + // 17 => APPLY DEFERRED (LOG) Combine from Temporal + Pass { CGPROGRAM DeferredOutput frag( v2f_in IN ) { return ApplyDeferredCombineFromTemporal( IN, true ); } ENDCG } + } + + /////////////////////////////////////////////////////////////////////////////////////// + // NO MRT BLENDING FALLBACK + /////////////////////////////////////////////////////////////////////////////////////// + + SubShader + { + Tags { "MRTBlending" = "False" } + ZTest Always Cull Off ZWrite Off + + // -- APPLICATION METHODS -------------------------------------------------------------- + // 0 => APPLY DEBUG + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return ApplyDebug( IN ); } ENDCG } + // 1 => APPLY DEBUG Temporal + Pass { CGPROGRAM PostEffectOutputTemporal frag( v2f_in IN ) { return ApplyDebugTemporal( IN, false ); } ENDCG } + Pass { CGPROGRAM PostEffectOutputTemporal frag( v2f_in IN ) { return ApplyDebugTemporal( IN, true ); } ENDCG } + + // 3 => APPLY DEFERRED + Pass + { + Blend DstColor Zero, DstAlpha Zero + CGPROGRAM DeferredOutput frag( v2f_in IN ) { return ApplyDeferred( IN, false ); } ENDCG + } + // 4 => APPLY DEFERRED Temporal + Pass { CGPROGRAM DeferredOutputTemporal frag( v2f_in IN ) { return ApplyDeferredTemporal( IN, false, false ); } ENDCG } + Pass { CGPROGRAM DeferredOutputTemporal frag( v2f_in IN ) { return ApplyDeferredTemporal( IN, false, true ); } ENDCG } + + // 6 => APPLY DEFERRED (LOG) + Pass { CGPROGRAM DeferredOutput frag( v2f_in IN ) { return ApplyDeferred( IN, true ); } ENDCG } + // 7 => APPLY DEFERRED (LOG) Temporal + Pass { CGPROGRAM DeferredOutputTemporal frag( v2f_in IN ) { return ApplyDeferredTemporal( IN, true, false ); } ENDCG } + Pass { CGPROGRAM DeferredOutputTemporal frag( v2f_in IN ) { return ApplyDeferredTemporal( IN, true, true ); } ENDCG } + + // 9 => APPLY POST-EFFECT + Pass { Blend DstColor Zero CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return ApplyPostEffect( IN ); } ENDCG } + // 10 => APPLY POST-EFFECT Temporal + Pass { CGPROGRAM PostEffectOutputTemporal frag( v2f_in IN ) { return ApplyPostEffectTemporal( IN, false ); } ENDCG } + Pass { CGPROGRAM PostEffectOutputTemporal frag( v2f_in IN ) { return ApplyPostEffectTemporal( IN, true ); } ENDCG } + + // 12 => APPLY POST-EFFECT Temporal Multiply + Pass + { + Blend DstColor Zero + CGPROGRAM half4 frag( v2f_in IN ) : SV_Target0 { return ApplyPostEffectTemporalMultiply( IN ); } ENDCG + } + + // 13 => APPLY DEFERRED Temporal Multiply + Pass + { + Blend DstColor Zero, DstAlpha Zero + CGPROGRAM DeferredOutput frag( v2f_in IN ) { return ApplyDeferredTemporalMultiply( IN ); } ENDCG + } + + // 14 => APPLY DEBUG Combine from Temporal + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return ApplyDebugCombineFromTemporal( IN ); } ENDCG } + + // 15 => APPLY POST-EFFECT Combine from Temporal + Pass + { + Blend DstColor Zero + CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return ApplyCombineFromTemporal( IN ); } ENDCG + } + + // 16 => APPLY DEFERRED Combine from Temporal + Pass + { + Blend DstColor Zero, DstAlpha Zero + CGPROGRAM DeferredOutput frag( v2f_in IN ) { return ApplyDeferredCombineFromTemporal( IN, false ); } ENDCG + } + + // 17 => APPLY DEFERRED (LOG) Combine from Temporal + Pass { CGPROGRAM DeferredOutput frag( v2f_in IN ) { return ApplyDeferredCombineFromTemporal( IN, true ); } ENDCG } + } + + Fallback Off +} diff --git a/SubmersedVR/AmplifyOcclusion/Resources/Apply.shader.meta b/SubmersedVR/AmplifyOcclusion/Resources/Apply.shader.meta new file mode 100644 index 0000000..0b1a5b1 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/Apply.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 537ee96eb0f0e5348bead799d732fa65 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/ApplyPostEffect.cginc b/SubmersedVR/AmplifyOcclusion/Resources/ApplyPostEffect.cginc new file mode 100644 index 0000000..399e305 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/ApplyPostEffect.cginc @@ -0,0 +1,99 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +#ifndef AMPLIFY_AO_APPLY_POSTEFFECT +#define AMPLIFY_AO_APPLY_POSTEFFECT + +struct PostEffectOutputTemporal +{ + half4 occlusionColor : SV_Target0; + half4 temporalAcc : SV_Target1; +}; + +half4 ApplyDebug( const v2f_in IN ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 screenPos = IN.uv.xy; + + const half2 occlusionDepth = FetchOcclusionDepth( screenPos ); + + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + const half4 occlusionRGBA = CalcOcclusion( occlusionDepth.x, linearEyeDepth ); + + return half4( occlusionRGBA.rgb, 1 ); +} + + +half4 ApplyDebugCombineFromTemporal( const v2f_in IN ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 screenPos = IN.uv.xy; + + const half depthSample = SampleDepth0( screenPos ); + + const half2 occlusionLinearEyeDepth = ComputeCombineDownsampledOcclusionFromTemporal( screenPos, depthSample ); + + const half4 occlusionRGBA = CalcOcclusion( occlusionLinearEyeDepth.x, occlusionLinearEyeDepth.y ); + + return half4( occlusionRGBA.rgb, 1 ); +} + + +half4 ApplyCombineFromTemporal( const v2f_in IN ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 screenPos = IN.uv.xy; + + const half depthSample = SampleDepth0( screenPos ); + + const half2 occlusionLinearEyeDepth = ComputeCombineDownsampledOcclusionFromTemporal( screenPos, depthSample ); + + const half4 occlusionRGBA = CalcOcclusion( occlusionLinearEyeDepth.x, occlusionLinearEyeDepth.y ); + + return half4( ( occlusionLinearEyeDepth.y < HALF_MAX )?occlusionRGBA.rgb:(1).xxx, 1 ); +} + + +PostEffectOutputTemporal ApplyDebugTemporal( const v2f_in IN, const bool aUseMotionVectors ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const float2 screenPos = IN.uv.xy; + + const half2 occlusionDepth = FetchOcclusionDepth( screenPos ); + + PostEffectOutputTemporal OUT; + + if( occlusionDepth.y < HALF_MAX ) + { + const half linear01Depth = occlusionDepth.y * ONE_OVER_DEPTH_SCALE; + const half sampledDepth = Linear01ToSampledDepth( linear01Depth ); + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + half occlusion; + const half4 temporalAcc = TemporalFilter( screenPos, occlusionDepth.x, sampledDepth, linearEyeDepth, linear01Depth, aUseMotionVectors, occlusion ); + + const half4 occlusionRGBA = CalcOcclusion( occlusion, linearEyeDepth ); + + OUT.occlusionColor = occlusionRGBA; + OUT.temporalAcc = temporalAcc; + } + else + { + OUT.occlusionColor = half4( (1).xxxx ); + OUT.temporalAcc = half4( (1).xxxx ); + } + + return OUT; +} + + +#endif diff --git a/SubmersedVR/AmplifyOcclusion/Resources/ApplyPostEffect.cginc.meta b/SubmersedVR/AmplifyOcclusion/Resources/ApplyPostEffect.cginc.meta new file mode 100644 index 0000000..82ccc23 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/ApplyPostEffect.cginc.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 4f6e1460bc909e1459e29dbab8171ec9 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/Blur.shader b/SubmersedVR/AmplifyOcclusion/Resources/Blur.shader new file mode 100644 index 0000000..726fb8b --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/Blur.shader @@ -0,0 +1,54 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +Shader "Hidden/Amplify Occlusion/Blur" +{ + Properties { } + CGINCLUDE + #pragma vertex vert + #pragma fragment frag + #pragma target 3.0 + #pragma exclude_renderers gles d3d11_9x n3ds + + #include "Common.cginc" + #include "BlurFunctions.cginc" + ENDCG + + + SubShader + { + ZTest Always Cull Off ZWrite Off + + // 0 => BLUR HORIZONTAL R:1 + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return blur1D_1x( IN, half2( _AO_CurrOcclusionDepth_TexelSize.x, 0 ) ); } ENDCG } + + // 1 => BLUR VERTICAL R:1 + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return blur1D_1x( IN, half2( 0, _AO_CurrOcclusionDepth_TexelSize.y ) ); } ENDCG } + + // 2 => BLUR HORIZONTAL R:2 + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return blur1D_2x( IN, half2( _AO_CurrOcclusionDepth_TexelSize.x, 0 ) ); } ENDCG } + + // 3 => BLUR VERTICAL R:2 + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return blur1D_2x( IN, half2( 0, _AO_CurrOcclusionDepth_TexelSize.y ) ); } ENDCG } + + // 4 => BLUR HORIZONTAL R:3 + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return blur1D_3x( IN, half2( _AO_CurrOcclusionDepth_TexelSize.x, 0 ) ); } ENDCG } + + // 5 => BLUR VERTICAL R:3 + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return blur1D_3x( IN, half2( 0, _AO_CurrOcclusionDepth_TexelSize.y ) ); } ENDCG } + + // 6 => BLUR HORIZONTAL R:4 + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return blur1D_4x( IN, half2( _AO_CurrOcclusionDepth_TexelSize.x, 0 ) ); } ENDCG } + + // 7 => BLUR VERTICAL R:4 + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return blur1D_4x( IN, half2( 0, _AO_CurrOcclusionDepth_TexelSize.y ) ); } ENDCG } + + // 8 => BLUR HORIZONTAL INTENSITY + Pass { CGPROGRAM half frag( v2f_in IN ) : SV_Target { return blur1D_Intensity( IN, half2( _AO_CurrMotionIntensity_TexelSize.x, 0 ) ); } ENDCG } + + // 9 => BLUR VERTICAL INTENSITY + Pass { CGPROGRAM half frag( v2f_in IN ) : SV_Target { return blur1D_Intensity( IN, half2( 0, _AO_CurrMotionIntensity_TexelSize.y ) ); } ENDCG } + } + + Fallback Off +} diff --git a/SubmersedVR/AmplifyOcclusion/Resources/Blur.shader.meta b/SubmersedVR/AmplifyOcclusion/Resources/Blur.shader.meta new file mode 100644 index 0000000..57ca388 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/Blur.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 721d4727a93c1e548a5c6160a6229427 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/BlurFunctions.cginc b/SubmersedVR/AmplifyOcclusion/Resources/BlurFunctions.cginc new file mode 100644 index 0000000..01d3fc6 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/BlurFunctions.cginc @@ -0,0 +1,192 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +#ifndef AMPLIFY_AO_BLURFUNCTIONS +#define AMPLIFY_AO_BLURFUNCTIONS + + +float _AO_BlurSharpness; + +inline half ComputeSharpness( half linearEyeDepth ) +{ + return _AO_BlurSharpness * ( saturate( 1 - linearEyeDepth ) + 0.01 ); +} + +inline half ComputeFalloff( const int radius ) +{ + return 2.0 / ( radius * radius ); +} + +inline half2 CrossBilateralWeight( const half2 r, half2 d, half d0, const half sharpness, const half falloff ) +{ + half2 diff = ( d0 - d ) * sharpness; + return exp2( -( r * r ) * falloff - diff * diff ); +} + +inline half4 CrossBilateralWeight( const half4 r, half4 d, half d0, const half sharpness, const half falloff ) +{ + half4 diff = ( d0 - d ) * sharpness; + return exp2( -( r * r ) * falloff - diff * diff ); +} + + +half4 blur1D_1x( v2f_in IN, half2 deltaUV ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 occlusionDepth = FetchOcclusionDepth( IN.uv ); + + const half occlusion = occlusionDepth.x; + const half depth = occlusionDepth.y; + + const half2 offset1 = 1.55 * deltaUV; + + half4 s1; + s1.xy = FetchOcclusionDepth( IN.uv + offset1 ).xy; + s1.zw = FetchOcclusionDepth( IN.uv - offset1 ).xy; + + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + const half sharpness = ComputeSharpness( linearEyeDepth ); + const half falloff = ComputeFalloff( 2 ); + + const half2 w1 = CrossBilateralWeight( ( 1.0 ).xx, s1.yw, depth, sharpness, falloff ); + + half ao = occlusion + dot( s1.xz, w1 ); + ao /= 1.0 + dot( ( 1.0 ).xx, w1 ); + + return half4( ao, depth, 0.0, 0.0 ); +} + +half4 blur1D_2x( v2f_in IN, half2 deltaUV ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 occlusionDepth = FetchOcclusionDepth( IN.uv ); + + const half occlusion = occlusionDepth.x; + const half depth = occlusionDepth.y; + + const half2 offset1 = 1.2 * deltaUV; + const half2 offset2 = 2.5 * deltaUV; + + half4 s1, s2; + s2.zw = FetchOcclusionDepth( IN.uv - offset2 ).xy; + s1.zw = FetchOcclusionDepth( IN.uv - offset1 ).xy; + s1.xy = FetchOcclusionDepth( IN.uv + offset1 ).xy; + s2.xy = FetchOcclusionDepth( IN.uv + offset2 ).xy; + + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + const half sharpness = ComputeSharpness( linearEyeDepth ); + const half falloff = ComputeFalloff( 4 ); + + const half4 w12 = CrossBilateralWeight( half4( 1, 1, 2, 2 ), half4( s1.yw, s2.yw ), depth, sharpness, falloff ); + + half ao = occlusion + dot( half4( s1.xz, s2.xz ), w12 ); + ao /= 1.0 + dot( ( 1.0 ).xxxx, w12 ); + + return half4( ao, depth, 0.0, 0.0 ); +} + + +half4 blur1D_3x( v2f_in IN, half2 deltaUV ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 occlusionDepth = FetchOcclusionDepth( IN.uv ); + + const half occlusion = occlusionDepth.x; + const half depth = occlusionDepth.y; + + const half2 offset1 = 1.0 * deltaUV; + const half2 offset2 = 2.2 * deltaUV; + const half2 offset3 = 3.5 * deltaUV; + + half4 s1, s2, s3; + s3.zw = FetchOcclusionDepth( IN.uv - offset3 ).xy; + s2.zw = FetchOcclusionDepth( IN.uv - offset2 ).xy; + s1.zw = FetchOcclusionDepth( IN.uv - offset1 ).xy; + s1.xy = FetchOcclusionDepth( IN.uv + offset1 ).xy; + s2.xy = FetchOcclusionDepth( IN.uv + offset2 ).xy; + s3.xy = FetchOcclusionDepth( IN.uv + offset3 ).xy; + + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + const half sharpness = ComputeSharpness( linearEyeDepth ); + const half falloff = ComputeFalloff( 6 ); + + const half4 w12 = CrossBilateralWeight( half4( 1, 1, 2, 2 ), half4( s1.yw, s2.yw ), depth, sharpness, falloff ); + const half2 w3 = CrossBilateralWeight( ( 3 ).xx, s3.yw, depth, sharpness, falloff ); + + half ao = occlusion + dot( half4( s1.xz, s2.xz ), w12 ) + dot( s3.xz, w3 ); + ao /= 1.0 + dot( ( 1.0 ).xxxx, w12 ) + dot( ( 1 ).xx, w3 ); + + return half4( ao, depth, 0.0, 0.0 ); +} + + +half4 blur1D_4x( v2f_in IN, half2 deltaUV ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const half2 occlusionDepth = FetchOcclusionDepth( IN.uv ); + + const half occlusion = occlusionDepth.x; + const half depth = occlusionDepth.y; + + const half2 offset1 = 1.1 * deltaUV; + const half2 offset2 = 2.2 * deltaUV; + const half2 offset3 = 3.3 * deltaUV; + const half2 offset4 = 4.5 * deltaUV; + + half4 s1, s2, s3, s4; + s4.zw = FetchOcclusionDepth( IN.uv - offset4 ).xy; + s3.zw = FetchOcclusionDepth( IN.uv - offset3 ).xy; + s2.zw = FetchOcclusionDepth( IN.uv - offset2 ).xy; + s1.zw = FetchOcclusionDepth( IN.uv - offset1 ).xy; + s1.xy = FetchOcclusionDepth( IN.uv + offset1 ).xy; + s2.xy = FetchOcclusionDepth( IN.uv + offset2 ).xy; + s3.xy = FetchOcclusionDepth( IN.uv + offset3 ).xy; + s4.xy = FetchOcclusionDepth( IN.uv + offset4 ).xy; + + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + const half sharpness = ComputeSharpness( linearEyeDepth ); + const half falloff = ComputeFalloff( 8 ); + + const half4 w12 = CrossBilateralWeight( half4( 1, 1, 2, 2 ), half4( s1.yw, s2.yw ), depth, sharpness, falloff ); + const half4 w34 = CrossBilateralWeight( half4( 3, 3, 4, 4 ), half4( s3.yw, s4.yw ), depth, sharpness, falloff ); + + half ao = occlusion + dot( half4( s1.xz, s2.xz ), w12 ) + dot( half4( s3.xz, s4.xz ), w34 ); + ao /= 1.0 + dot( ( 1.0 ).xxxx, w12 ) + dot( ( 1 ).xxxx, w34 ); + + return half4( ao, depth, 0.0, 0.0 ); +} + + +half4 blur1D_Intensity( v2f_in IN, half2 deltaUV ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + half3 c; + + c.x = FetchMotionIntensity( IN.uv ); + c.y = FetchMotionIntensity( IN.uv + deltaUV * 1.5 ); + c.z = FetchMotionIntensity( IN.uv - deltaUV * 1.5 ); + + half2 c2; + c2.x = FetchMotionIntensity( IN.uv + deltaUV * 3.3 ); + c2.y = FetchMotionIntensity( IN.uv - deltaUV * 3.3 ); + + const half outV = saturate( dot( half3( 0.30, 0.25, 0.25 ), c ) + dot( (0.15).xx, c2 ) ); + + return outV; +} + +#endif diff --git a/SubmersedVR/AmplifyOcclusion/Resources/BlurFunctions.cginc.meta b/SubmersedVR/AmplifyOcclusion/Resources/BlurFunctions.cginc.meta new file mode 100644 index 0000000..9f75370 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/BlurFunctions.cginc.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 858fbcabf8fbfcf46bef60ce5729a5ce +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/Common.cginc b/SubmersedVR/AmplifyOcclusion/Resources/Common.cginc new file mode 100644 index 0000000..14e959e --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/Common.cginc @@ -0,0 +1,165 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +#ifndef AMPLIFY_AO_COMMON_INCLUDED +#define AMPLIFY_AO_COMMON_INCLUDED + +#pragma multi_compile_instancing + +#include "UnityCG.cginc" + + +#if !defined( UNITY_DECLARE_DEPTH_TEXTURE ) +#define UNITY_DECLARE_DEPTH_TEXTURE(tex) sampler2D_float tex +#endif + +#if !defined( UNITY_DECLARE_SCREENSPACE_TEXTURE ) +#define UNITY_DECLARE_SCREENSPACE_TEXTURE(tex) sampler2D tex; +#endif + +#if !defined( UNITY_SAMPLE_SCREENSPACE_TEXTURE ) +#define UNITY_SAMPLE_SCREENSPACE_TEXTURE(tex, uv) tex2D(tex, uv) +#endif + +#if !defined( UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX ) +#define UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(x) +#endif + + +inline float2 AO_ComputeScreenPos( float4 aVertex ) +{ + return ComputeScreenPos( aVertex ).xy; +} + + +#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED) +UNITY_DECLARE_SCREENSPACE_TEXTURE( _CameraMotionVectorsTexture ); +#else +sampler2D_half _CameraMotionVectorsTexture; +#endif + +float4 _CameraMotionVectorsTexture_TexelSize; + +inline half2 FetchMotion( const half2 aUV ) +{ + return UNITY_SAMPLE_SCREENSPACE_TEXTURE( _CameraMotionVectorsTexture, UnityStereoTransformScreenSpaceTex( aUV) ).rg; +} + + +UNITY_DECLARE_SCREENSPACE_TEXTURE( _AO_CurrMotionIntensity ); +float4 _AO_CurrMotionIntensity_TexelSize; + +inline half FetchMotionIntensity( const half2 aUV ) +{ + return UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_CurrMotionIntensity, UnityStereoTransformScreenSpaceTex( aUV ) ).r; +} + + +UNITY_DECLARE_SCREENSPACE_TEXTURE( _AO_CurrOcclusionDepth ); +float4 _AO_CurrOcclusionDepth_TexelSize; + +inline half2 FetchOcclusionDepth( const half2 aUV ) +{ + return UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_CurrOcclusionDepth, UnityStereoTransformScreenSpaceTex( aUV ) ).rg; +} + + +UNITY_DECLARE_DEPTH_TEXTURE( _CameraDepthTexture ); +half4 _CameraDepthTexture_TexelSize; + +inline half SampleDepth0( const half2 aScreenPos ) +{ + return SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, half4( UnityStereoTransformScreenSpaceTex( aScreenPos ), 0, 0 ) ); +} + +UNITY_DECLARE_SCREENSPACE_TEXTURE( _AO_SourceDepthMipmap ); +half4 _AO_SourceDepthMipmap_TexelSize; + +inline half SampleDepth( const half2 aScreenPos, half aLOD ) +{ + return SAMPLE_DEPTH_TEXTURE_LOD( _AO_SourceDepthMipmap, half4( UnityStereoTransformScreenSpaceTex( aScreenPos ), 0, aLOD ) ); +} + + +UNITY_DECLARE_SCREENSPACE_TEXTURE( _CameraDepthNormalsTexture ); + +inline half4 FetchDepthNormals( const half2 aScreenPos ) +{ + return UNITY_SAMPLE_SCREENSPACE_TEXTURE( _CameraDepthNormalsTexture, UnityStereoTransformScreenSpaceTex( aScreenPos ) ); +} + + +UNITY_DECLARE_SCREENSPACE_TEXTURE( _AO_TemporalAccumm ); +float4 _AO_TemporalAccumm_TexelSize; + +inline half4 FetchTemporal( const half2 aScreenPos ) +{ + return UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_TemporalAccumm, UnityStereoTransformScreenSpaceTex( aScreenPos ) ); +} + + +UNITY_DECLARE_SCREENSPACE_TEXTURE( _AO_GBufferNormals ); + +inline half4 FetchGBufferNormals( const half2 aScreenPos ) +{ + return UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_GBufferNormals, UnityStereoTransformScreenSpaceTex( aScreenPos ) ); +} + +inline half3 FetchGBufferNormalWS( const half2 aScreenPos, const bool aIsGBufferOctaEnconded ) +{ + const half4 gbuffer2 = FetchGBufferNormals( aScreenPos ); + + half3 N = gbuffer2.rgb * 2.0 - 1.0; + + if ( ( aIsGBufferOctaEnconded == true ) && ( gbuffer2.a < 1 ) ) + { + N.z = 1 - abs( N.x ) - abs( N.y ); + N.xy = ( N.z >= 0 ) ? N.xy : ( ( 1 - abs( N.yx ) ) * sign( N.xy ) ); + } + + return N; +} + + +UNITY_DECLARE_SCREENSPACE_TEXTURE( _AO_CurrDepthSource ); +half4 _AO_CurrDepthSource_TexelSize;; + +inline half FetchCurrDepthSource( const half2 aScreenPos ) +{ + return UNITY_SAMPLE_SCREENSPACE_TEXTURE( _AO_CurrDepthSource, UnityStereoTransformScreenSpaceTex( aScreenPos ) ); +} + + +#include "CommonFunctions.cginc" + + +v2f_out vert( appdata v ) +{ + v2f_out o; + UNITY_SETUP_INSTANCE_ID( v ); + UNITY_TRANSFER_INSTANCE_ID( v, o ); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO( o ); + + float4 vertex = float4( v.vertex.xy * 2.0 - 1.0, 0.0, 1.0 ); + +#ifdef UNITY_HALF_TEXEL_OFFSET + vertex.xy += ( 1.0 / _AO_Target_TexelSize.zw ) * float2( -1, 1 ); +#endif + + o.pos = vertex; + +#ifdef UNITY_SINGLE_PASS_STEREO + #if UNITY_UV_STARTS_AT_TOP + o.uv = float2( v.uv.x, 1.0 - v.uv.y ); + #else + o.uv = v.uv; + #endif +#else + o.uv = AO_ComputeScreenPos( vertex ); +#endif + + return o; +} + + +#endif diff --git a/SubmersedVR/AmplifyOcclusion/Resources/Common.cginc.meta b/SubmersedVR/AmplifyOcclusion/Resources/Common.cginc.meta new file mode 100644 index 0000000..8709231 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/Common.cginc.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 5f16bb82b4f4448dc9f754decd0f8796 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/CommonFunctions.cginc b/SubmersedVR/AmplifyOcclusion/Resources/CommonFunctions.cginc new file mode 100644 index 0000000..3439028 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/CommonFunctions.cginc @@ -0,0 +1,201 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +#ifndef AMPLIFY_AO_COMMON_FUNCTIONS_INCLUDED +#define AMPLIFY_AO_COMMON_FUNCTIONS_INCLUDED + +#define PIXEL_RADIUS_LIMIT ( 512 ) + +#define HALF_MAX 65504.0 // (2 - 2^-10) * 2^15 +#define HALF_MAX_MINUS1 65472.0 // (2 - 2^-9) * 2^15 +#define DEPTH_EPSILON 1e-6 +#define INTENSITY_THRESHOLD 1e-4 + +#if defined( SHADER_API_MOBILE ) +#define DEPTH_SCALE 16376.0 +#else +#define DEPTH_SCALE 65504.0 +#endif + +#define ONE_OVER_DEPTH_SCALE ( 1.0 / DEPTH_SCALE ) + +float _AO_TemporalMotionSensibility; +half _AO_TemporalDirections; +half _AO_TemporalOffsets; +half _AO_HalfProjScale; +half _AO_Radius; +float _AO_BufDepthToLinearEye; + +inline half DecAO( const half ao ) +{ + return sqrt( ao ); +} + +inline half EncAO( const half ao ) +{ + return saturate( ao * ao ); +} + +inline half DecDepth( const float2 aEncodedDepth ) +{ + return DecodeFloatRG( aEncodedDepth ); +} + +inline half2 EncDepth( const half aDepth ) +{ + return EncodeFloatRG( aDepth ); +} + +float2 _AO_FadeParams; +float4 _AO_FadeValues; +half4 _AO_Levels; +half4 _AO_FadeToTint; +half _AO_PowExponent; + +inline half ComputeDistanceFade( const half distance ) +{ + return saturate( max( 0.0, distance - _AO_FadeParams.x ) * _AO_FadeParams.y ); +} + +inline half4 CalcOcclusion( const half aOcclusion, const half aLinearDepth ) +{ + const half distanceFade = ComputeDistanceFade( aLinearDepth ); + + const half exponent = lerp( _AO_PowExponent, _AO_FadeValues.z, distanceFade ); + + const half occlusion = pow( max( aOcclusion, 0.0 ), exponent ); + + half3 tintedOcclusion = lerp( _AO_Levels.rgb, _AO_FadeToTint.rgb, distanceFade ); + + tintedOcclusion = lerp( tintedOcclusion, ( 1 ).xxx, occlusion.xxx ); + + const half intensity = lerp( _AO_Levels.a, _AO_FadeValues.x, distanceFade ); + + return lerp( ( 1 ).xxxx, half4( tintedOcclusion.rgb, occlusion ), intensity ); +} + +inline float LinearEyeToSampledDepth( float linearEyeDepth ) +{ + return ( 1.0 - linearEyeDepth * _ZBufferParams.w ) / ( linearEyeDepth * _ZBufferParams.z ); +} + +inline float2 LinearEyeToSampledDepth( float2 linearEyeDepth ) +{ + return ( (1.0).xx - linearEyeDepth * ( _ZBufferParams.w ).xx ) / ( linearEyeDepth * ( _ZBufferParams.z ).xx ); +} + +inline float3 LinearEyeToSampledDepth( float3 linearEyeDepth ) +{ + return ( (1.0).xxx - linearEyeDepth * ( _ZBufferParams.w ).xxx ) / ( linearEyeDepth * ( _ZBufferParams.z ).xxx ); +} + +inline float4 LinearEyeToSampledDepth( float4 linearEyeDepth ) +{ + return ( (1.0).xxxx - linearEyeDepth * ( _ZBufferParams.w ).xxxx ) / ( linearEyeDepth * ( _ZBufferParams.z ).xxxx ); +} + +inline float Linear01ToSampledDepth( float linear01Depth ) +{ + return ( 1.0 - linear01Depth * _ZBufferParams.y ) / ( linear01Depth * _ZBufferParams.x ); +} + +inline float2 Linear01ToSampledDepth( float2 linear01Depth ) +{ + return ( (1.0).xx - linear01Depth * _ZBufferParams.y ) / ( linear01Depth * ( _ZBufferParams.x ).xx ); +} + +inline float4 Linear01ToSampledDepth( float4 linear01Depth ) +{ + return ( (1.0).xxxx - linear01Depth * ( _ZBufferParams.y ).xxxx ) / ( linear01Depth * ( _ZBufferParams.x ).xxxx ); +} + + +struct appdata +{ + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + + +struct v2f_out +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID + UNITY_VERTEX_OUTPUT_STEREO +}; + + +struct v2f_in +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID + UNITY_VERTEX_OUTPUT_STEREO +}; + + +half4 _AO_Target_TexelSize; + +// Jimenez's "Interleaved Gradient Noise" +inline half JimenezNoise( const half2 xyPixelPos ) +{ + return frac( 52.9829189 * frac( dot( xyPixelPos, half2( 0.06711056, 0.00583715 ) ) ) ); +} + + +inline void GetSpatialDirections_Offsets_JimenezNoise( const half2 aScreenPos, + const half2 aTextureSizeZW, + out half outNoiseSpatialOffsets, + out half outNoiseSpatialDirections ) +{ +#if defined( SHADER_API_D3D9 ) || defined( SHADER_API_MOBILE ) + // Spatial Offsets and Directions - s2016_pbs_activision_occlusion - Slide 93 + const half2 xyPixelPos = ceil( UnityStereoTransformScreenSpaceTex( aScreenPos ) * aTextureSizeZW ); + outNoiseSpatialOffsets = ( 1.0 / 4.0 ) * (half)( frac( ( xyPixelPos.y - xyPixelPos.x ) / 4.0 ) * 4.0 ); + + outNoiseSpatialDirections = JimenezNoise( (half2)xyPixelPos ); +#else + // Spatial Offsets and Directions - s2016_pbs_activision_occlusion - Slide 93 + const int2 xyPixelPos = (int2)( UnityStereoTransformScreenSpaceTex( aScreenPos ) * aTextureSizeZW ); + outNoiseSpatialOffsets = ( 1.0 / 4.0 ) * (half)( ( xyPixelPos.y - xyPixelPos.x ) & 3 ); + + outNoiseSpatialDirections = JimenezNoise( (half2)xyPixelPos ); +#endif +} + +inline void GetSpatialDirections_Offsets( const half2 aScreenPos, + const half2 aTextureSizeZW, + out half outNoiseSpatialOffsets, + out half outNoiseSpatialDirections ) +{ +#if defined( SHADER_API_D3D9 ) || defined( SHADER_API_MOBILE ) + // Spatial Offsets and Directions - s2016_pbs_activision_occlusion - Slide 93 + const half2 xyPixelPos = ceil( UnityStereoTransformScreenSpaceTex( aScreenPos ) * aTextureSizeZW ); + outNoiseSpatialOffsets = ( 1.0 / 4.0 ) * (half)( frac( ( xyPixelPos.y - xyPixelPos.x ) / 4.0 ) * 4.0 ); + + // Noise Spatial Directions + // X 0 1 2 3 + // Y0 00 05 10 15 + // Y1 04 09 14 03 + // Y2 08 13 02 07 + // Y3 12 01 06 11 + outNoiseSpatialDirections = ( 1.0 / 16.0 ) * (half)( ( ( frac( ( xyPixelPos.x + xyPixelPos.y ) / 4.0 ) * 4.0 ) * 4.0 ) + frac( xyPixelPos.x / 4.0 ) * 4.0 ); +#else + // Spatial Offsets and Directions - s2016_pbs_activision_occlusion - Slide 93 + const int2 xyPixelPos = (int2)( UnityStereoTransformScreenSpaceTex( aScreenPos ) * aTextureSizeZW ); + outNoiseSpatialOffsets = ( 1.0 / 4.0 ) * (half)( ( xyPixelPos.y - xyPixelPos.x ) & 3 ); + + // Noise Spatial Directions + // X 0 1 2 3 + // Y0 00 05 10 15 + // Y1 04 09 14 03 + // Y2 08 13 02 07 + // Y3 12 01 06 11 + outNoiseSpatialDirections = ( 1.0 / 16.0 ) * (half)( ( ( ( xyPixelPos.x + xyPixelPos.y ) & 0x3 ) << 2 ) | ( xyPixelPos.x & 0x3 ) ); +#endif +} + + +#endif diff --git a/SubmersedVR/AmplifyOcclusion/Resources/CommonFunctions.cginc.meta b/SubmersedVR/AmplifyOcclusion/Resources/CommonFunctions.cginc.meta new file mode 100644 index 0000000..d7d35d7 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/CommonFunctions.cginc.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 7f28133a2946e6b4dafae26e24d4fcc9 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/GTAO.cginc b/SubmersedVR/AmplifyOcclusion/Resources/GTAO.cginc new file mode 100644 index 0000000..9245f7b --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/GTAO.cginc @@ -0,0 +1,366 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +#ifndef AMPLIFY_AO_GTAO +#define AMPLIFY_AO_GTAO + +half4x4 _AO_CameraViewLeft; +half4x4 _AO_CameraViewRight; + +#ifdef UNITY_SINGLE_PASS_STEREO +half4x4 _AO_ProjMatrixLeft; +half4x4 _AO_ProjMatrixRight; +#endif + + +half4 _AO_UVToView; + +half _AO_Bias; + +half _AO_ThicknessDecay; + +half4 _AO_Source_TexelSize; + +#define NORMALS_NONE ( 0 ) +#define NORMALS_CAMERA ( 1 ) +#define NORMALS_GBUFFER ( 2 ) +#define NORMALS_GBUFFER_OCTA_ENCODED ( 3 ) + + +inline half4 ConvertDepth( const half2 aUV, const half aSampledDepth ) +{ + const half viewDepth = LinearEyeDepth( aSampledDepth ); + + #if defined(UNITY_REVERSED_Z) + const half sampledDepth = 1.0 - aSampledDepth; + #else + const half sampledDepth = aSampledDepth; + #endif + + #ifdef UNITY_SINGLE_PASS_STEREO + const half4 clipPos = half4( aUV * 2.0 - 1.0, sampledDepth, 1.0f ); + + half4x4 projMatrix = ( unity_StereoEyeIndex == 0 ) ? _AO_ProjMatrixLeft : _AO_ProjMatrixRight; + half4 viewPos = mul( projMatrix, clipPos ); + + return half4( viewPos.xy * viewDepth, viewDepth, sampledDepth ); + #else + return half4( ( aUV * _AO_UVToView.xy + _AO_UVToView.zw ) * viewDepth, viewDepth, sampledDepth ); + #endif +} + +inline half4 FetchPosition0( const half2 aUV ) +{ + const half sampledDepth = SampleDepth0( aUV ); + + return ConvertDepth( aUV, sampledDepth ); +} + + +inline half4 FetchPosition( const half2 aUV, half aLOD ) +{ + const half sampledDepth = SampleDepth( aUV, aLOD ); + + return ConvertDepth( aUV, sampledDepth ); +} + + +inline half3 FetchNormal( const half2 uv, const uint normalSource ) +{ + if( normalSource == NORMALS_CAMERA ) + { + const half4 cameraDepthNormals = FetchDepthNormals( uv ); + const half3 normalScreenSpace = DecodeViewNormalStereo( cameraDepthNormals ); + + return half3( normalScreenSpace.xy, -normalScreenSpace.z ); + } + else if ( (normalSource == NORMALS_GBUFFER) || (normalSource == NORMALS_GBUFFER_OCTA_ENCODED) ) + { + const half3 normalWS = FetchGBufferNormalWS( uv, ( normalSource == NORMALS_GBUFFER_OCTA_ENCODED ) ); + + #ifdef UNITY_SINGLE_PASS_STEREO + const half4x4 cameraView = ( unity_StereoEyeIndex == 0 ) ? _AO_CameraViewLeft : _AO_CameraViewRight; + #else + const half4x4 cameraView = _AO_CameraViewLeft; + #endif + + const half3 normalScreenSpace = normalize( mul( ( half3x3 ) cameraView, normalWS ) ); + + return half3( normalScreenSpace.xy, -normalScreenSpace.z ); + } + else + { + const float3 c = FetchPosition0( uv ).xyz; + const float3 r = FetchPosition0( uv + half2( +1.0 , 0.0 ) * _AO_Target_TexelSize.xy ).xyz; + const float3 l = FetchPosition0( uv + half2( -1.0 , 0.0 ) * _AO_Target_TexelSize.xy ).xyz; + const float3 t = FetchPosition0( uv + half2( 0.0, +1.0 ) * _AO_Target_TexelSize.xy ).xyz; + const float3 b = FetchPosition0( uv + half2( 0.0, -1.0 ) * _AO_Target_TexelSize.xy ).xyz; + const float3 vr = ( r - c ), vl = ( c - l ), vt = ( t - c ), vb = ( c - b ); + const float3 min_horiz = ( dot( vr, vr ) < dot( vl, vl ) ) ? vr : vl; + const float3 min_vert = ( dot( vt, vt ) < dot( vb, vb ) ) ? vt : vb; + const float3 normalScreenSpace = normalize( cross( min_horiz, min_vert ) ); + + return half3( -normalScreenSpace.x, -normalScreenSpace.y, -normalScreenSpace.z ); + } +} + + +inline void GetFadeIntensity_Radius_Thickness( const half aLinearDepth, + out half outIntensity, + out half outRadius, + out half outThicknessDecay ) +{ + // Calc distance fade parameters + const half3 intensity_radius_thickness = lerp( half3( _AO_Levels.a, _AO_Radius, _AO_ThicknessDecay ), + _AO_FadeValues.xyw, + ( ComputeDistanceFade( aLinearDepth ) ).xxx ); + + outIntensity = intensity_radius_thickness.x; + outRadius = intensity_radius_thickness.y; + outThicknessDecay = intensity_radius_thickness.z; +} + + +inline half2 SampleStepsDynamicDepthMips( const int aStepCount, + const half2 aScreenPos, + const half2 aSlideDir_x_TexelSize, + const half aStepRadius, + const half aInitialRayStep, + const half aTwoOverSquaredRadius, + const half aThicknessDecay, + const half3 aVpos, + const half3 aVdir ) +{ + half2 h = half2( -1.0, -1.0 ); + + UNITY_UNROLL + for ( int s = 0; s < aStepCount; s++ ) + { + const half2 uvOffset = aSlideDir_x_TexelSize * max( aStepRadius * ( (half)s + aInitialRayStep ), 1.0 + (half)s ); + + const half4 uv = aScreenPos.xyxy + float4( +uvOffset.xy, -uvOffset.xy ); + + // ds.v / ||ds|| + // dt.v / ||dt|| + half3 ds, dt; + + if( s == 0 ) + { + ds = FetchPosition0( uv.xy ).xyz; + dt = FetchPosition0( uv.zw ).xyz; + } + else + { + const half2 offsetPixels = _AO_Source_TexelSize.zw * uvOffset.xy; + const half distancePixels = max( offsetPixels.x, offsetPixels.y ); + const half mipLevel = max( log2( distancePixels ) * 0.70 - 2.0, 0.0 ); + + ds = FetchPosition( uv.xy , mipLevel ).xyz; + dt = FetchPosition( uv.zw , mipLevel ).xyz; + } + + ds = ds - aVpos.xyz; + dt = dt - aVpos.xyz; + + const half2 dsdt = half2( dot( ds, ds ), dot( dt, dt ) ); + const half2 rLength = rsqrt( dsdt + ( 0.0001 ).xx ); + + half2 H = half2( dot( ds, aVdir ), dot( dt, aVdir ) ) * rLength.xy; + + // "Conservative attenuation" - s2016_pbs_activision_occlusion - Slide 103 + // "Our attenuation function is a linear blending from 1 to 0 from a given, + // large enough distance, to the maximum search radius." GTAO - Page 4 + const half2 attn = saturate( dsdt.xy * aTwoOverSquaredRadius.xx ); + + H = lerp( H, h, attn ); + + h.xy = ( H.xy > h.xy ) ? H.xy : lerp( H.xy, h.xy, aThicknessDecay.xx ); + } + + return h; +} + + +inline half2 SampleSteps( const int aStepCount, + const half2 aScreenPos, + const half2 aSlideDir_x_TexelSize, + const half aStepRadius, + const half aInitialRayStep, + const half aTwoOverSquaredRadius, + const half aThicknessDecay, + const half3 aVpos, + const half3 aVdir ) +{ + half2 h = half2( -1.0, -1.0 ); + + UNITY_UNROLL + for ( int s = 0; s < aStepCount; s++ ) + { + const half2 uvOffset = aSlideDir_x_TexelSize * max( aStepRadius * ( (half)s + aInitialRayStep ), 1.0 + (half)s ); + + // s2016_pbs_activision_occlusion - Slide 54 + + const half4 uv = aScreenPos.xyxy + float4( +uvOffset.xy, -uvOffset.xy ); + + // ds.v / ||ds|| + half3 ds = FetchPosition0( uv.xy ).xyz - aVpos.xyz; + + // dt.v / ||dt|| + half3 dt = FetchPosition0( uv.zw ).xyz - aVpos.xyz; + + const half2 dsdt = half2( dot( ds, ds ), dot( dt, dt ) ); + const half2 rLength = rsqrt( dsdt + ( 0.0001 ).xx ); + + half2 H = half2( dot( ds, aVdir ), dot( dt, aVdir ) ) * rLength.xy; + + // "Conservative attenuation" - s2016_pbs_activision_occlusion - Slide 103 + // "Our attenuation function is a linear blending from 1 to 0 from a given, + // large enough distance, to the maximum search radius." GTAO - Page 4 + const half2 attn = saturate( dsdt.xy * aTwoOverSquaredRadius.xx ); + + H = lerp( H, h, attn ); + + h.xy = ( H.xy > h.xy ) ? H.xy : lerp( H.xy, h.xy, aThicknessDecay.xx ); + } + + return h; +} + + +void GetGTAO( const v2f_in ifrag, + const bool useDynamicDepthMips, + const int directionCount, + const int stepCount, + const int normalSource, + out half outDepth, + out half4 outRGBA ) +{ + + const half2 screenPos = ifrag.uv.xy; + + const half2 depthUVoffset = half2( 0.25, 0.25 ) * _AO_Target_TexelSize.xy; // Avoids occluded horizontal and vertical bands when using half resolution depth sampling. + + const half2 sampleUVdepth = screenPos - depthUVoffset; + + const half sampledDepth = SampleDepth0( sampleUVdepth ); + + const half4 vpos = ConvertDepth( sampleUVdepth, sampledDepth ); + + half intensity, radius, thicknessDecay; + GetFadeIntensity_Radius_Thickness( vpos.z, intensity, radius, thicknessDecay ); + + //UNITY_BRANCH + #if defined(UNITY_REVERSED_Z) + if( ( sampledDepth <= DEPTH_EPSILON ) || ( intensity < INTENSITY_THRESHOLD ) ) + #else + if( ( sampledDepth >= ( 1.0 - DEPTH_EPSILON ) ) || ( intensity < INTENSITY_THRESHOLD ) ) + #endif + { + outDepth = HALF_MAX; + outRGBA = half4( (1.0).xxxx ); + + return; + } + + const half3 vnormal = FetchNormal( screenPos - depthUVoffset, normalSource ); + + const half3 vdir = normalize( (0).xxx - vpos.xyz ); + + const half vdirXYDot = dot( vdir.xy, vdir.xy ); + + const half radiusToScreen = radius * _AO_HalfProjScale; + const half screenRadius = max( min( radiusToScreen / vpos.z, PIXEL_RADIUS_LIMIT ), (half)stepCount ); + + const half twoOverSquaredRadius = 2.0 / ( radius * radius ); + + const half stepRadius = screenRadius / ( (half)stepCount + 1.0 ); + + half noiseSpatialOffsets, noiseSpatialDirections; + GetSpatialDirections_Offsets_JimenezNoise( screenPos, _AO_Target_TexelSize.zw, noiseSpatialOffsets, noiseSpatialDirections ); + + noiseSpatialDirections += _AO_TemporalDirections; + + const half initialRayStep = frac( noiseSpatialOffsets + _AO_TemporalOffsets ); + + const half piOver_directionCount = UNITY_PI / (half)directionCount; + const half noiseSpatialDirections_x_piOver_directionCount = noiseSpatialDirections * piOver_directionCount; + + half occlusionAcc = 0.0; + + for ( int dirCnt = 0; dirCnt < directionCount; dirCnt++ ) + { + const half angle = (half)dirCnt * piOver_directionCount + noiseSpatialDirections_x_piOver_directionCount; + + const half2 cos_sin = half2( cos( angle ), sin( angle ) ); + + const half3 sliceDir = half3( cos_sin.xy, 0.0 ); + + half wallDarkeningCorrection = dot( vnormal, cross( vdir, sliceDir ) ) * vdirXYDot; + wallDarkeningCorrection = wallDarkeningCorrection * wallDarkeningCorrection; + + const half2 slideDir_x_TexelSize = sliceDir.xy * _AO_Target_TexelSize.xy; + + half2 h; + + if( useDynamicDepthMips == true ) + { + h = SampleStepsDynamicDepthMips( stepCount, + screenPos, + slideDir_x_TexelSize, + stepRadius, + initialRayStep, + twoOverSquaredRadius, + thicknessDecay, + vpos.xyz, + vdir ); + } + else + { + h = SampleSteps( stepCount, + screenPos, + slideDir_x_TexelSize, + stepRadius, + initialRayStep, + twoOverSquaredRadius, + thicknessDecay, + vpos.xyz, + vdir ); + } + + // "Project (and normalize) the normal to the slice plane for the integration" - s2016_pbs_activision_occlusion - Slide 62 + const half3 normalSlicePlane = normalize( cross( sliceDir, vdir ) ); + const half3 tangent = cross( vdir, normalSlicePlane ); + + // Gram-Schmidt process + // https://en.wikipedia.org/wiki/Gram-Schmidt_process + const half3 normalProjected = vnormal - normalSlicePlane * dot( vnormal, normalSlicePlane ); // There is no need to divide by ||np|| as np is normalized + + const half projLength = length( normalProjected ) + 0.0001; + const half cos_gamma = clamp( dot( normalProjected, vdir /*w0*/ ) / projLength, -1.0, 1.0 ); // cos() = a.b / ( ||a|| * ||b|| ) + const half gamma = -sign( dot( normalProjected, tangent ) ) * acos( cos_gamma ); + + h = acos( clamp( h, -1.0, 1.0 ) ); + + // "We have to clamp them with the normal hemisphere" - s2016_pbs_activision_occlusion - Slide 58 + h.x = gamma + max( -h.x - gamma, -UNITY_HALF_PI ); + h.y = gamma + min( +h.y - gamma, +UNITY_HALF_PI ); + + const half sin_gamma = sin( gamma ); + const half2 h2 = 2.0 * h; + + const half2 innerIntegral = ( -cos( h2 - gamma ) + cos_gamma + h2 * sin_gamma ); + + // "Multiply each slice contribution by the length of the projected normal" - s2016_pbs_activision_occlusion - Slide 62 + occlusionAcc += ( projLength + wallDarkeningCorrection ) * 0.25 * ( innerIntegral.x + innerIntegral.y + _AO_Bias ); + } + + occlusionAcc /= (half)directionCount; + + const half outAO = saturate( occlusionAcc ); + + outRGBA = half4( (1).xxx, outAO ); + + outDepth = DEPTH_SCALE * Linear01Depth( sampledDepth ); +} + +#endif diff --git a/SubmersedVR/AmplifyOcclusion/Resources/GTAO.cginc.meta b/SubmersedVR/AmplifyOcclusion/Resources/GTAO.cginc.meta new file mode 100644 index 0000000..daefd44 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/GTAO.cginc.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 19bf4ddbaf23247e790ed5c05d3f2244 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/Occlusion.shader b/SubmersedVR/AmplifyOcclusion/Resources/Occlusion.shader new file mode 100644 index 0000000..bba63c3 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/Occlusion.shader @@ -0,0 +1,93 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +Shader "Hidden/Amplify Occlusion/Occlusion" +{ + CGINCLUDE + #pragma vertex vert + #pragma fragment frag + #pragma target 3.0 + #pragma exclude_renderers gles d3d11_9x n3ds + + #include "Common.cginc" + #include "GTAO.cginc" + #include "OcclusionFunctions.cginc" + #include "TemporalFilter.cginc" + ENDCG + + SubShader + { + ZTest Always + Cull Off + ZWrite Off + + // 0-3 => FULL OCCLUSION - LOW QUALITY directionCount / sampleCount + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 2, 4, NORMALS_NONE ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 2, 4, NORMALS_CAMERA ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 2, 4, NORMALS_GBUFFER ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 2, 4, NORMALS_GBUFFER_OCTA_ENCODED ); } ENDCG } + + // 04-07 => FULL OCCLUSION / MEDIUM QUALITY + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 2, 6, NORMALS_NONE ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 2, 6, NORMALS_CAMERA ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 2, 6, NORMALS_GBUFFER ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 2, 6, NORMALS_GBUFFER_OCTA_ENCODED ); } ENDCG } + + // 08-11 => FULL OCCLUSION - HIGH QUALITY + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 3, 8, NORMALS_NONE ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 3, 8, NORMALS_CAMERA ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 3, 8, NORMALS_GBUFFER ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 3, 8, NORMALS_GBUFFER_OCTA_ENCODED ); } ENDCG } + + // 12-15 => FULL OCCLUSION / VERYHIGH QUALITY + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 4, 10, NORMALS_NONE ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 4, 10, NORMALS_CAMERA ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 4, 10, NORMALS_GBUFFER ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, false, 4, 10, NORMALS_GBUFFER_OCTA_ENCODED ); } ENDCG } + + + // 16-19 => FULL OCCLUSION - LOW QUALITY directionCount / sampleCount + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 2, 4, NORMALS_NONE ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 2, 4, NORMALS_CAMERA ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 2, 4, NORMALS_GBUFFER ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 2, 4, NORMALS_GBUFFER_OCTA_ENCODED ); } ENDCG } + + // 20-23 => FULL OCCLUSION / MEDIUM QUALITY + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 2, 6, NORMALS_NONE ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 2, 6, NORMALS_CAMERA ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 2, 6, NORMALS_GBUFFER ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 2, 6, NORMALS_GBUFFER_OCTA_ENCODED ); } ENDCG } + + // 24-27 => FULL OCCLUSION - HIGH QUALITY + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 3, 8, NORMALS_NONE ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 3, 8, NORMALS_CAMERA ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 3, 8, NORMALS_GBUFFER ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 3, 8, NORMALS_GBUFFER_OCTA_ENCODED ); } ENDCG } + + // 28-31 => FULL OCCLUSION / VERYHIGH QUALITY + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 4, 10, NORMALS_NONE ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 4, 10, NORMALS_CAMERA ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 4, 10, NORMALS_GBUFFER ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return GTAO( IN, true, 4, 10, NORMALS_GBUFFER_OCTA_ENCODED ); } ENDCG } + + // 32 => CombineDownsampledOcclusionDepth + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return CombineDownsampledOcclusionDepth( IN ); } ENDCG } + + // 33 => Neighbor Motion Intensity + Pass { CGPROGRAM half2 frag( v2f_in IN ) : SV_Target { return NeighborMotionIntensity( IN ); } ENDCG } + + // 34 => Clear Temporal + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return ClearTemporal( IN ); } ENDCG } + + // 35 => ScaleDownCloserDepthEven + Pass { CGPROGRAM float frag( v2f_in IN ) : SV_Target { return ScaleDownCloserDepthEven( IN ); } ENDCG } + + // 36 => ScaleDownCloserDepthEven_CameraDepthTexture + Pass { CGPROGRAM float frag( v2f_in IN ) : SV_Target { return ScaleDownCloserDepthEven_CameraDepthTexture( IN ); } ENDCG } + + // 37 => Temporal + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return Temporal( IN, false ); } ENDCG } + Pass { CGPROGRAM half4 frag( v2f_in IN ) : SV_Target { return Temporal( IN, true ); } ENDCG } + } +} + diff --git a/SubmersedVR/AmplifyOcclusion/Resources/Occlusion.shader.meta b/SubmersedVR/AmplifyOcclusion/Resources/Occlusion.shader.meta new file mode 100644 index 0000000..f744d9d --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/Occlusion.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: ca3c44b6d4f2d4d1095e1e783d9684ba +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/OcclusionFunctions.cginc b/SubmersedVR/AmplifyOcclusion/Resources/OcclusionFunctions.cginc new file mode 100644 index 0000000..e064dad --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/OcclusionFunctions.cginc @@ -0,0 +1,195 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +#ifndef AMPLIFY_AO_OCCLUSIONFUNCTIONS +#define AMPLIFY_AO_OCCLUSIONFUNCTIONS + + +half4 GTAO( const v2f_in ifrag, const bool useDynamicDepthMips, const int directionCount, const int sampleCount, const int normalSource ) +{ + UNITY_SETUP_INSTANCE_ID( ifrag ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( ifrag ); + + half outDepth; + half4 outRGBA; + + GetGTAO( ifrag, useDynamicDepthMips, directionCount, sampleCount / 2, normalSource, outDepth, outRGBA ); + + return half4( outRGBA.a, outDepth, 0, 0 ); +} + + +inline half4 ComputeCombineDownsampledOcclusionDepth( const half2 aScreenPos, const half aDepthSample ) +{ + const half referenceDepth = LinearEyeDepth( aDepthSample ); + + const half intensity = lerp( _AO_Levels.a, _AO_FadeValues.x, ComputeDistanceFade( referenceDepth ) ); + + //UNITY_BRANCH + #if defined(UNITY_REVERSED_Z) + if( ( aDepthSample <= DEPTH_EPSILON ) || ( intensity < INTENSITY_THRESHOLD ) ) + #else + if( ( aDepthSample >= ( 1.0 - DEPTH_EPSILON ) ) || ( intensity < INTENSITY_THRESHOLD ) ) + #endif + { + return half4( 1.0, HALF_MAX, 0, 0 ); + } + + const half2 screenPosPixels = aScreenPos * _AO_CurrOcclusionDepth_TexelSize.zw; + const half2 screenPosPixelsFloor = floor( screenPosPixels ); + const half2 screenPosPixelsDelta = screenPosPixels - screenPosPixelsFloor; + + const half2 sPosAdjusted = screenPosPixelsFloor * _AO_CurrOcclusionDepth_TexelSize.xy + half2( 0.5, 0.5 ) * _AO_CurrOcclusionDepth_TexelSize.xy; + const half s = ( screenPosPixelsDelta.y < 0.5 )?-1.0:1.0; + + half2 odC = FetchOcclusionDepth( sPosAdjusted ); + + half2 odL = FetchOcclusionDepth( sPosAdjusted + half2( -1.0, 0.0 ) * _AO_CurrOcclusionDepth_TexelSize.xy ); + half2 odR = FetchOcclusionDepth( sPosAdjusted + half2( +1.0, 0.0 ) * _AO_CurrOcclusionDepth_TexelSize.xy ); + half2 odM = FetchOcclusionDepth( sPosAdjusted + half2( 0.0, s ) * _AO_CurrOcclusionDepth_TexelSize.xy ); + + const half4 o0123 = half4( odC.x, odL.x, odR.x, odM.x ); + const half4 d0123 = half4( odC.y, odL.y, odR.y, odM.y ); + + half4 depthWeight0123 = saturate( 1.0 / ( abs( Linear01ToSampledDepth( d0123 * ONE_OVER_DEPTH_SCALE ) - ( aDepthSample ).xxxx ) * 32768 + 0.95 ) ); + + const half4 pixelDeltaWeight = half4( screenPosPixelsDelta.x * screenPosPixelsDelta.y + 0.5, + 1.0 - screenPosPixelsDelta.x, + screenPosPixelsDelta.x, + 0.80 ); + + depthWeight0123 = depthWeight0123 * depthWeight0123 * pixelDeltaWeight; + + half weightOcclusion = dot( o0123, depthWeight0123 ); + + const half outOcclusion = saturate( weightOcclusion / dot( ( 1 ).xxxx, depthWeight0123 ) ); + + const half linearDepth01 = Linear01Depth( aDepthSample ); + + return half4( outOcclusion, DEPTH_SCALE * linearDepth01, 0, 0 ); +} + + +half4 CombineDownsampledOcclusionDepth( const v2f_in ifrag ) +{ + UNITY_SETUP_INSTANCE_ID( ifrag ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( ifrag ); + + const half2 screenPos = ifrag.uv.xy; + + const half depthSample = SampleDepth0( screenPos ); + + return ComputeCombineDownsampledOcclusionDepth( screenPos, depthSample ); +} + + +inline half FetchNeighborMotion( const half2 aUV, const half aRadiusPixels, const half2 aCentralmv ) +{ + half noiseSpatialOffsets, noiseSpatialDirections; + GetSpatialDirections_Offsets_JimenezNoise( aUV, _CameraMotionVectorsTexture_TexelSize.zw * 0.5, noiseSpatialOffsets, noiseSpatialDirections ); + + const half angle = ( noiseSpatialDirections ) * UNITY_PI; + const half2 cos_sin = half2( cos( angle ), sin( angle ) ); + const half2 xy = ( aRadiusPixels * noiseSpatialOffsets + 8.0 ).xx * cos_sin; + + const half2 xLyT = FetchMotion( half2( -xy.x, -xy.y ) * _CameraMotionVectorsTexture_TexelSize.xy + aUV ); + const half2 xRyD = FetchMotion( half2( +xy.x, +xy.y ) * _CameraMotionVectorsTexture_TexelSize.xy + aUV ); + + const half2 xLyT_v = xLyT - aCentralmv; + const half2 xRyD_v = xRyD - aCentralmv; + + const half2 sqrLength2 = half2( dot( xLyT_v, xLyT_v ), dot( xRyD_v, xRyD_v ) ); + const half neighborMotion = max( sqrLength2.x, sqrLength2.y ); + const half oneOverPixelDeltaSquared = 1.0 / ( _CameraMotionVectorsTexture_TexelSize.x * _CameraMotionVectorsTexture_TexelSize.x ); + + const half outNeighborMotion = saturate( neighborMotion * oneOverPixelDeltaSquared - 1.0 ) * _AO_TemporalMotionSensibility; + + return outNeighborMotion; +} + + +half NeighborMotionIntensity( v2f_in IN ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const float2 screenPos = IN.uv.xy; + + const half depthSample = SampleDepth0( screenPos ); + + if( depthSample < HALF_MAX ) + { + half linearEyeDepth = LinearEyeDepth( depthSample ); + + const half radius = lerp( _AO_Radius, _AO_FadeValues.y, ComputeDistanceFade( linearEyeDepth ) ); + const half radiusToScreen = radius * _AO_HalfProjScale; + const half screenRadius = max( min( ( radiusToScreen / linearEyeDepth ), PIXEL_RADIUS_LIMIT ), 2 ); + + const half2 cMv = FetchMotion( screenPos ); + + const half motionNeighborDisoclusion = FetchNeighborMotion( screenPos, screenRadius, cMv ); + + return motionNeighborDisoclusion; + } + else + { + return 0; + } +} + + +half4 ClearTemporal( const v2f_in ifrag ) +{ + UNITY_SETUP_INSTANCE_ID( ifrag ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( ifrag ); + + return half4( EncAO( 0.99 ), 1.0, 1.0, 0.0 ); +} + + +float ScaleDownCloserDepthEven( v2f_in IN ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + // Snap uv to source pixel center + const half2 screenPos = floor( ( IN.uv.xy + half2( 1.0, 1.0 ) * _AO_CurrDepthSource_TexelSize.xy ) * _AO_CurrDepthSource_TexelSize.zw ) * _AO_CurrDepthSource_TexelSize.xy - half2( 0.5, 0.5 ) * _AO_CurrDepthSource_TexelSize.xy; + + const float d0 = FetchCurrDepthSource( screenPos + half2( 0.0, 0.0 ) * _AO_CurrDepthSource_TexelSize.xy ); + const float d1 = FetchCurrDepthSource( screenPos + half2( 1.0, 0.0 ) * _AO_CurrDepthSource_TexelSize.xy ); + const float d2 = FetchCurrDepthSource( screenPos + half2( 0.0, 1.0 ) * _AO_CurrDepthSource_TexelSize.xy ); + const float d3 = FetchCurrDepthSource( screenPos + half2( 1.0, 1.0 ) * _AO_CurrDepthSource_TexelSize.xy ); + + #if defined(UNITY_REVERSED_Z) + const float closerDepth = max( d0, max( d1, max( d2, d3 ) ) ); + #else + const float closerDepth = min( d0, min( d1, min( d2, d3 ) ) ); + #endif + + return closerDepth; +} + + +float ScaleDownCloserDepthEven_CameraDepthTexture( v2f_in IN ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + // Snap uv to source pixel center + const half2 screenPos = floor( ( IN.uv.xy + half2( 1.0, 1.0 ) * _CameraDepthTexture_TexelSize.xy ) * _CameraDepthTexture_TexelSize.zw ) * _CameraDepthTexture_TexelSize.xy - half2( 0.5, 0.5 ) * _CameraDepthTexture_TexelSize.xy; + + const float d0 = SampleDepth0( screenPos + half2( 0.0, 0.0 ) * _CameraDepthTexture_TexelSize.xy ); + const float d1 = SampleDepth0( screenPos + half2( 1.0, 0.0 ) * _CameraDepthTexture_TexelSize.xy ); + const float d2 = SampleDepth0( screenPos + half2( 0.0, 1.0 ) * _CameraDepthTexture_TexelSize.xy ); + const float d3 = SampleDepth0( screenPos + half2( 1.0, 1.0 ) * _CameraDepthTexture_TexelSize.xy ); + + #if defined(UNITY_REVERSED_Z) + const float closerDepth = max( d0, max( d1, max( d2, d3 ) ) ); + #else + const float closerDepth = min( d0, min( d1, min( d2, d3 ) ) ); + #endif + + return closerDepth; +} + +#endif \ No newline at end of file diff --git a/SubmersedVR/AmplifyOcclusion/Resources/OcclusionFunctions.cginc.meta b/SubmersedVR/AmplifyOcclusion/Resources/OcclusionFunctions.cginc.meta new file mode 100644 index 0000000..2025500 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/OcclusionFunctions.cginc.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 03e610c22ca9f8c4298d97ae79125541 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Resources/TemporalFilter.cginc b/SubmersedVR/AmplifyOcclusion/Resources/TemporalFilter.cginc new file mode 100644 index 0000000..609446b --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/TemporalFilter.cginc @@ -0,0 +1,594 @@ +// Amplify Occlusion 2 - Robust Ambient Occlusion for Unity +// Copyright (c) Amplify Creations, Lda + +#ifndef AMPLIFY_AO_TEMPORALFILTER +#define AMPLIFY_AO_TEMPORALFILTER + + +//#define NEIGHBOR_SAMPLE_4TAP +#define NEIGHBOR_SAMPLE_4TAP_CROSS +//#define NEIGHBOR_SAMPLE_8TAP + +#define CLAMP_MINMAX + +#define DEPTH_WEIGHTING ( _AO_TemporalMotionSensibility * 1000 + 200.0 ) +#define DEPTH_WEIGHTING_OFFSET ( 0.10 ) + +//#define MINMAX_DEVIATION +#define VARIANCE_CLIPPING ( 7.0 ) +#define VARIANCE_CLIPPING_OFFSET ( 1.5 ) + +#define SIGMA_SCALE_MAX ( 0.75 ) +#define SIGMA_SCALE_MIN ( 1.00 ) + +#define MOTION_ATTEN +#define DISOCCLUSION_ATTEN +#define DISOCCLUSION_NEIGHBOR +#define OUTOFRANGE_COMPENSATION +#define OUTOFRANGE_ENDCOMPENSATION + + +float4x4 _AO_InvViewProjMatrixLeft; +float4x4 _AO_PrevViewProjMatrixLeft; +float4x4 _AO_PrevInvViewProjMatrixLeft; + +float4x4 _AO_InvViewProjMatrixRight; +float4x4 _AO_PrevViewProjMatrixRight; +float4x4 _AO_PrevInvViewProjMatrixRight; + +float _AO_TemporalCurveAdj; + +inline half CalcDisocclusion( const half aDepth, const half aPrevDepth, const half aSensibility ) +{ + const half depthDiff = abs( aDepth - aPrevDepth ); + return saturate( aSensibility * depthDiff - 0.02 ); +} + + +inline half2 FetchPrevAO_Depth( const float2 aUV ) +{ + float2 uv = aUV; +/*#if UNITY_UV_STARTS_AT_TOP + uv.y = ( _ProjectionParams.x > 0 ) ? 1 - aUV.y : aUV.y; +#endif*/ + const half4 temporalAccummEnc = FetchTemporal( uv ); + + return half2( DecAO( temporalAccummEnc.x ), DecDepth( temporalAccummEnc.yz ) ); +} + + +inline half3 FetchPrevAO_Depth_N( const float2 aUV ) +{ + float2 uv = aUV; +#if UNITY_UV_STARTS_AT_TOP + uv.y = ( _ProjectionParams.x > 0 ) ? 1 - aUV.y : aUV.y; +#endif + const half4 temporalAccummEnc = FetchTemporal( uv ); + + return half3( DecAO( temporalAccummEnc.x ), DecDepth( temporalAccummEnc.yz ), temporalAccummEnc.w ); +} + + +inline half FetchPrevN( const float2 aUV ) +{ + float2 uv = aUV; +#if UNITY_UV_STARTS_AT_TOP + uv.y = ( _ProjectionParams.x > 0 ) ? 1 - aUV.y : aUV.y; +#endif + const half4 temporalAccummEnc = FetchTemporal( uv ); + + return temporalAccummEnc.w; +} + + +#if defined( VARIANCE_CLIPPING ) + +inline half ComputeSigma( const half aInSigma, const half aMotionNeighborIntensity, const half aDisocclusion, const half aPrevN ) +{ + // "Larger gammas produced more temporally stable results at the cost of increased ghosting." - msalvi_temporal_supersampling.pdf - page 24 + half outSigma = VARIANCE_CLIPPING * ( VARIANCE_CLIPPING_OFFSET - _AO_TemporalMotionSensibility ) * aInSigma; + + #if defined( MOTION_ATTEN ) + outSigma = outSigma * ( 1.0 - aMotionNeighborIntensity ); + #endif + + #if defined( DISOCCLUSION_ATTEN ) + outSigma = outSigma * ( 1.0 - aDisocclusion ); + #endif + + outSigma = outSigma * aPrevN; + + return max( outSigma, 0.0 ); +} + +#else + +#if defined( MINMAX_DEVIATION ) + +inline half ComputeDeviation( const half aMotionNeighborIntensity, const half aDisocclusion, const half aPrevN, const half aOutOfRange ) +{ + half outDeviation = 0.15 * ( ( 1.25 ) - _AO_TemporalMotionSensibility ) + aOutOfRange; + + #if defined( MOTION_ATTEN ) + outDeviation = outDeviation * ( 1.0 - aMotionNeighborIntensity ); + #endif + + #if defined( DISOCCLUSION_ATTEN ) + outDeviation = outDeviation * ( 1.0 - aDisocclusion ); + #endif + + outDeviation = outDeviation * aPrevN; + + return saturate( outDeviation ); +} + +#endif + +#endif + + +#if defined( DISOCCLUSION_NEIGHBOR ) + +inline half FetchNeighborPrevN( const half2 aUV, const half aRadiusPixels ) +{ + half noiseSpatialOffsets, noiseSpatialDirections; + GetSpatialDirections_Offsets_JimenezNoise( aUV, _AO_TemporalAccumm_TexelSize.zw, noiseSpatialOffsets, noiseSpatialDirections ); + + const half angle = ( noiseSpatialDirections + _AO_TemporalDirections ) * UNITY_PI; + const half2 cos_sin = half2( cos( angle ), sin( angle ) ); + const half2 xy = ( aRadiusPixels * noiseSpatialOffsets + 2.0 ).xx * cos_sin; + + const half dL = FetchPrevN( half2( -xy.x, -xy.y ) * _AO_TemporalAccumm_TexelSize.xy + aUV ); + const half dR = FetchPrevN( half2( +xy.x, +xy.y ) * _AO_TemporalAccumm_TexelSize.xy + aUV ); + + return min( dL, dR ); +} + + +half NeighborPrevN( const float2 aScreenPos, const half aLinearEyeDepth ) +{ + const half radius = lerp( _AO_Radius, _AO_FadeValues.y, ComputeDistanceFade( aLinearEyeDepth ) ); + const half radiusToScreen = 0.50 * radius * _AO_HalfProjScale; + const half screenRadius = max( min( ( radiusToScreen / aLinearEyeDepth ), PIXEL_RADIUS_LIMIT ), 2 ); + + return FetchNeighborPrevN( aScreenPos, screenRadius ); +} + +#endif +void GetTemporalFilter( const float2 aScreenPos, + const half aCurrAO, + const half aSampledDepth, + const half aLinearEyeDepth, + const half aLinear01Depth, + const bool aUseMotionVectors, + out half outAO, + out half outNewN, + out half out_temporal_AO ) +{ + + float2 reproj_screenPos = (0).xx; + + half2 mv = (0).xx; + + half motionNeighborIntensity = 0; + + if( aUseMotionVectors == false ) + { + half depth01 = aSampledDepth; + + #if defined(UNITY_REVERSED_Z) + depth01 = 1.0 - depth01; + #endif + + #if defined( SHADER_API_OPENGL ) || defined( SHADER_API_GLES ) || defined( SHADER_API_GLES3 ) || defined( SHADER_API_GLCORE ) + const float4 vpos = float4( float3( aScreenPos, depth01 ) * 2.0 - 1.0, 1.0 ); + #else + const float4 vpos = float4( ( aScreenPos * 2.0 - 1.0 ), ( 1.0 - depth01 ), 1.0 ); + #endif + + #ifdef UNITY_SINGLE_PASS_STEREO + float4x4 invViewProjMatrix; + + if ( unity_StereoEyeIndex == 0 ) + { + invViewProjMatrix = _AO_InvViewProjMatrixLeft; + } + else + { + invViewProjMatrix = _AO_InvViewProjMatrixRight; + } + + float4 wpos = mul( invViewProjMatrix, vpos ); + + #else + + float4 wpos = mul( _AO_InvViewProjMatrixLeft, vpos ); + + #endif + + wpos = wpos / wpos.w; + + #ifdef UNITY_SINGLE_PASS_STEREO + float4x4 prevViewProjMatrix; + + if ( unity_StereoEyeIndex == 0 ) + { + prevViewProjMatrix = _AO_PrevViewProjMatrixLeft; + } + else + { + prevViewProjMatrix = _AO_PrevViewProjMatrixRight; + } + + const float4 reproj_vpos = mul( prevViewProjMatrix, wpos ); + + #else + const float4 reproj_vpos = mul( _AO_PrevViewProjMatrixLeft, wpos ); + #endif + + reproj_screenPos = ( reproj_vpos.xy / reproj_vpos.w ) * 0.5 + 0.5; + + mv = aScreenPos - reproj_screenPos; + } + else + { + mv = FetchMotion( aScreenPos ); + + motionNeighborIntensity = FetchMotionIntensity( aScreenPos ); + + reproj_screenPos = aScreenPos - mv; + } + + + if( ( ( reproj_screenPos.x < 0.0 ) || + ( reproj_screenPos.y < 0.0 ) || + ( reproj_screenPos.x > 1.0 ) || + ( reproj_screenPos.y > 1.0 ) ) == false ) + { + const half3 prev_AO_Depth_N = FetchPrevAO_Depth_N( reproj_screenPos ); + + const half cM = aCurrAO; + const half cAcc = prev_AO_Depth_N.x; + half prev_linear01Depth = prev_AO_Depth_N.y; + half prev_sampledDepth = Linear01ToSampledDepth( prev_linear01Depth ); + half prev_N = prev_AO_Depth_N.z; + + const half mvLenght = length( mv ); + + half disocclusion = CalcDisocclusion( aSampledDepth, prev_sampledDepth, ( _AO_TemporalMotionSensibility * 20.0 + 1.0 ) * aLinearEyeDepth ); + + #if defined( DISOCCLUSION_NEIGHBOR ) + if( aUseMotionVectors == false ) + { + prev_N = lerp( prev_N, min( prev_N , NeighborPrevN( aScreenPos, aLinearEyeDepth ) ), saturate( _AO_TemporalMotionSensibility * 0.50 + 0.50 ) ); + } + #endif + + #if defined( NEIGHBOR_SAMPLE_4TAP ) + + // "variable distance to 4 sample points, decided per-pixel" + // "higher velocity ⇒ closer to center texel (strict on motion)" - slide 26 + // "sample offset 0.5-0.666 from texel center" - slide 26 + const half vd = lerp( 0.71, 0.50, mvLenght ); + + half2 cTL = FetchOcclusionDepth( half2( -vd, -vd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cTR = FetchOcclusionDepth( half2( +vd, -vd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cBL = FetchOcclusionDepth( half2( -vd, +vd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cBR = FetchOcclusionDepth( half2( +vd, +vd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + + half4 o0123 = half4( cTL.x, cTR.x, cBL.x, cBR.x ); + + #if defined( DEPTH_WEIGHTING ) + const half4 d0123 = half4( cTL.y, cTR.y, cBL.y, cBR.y ); + half4 depthWeight0123 = saturate( 1.0 / ( abs( Linear01ToSampledDepth( d0123 * ONE_OVER_DEPTH_SCALE ) - ( aSampledDepth ).xxxx ) * DEPTH_WEIGHTING + DEPTH_WEIGHTING_OFFSET ) ); + depthWeight0123 = depthWeight0123 * depthWeight0123; + const half depthVariance = dot( (1).xxxx, depthWeight0123 ) * 0.25; + o0123 = lerp( (cM).xxxx, o0123, depthWeight0123 ); + cTL.x = o0123.x; + cTR.x = o0123.y; + cBL.x = o0123.z; + cBR.x = o0123.w; + #endif + + #if defined( VARIANCE_CLIPPING ) + // Salvi 2016 - msalvi_temporal_supersampling.pdf - page 23 + const half m1 = dot( (1).xxxx, o0123 ); + + const half4 o0123_squared = o0123 * o0123; + + const half m2 = dot( (1).xxxx, o0123_squared ); + + const half oneOverN = 1.0 / 4.0; + const half mu = m1 * oneOverN; + float Sigma = sqrt( max( m2 * oneOverN - mu * mu, 0.0 ) ); + + Sigma = ComputeSigma( Sigma, motionNeighborIntensity, disocclusion, prev_N ); + + half cMin = max( mu - Sigma * SIGMA_SCALE_MIN, 0.0 ); + half cMax = min( mu + Sigma * SIGMA_SCALE_MAX, 1.0 ); + #else + half cMax = max( aCurrAO, max( cTL.x, max( cTR.x, max( cBL.x, cBR.x ) ) ) ); + half cMin = min( aCurrAO, min( cTL.x, min( cTR.x, min( cBL.x, cBR.x ) ) ) ); + #endif + + + const half2 cAvrg = ( cTL + cTR + cBL + cBR ) * (0.25).xx; + #else + #if defined( NEIGHBOR_SAMPLE_4TAP_CROSS ) + const half vd = 1.0; + half2 cT = FetchOcclusionDepth( half2( 0, -vd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cL = FetchOcclusionDepth( half2( -vd, 0 ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cR = FetchOcclusionDepth( half2( +vd, 0 ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cB = FetchOcclusionDepth( half2( 0, +vd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + + half4 o0123 = half4( cT.x, cL.x, cR.x, cB.x ); + + #if defined( DEPTH_WEIGHTING ) + const half4 d0123 = half4( cT.y, cL.y, cR.y, cB.y ); + half4 depthWeight0123 = saturate( 1.0 / ( abs( Linear01ToSampledDepth( d0123 * ONE_OVER_DEPTH_SCALE ) - ( aSampledDepth ).xxxx ) * DEPTH_WEIGHTING + DEPTH_WEIGHTING_OFFSET ) ); + depthWeight0123 = depthWeight0123 * depthWeight0123; + const half depthVariance = dot( (1).xxxx, depthWeight0123 ) * 0.25; + o0123 = lerp( (cM).xxxx, o0123, depthWeight0123 ); + cT.x = o0123.x; + cL.x = o0123.y; + cR.x = o0123.z; + cB.x = o0123.w; + #endif + + #if defined( VARIANCE_CLIPPING ) + // Salvi 2016 - msalvi_temporal_supersampling.pdf - page 23 + const half m1 = dot( (1).xxxx, o0123 ); + + const half4 o0123_squared = o0123 * o0123; + + const half m2 = dot( (1).xxxx, o0123_squared ); + + const half oneOverN = 1.0 / 4.0; + const half mu = m1 * oneOverN; + float Sigma = sqrt( max( m2 * oneOverN - mu * mu, 0.0 ) ); + + Sigma = ComputeSigma( Sigma, motionNeighborIntensity, disocclusion, prev_N ); + + half cMin = max( mu - Sigma * SIGMA_SCALE_MIN, 0.0 ); + half cMax = min( mu + Sigma * SIGMA_SCALE_MAX, 1.0 ); + #else + half cMax = max( aCurrAO, max( cT.x, max( cL.x, max( cR.x, cB.x ) ) ) ); + half cMin = min( aCurrAO, min( cT.x, min( cL.x, min( cR.x, cB.x ) ) ) ); + #endif + + + const half2 cAvrg = ( cT + cL + cR + cB ) * (0.25).xx; + #else + #if defined( NEIGHBOR_SAMPLE_8TAP ) + const half vd = 1.0; + half2 cT = FetchOcclusionDepth( half2( 0, -vd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cL = FetchOcclusionDepth( half2( -vd, 0 ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cR = FetchOcclusionDepth( half2( +vd, 0 ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cB = FetchOcclusionDepth( half2( 0, +vd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + + const half dd = 1.55; + half2 cTL = FetchOcclusionDepth( half2( -dd, -dd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cTR = FetchOcclusionDepth( half2( +dd, -dd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cBL = FetchOcclusionDepth( half2( -dd, +dd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + half2 cBR = FetchOcclusionDepth( half2( +dd, +dd ) * _AO_CurrOcclusionDepth_TexelSize.xy + aScreenPos ); + + half4 o0123 = half4( cT.x, cL.x, cR.x, cB.x ); + half4 o4567 = half4( cTL.x, cTR.x, cBL.x, cBR.x ); + + #if defined( DEPTH_WEIGHTING ) + const half4 d0123 = half4( cT.y, cL.y, cR.y, cB.y ); + half4 depthWeight0123 = saturate( 1.0 / ( abs( Linear01ToSampledDepth( d0123 * ONE_OVER_DEPTH_SCALE ) - ( aSampledDepth ).xxxx ) * DEPTH_WEIGHTING + DEPTH_WEIGHTING_OFFSET ) ); + depthWeight0123 = depthWeight0123 * depthWeight0123; + o0123 = lerp( (cM).xxxx, o0123, depthWeight0123 ); + cT.x = o0123.x; + cL.x = o0123.y; + cR.x = o0123.z; + cB.x = o0123.w; + + const half4 d4567 = half4( cTL.y, cTR.y, cBL.y, cBR.y ); + half4 depthWeight4567 = saturate( 1.0 / ( abs( Linear01ToSampledDepth( d4567 * ONE_OVER_DEPTH_SCALE ) - ( aSampledDepth ).xxxx ) * DEPTH_WEIGHTING + DEPTH_WEIGHTING_OFFSET ) ); + depthWeight4567 = depthWeight4567 * depthWeight4567; + o4567 = lerp( (cM).xxxx, o4567, depthWeight4567 ); + cTL.x = o4567.x; + cTR.x = o4567.y; + cBL.x = o4567.z; + cBR.x = o4567.w; + + const half depthVariance = ( dot( (1).xxxx, depthWeight0123 ) + dot( (1).xxxx, depthWeight4567 ) ) * 0.125; + #endif + + #if defined( VARIANCE_CLIPPING ) + // Salvi 2016 - msalvi_temporal_supersampling.pdf - page 23 + const half m1 = dot( (1).xxxx, o0123 ) + dot( (1).xxxx, o4567 ); + + const half4 o0123_squared = o0123 * o0123; + const half4 o4567_squared = o4567 * o4567; + + const half m2 = dot( (1).xxxx, o0123_squared ) + dot( (1).xxxx, o4567_squared ); + + const half oneOverN = 1.0 / 8.0; + const half mu = m1 * oneOverN; + float Sigma = sqrt( max( m2 * oneOverN - mu * mu, 0.0 ) ); + + Sigma = ComputeSigma( Sigma, motionNeighborIntensity, disocclusion, prev_N ); + + half cMin = max( mu - Sigma * SIGMA_SCALE_MIN, 0.0 ); + half cMax = min( mu + Sigma * SIGMA_SCALE_MAX, 1.0 ); + #else + half cMax = max( aCurrAO, max( cTL.x, max( cTR.x, max( cBL.x, max( cBR.x, max( cT.x, max( cL.x, max( cR.x, cB.x ) ) ) ) ) ) ) ); + half cMin = min( aCurrAO, min( cTL.x, min( cTR.x, min( cBL.x, min( cBR.x, min( cT.x, min( cL.x, min( cR.x, cB.x ) ) ) ) ) ) ) ); + #endif + + const half2 cAvrg = ( cT + cL + cR + cB + cTL + cTR + cBL + cBR ) * (0.125).xx; + #endif + #endif + #endif + + #if defined( OUTOFRANGE_COMPENSATION ) + + #if defined( VARIANCE_CLIPPING ) + half accOutOfRange = saturate( ( max( abs( cAcc - cMin.x ), abs( cAcc - cMax.x ) ) * 3.5 - 0.03 ) ); + #else + const half accOutOfRange = saturate( ( max( abs( cAcc - cMin.x ), abs( cAcc - cMax.x ) ) - 0.01 ) ); + #endif + + #else + const half accOutOfRange = 0.0; + #endif + + #if defined( MINMAX_DEVIATION ) + const half deviation = ComputeDeviation( motionNeighborIntensity, disocclusion, prev_N, accOutOfRange ); + cMax.x = min( cMax.x + deviation * SIGMA_SCALE_MAX, 1.0 ); + cMin.x = max( cMin.x - deviation * SIGMA_SCALE_MIN, 0.0 ); + #endif + + #if defined( CLAMP_MINMAX ) + half clampedAcc = clamp( cAcc, cMin.x, cMax.x ); + + #if defined( VARIANCE_CLIPPING ) + #if defined( OUTOFRANGE_COMPENSATION ) + clampedAcc = lerp( clampedAcc, cAcc, accOutOfRange ); + #endif + #endif + + #else + half clampedAcc = cM; + #endif + + half newN = saturate( prev_N + ( 1.0 / 6.0 ) ); + const half oneMinusDisocclusion = ( 1.0 - disocclusion ); + newN = min( newN, oneMinusDisocclusion * oneMinusDisocclusion ); + + if( aUseMotionVectors == true ) + { + newN = min( newN, 1.0 - motionNeighborIntensity ); + } + + #if defined( MOTION_ATTEN ) + const half2 mvToPixels = mv * _AO_CurrOcclusionDepth_TexelSize.zw; + const half mvLenghtPixelsSquared = dot( mvToPixels, mvToPixels ); + const half mvWeighting = saturate( mvLenghtPixelsSquared * 0.0005 * _AO_TemporalMotionSensibility ); + const half mvMaxMotion = saturate( max( motionNeighborIntensity, mvWeighting ) ); + const half mvIntensity = ( 1.0 - mvMaxMotion ); + + const half finalLerp = min( saturate( prev_N * mvIntensity - disocclusion ), 0.96 ); + #else + const half finalLerp = min( saturate( prev_N - disocclusion ), 0.96 ); + #endif + + half new_ao = lerp( cM, lerp( clampedAcc, cAcc, _AO_TemporalCurveAdj ), finalLerp ); + + #if defined( OUTOFRANGE_ENDCOMPENSATION ) + half outOfRangeCompensation = saturate( abs( new_ao - cAcc ) * ( 0.3 + _AO_TemporalMotionSensibility ) ); + + new_ao = lerp( new_ao, cM, outOfRangeCompensation ); + newN = saturate( newN - min( outOfRangeCompensation * outOfRangeCompensation, 1.0 / 7.0 ) ); + #endif + + out_temporal_AO = new_ao; + outAO = new_ao; + outNewN = newN; + } + else + { + out_temporal_AO = aCurrAO; + outAO = aCurrAO; + outNewN = 0.0; + } +} + + +half4 TemporalFilter( const half2 aScreenPos, const half aOcclusion, const half aSampledDepth, const half aLinearEyeDepth, const half aLinear01Depth, const bool aUseMotionVectors, out half outOcclusion ) +{ + half out_ao, out_newN, out_temporal_ao; + + GetTemporalFilter( aScreenPos, + aOcclusion, + aSampledDepth, + aLinearEyeDepth, + aLinear01Depth, + aUseMotionVectors, + out_ao, + out_newN, + out_temporal_ao ); + + outOcclusion = out_ao; + + return half4( EncAO( out_temporal_ao ), EncDepth( aLinear01Depth ), out_newN ); +} + + +half4 Temporal( v2f_in IN, const bool aUseMotionVectors ) +{ + UNITY_SETUP_INSTANCE_ID( IN ); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX( IN ); + + const float2 screenPos = IN.uv.xy; + + const half2 occlusionDepth = FetchOcclusionDepth( screenPos ); + + if( occlusionDepth.y < HALF_MAX ) + { + const half linear01Depth = occlusionDepth.y * ONE_OVER_DEPTH_SCALE; + const half sampledDepth = Linear01ToSampledDepth( linear01Depth ); + const half linearEyeDepth = occlusionDepth.y * _AO_BufDepthToLinearEye; + + half dummy_occlusion; + return TemporalFilter( screenPos, occlusionDepth.x, sampledDepth, linearEyeDepth, linear01Depth, aUseMotionVectors, dummy_occlusion ); + } + else + { + return half4( (1).xxxx ); + } +} + + +inline half2 ComputeCombineDownsampledOcclusionFromTemporal( const half2 aScreenPos, const half aDepthSample ) +{ + const half referenceDepth = LinearEyeDepth( aDepthSample ); + + const half intensity = lerp( _AO_Levels.a, _AO_FadeValues.x, ComputeDistanceFade( referenceDepth ) ); + + //UNITY_BRANCH + #if defined(UNITY_REVERSED_Z) + if( ( aDepthSample <= DEPTH_EPSILON ) || ( intensity < INTENSITY_THRESHOLD ) ) + #else + if( ( aDepthSample >= ( 1.0 - DEPTH_EPSILON ) ) || ( intensity < INTENSITY_THRESHOLD ) ) + #endif + { + return half2( 1.0, HALF_MAX ); + } + + const half2 screenPosPixels = aScreenPos * _AO_TemporalAccumm_TexelSize.zw; + const half2 screenPosPixelsFloor = floor( screenPosPixels ); + const half2 screenPosPixelsDelta = screenPosPixels - screenPosPixelsFloor; + + const half2 sPosAdjusted = screenPosPixelsFloor * _AO_TemporalAccumm_TexelSize.xy + half2( 0.5, 0.5 ) * _AO_TemporalAccumm_TexelSize.xy; + const half s = ( screenPosPixelsDelta.y < 0.5 )?-1.0:1.0; + + half2 odC = FetchPrevAO_Depth( sPosAdjusted ); + + half2 odL = FetchPrevAO_Depth( sPosAdjusted + half2( -1.0, 0.0 ) * _AO_TemporalAccumm_TexelSize.xy ); + half2 odR = FetchPrevAO_Depth( sPosAdjusted + half2( +1.0, 0.0 ) * _AO_TemporalAccumm_TexelSize.xy ); + half2 odM = FetchPrevAO_Depth( sPosAdjusted + half2( 0.0, s ) * _AO_TemporalAccumm_TexelSize.xy ); + + const half4 o0123 = half4( odC.x, odL.x, odR.x, odM.x ); + const half4 d0123 = half4( odC.y, odL.y, odR.y, odM.y ); + + half4 depthWeight0123 = saturate( 1.0 / ( abs( Linear01ToSampledDepth( d0123 ) - ( aDepthSample ).xxxx ) * 32768 + 0.95 ) ); + + const half4 pixelDeltaWeight = half4( screenPosPixelsDelta.x * screenPosPixelsDelta.y + 0.5, + 1.0 - screenPosPixelsDelta.x, + screenPosPixelsDelta.x, + 0.80 ); + + depthWeight0123 = depthWeight0123 * depthWeight0123 * pixelDeltaWeight; + + half weightOcclusion = dot( o0123, depthWeight0123 ); + + const half outOcclusion = saturate( weightOcclusion / dot( ( 1 ).xxxx, depthWeight0123 ) ); + + return half2( outOcclusion, referenceDepth ); +} + +#endif diff --git a/SubmersedVR/AmplifyOcclusion/Resources/TemporalFilter.cginc.meta b/SubmersedVR/AmplifyOcclusion/Resources/TemporalFilter.cginc.meta new file mode 100644 index 0000000..de3e173 --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Resources/TemporalFilter.cginc.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 99bb66f53a3674e6cad462ff44c28826 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: amplify_resources + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Textures.meta b/SubmersedVR/AmplifyOcclusion/Textures.meta new file mode 100644 index 0000000..7244dfb --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Textures.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 592aabdddf923fb4b9c5632a34cef9b1 +folderAsset: yes +timeCreated: 1455194259 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/AmplifyOcclusion/Textures/About.png b/SubmersedVR/AmplifyOcclusion/Textures/About.png new file mode 100644 index 0000000000000000000000000000000000000000..55b5017baef01e37d85cb8c88122d5599e31d810 GIT binary patch literal 57164 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 KMNUMnLSTaJGD55X literal 0 HcmV?d00001 diff --git a/SubmersedVR/AmplifyOcclusion/Textures/About.png.meta b/SubmersedVR/AmplifyOcclusion/Textures/About.png.meta new file mode 100644 index 0000000..f1ad86b --- /dev/null +++ b/SubmersedVR/AmplifyOcclusion/Textures/About.png.meta @@ -0,0 +1,57 @@ +fileFormatVersion: 2 +guid: 01d9f923d6f8ee6499b20e69fe1c53be +timeCreated: 1455194262 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 7 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 512 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + outline: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/SubmersedVR/Settings.cs b/SubmersedVR/Settings.cs index fc402c4..7f437d5 100644 --- a/SubmersedVR/Settings.cs +++ b/SubmersedVR/Settings.cs @@ -2,6 +2,9 @@ using HarmonyLib; using UnityEngine; using UnityEngine.Events; +using Yangrc.VolumeCloud; +using UnityEngine.XR; +using rail; namespace SubmersedVR { @@ -9,6 +12,7 @@ public class Settings { public delegate void BooleanChanged(bool newValue); public delegate void FloatChanged(float newValue); + public delegate void VoidChanged(); public static bool IsSnapTurningEnabled; public static event BooleanChanged IsSnapTurningEnabledChanged; @@ -30,6 +34,26 @@ public class Settings public static bool InvertYAxis; public static event BooleanChanged InvertYAxisChanged; + //Ambient Occlusion Settings + public static bool AOEnabled = false; + public static string AOMethod = "Post Effect"; + public static string AOSampleCount = "Medium"; + public static string AOPerPixelNormals = "Camera"; + public static float AOIntensity = 1.0f; + public static float AORadius = 2.0f; + public static float AOPowerExponent = 1.8f; + public static float AOBias = 0.05f; + public static float AOThickness = 1.0f; + public static bool AODownSample = true; + public static bool AOCacheAware = true; + public static bool AOTemporalFilterEnabled = true; + public static bool AOTemporalFilterDownsampleEnabled = true; + public static float AOTemporalFilterBlending = 0.8f; + public static float AOTemporalFilterResponse = 0.5f; + + public static event VoidChanged AmbientOcclusionSettingsChanged; + // + public static bool AlwaysShowControllers; public static event BooleanChanged AlwaysShowControllersChanged; @@ -61,6 +85,7 @@ public class Settings public static bool HandBasedTurning = false; + public static bool LeftHandBasedTurning = false; public static bool ArticulatedHands = true; public static bool PhysicalDriving = false; public static bool PhysicalLockedGrips = false; @@ -142,13 +167,54 @@ internal static void Serialize(GameSettings.ISerializer serializer) } } + internal static void AddToGraphicsOptions(uGUI_OptionsPanel panel) + { + int tab = panel.tabs.Count - 1; + + string space = " "; + panel.AddHeading(tab, "Ambient Occlusion"); + panel.AddToggleOption(tab, space + "Enable", AOEnabled, (value) => { AOEnabled = AmbientOcclusionVR.enabled = value; AmbientOcclusionSettingsChanged(); }, "Use ambient occlusion. Increases demand on GPU."); + panel.AddChoiceOption(tab, space + "Method", new string[] {"Post Effect", "Deferred", "Debug"}, AOMethod, (value) => { + AOMethod = value; + if (AmbientOcclusionSettingsChanged != null) { + AmbientOcclusionSettingsChanged(); + } + }); + panel.AddChoiceOption(tab, space + "Sample Count", new string[] {"Low", "Medium", "High", "Very High"}, AOSampleCount, (value) => { + AOSampleCount = value; + if (AmbientOcclusionSettingsChanged != null) { + AmbientOcclusionSettingsChanged(); + } + }); + panel.AddChoiceOption(tab, space + "Per Pixel Normals", new string[] {"None", "Camera", "GBuffer", "Octa"}, AOPerPixelNormals, (value) => { + AOPerPixelNormals = value; + if (AmbientOcclusionSettingsChanged != null) { + AmbientOcclusionSettingsChanged(); + } + }); + panel.AddSliderOption(tab, space + "Intensity", AOIntensity, 0f, 1.0f, AOIntensity, 0.02f, (value) => { AOIntensity = value; AmbientOcclusionSettingsChanged(); }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, space + "Radius", AORadius, 0f, 10.0f, AORadius, 0.1f, (value) => { AORadius = value; AmbientOcclusionSettingsChanged(); }, SliderLabelMode.Float, "0.0"); + panel.AddSliderOption(tab, space + "Power Exponent", AOPowerExponent, 0f, 16f, AOPowerExponent, 0.1f, (value) => { AOPowerExponent = value; AmbientOcclusionSettingsChanged(); }, SliderLabelMode.Float, "0.0"); + panel.AddSliderOption(tab, space + "Bias", AOBias, 0f, 0.99f, AOBias, 0.02f, (value) => { AOBias = value; AmbientOcclusionSettingsChanged(); }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, space + "Thickness", AOThickness, 0f, 1.0f, AOThickness, 0.02f, (value) => { AOThickness = value; AmbientOcclusionSettingsChanged(); }, SliderLabelMode.Float, "0.00"); + panel.AddToggleOption(tab, space + "Downsample", AODownSample, (value) => { AODownSample = value; AmbientOcclusionSettingsChanged(); }, "Compute the Occlusion and Blur at half of the resolution."); + panel.AddToggleOption(tab, space + "Cache Aware", AOCacheAware, (value) => { AOCacheAware = value; AmbientOcclusionSettingsChanged(); }, "Cache optimization for best performance / quality tradeoff."); + panel.AddToggleOption(tab, space + "Enable Temporal Filter", AOTemporalFilterEnabled, (value) => { AOTemporalFilterEnabled = AmbientOcclusionVR.FilterEnabled = value; AmbientOcclusionSettingsChanged(); }, "Accumulates the effect over the time."); + panel.AddToggleOption(tab, space + "Temporal Filter Downsample", AOTemporalFilterDownsampleEnabled, (value) => { AOTemporalFilterDownsampleEnabled = value; AmbientOcclusionSettingsChanged(); }, "Effect at half of the resolution."); + panel.AddSliderOption(tab, space + "Temporal Filter Blending", AOTemporalFilterBlending, 0f, 1.0f, AOTemporalFilterBlending, 0.02f, (value) => { AOTemporalFilterBlending = value; AmbientOcclusionSettingsChanged(); }, SliderLabelMode.Float, "0.00"); + panel.AddSliderOption(tab, space + "Temporal Filter Response", AOTemporalFilterResponse, 0f, 1.0f, AOTemporalFilterResponse, 0.02f, (value) => { AOTemporalFilterResponse = value; AmbientOcclusionSettingsChanged(); }, SliderLabelMode.Float, "0.00"); + + + } + internal static void AddMenu(uGUI_OptionsPanel panel) { int tab = panel.AddTab("Submersed VR"); panel.AddHeading(tab, "Controls"); - panel.AddChoiceOption(tab, "Movement Mode", new string[] {"Head Based", "Hand Based"}, HandBasedTurning ? "Hand Based" : "Head Based", (value) => { - HandBasedTurning = value == "Hand Based"; + panel.AddChoiceOption(tab, "Movement Mode", new string[] {"Head Based", "Right Hand Based", "Left Hand Based"}, HandBasedTurning ? (LeftHandBasedTurning ? "Left Hand Based" : "Right Hand Based") : "Head Based", (value) => { + HandBasedTurning = value == "Right Hand Based" || value == "Left Hand Based"; + LeftHandBasedTurning = value == "Left Hand Based"; }); panel.AddToggleOption(tab, "Enable Player Snap Turning", IsSnapTurningEnabled, (value) => { IsSnapTurningEnabled = value; if (IsSnapTurningEnabledChanged != null) { @@ -209,7 +275,7 @@ internal static void AddMenu(uGUI_OptionsPanel panel) panel.AddToggleOption(tab, "Always show controllers", AlwaysShowControllers, (value) => { AlwaysShowControllers = value; AlwaysShowControllersChanged(value); }, "Shows the controllers at all times."); //panel.AddToggleOption(tab, "Always show laserpointer", AlwaysShowLaserPointer, (value) => { AlwaysShowLaserPointer = value; AlwaysShowLaserPointerChanged(value); }, "Show the laserpointer at all times."); - tab = panel.AddTab("Vehicles"); + tab = panel.AddTab("Vehicles VR"); panel.AddHeading(tab, "Comfort"); panel.AddSliderOption(tab, "SeaTruck Pilot Position Offset", SeaTruckZOffset, -0.4f, 0.4f, SeaTruckZOffset, 0.01f, (value) => { SeaTruckZOffset = value; }, SliderLabelMode.Float, "0.00"); panel.AddSliderOption(tab, "SeaTruck Pilot Height Offset", SeaTruckYOffset, -0.4f, 0.4f, SeaTruckYOffset, 0.01f, (value) => { SeaTruckYOffset = value; }, SliderLabelMode.Float, "0.00"); @@ -323,6 +389,61 @@ public static void Postfix(uGUI_OptionsPanel __instance) } } + [HarmonyPatch(typeof(uGUI_OptionsPanel), nameof(uGUI_OptionsPanel.AddGraphicsTab))] + static class UpdateGraphicsOptions + { + //Get rid of the default Ambient Occlusion Option + public static bool Prefix(uGUI_OptionsPanel __instance) + { + int tabIndex = __instance.AddTab("Graphics"); + __instance.AddSliderOption(tabIndex, "Gamma", GammaCorrection.gamma, 0.1f, 2.8f, 1f, 0.01f, delegate(float value) + { + GammaCorrection.gamma = value; + }, SliderLabelMode.Float, "0.00", null); + int qualityPresetIndex = __instance.GetQualityPresetIndex(); + __instance.qualityPresetOption = __instance.AddChoiceOption(tabIndex, "Preset", uGUI_OptionsPanel.presetOptions, qualityPresetIndex, new UnityAction(__instance.OnQualityPresetChanged), null); + __instance.ApplyQualityPreset(qualityPresetIndex); + __instance.AddHeading(tabIndex, "Advanced"); + if (uGUI_MainMenu.main) + { + int currentIndex; + string[] detailOptions = uGUI_OptionsPanel.GetDetailOptions(out currentIndex); + __instance.detailOption = __instance.AddChoiceOption(tabIndex, "Detail", detailOptions, currentIndex, new UnityAction(__instance.OnDetailChanged), null); + } + __instance.waterQualityOption = __instance.AddChoiceOption(tabIndex, "WaterQuality", WaterSurface.GetQualityOptions(), WaterSurface.GetQuality(), new UnityAction(__instance.OnWaterQualityChanged), null); + __instance.skyboxQualityOption = __instance.AddChoiceOption(tabIndex, "SkyboxQuality", uGUI_OptionsPanel.skyboxQualityOptions, VolumeCloudRenderer.GetQuality(), new UnityAction(__instance.OnASkyboxqualityChanged), null); + int currentIndex2; + string[] antiAliasingOptions = uGUI_OptionsPanel.GetAntiAliasingOptions(out currentIndex2); + __instance.aaModeOption = __instance.AddChoiceOption(tabIndex, "Antialiasing", antiAliasingOptions, currentIndex2, new UnityAction(__instance.OnAAmodeChanged), null); + __instance.aaQualityOption = __instance.AddChoiceOption(tabIndex, "AntialiasingQuality", uGUI_OptionsPanel.postFXQualityNames, UwePostProcessingManager.GetAaQuality(), new UnityAction(__instance.OnAAqualityChanged), null); + __instance.bloomOption = __instance.AddToggleOption(tabIndex, "Bloom", UwePostProcessingManager.GetBloomEnabled(), new UnityAction(__instance.OnBloomChanged), null); + if (!XRSettings.enabled) + { + __instance.lensDirtOption = __instance.AddToggleOption(tabIndex, "LensDirt", UwePostProcessingManager.GetBloomLensDirtEnabled(), new UnityAction(__instance.OnBloomLensDirtChanged), null); + if (!GraphicsUtil.IsOpenGL()) + { + __instance.dofOption = __instance.AddToggleOption(tabIndex, "DepthOfField", UwePostProcessingManager.GetDofEnabled(), new UnityAction(__instance.OnDofChanged), null); + } + __instance.motionBlurQualityOption = __instance.AddChoiceOption(tabIndex, "MotionBlurQuality", uGUI_OptionsPanel.postFXQualityNames, UwePostProcessingManager.GetMotionBlurQuality(), new UnityAction(__instance.OnMotionBlurQualityChanged), null); + } + //__instance.aoQualityOption = __instance.AddChoiceOption(tabIndex, "AmbientOcclusion", uGUI_OptionsPanel.postFXQualityNames, UwePostProcessingManager.GetAoQuality(), new UnityAction(this.OnAOqualityChanged), null); + if (!XRSettings.enabled) + { + __instance.ssrQualityOption = __instance.AddChoiceOption(tabIndex, "ScreenSpaceReflections", uGUI_OptionsPanel.postFXQualityNames, UwePostProcessingManager.GetSsrQuality(), new UnityAction(__instance.OnSSRqualityChanged), null); + __instance.ditheringOption = __instance.AddToggleOption(tabIndex, "Dithering", UwePostProcessingManager.GetDitheringEnabled(), new UnityAction(__instance.OnDitheringChanged), null); + } + __instance.weatherQualityOption = __instance.AddChoiceOption(tabIndex, "WeatherQuality", uGUI_OptionsPanel.weatherQualityOptions, VFXWeatherManager.GetQuality(), new UnityAction(__instance.OnAWeatherQualityChanged), null); + + return false; + } + + //Add in our own Ambient Occlusion Option + public static void Postfix(uGUI_OptionsPanel __instance) + { + Settings.AddToGraphicsOptions(__instance); + } + } + [HarmonyPatch(typeof(uGUI_OptionsPanel), nameof(uGUI_OptionsPanel.OnScreenChanged))] static class OnScreenChangedFixer { @@ -362,12 +483,16 @@ public static class FixGraphicsForVR { public static void Postfix(UwePostProcessingManager __instance) { + //Mod.logger.LogInfo($"UwePostProcessingManager ApplySettingsToProfile called"); __instance.SetAO(0); //__instance.SetDof(0); //__instance.SetSSR(0); //__instance.SetMotionBlur(0); MiscSettings.cameraBobbing = VROptions.enableCinematics; + //AmbientOcclusionVR.enabled = Settings.UseAmbientOcclusion; + //AmbientOcclusionVR.FilterEnabled = Settings.AmbientOcclusionTemporalFilterEnabled; + } } diff --git a/SubmersedVR/StreamingAssets/Amplify/amplify_resources b/SubmersedVR/StreamingAssets/Amplify/amplify_resources new file mode 100644 index 0000000000000000000000000000000000000000..bb9de344a5e554652361b3e03cf4f7b4cf28a791 GIT binary patch literal 55890 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

!OF^uxtqETjUG$&XbSjf;A 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 P-6Lj^p3pA;{xJEXa$kBF literal 0 HcmV?d00001 diff --git a/SubmersedVR/StreamingAssets/Amplify/amplify_resources.manifest b/SubmersedVR/StreamingAssets/Amplify/amplify_resources.manifest new file mode 100644 index 0000000..0bdff29 --- /dev/null +++ b/SubmersedVR/StreamingAssets/Amplify/amplify_resources.manifest @@ -0,0 +1,19 @@ +ManifestFileVersion: 0 +CRC: 923786322 +Hashes: + AssetFileHash: + serializedVersion: 2 + Hash: 818576b52503d05ad19649e361d17fe3 + TypeTreeHash: + serializedVersion: 2 + Hash: 55d48f4ad9c3b13330b9eb5ee5686477 +HashAppended: 0 +ClassTypes: +- Class: 48 + Script: {instanceID: 0} +SerializeReferenceClassIdentifiers: [] +Assets: +- Assets/AmplifyOcclusion/Resources/Occlusion.shader +- Assets/AmplifyOcclusion/Resources/Blur.shader +- Assets/AmplifyOcclusion/Resources/Apply.shader +Dependencies: [] diff --git a/SubmersedVR/SubmersedVR.csproj b/SubmersedVR/SubmersedVR.csproj index f2c0ed0..62e52f6 100644 --- a/SubmersedVR/SubmersedVR.csproj +++ b/SubmersedVR/SubmersedVR.csproj @@ -4,7 +4,7 @@ net472 SubmersedVR A VR Conversion Mod for Subnautica - 0.6.0 + 0.7.0 true latest @@ -75,6 +75,7 @@ + @@ -83,6 +84,8 @@ + + diff --git a/SubmersedVR/Tweaks/AmbientOcclusion.cs b/SubmersedVR/Tweaks/AmbientOcclusion.cs new file mode 100644 index 0000000..82280be --- /dev/null +++ b/SubmersedVR/Tweaks/AmbientOcclusion.cs @@ -0,0 +1,75 @@ +using System.IO; +using UnityEngine; + +namespace SubmersedVR +{ + + static class AmbientOcclusionVR + { + private static readonly string AMPLIFY_SHADERS = "amplify_resources"; + private static bool initialized = false; + public static bool enabled = false; + public static AmplifyOcclusionEffect.ApplicationMethod ApplyMethod = AmplifyOcclusionEffect.ApplicationMethod.PostEffect; + public static bool FilterEnabled = true; + public static bool FilterDownsample = true; + + public static void Init() + { + //Mod.logger.LogInfo($"Shader Path: {Path.Combine(Application.streamingAssetsPath, AMPLIFY_SHADERS)}"); + Valve.VR.ShaderLoader.Initialize(Path.Combine(Application.streamingAssetsPath, AMPLIFY_SHADERS)); + initialized = true; + } + + public static void OnAmbientOcclusionSettingsChanged(Camera camera) + { + AmplifyOcclusionEffect effect = camera?.gameObject.GetComponent(); + if (effect != null) + { + effect.enabled = Settings.AOEnabled; //enabled; + effect.SampleCount = Settings.AOMethod == "Low" ? AmplifyOcclusion.SampleCountLevel.Low : (Settings.AOMethod == "High" ? AmplifyOcclusion.SampleCountLevel.High : (Settings.AOMethod == "Very High" ? AmplifyOcclusion.SampleCountLevel.VeryHigh : AmplifyOcclusion.SampleCountLevel.Medium)); + effect.ApplyMethod = Settings.AOMethod == "Deferred" ? AmplifyOcclusionEffect.ApplicationMethod.Deferred : (Settings.AOMethod == "Debug" ? AmplifyOcclusionEffect.ApplicationMethod.Debug : AmplifyOcclusionEffect.ApplicationMethod.PostEffect) ; + effect.PerPixelNormals = Settings.AOPerPixelNormals == "None" ? AmplifyOcclusionEffect.PerPixelNormalSource.None : (Settings.AOPerPixelNormals == "GBuffer" ? AmplifyOcclusionEffect.PerPixelNormalSource.GBuffer : (Settings.AOPerPixelNormals == "Octa" ? AmplifyOcclusionEffect.PerPixelNormalSource.GBufferOctaEncoded : AmplifyOcclusionEffect.PerPixelNormalSource.Camera)) ; + effect.Intensity = Settings.AOIntensity; + effect.Radius = Settings.AORadius; + effect.PowerExponent = Settings.AOPowerExponent; + effect.Bias = Settings.AOBias; + effect.Thickness = Settings.AOThickness; + effect.Downsample = Settings.AODownSample; + effect.CacheAware = Settings.AOCacheAware; + effect.FilterEnabled = Settings.AOTemporalFilterEnabled; //FilterEnabled; + effect.FilterDownsample = Settings.AOTemporalFilterDownsampleEnabled; //FilterEnabled; + effect.FilterBlending = Settings.AOTemporalFilterBlending; //FilterEnabled; + effect.FilterResponse = Settings.AOTemporalFilterResponse; //FilterEnabled; + + Mod.logger.LogInfo($"OnUseAmbientOcclusionChanged:\nenabled:{effect.enabled}\nfilter enabled:{effect.FilterEnabled}\nsample count: {effect.SampleCount}\nmethod:{effect.ApplyMethod}\nperpixelnormals:{effect.PerPixelNormals}\nintensity:{effect.Intensity}"); + } + + } + + public static void AddOcclusionEffect(Camera camera) + { + if(!initialized) + { + Init(); + } + AmplifyOcclusionEffect effect = camera?.gameObject.GetComponent(); + if (effect != null) + { + return; + } + camera.gameObject.AddComponent(); + OnAmbientOcclusionSettingsChanged(camera); + /* + effect = camera.gameObject.GetComponent(); + effect.SampleCount = AmplifyOcclusion.SampleCountLevel.Medium; + effect.ApplyMethod = AmplifyOcclusionEffect.ApplicationMethod.Deferred; + effect.FilterEnabled = Settings.AmbientOcclusionTemporalFilterEnabled; + effect.enabled = Settings.UseAmbientOcclusion; + */ + } + + } + + + +} \ No newline at end of file diff --git a/SubmersedVR/VR/ShaderLoader.cs b/SubmersedVR/VR/ShaderLoader.cs new file mode 100644 index 0000000..aaf25b5 --- /dev/null +++ b/SubmersedVR/VR/ShaderLoader.cs @@ -0,0 +1,41 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System.IO; + +namespace Valve.VR { + public class ShaderLoader + { + private static bool initialized = false; + private static Dictionary shaders = new Dictionary(); + + public static bool Initialize(string assetBundlePath) { + return loadAllShadersFromAssetBundle(assetBundlePath); + } + + public static Shader GetShader(string name) { + Debug.Log("GetShader( " + name + " )"); + if (!initialized) { + Debug.Log("GetShader called before Initializing."); + return null; + } + if (!shaders.ContainsKey(name)) { + Debug.Log("shaders dictionary does not contain shader: " + name); + return null; + } + return shaders[name]; + } + + private static bool loadAllShadersFromAssetBundle(string assetBundlePath) { + var loadedAssetBundle = AssetBundle.LoadFromFile(assetBundlePath); + Debug.Log("loadAllShadersFromAssetBundle called."); + foreach (var a in loadedAssetBundle.LoadAllAssets()) { + Debug.Log($"{a.name}"); + shaders[a.name] = a as Shader; + } + initialized = true; + return true; + } + + } +} \ No newline at end of file diff --git a/SubmersedVR/VR/VRCameraRig.cs b/SubmersedVR/VR/VRCameraRig.cs index 307d59c..743a059 100644 --- a/SubmersedVR/VR/VRCameraRig.cs +++ b/SubmersedVR/VR/VRCameraRig.cs @@ -101,9 +101,14 @@ public TransformOffset TargetTransform _targetTransform = value; value.Apply(laserPointerUI.transform); value.Apply(laserPointer.transform); + value.Apply(laserPointerLeft.transform); } } + public static Transform GetLeftTargetTansform() + { + return VRCameraRig.instance.laserPointerLeft.transform; + } public static Transform GetTargetTansform() { return VRCameraRig.instance.laserPointer.transform; @@ -298,6 +303,9 @@ public void Awake() public void Start() { + Settings.AmbientOcclusionSettingsChanged -= OnAmbientOcclusionSettingsChanged; + Settings.AmbientOcclusionSettingsChanged += OnAmbientOcclusionSettingsChanged; + SetupControllers(); StartCoroutine(DelayedRecenter(1.0f)); } @@ -331,6 +339,11 @@ public void UpdateShowControllers() modelR?.SetActive(alwaysShow || inMainMenu); } + public static void OnAmbientOcclusionSettingsChanged() + { + AmbientOcclusionVR.OnAmbientOcclusionSettingsChanged(VRCameraRig.instance.vrCamera); + } + // This is used to get the camera from the main menu // Main issue with making a new camera was the water surface but that should also be fixable // TODO: Maybe remove this, so we only have one common camera @@ -349,6 +362,8 @@ public void StealCamera(Camera camera) Vector3 oldPos = camera.transform.position; transform.position = oldPos; vrCamera.transform.parent = this.transform; + + AmbientOcclusionVR.AddOcclusionEffect(vrCamera); } public void StealUICamera(Camera camera, bool fromGame = false) @@ -563,7 +578,7 @@ static bool Prefix(PlayerController __instance, ref Transform __result) controllerTransform = new GameObject().transform; } controllerTransform.position = MainCamera.camera.transform.position; - controllerTransform.rotation = VRCameraRig.GetTargetTansform().rotation; //the laser pointer transform + controllerTransform.rotation = Settings.LeftHandBasedTurning ? VRCameraRig.GetLeftTargetTansform().rotation : VRCameraRig.GetTargetTansform().rotation; //the laser pointer transform __result = controllerTransform; } else