WuhuIslandTesting/Library/PackageCache/com.unity.splines@1.0.1/Runtime/SplineInstantiate.cs

829 lines
26 KiB
C#
Raw Permalink Normal View History

2025-01-07 02:06:59 +01:00
using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
using Random = UnityEngine.Random;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// SplineInstantiate is used to automatically instantiate prefabs or objects along a spline.
/// </summary>
[ExecuteInEditMode]
[AddComponentMenu("Splines/Spline Instantiate")]
public class SplineInstantiate : SplineComponent
{
/// <summary>
/// The space in which to interpret the offset, this can be different from the orientation space used to instantiate objects.
/// </summary>
public enum OffsetSpace
{
/// <summary> Use the spline space to orient instances.</summary>
[InspectorName("Spline Element")]
Spline = Space.Spline,
/// <summary> Use the spline GameObject space to orient instances.</summary>
[InspectorName("Spline Object")]
Local = Space.Local,
/// <summary> Use world space to orient instances.</summary>
[InspectorName("World Space")]
World = Space.World,
/// <summary> Use the original object space to orient instances.</summary>
[InspectorName("Instantiated Object")]
Object
}
[Serializable]
internal struct Vector3Offset
{
[Flags]
public enum Setup
{
None = 0x0,
HasOffset= 0x1,
HasCustomSpace= 0x2
}
public Setup setup;
public Vector3 min;
public Vector3 max;
public bool randomX;
public bool randomY;
public bool randomZ;
public OffsetSpace space;
public bool hasOffset => ( setup & Setup.HasOffset ) != 0;
public bool hasCustomSpace => ( setup & Setup.HasCustomSpace ) != 0;
internal Vector3 GetNextOffset()
{
if(( setup & Setup.HasOffset ) != 0)
{
return new Vector3(
randomX ? Random.Range(min.x,max.x) : min.x,
randomY ? Random.Range(min.y,max.y) : min.y,
randomZ ? Random.Range(min.z,max.z) : min.z);
}
return Vector3.zero;
}
internal void CheckMinMaxValidity()
{
max.x = Mathf.Max(min.x, max.x);
max.y = Mathf.Max(min.y, max.y);
max.z = Mathf.Max(min.z, max.z);
}
internal void CheckMinMax()
{
CheckMinMaxValidity();
if(max.magnitude > 0)
setup |= Setup.HasOffset;
else
setup &= ~Setup.HasOffset;
}
internal void CheckCustomSpace(Space instanceSpace)
{
if((int)space == (int)instanceSpace)
setup &= ~Setup.HasCustomSpace;
else
setup |= Setup.HasCustomSpace;
}
}
/// <summary>
/// Describe the item prefab to instantiate and associate it with a probability
/// </summary>
[Serializable]
public struct InstantiableItem
{
/// <summary> The prefab to instantiate.</summary>
public GameObject prefab;
/// <summary> Probability for this prefab. </summary>
public float probability;
}
/// <summary>
/// Describe the possible methods to instantiate instances along the spline.
/// </summary>
public enum Method
{
/// <summary> Use exact number of instances.</summary>
[InspectorName("Instance Count")]
InstanceCount,
/// <summary> Use distance along the spline between 2 instances.</summary>
[InspectorName("Spline Distance")]
SpacingDistance,
/// <summary> Use distance in straight line between 2 instances.</summary>
[InspectorName("Linear Distance")]
LinearDistance
}
/// <summary>
/// Describe the space that is used to orientate the instantiated object
/// </summary>
public enum Space
{
/// <summary> Use the spline space to orient instances.</summary>
[InspectorName("Spline Element")]
Spline,
/// <summary> Use the spline GameObject space to orient instances.</summary>
[InspectorName("Spline Object")]
Local,
/// <summary> Use world space to orient instances.</summary>
[InspectorName("World Space")]
World,
}
[SerializeField]
SplineContainer m_Container;
/// <summary>
/// The SplineContainer containing the targeted spline.
/// </summary>
public SplineContainer container
{
get => m_Container;
set => m_Container = value;
}
[SerializeField]
List<InstantiableItem> m_ItemsToInstantiate = new List<InstantiableItem>();
[SerializeField]
Method m_Method = Method.SpacingDistance;
/// <summary>
/// The instantiation method to use.
/// </summary>
public Method method
{
get => m_Method;
set => m_Method = value;
}
[SerializeField]
Space m_Space = Space.Spline;
/// <summary>
/// The space in which to orient the instanced object.
/// </summary>
public Space space
{
get => m_Space;
set => m_Space = value;
}
[SerializeField]
Vector2 m_Spacing = new Vector2(1f,1f);
/// <summary>
/// Minimum spacing between 2 generated instances,
/// if equal to the maxSpacing, then all instances will have the exact same spacing
/// </summary>
float minSpacing
{
get => m_Spacing.x;
set
{
m_Spacing = new Vector2(value, m_Spacing.y);
ValidateSpacing();
}
}
/// <summary>
/// Maximum spacing between 2 generated instances,
/// if equal to the minSpacing, then all instances will have the exact same spacing
/// </summary>
float maxSpacing
{
get => m_Spacing.y;
set
{
m_Spacing = new Vector2(m_Spacing.x, value);
ValidateSpacing();
}
}
[SerializeField]
AlignAxis m_Up = AlignAxis.YAxis;
/// <summary>
/// Up axis of the object, by default set to the Y Axis
/// </summary>
public AlignAxis upAxis
{
get => m_Up;
set => m_Up = value;
}
[SerializeField]
AlignAxis m_Forward = AlignAxis.ZAxis;
/// <summary>
/// Forward axis of the object, by default set to the Z Axis
/// </summary>
public AlignAxis forwardAxis
{
get => m_Forward;
set
{
m_Forward = value;
ValidateAxis();
}
}
[SerializeField]
Vector3Offset m_PositionOffset;
/// <summary>
/// Minimum (X,Y,Z) position offset to randomize instanced objects positions.
/// (X,Y and Z) values have to be lower to the ones of maxPositionOffset.
/// </summary>
public Vector3 minPositionOffset
{
get => m_PositionOffset.min;
set
{
m_PositionOffset.min = value;
m_PositionOffset.CheckMinMax();
}
}
/// <summary>
/// Maximum (X,Y,Z) position offset to randomize instanced objects positions.
/// (X,Y and Z) values have to be higher to the ones of minPositionOffset.
/// </summary>
public Vector3 maxPositionOffset
{
get => m_PositionOffset.max;
set
{
m_PositionOffset.max = value;
m_PositionOffset.CheckMinMax();
}
}
/// <summary>
/// Coordinate space to use to offset positions of the instances.
/// </summary>
public OffsetSpace positionSpace
{
get => m_PositionOffset.space;
set
{
m_PositionOffset.space = value;
m_PositionOffset.CheckCustomSpace(m_Space);
}
}
[SerializeField]
Vector3Offset m_RotationOffset;
/// <summary>
/// Minimum (X,Y,Z) euler rotation offset to randomize instanced objects rotations.
/// (X,Y and Z) values have to be lower to the ones of maxRotationOffset.
/// </summary>
public Vector3 minRotationOffset
{
get => m_RotationOffset.min;
set
{
m_RotationOffset.min = value;
m_RotationOffset.CheckMinMax();
}
}
/// <summary>
/// Maximum (X,Y,Z) euler rotation offset to randomize instanced objects rotations.
/// (X,Y and Z) values have to be higher to the ones of minRotationOffset.
/// </summary>
public Vector3 maxRotationOffset
{
get => m_RotationOffset.max;
set
{
m_RotationOffset.max = value;
m_RotationOffset.CheckMinMax();
}
}
/// <summary>
/// Coordinate space to use to offset rotations of the instances.
/// </summary>
public OffsetSpace rotationSpace
{
get => m_RotationOffset.space;
set
{
m_RotationOffset.space = value;
m_RotationOffset.CheckCustomSpace(m_Space);
}
}
[SerializeField]
Vector3Offset m_ScaleOffset;
/// <summary>
/// Minimum (X,Y,Z) scale offset to randomize instanced objects scales.
/// (X,Y and Z) values have to be lower to the ones of maxScaleOffset.
/// </summary>
public Vector3 minScaleOffset
{
get => m_ScaleOffset.min;
set
{
m_ScaleOffset.min = value;
m_ScaleOffset.CheckMinMax();
}
}
/// <summary>
/// Maximum (X,Y,Z) scale offset to randomize instanced objects scales.
/// (X,Y and Z) values have to be higher to the ones of minScaleOffset.
/// </summary>
public Vector3 maxScaleOffset
{
get => m_ScaleOffset.max;
set
{
m_ScaleOffset.max = value;
m_ScaleOffset.CheckMinMax();
}
}
/// <summary>
/// Coordinate space to use to offset rotations of the instances (usually OffsetSpace.Object).
/// </summary>
public OffsetSpace scaleSpace
{
get => m_ScaleOffset.space;
set
{
m_ScaleOffset.space = value;
m_ScaleOffset.CheckCustomSpace(m_Space);
}
}
[SerializeField]
List<GameObject> m_Instances = new List<GameObject>();
internal List<GameObject> instances => m_Instances;
bool m_InstancesCacheDirty = false;
[SerializeField]
bool m_AutoRefresh = true;
InstantiableItem m_CurrentItem;
bool m_SplineDirty = false;
float m_MaxProbability = 1f;
float maxProbability
{
get => m_MaxProbability;
set
{
if(m_MaxProbability != value)
{
m_MaxProbability = value;
m_InstancesCacheDirty = true;
}
}
}
[HideInInspector]
[SerializeField]
int m_Seed = 0;
int seed
{
get => m_Seed;
set
{
m_Seed = value;
m_InstancesCacheDirty = true;
Random.InitState(m_Seed);
}
}
void OnEnable()
{
if(m_Seed == 0)
m_Seed = GetInstanceID();
#if UNITY_EDITOR
Undo.undoRedoPerformed += UndoRedoPerformed;
#endif
}
void OnDestroy()
{
#if UNITY_EDITOR
Undo.undoRedoPerformed -= UndoRedoPerformed;
#endif
Clear();
}
void UndoRedoPerformed()
{
m_InstancesCacheDirty = true;
m_SplineDirty = true;
UpdateInstances();
}
void OnValidate()
{
if(m_Container != null && m_Container.Spline != null)
m_Container.Spline.changed += delegate() { m_SplineDirty = m_AutoRefresh; };
ValidateSpacing();
m_SplineDirty = m_AutoRefresh;
float probability = 0;
for(int i = 0; i<m_ItemsToInstantiate.Count; i++)
{
var item = m_ItemsToInstantiate[i];
if(item.prefab != null)
probability += item.probability;
}
m_PositionOffset.CheckMinMaxValidity();
m_RotationOffset.CheckMinMaxValidity();
m_ScaleOffset.CheckMinMaxValidity();
maxProbability = probability;
}
void ValidateSpacing()
{
var xSpacing = Mathf.Max(0.1f, m_Spacing.x);
if(m_Method != Method.LinearDistance)
{
var ySpacing = float.IsNaN(m_Spacing.y) ? xSpacing : Mathf.Max(0.1f, m_Spacing.y);
m_Spacing = new Vector2(xSpacing, Mathf.Max(xSpacing, ySpacing));
} else if(m_Method == Method.LinearDistance)
{
var ySpacing = float.IsNaN(m_Spacing.y) ? m_Spacing.y : xSpacing;
m_Spacing = new Vector2(xSpacing, ySpacing);
}
}
/// <summary>
/// This method prevents Up and Forward axis to be aligned.
/// Up axis will always be kept as the prioritized one.
/// If Forward axis is in the same direction than the Up (or -Up) it'll be changed to the next axis.
/// </summary>
void ValidateAxis()
{
if(m_Forward == m_Up || (int)m_Forward == ( (int)m_Up + 3 ) % 6)
m_Forward = (AlignAxis)(( (int)m_Forward + 1 ) % 6);
}
internal void SetSplineDirty(Spline spline)
{
if(m_Container != null && spline == m_Container.Spline && m_AutoRefresh)
UpdateInstances();
}
void InitContainer()
{
if(m_Container == null)
m_Container = GetComponent<SplineContainer>();
if(m_Container != null && m_Container.Spline != null)
{
m_Container.Spline.changed += () => { m_SplineDirty = m_AutoRefresh; };
}
}
/// <summary>
/// Clear all the created instances along the spline
/// </summary>
public void Clear()
{
SetDirty();
TryClearCache();
}
/// <summary>
/// Set the created instances dirty to erase them next time instances will be generated
/// (otherwise the next generation will reuse cached instances)
/// </summary>
public void SetDirty()
{
m_InstancesCacheDirty = true;
}
void TryClearCache()
{
if(!m_InstancesCacheDirty)
{
for(int i = 0; i < m_Instances.Count; i++)
{
if(m_Instances[i] == null)
{
m_InstancesCacheDirty = true;
break;
}
}
}
if(m_InstancesCacheDirty)
{
for(int i = m_Instances.Count - 1; i >= 0; --i)
{
#if UNITY_EDITOR
DestroyImmediate(m_Instances[i]);
#else
Destroy(m_Instances[i]);
#endif
}
m_Instances.Clear();
m_InstancesCacheDirty = false;
}
}
/// <summary>
/// Change the Random seed to obtain a new generation along the Spline
/// </summary>
public void Randomize()
{
#if UNITY_EDITOR
Undo.RecordObject(this, "Changing SplineInstantiate seed");
#endif
seed = Random.Range(int.MinValue, int.MaxValue);
m_SplineDirty = true;
}
void Update()
{
if(m_SplineDirty)
UpdateInstances();
}
/// <summary>
/// Create and update all instances along the spline based on the list of available prefabs/objects.
/// </summary>
public void UpdateInstances()
{
TryClearCache();
if(m_Container == null)
InitContainer();
if(m_Container == null || m_ItemsToInstantiate.Count == 0)
return;
#if UNITY_EDITOR
Undo.RegisterFullObjectHierarchyUndo(this,"Update spline instances");
#endif
using(var nativeSpline = new NativeSpline(m_Container.Spline, m_Container.transform.localToWorldMatrix, Allocator.TempJob))
{
float currentDist = 0f;
float splineLength = m_Container.CalculateLength();
Random.InitState(m_Seed);
//Spawning instances
var times = new List<float>();
int index = 0;
var spacing = Random.Range(m_Spacing.x, m_Spacing.y);
if(m_Method == Method.InstanceCount && spacing <= 1)
currentDist = (int)spacing == 1 ? splineLength / 2f : splineLength + 1f;
while(currentDist <= splineLength)
{
var prefabIndex = m_ItemsToInstantiate.Count == 1 ? 0 : GetPrefabIndex();
m_CurrentItem = m_ItemsToInstantiate[prefabIndex];
if(m_CurrentItem.prefab == null)
return;
if(index >= m_Instances.Count)
{
#if UNITY_EDITOR
var assetType = PrefabUtility.GetPrefabAssetType(m_CurrentItem.prefab);
if(assetType == PrefabAssetType.MissingAsset)
{
Debug.LogError($"Trying to instantiate a missing asset for item index [{prefabIndex}].");
return;
}
if(assetType != PrefabAssetType.NotAPrefab)
m_Instances.Add(PrefabUtility.InstantiatePrefab(m_CurrentItem.prefab, transform) as GameObject);
else
#endif
m_Instances.Add(Instantiate(m_CurrentItem.prefab, transform));
m_Instances[index].hideFlags |= HideFlags.HideInHierarchy;
}
m_Instances[index].transform.localPosition = m_CurrentItem.prefab.transform.localPosition;
m_Instances[index].transform.localRotation = m_CurrentItem.prefab.transform.localRotation;
m_Instances[index].transform.localScale = m_CurrentItem.prefab.transform.localScale;
times.Add(currentDist / splineLength);
if(m_Method == Method.SpacingDistance)
{
spacing = Random.Range(m_Spacing.x, m_Spacing.y);
currentDist += spacing;
}
else if(m_Method == Method.InstanceCount)
{
if(spacing > 1)
{
var previousDist = currentDist;
currentDist += splineLength / ( nativeSpline.Closed ? (int)spacing : (int)spacing - 1 );
if(previousDist < splineLength && currentDist > splineLength)
currentDist = splineLength;
}
else
currentDist += splineLength;
}
else if(m_Method == Method.LinearDistance)
{
//m_Spacing.y is set to NaN to trigger automatic computation
if(float.IsNaN(m_Spacing.y))
{
var meshfilter = m_Instances[index].GetComponent<MeshFilter>();
var axis = Vector3.right;
if(m_Forward == AlignAxis.ZAxis || m_Forward == AlignAxis.NegativeZAxis)
axis = Vector3.forward;
if(m_Forward == AlignAxis.YAxis || m_Forward == AlignAxis.NegativeYAxis)
axis = Vector3.up;
if(meshfilter == null)
{
meshfilter = m_Instances[index].GetComponentInChildren<MeshFilter>();
if(meshfilter != null)
axis = Vector3.Scale(meshfilter.transform.InverseTransformDirection(m_Instances[index].transform.TransformDirection(axis)), meshfilter.transform.lossyScale);
}
if(meshfilter != null)
{
var bounds = meshfilter.sharedMesh.bounds;
var filters = meshfilter.GetComponentsInChildren<MeshFilter>();
foreach(var filter in filters)
{
var localBounds = filter.sharedMesh.bounds;
bounds.size = new Vector3(Mathf.Max(bounds.size.x, localBounds.size.x),
Mathf.Max(bounds.size.z, localBounds.size.z),
Mathf.Max(bounds.size.z, localBounds.size.z));
}
spacing = Vector3.Scale(bounds.size, axis).magnitude;
}
}
else
spacing = Random.Range(m_Spacing.x, m_Spacing.y);
nativeSpline.GetPointAtLinearDistance(times[index], spacing, out var nextT);
currentDist = nextT >= 1f ? splineLength + 1f : nextT * splineLength;
}
index++;
}
//removing extra unnecessary instances
for(int u = m_Instances.Count-1; u >= index; u--)
{
if(m_Instances[u] != null)
{
#if UNITY_EDITOR
DestroyImmediate(m_Instances[u]);
#else
Destroy(m_Instances[u]);
#endif
m_Instances.RemoveAt(u);
}
}
//Positioning elements
for(int i = 0; i < index; i++)
{
var instance = m_Instances[i];
var splineT = times[i];
nativeSpline.Evaluate(splineT, out var position, out var direction, out var splineUp);
instance.transform.position = position;
if(m_Method == Method.LinearDistance)
{
var nextPosition = nativeSpline.EvaluatePosition(i + 1 < index ? times[i + 1] : 1f);
direction = nextPosition - position;
}
var up = splineUp;
var forward = direction;
if(m_Space == Space.World)
{
up = Vector3.up;
forward = Vector3.forward;
}else if(m_Space == Space.Local)
{
up = transform.TransformDirection(Vector3.up);
forward = transform.TransformDirection(Vector3.forward);
}
// Correct forward and up vectors based on axis remapping parameters
var remappedForward = GetAxis(m_Forward);
var remappedUp = GetAxis(m_Up);
var axisRemapRotation = Quaternion.Inverse(Quaternion.LookRotation(remappedForward, remappedUp));
instance.transform.rotation = Quaternion.LookRotation(forward, up) * axisRemapRotation;
var customUp = up;
var customForward = forward;
if(m_PositionOffset.hasOffset)
{
if(m_PositionOffset.hasCustomSpace)
GetCustomSpaceAxis(m_PositionOffset.space,splineUp, direction, instance.transform, out customUp, out customForward);
var offset = m_PositionOffset.GetNextOffset();
var right = Vector3.Cross(customUp, customForward).normalized;
instance.transform.position += offset.x * right + offset.y * (Vector3)customUp + offset.z * (Vector3)customForward;
}
if(m_RotationOffset.hasOffset)
{
customUp = up;
customForward = forward;
if(m_RotationOffset.hasCustomSpace)
{
GetCustomSpaceAxis(m_RotationOffset.space,splineUp, direction, instance.transform, out customUp, out customForward);
if(m_RotationOffset.space == OffsetSpace.Object)
axisRemapRotation = quaternion.identity;
}
var offset = m_RotationOffset.GetNextOffset();
var right = Vector3.Cross(customUp, customForward).normalized;
customForward = Quaternion.AngleAxis(offset.y, customUp) * Quaternion.AngleAxis(offset.x, right) * customForward;
customUp = Quaternion.AngleAxis(offset.x, right) * Quaternion.AngleAxis(offset.z, customForward) * customUp;
instance.transform.rotation = Quaternion.LookRotation(customForward, customUp) * axisRemapRotation;
}
if(m_ScaleOffset.hasOffset)
{
customUp = up;
customForward = forward;
if(m_ScaleOffset.hasCustomSpace)
GetCustomSpaceAxis(m_ScaleOffset.space,splineUp, direction, instance.transform, out customUp, out customForward);
customUp = instance.transform.InverseTransformDirection(customUp);
customForward = instance.transform.InverseTransformDirection(customForward);
var offset = m_ScaleOffset.GetNextOffset();
var right = Vector3.Cross(customUp, customForward).normalized;
instance.transform.localScale += offset.x * right + offset.y * (Vector3)customUp + offset.z * (Vector3)customForward;;
}
}
}
m_SplineDirty = false;
}
void GetCustomSpaceAxis(OffsetSpace space, float3 splineUp, float3 direction, Transform instanceTransform, out float3 customUp,out float3 customForward)
{
customUp = Vector3.up;
customForward = Vector3.forward;
if(space == OffsetSpace.Local)
{
customUp = transform.TransformDirection(Vector3.up);
customForward = transform.TransformDirection(Vector3.forward);
}
else if(space == OffsetSpace.Spline)
{
customUp = splineUp;
customForward = direction;
}
else if(space == OffsetSpace.Object)
{
customUp = instanceTransform.TransformDirection(Vector3.up);
customForward = instanceTransform.TransformDirection(Vector3.forward);
}
}
int GetPrefabIndex()
{
var prefabChoice = Random.Range(0, m_MaxProbability);
var currentProbability = 0f;
for(int i = 0; i < m_ItemsToInstantiate.Count; i++)
{
if(m_ItemsToInstantiate[i].prefab == null)
continue;
var itemProbability = m_ItemsToInstantiate[i].probability;
if(prefabChoice < currentProbability + itemProbability)
return i;
currentProbability += itemProbability;
}
return 0;
}
}