WuhuIslandTesting/Library/PackageCache/com.unity.splines@1.0.1/Samples~/Runtime/SpawnWithinSplineBounds.cs

331 lines
12 KiB
C#
Raw Normal View History

2025-01-07 02:06:59 +01:00
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;
using Unity.Mathematics;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Splines;
#endif
using Interpolators = UnityEngine.Splines.Interpolators;
using Random = UnityEngine.Random;
namespace Unity.Splines.Examples
{
[ExecuteInEditMode]
[DisallowMultipleComponent]
public class SpawnWithinSplineBounds : MonoBehaviour
{
struct SpawnPoint
{
public float3 pos;
public float3 right;
public float3 up;
}
[SerializeField]
SplineContainer m_SplineContainer;
public SplineContainer splineContainer => m_SplineContainer;
[SerializeField]
Transform m_SpawnContainer;
[SerializeField]
int m_MaxIterations;
[Header("Spawning")]
[SerializeField]
List<GameObject> m_Prefabs;
[SerializeField]
float m_SpawnSpacing;
[SerializeField]
[Range(0, 1)]
float m_SpawnChance;
[SerializeField]
List<GameObject> m_BorderPrefabs;
[SerializeField]
float m_BorderSpawnSpacing;
[SerializeField]
[Range(0, 1)]
float m_BorderSpawnChance;
[SerializeField]
SplineData<float> m_SpawnBorderData;
public SplineData<float> spawnBorderData => m_SpawnBorderData;
[Header("Randomization")]
[SerializeField]
int m_RandomSeed;
[SerializeField]
Vector2 m_RotationRandomRange;
int m_Iterations;
List<Vector2> m_SplineSegments = new List<Vector2>();
List<Vector2> m_ParentPointSegments = new List<Vector2>();
const int k_SegmentsPerCurve = 10;
void OnEnable()
{
#if UNITY_EDITOR
EditorSplineUtility.afterSplineWasModified += OnSplineModified;
m_SpawnBorderData.changed += OnSpawnBorderDataChanged;
#endif
}
void OnDisable()
{
#if UNITY_EDITOR
EditorSplineUtility.afterSplineWasModified -= OnSplineModified;
m_SpawnBorderData.changed -= OnSpawnBorderDataChanged;
#endif
}
void OnValidate()
{
#if UNITY_EDITOR
if (m_SplineContainer != null && !EditorApplication.isPlayingOrWillChangePlaymode)
EditorApplication.delayCall += () => OnSplineModified(m_SplineContainer.Spline);
#endif
}
void CleanUp()
{
if (m_SpawnContainer == null)
return;
for (int i = m_SpawnContainer.childCount - 1; i >= 0; --i)
{
var child = m_SpawnContainer.GetChild(i);
#if UNITY_EDITOR
DestroyImmediate(child.gameObject);
#else
Destroy(child.gameObject);
#endif
}
}
void OnSplineModified(Spline spline)
{
if (spline == m_SplineContainer.Spline && m_SpawnContainer != null)
{
CleanUp();
BuildSplineSegments();
Random.InitState(m_RandomSeed);
if (m_Prefabs.Count > 0)
SpawnObjectsWithinSpline(m_SplineContainer, m_SpawnChance, m_SpawnSpacing, false);
if (m_BorderPrefabs.Count > 0)
SpawnObjectsWithinSpline(m_SplineContainer, m_BorderSpawnChance, m_BorderSpawnSpacing, true);
}
}
void SpawnObjectsWithinSpline(SplineContainer splineContainer, float spawnChance, float spawnSpacing, bool spawnOnBorder)
{
var spline = splineContainer.Spline;
var splineLen = spline.GetLength();
var points = new List<SpawnPoint>();
var splineTime = 0f;
var spawnCount = Mathf.CeilToInt(splineLen / spawnSpacing);
var splineXform = splineContainer.transform;
for (int i = 0; i < spawnCount; ++i)
{
// Here we do not need to manually transform the output vectors as all SplineContainer's evaluation methods transform the results to world space.
splineContainer.Evaluate(splineTime, out var _, out var dir, out var up);
// Spline's evaluation methods return results in spline space therfore manual transforming to world space is required.
var pos = splineXform.TransformPoint(spline.GetPointAtLinearDistance(splineTime, spawnSpacing, out splineTime));
var right = splineXform.TransformDirection(Vector3.ProjectOnPlane(math.cross(math.normalize(dir), up), up));
var spawnBorder = m_SpawnBorderData.Evaluate(spline, splineTime, PathIndexUnit.Normalized, new Interpolators.LerpFloat());
if (spawnBorder <= spawnSpacing * 0.5f)
{
if (!spawnOnBorder)
SpawnRandomPrefab(m_Prefabs, pos, -right, up, spawnChance);
}
else if (spawnOnBorder)
SpawnRandomPrefab(m_BorderPrefabs, pos, -right, up, spawnChance);
points.Add(new SpawnPoint() { pos = pos, right = right, up = up });
}
m_Iterations = 1;
SpawnObjectsForPoints(points, spawnChance, spawnSpacing, spawnOnBorder);
}
void SpawnObjectsForPoints(List<SpawnPoint> points, float spawnChance, float spawnSpacing, bool spawnOnBorder)
{
if (m_Iterations == m_MaxIterations)
return;
// Backup parent points
var parentPoints = new List<SpawnPoint>(points);
// Offset all child points along right vector
for (int i = 0; i < points.Count; i++)
{
var splinePoint = points[i];
var nextIdx = (i == points.Count - 1 ? 0 : i + 1);
var nextPoint = points[nextIdx];
var right = (float3) Vector3.Slerp(splinePoint.right, nextPoint.right, 0.5f);
var up = (float3) Vector3.Slerp(splinePoint.up, nextPoint.up, 0.5f);
var pos = math.lerp(splinePoint.pos, nextPoint.pos, 0.5f);
splinePoint.pos = pos + right * spawnSpacing;
splinePoint.right = right;
splinePoint.up = up;
points[i] = splinePoint;
}
var pointsToRemove = new SortedSet<int>();
var spawnedPoints = new List<SpawnPoint>();
// Check if point should be discard - spawn otherwise
for (int i = 0; i < points.Count; ++i)
{
var discardPoint = false;
var pointWithinBorder = false;
var pointSplineSpace = m_SplineContainer.transform.InverseTransformPoint(points[i].pos);
// Check against border
var dist = SplineUtility.GetNearestPoint(m_SplineContainer.Spline, pointSplineSpace, out var _, out var splineTime);
var spawnOffset = m_SpawnBorderData.Evaluate(m_SplineContainer.Spline, splineTime, PathIndexUnit.Normalized, new Interpolators.LerpFloat());
if (dist < spawnOffset)
pointWithinBorder = true;
// Check against child points
for (int spawnedPointIdx = spawnedPoints.Count - 1; spawnedPointIdx >= 0; --spawnedPointIdx)
{
if (math.length(points[i].pos - spawnedPoints[spawnedPointIdx].pos) < spawnSpacing)
{
discardPoint = true;
pointsToRemove.Add(i);
break;
}
}
// Check against parent points
if (!discardPoint)
{
var nextIdx = i == points.Count - 1 ? 0 : i + 1;
while (i != nextIdx)
{
if (math.length(points[i].pos - parentPoints[nextIdx].pos) < spawnSpacing * 0.9f)
{
discardPoint = true;
pointsToRemove.Add(i);
break;
}
nextIdx = nextIdx == points.Count - 1 ? 0 : nextIdx + 1;
}
}
// Ensure point is within parent points bounds
if (!discardPoint)
{
m_ParentPointSegments.Clear();
foreach (var parentPoint in parentPoints)
m_ParentPointSegments.Add(new Vector2(parentPoint.pos.x, parentPoint.pos.z));
discardPoint = !PointInsidePolygon(new Vector2(points[i].pos.x, points[i].pos.z), m_ParentPointSegments);
}
// Ensure point is roughly within spline bounds
if (!discardPoint)
discardPoint = !PointInsidePolygon(new Vector2(pointSplineSpace.x, pointSplineSpace.z), m_SplineSegments);
if (!discardPoint)
{
if (!pointWithinBorder)
{
if (!spawnOnBorder)
SpawnRandomPrefab(m_Prefabs, points[i].pos, -points[i].right, points[i].up, spawnChance);
}
else if (spawnOnBorder)
SpawnRandomPrefab(m_BorderPrefabs, points[i].pos, -points[i].right, points[i].up, spawnChance);
spawnedPoints.Add(points[i]);
}
}
m_Iterations++;
foreach (var point in pointsToRemove.Reverse())
points.RemoveAt(point);
if (points.Count == 0)
return;
SpawnObjectsForPoints(points, spawnChance, spawnSpacing, spawnOnBorder);
}
void SpawnRandomPrefab(List<GameObject> prefabs, Vector3 position, Vector3 forward, Vector3 up, float spawnChance)
{
if (Random.Range(0f, 1f) > spawnChance)
return;
var prefab = prefabs[Random.Range(0, prefabs.Count)];
var go = Instantiate(prefab, position, quaternion.identity);
go.transform.rotation = Quaternion.LookRotation(forward, up) * Quaternion.AngleAxis(Random.Range(m_RotationRandomRange.x, m_RotationRandomRange.y), Vector3.up);
go.transform.SetParent(m_SpawnContainer, true);
}
void OnSpawnBorderDataChanged()
{
OnSplineModified(m_SplineContainer.Spline);
}
void BuildSplineSegments()
{
m_SplineSegments.Clear();
var spline = m_SplineContainer.Spline;
var curveCount = spline.Closed ? spline.Count : spline.Count - 1;
var stepSize = 1f / k_SegmentsPerCurve;
for (int curveIndex = 0; curveIndex < curveCount; ++curveIndex)
{
for (int step = 0; step < k_SegmentsPerCurve; ++step)
{
var splineTime = spline.CurveToSplineT(curveIndex + step * stepSize);
var pos = spline.EvaluatePosition(splineTime);
m_SplineSegments.Add(new Vector2(pos.x, pos.z));
}
}
}
bool PointInsidePolygon(Vector2 point, List<Vector2> polygon)
{
Vector2 p1, p2;
p1 = polygon[0];
var counter = 0;
for (int i = 1; i <= polygon.Count; i++)
{
p2 = polygon[i % polygon.Count];
if (point.y > Mathf.Min(p1.y, p2.y))
{
if (point.y <= Mathf.Max(p1.y, p2.y))
{
if (point.x <= Mathf.Max(p1.x, p2.x))
{
if (p1.y != p2.y)
{
var xinters = (point.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
if (p1.x == p2.x || point.x <= xinters)
counter++;
}
}
}
}
p1 = p2;
}
if (counter % 2 == 0)
return false;
else
return true;
}
}
}