Advertisement
Staggart

Spline Caps

Aug 1st, 2024 (edited)
312
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 9.10 KB | None | 0 0
  1. using System;
  2. using System.Linq;
  3. using Unity.Mathematics;
  4. using UnityEngine;
  5. using UnityEngine.Rendering;
  6. using UnityEngine.Serialization;
  7. using UnityEngine.Splines;
  8. using Object = UnityEngine.Object;
  9. #if UNITY_EDITOR
  10. using UnityEditor;
  11. #endif
  12.  
  13. namespace sc.modeling.splines.runtime.auxiliary
  14. {
  15.     [ExecuteAlways]
  16.     public class SplineCaps : MonoBehaviour
  17.     {
  18.         public SplineContainer splineContainer;
  19.         [SerializeField] [ HideInInspector]
  20.         private int splineCount;
  21.        
  22.         [Serializable]
  23.         public class Cap
  24.         {
  25.             public Cap(Position position)
  26.             {
  27.                 this.position = position;
  28.             }
  29.            
  30.             public enum Position
  31.             {
  32.                 Start,
  33.                 End
  34.             }
  35.             public Position position;
  36.            
  37.             public GameObject prefab;
  38.             [SerializeField] [ HideInInspector]
  39.             private int previousPrefabID;
  40.  
  41.             public bool HasPrefabChanged()
  42.             {
  43.                 if (prefab == null) return true;
  44.  
  45.                 int hashCode = prefab.GetHashCode();
  46.                 if (hashCode != previousPrefabID)
  47.                 {
  48.                     previousPrefabID = hashCode;
  49.                    
  50.                     return true;
  51.                 }
  52.                
  53.                 return false;
  54.             }
  55.            
  56.             [Space]
  57.             [Header("Position")]
  58.             [Tooltip("Positional offset, relative to the curve's tangent")]
  59.             public Vector3 offset;
  60.             [Tooltip("Shifts the object along the spline curve by this many units")]
  61.             [Min(0f)]
  62.             public float shift = 0f;
  63.        
  64.             [Space]
  65.             [Header("Rotation")]
  66.             [Tooltip("Align the object's forward direction to the tangent of the spline")]
  67.             [FormerlySerializedAs("alignRotation")]
  68.             public bool align = true;
  69.             [Tooltip("Rotation in degrees, added to the object's rotation")]
  70.             [FormerlySerializedAs("addedRotation")]
  71.             public Vector3 rotation;
  72.  
  73.             [Header("Scale")]
  74.             public Vector3 scale = Vector3.one;
  75.  
  76.             //Save a reference to the instantiated objects, so they can be accessed again, deleted when necessary.
  77.             [HideInInspector]
  78.             public GameObject[] instances = Array.Empty<GameObject>();
  79.  
  80.             public bool HasMissingInstances()
  81.             {
  82.                 for (int i = 0; i < instances.Length; i++)
  83.                 {
  84.                     if (instances[i] == null) return true;
  85.                 }
  86.                 return false;
  87.             }
  88.         }
  89.  
  90.         [Space]
  91.  
  92.         public Cap[] caps = new[]
  93.         {
  94.             new Cap(Cap.Position.Start),
  95.             new Cap(Cap.Position.End)
  96.         };
  97.  
  98.         [Space]
  99.        
  100.         [Tooltip("Hide the prefab instances in the hierarchy")]
  101.         public bool hideInstances = false;
  102.        
  103.         private void Reset()
  104.         {
  105.             splineContainer = gameObject.GetComponentInParent<SplineContainer>();
  106.         }
  107.  
  108.         private void OnEnable()
  109.         {
  110.             Spline.Changed += OnSplineChanged;
  111.         }
  112.  
  113.         private void OnDisable()
  114.         {
  115.             Spline.Changed -= OnSplineChanged;
  116.         }
  117.        
  118.         private void OnSplineChanged(Spline spline, int index, SplineModification modificationType)
  119.         {
  120.             if (!splineContainer) return;
  121.  
  122.             //Spline belongs to the assigned container?
  123.             var splineIndex = Array.IndexOf(splineContainer.Splines.ToArray(), spline);
  124.             if (splineIndex < 0)
  125.                 return;
  126.  
  127.             Apply();
  128.         }
  129.        
  130.         public void Apply()
  131.         {
  132.             if (!splineContainer) return;
  133.  
  134.             var splineCountChanged = splineContainer.Splines.Count != splineCount;
  135.             //if(splineCountChanged) Debug.Log($"Spline count changed from {splineCount} to {splineContainer.Splines.Count}");
  136.             splineCount = splineContainer.Splines.Count;
  137.            
  138.             for (int i = 0; i < caps.Length; i++)
  139.             {
  140.                 if (splineCountChanged || caps[i].HasPrefabChanged() || caps[i].HasMissingInstances())
  141.                 {
  142.                     //Debug.Log($"Respawning cap {i}");
  143.                     Respawn(caps[i]);
  144.                 }
  145.             }
  146.  
  147.             for (int i = 0; i < caps.Length; i++)
  148.             {
  149.                 ApplyTransform(caps[i]);
  150.             }
  151.         }
  152.  
  153.         private void Respawn(Cap cap)
  154.         {
  155.             //Destroy any existing instances
  156.             for (int i = 0; i < cap.instances.Length; i++)
  157.             {
  158.                 CoreUtils.Destroy(cap.instances[i]);
  159.             }
  160.             cap.instances = Array.Empty<GameObject>();
  161.  
  162.             if (cap.prefab == null) return;
  163.  
  164.             //Respawn the prefabs (once per spline)
  165.             cap.instances = new GameObject[splineContainer.Splines.Count];
  166.  
  167.             for (int i = 0; i < cap.instances.Length; i++)
  168.             {
  169.                 GameObject instance = Instantiate(cap.prefab);
  170.                 instance.transform.SetParent(this.transform);
  171.                
  172.                 cap.instances[i] = instance;
  173.             }
  174.         }
  175.  
  176.         void ApplyTransform(Cap cap)
  177.         {
  178.             for (int splineIndex = 0; splineIndex < cap.instances.Length; splineIndex++)
  179.             {
  180.                 TransformToSpline(cap, cap.instances[splineIndex].transform, splineIndex);
  181.                
  182.                 //Gray out fields as any chances would be overwritten anyway
  183.                 cap.instances[splineIndex].transform.hideFlags =  HideFlags.NotEditable;
  184.  
  185.                 cap.instances[splineIndex].hideFlags = hideInstances ? HideFlags.HideInHierarchy : HideFlags.None;
  186.             }
  187.         }
  188.  
  189.         private bool IsPrefab(Object source)
  190.         {
  191.             bool isPrefab = false;
  192.            
  193.             #if UNITY_EDITOR
  194.             if (UnityEditor.PrefabUtility.GetPrefabAssetType(source) == PrefabAssetType.Variant)
  195.             {
  196.                 //PrefabUtility.GetCorrespondingObjectFromSource still returns the base prefab. However, this does work.
  197.                 isPrefab = source;
  198.             }
  199.             else
  200.             {
  201.                 isPrefab = UnityEditor.PrefabUtility.GetCorrespondingObjectFromOriginalSource(source);
  202.             }
  203.             #endif
  204.  
  205.             return isPrefab;
  206.         }
  207.        
  208.         private new GameObject Instantiate(Object source)
  209.         {
  210.             bool isPrefab = IsPrefab(source);
  211.             GameObject instance = null;
  212.            
  213.             #if UNITY_EDITOR
  214.             if (isPrefab)
  215.             {
  216.                 instance = UnityEditor.PrefabUtility.InstantiatePrefab(source, this.gameObject.scene) as GameObject;
  217.             }
  218.             #endif
  219.  
  220.             //Non-prefabs and builds
  221.             if (!isPrefab)
  222.             {
  223.                 instance = GameObject.Instantiate(source) as GameObject;
  224.             }
  225.  
  226.             return instance;
  227.         }
  228.  
  229.         private void TransformToSpline(Cap cap, Transform target, int splineIndex)
  230.         {
  231.             //Coincidentally the two enum values correspond to 0 and 1
  232.             float t = (int)cap.position;
  233.  
  234.             //Shift along spline by X-units
  235.             float shift = cap.shift / splineContainer.Splines[splineIndex].GetLength();
  236.             if (cap.position == Cap.Position.End) shift = -shift;
  237.  
  238.             t += shift;
  239.            
  240.             //Ensure a tangent can always be derived at very the start and end
  241.             t = Mathf.Clamp(t, 0.0001f, 0.9999f);
  242.            
  243.             splineContainer.Splines[splineIndex].Evaluate(t, out float3 splinePoint, out float3 tangent, out float3 up);
  244.  
  245.             float3 position = splinePoint;
  246.             float3 forward = math.normalize(tangent);
  247.             float3 right = math.cross(forward, up);
  248.            
  249.             //Offset
  250.             position += right * cap.offset.x;
  251.             position += up * cap.offset.y;
  252.             position += forward * cap.offset.z;
  253.            
  254.             //Position of point on spline in world-space
  255.             position = splineContainer.transform.TransformPoint(position);
  256.  
  257.             Quaternion rotation = Quaternion.Euler(cap.rotation);
  258.             if (cap.align)
  259.             {
  260.                 rotation = Quaternion.LookRotation(forward) * Quaternion.Euler(cap.rotation);
  261.             }
  262.  
  263.             target.SetPositionAndRotation(position, rotation);
  264.             target.localScale = cap.scale;
  265.         }
  266.     }
  267.    
  268.     #if UNITY_EDITOR
  269.     [CustomEditor(typeof(SplineCaps))]
  270.     [CanEditMultipleObjects]
  271.     public class SplineCapsEditor : Editor
  272.     {
  273.         public override void OnInspectorGUI()
  274.         {
  275.             EditorGUI.BeginChangeCheck();
  276.  
  277.             base.OnInspectorGUI();
  278.  
  279.             if (EditorGUI.EndChangeCheck())
  280.             {
  281.                 foreach (var m_target in targets)
  282.                     ((SplineCaps)m_target).Apply();
  283.             }
  284.         }
  285.     }
  286.     #endif
  287. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement