From a892658dcbba567eaa4f3a3f7d00a393d777ac25 Mon Sep 17 00:00:00 2001 From: Tobey Blaber Date: Thu, 27 May 2021 00:19:58 +0100 Subject: [PATCH 1/2] Initial implementation --- .../OrientedBoundsExtensionMethods.cs | 22 ++++ SnapBuilder/SnapBuilder.cs | 123 +++++++++++++++--- ...tjacket.Subnautica.Mods.SnapBuilder.csproj | 1 + 3 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 SnapBuilder/ExtensionMethods/OrientedBoundsExtensionMethods.cs diff --git a/SnapBuilder/ExtensionMethods/OrientedBoundsExtensionMethods.cs b/SnapBuilder/ExtensionMethods/OrientedBoundsExtensionMethods.cs new file mode 100644 index 0000000..f4e7ed1 --- /dev/null +++ b/SnapBuilder/ExtensionMethods/OrientedBoundsExtensionMethods.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Straitjacket.Subnautica.Mods.SnapBuilder.ExtensionMethods +{ + internal static class OrientedBoundsExtensionMethods + { + public static List GetCorners(this OrientedBounds bounds) + { + List corners = new List(); + corners.Add(bounds.position - bounds.extents); + corners.Add(bounds.position + bounds.extents); + corners.Add(new Vector3(corners[0].x, corners[0].y, corners[1].z)); + corners.Add(new Vector3(corners[0].x, corners[1].y, corners[0].z)); + corners.Add(new Vector3(corners[1].x, corners[0].y, corners[0].z)); + corners.Add(new Vector3(corners[0].x, corners[1].y, corners[1].z)); + corners.Add(new Vector3(corners[1].x, corners[1].y, corners[0].z)); + corners.Add(new Vector3(corners[1].x, corners[0].y, corners[1].z)); + return corners; + } + } +} diff --git a/SnapBuilder/SnapBuilder.cs b/SnapBuilder/SnapBuilder.cs index b39384a..9e190c7 100644 --- a/SnapBuilder/SnapBuilder.cs +++ b/SnapBuilder/SnapBuilder.cs @@ -1,14 +1,16 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Reflection; using HarmonyLib; using SMLHelper.V2.Handlers; +using Straitjacket.ExtensionMethods.UnityEngine; using UnityEngine; namespace Straitjacket.Subnautica.Mods.SnapBuilder { using BepInEx.Subnautica; - using ExtensionMethods.UnityEngine; + using ExtensionMethods; using Patches; internal static class SnapBuilder @@ -109,27 +111,44 @@ public static Transform SnapBuilderAimTransform }; /// - /// Get the hit point and normal localised to the hit transform + /// Get a new hit where the point and normal are localised the given transform /// /// - /// - /// /// - private static void GetLocalisedHit(RaycastHit hit, out Vector3 localisedHitPoint, out Vector3 localisedHitNormal, Transform transform = null) + /// + private static RaycastHit GetLocalisedHit(RaycastHit hit, Transform transform = null) { transform ??= hit.transform; - localisedHitPoint = transform.InverseTransformPoint(hit.point); // Get the hit point localised relative to the hit transform - localisedHitNormal = transform.InverseTransformDirection(hit.normal).normalized; // Get the hit normal localised to the hit transform + hit.point = transform.InverseTransformPoint(hit.point); // Get the hit point localised relative to the hit transform + hit.normal = transform.InverseTransformDirection(hit.normal).normalized; // Get the hit normal localised to the hit transform + return hit; } /// - /// Get the hit point snapped based on the hit normal and current round factor + /// Gets a new hit in world space + /// + /// + /// + /// + private static RaycastHit GetWorldSpaceHit(RaycastHit hit, Transform transform = null) + { + transform ??= hit.transform; + hit.point = transform.TransformPoint(hit.point); + hit.normal = transform.TransformDirection(hit.normal).normalized; + return hit; + } + + /// + /// Gets a new hit where the point is snapped based on the normal and current round factor /// /// /// /// - private static Vector3 GetSnappedHitPoint(Vector3 hitPoint, Vector3 hitNormal) + private static RaycastHit GetSnappedHit(RaycastHit hit) { + Vector3 hitPoint = hit.point; + Vector3 hitNormal = hit.normal; + hitNormal.x = Mathf.Abs(hitNormal.x); hitNormal.y = Mathf.Abs(hitNormal.y); hitNormal.z = Mathf.Abs(hitNormal.z); @@ -152,7 +171,73 @@ private static Vector3 GetSnappedHitPoint(Vector3 hitPoint, Vector3 hitNormal) hitPoint.z = Math.RoundToNearest(hitPoint.z, roundFactor); } - return hitPoint; + hit.point = hitPoint; + return hit; + } + + /// + /// Gets a new hit popped onto the most appropriate surface at the most appropriate point, + /// or the original hit if the operation is not possible + /// + /// + /// + private static RaycastHit PopHitOntoBestSurface(RaycastHit hit) + { + if (!Player.main.IsInsideWalkable()) + return hit; + + switch (Builder.GetSurfaceType(hit.normal)) + { + case SurfaceType.Wall + when !Builder.allowedSurfaceTypes.Contains(SurfaceType.Wall) + && Builder.allowedSurfaceTypes.Contains(SurfaceType.Ground): + + // Get the rotation of the object + Quaternion rotation = Builder.rotationEnabled + ? CalculateRotation(ref Builder.additiveRotation, hit, Builder.forceUpright || Player.main.IsInsideWalkable()) + : Quaternion.identity; + + // Get the corners of the object based on the Builder.bounds, localised to the hit point + IEnumerable corners = Builder.bounds + .Select(bounds => new { Bounds = bounds, Corners = bounds.GetCorners() }) + .SelectMany(x => x.Corners.Select(corner => hit.point + rotation * corner)); + + // Get the farthest corner from the player + Vector3 farthestCorner = corners.OrderByDescending(x + => Vector3.Distance(x, OffsetAimTransform.position)).First(); + + // Center the corner to the hit.point on the local X and Y axes + var empty = new GameObject(); + var child = new GameObject(); + empty.transform.position = hit.point; + empty.transform.forward = hit.normal; + child.transform.SetParent(empty.transform); + child.transform.position = farthestCorner; + child.transform.localPosition = new Vector3(0, 0, child.transform.localPosition.z); + Vector3 farthestCornerCentered = child.transform.position; + + // Clean up the GameObjects as we don't need them anymore + GameObject.Destroy(child); + GameObject.Destroy(empty); + + // Now move the hit.point outward from the wall just enough so that the object can fit + Vector3 poppedPoint = hit.point + hit.normal * Vector3.Distance(farthestCornerCentered, hit.point) * 1.001f; + + // Try to get a new hit by aiming at the floor from this popped point + if (Physics.Raycast(poppedPoint, + Vector3.down, + out RaycastHit poppedHit, + Builder.placeMaxDistance, + Builder.placeLayerMask, + QueryTriggerInteraction.Ignore)) + { + return poppedHit; + } + + break; + } + + return hit; } private static int lastCalculationFrame; @@ -172,11 +257,11 @@ public static Transform GetAimTransform() // If no hit, exit early if (!Physics.Raycast(OffsetAimTransform.position, - BuilderAimTransform.forward, - out RaycastHit hit, - Builder.placeMaxDistance, - Builder.placeLayerMask, - QueryTriggerInteraction.Ignore)) + BuilderAimTransform.forward, + out RaycastHit hit, + Builder.placeMaxDistance, + Builder.placeLayerMask, + QueryTriggerInteraction.Ignore)) { SnapBuilderAimTransform.position = OffsetAimTransform.position; SnapBuilderAimTransform.forward = BuilderAimTransform.forward; @@ -184,11 +269,13 @@ public static Transform GetAimTransform() } Transform hitTransform = GetAppropriateTransform(hit); - GetLocalisedHit(hit, out Vector3 localPoint, out Vector3 localNormal, hitTransform); - Vector3 snappedPoint = GetSnappedHitPoint(localPoint, localNormal); + RaycastHit localisedHit = GetLocalisedHit(hit, hitTransform); + RaycastHit snappedHit = GetSnappedHit(localisedHit); + RaycastHit snappedWorldSpaceHit = GetWorldSpaceHit(snappedHit, hitTransform); + RaycastHit poppedHit = PopHitOntoBestSurface(snappedWorldSpaceHit); SnapBuilderAimTransform.position = OffsetAimTransform.position; - SnapBuilderAimTransform.forward = hitTransform.TransformPoint(snappedPoint) - SnapBuilderAimTransform.position; + SnapBuilderAimTransform.forward = poppedHit.point - SnapBuilderAimTransform.position; return SnapBuilderAimTransform; } diff --git a/SnapBuilder/Straitjacket.Subnautica.Mods.SnapBuilder.csproj b/SnapBuilder/Straitjacket.Subnautica.Mods.SnapBuilder.csproj index f1ce96e..ce4b1b9 100644 --- a/SnapBuilder/Straitjacket.Subnautica.Mods.SnapBuilder.csproj +++ b/SnapBuilder/Straitjacket.Subnautica.Mods.SnapBuilder.csproj @@ -95,6 +95,7 @@ + From 550dcf4d34e5443c24a4d363c6cd5013bd463d95 Mon Sep 17 00:00:00 2001 From: Tobey Blaber Date: Thu, 27 May 2021 01:02:40 +0100 Subject: [PATCH 2/2] Made offset uniform and vary appropriately based on whether SN/BZ --- SnapBuilder/SnapBuilder.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/SnapBuilder/SnapBuilder.cs b/SnapBuilder/SnapBuilder.cs index 9e190c7..d1b5726 100644 --- a/SnapBuilder/SnapBuilder.cs +++ b/SnapBuilder/SnapBuilder.cs @@ -220,8 +220,15 @@ private static RaycastHit PopHitOntoBestSurface(RaycastHit hit) GameObject.Destroy(child); GameObject.Destroy(empty); + float offset +#if SUBNAUTICA + = 0.1f; // in subnautica, the collision boundary between objects is much larger than BZ +#elif BELOWZERO + = 0.0001f; +#endif + // Now move the hit.point outward from the wall just enough so that the object can fit - Vector3 poppedPoint = hit.point + hit.normal * Vector3.Distance(farthestCornerCentered, hit.point) * 1.001f; + Vector3 poppedPoint = hit.point + hit.normal * Vector3.Distance(farthestCornerCentered, hit.point) + hit.normal * offset; // Try to get a new hit by aiming at the floor from this popped point if (Physics.Raycast(poppedPoint,