using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace GeNa.Core { /// /// Spline Extension for running GeNa Spawners along a Spline /// [Serializable] [CreateAssetMenu(fileName = "Spawner", menuName = "Procedural Worlds/GeNa/Extensions/Spawner", order = 0)] public class GeNaSpawnerExtension : GeNaSplineExtension { #region Variables [SerializeField] protected SpawnerEntry m_spawnerEntry = null; [SerializeField] protected bool m_autoIterate = false; [SerializeField] protected bool m_alignToSpline = false; [SerializeField] protected bool m_alignChildrenToSpline = false; [SerializeField] protected bool m_conformToSlope = false; [SerializeField] protected bool m_conformChildrenToSlope = false; [SerializeField] protected bool m_snapToGround = false; [SerializeField] protected bool m_snapChildrenToGround = false; [SerializeField] protected UndoRecord m_undoRecord = null; [SerializeField] protected Vector3 m_intersectionBoundsSize = new Vector3(10f, 10f, 10f); [SerializeField] protected bool m_checkIntersectionBounds = false; [NonSerialized] private bool m_isProcessing = false; #endregion #region Properties public SpawnerEntry SpawnerEntry => m_spawnerEntry; public GeNaSpawner Spawner { get => m_spawnerEntry != null ? m_spawnerEntry.Spawner : null; set { if (m_spawnerEntry != null) { if (m_spawnerEntry.Spawner != value) m_spawnerEntry = new SpawnerEntry(value); } else { m_spawnerEntry = new SpawnerEntry(value); } } } public GeNaSpawnerData SpawnerData => m_spawnerEntry?.SpawnerData; public Transform Target { get => m_spawnerEntry?.Target; set { if (m_spawnerEntry != null) m_spawnerEntry.Target = value; } } public Vector3 OffsetPosition { get => m_spawnerEntry != null ? m_spawnerEntry.OffsetPosition : default; set { if (m_spawnerEntry != null) m_spawnerEntry.OffsetPosition = value; } } public Vector3 OffsetRotation { get => m_spawnerEntry != null ? m_spawnerEntry.OffsetRotation : default; set { if (m_spawnerEntry != null) m_spawnerEntry.OffsetRotation = value; } } public float FlowRate { get => m_spawnerEntry != null ? m_spawnerEntry.FlowRate : default; set { if (m_spawnerEntry != null) m_spawnerEntry.FlowRate = value; } } public float SpawnRange { get => m_spawnerEntry != null ? m_spawnerEntry.SpawnRange : default; set { if (m_spawnerEntry != null) m_spawnerEntry.SpawnRange = value; } } public float ThrowDistance { get => m_spawnerEntry != null ? m_spawnerEntry.ThrowDistance : default; set { if (m_spawnerEntry != null) m_spawnerEntry.ThrowDistance = value; } } public List SpawnCalls { get => m_spawnerEntry != null ? m_spawnerEntry.SpawnCalls : new List(); set { if (m_spawnerEntry != null) m_spawnerEntry.SpawnCalls = value; } } public bool AutoIterate { get => m_autoIterate; set => m_autoIterate = value; } public bool AlignToSpline { get => m_alignToSpline; set => m_alignToSpline = value; } public bool AlignChildrenToSpline { get => m_alignChildrenToSpline; set => m_alignChildrenToSpline = value; } public bool ConformToSlope { get => m_conformToSlope; set => m_conformToSlope = value; } public bool ConformChildrenToSlope { get => m_conformChildrenToSlope; set => m_conformChildrenToSlope = value; } public bool SnapToGround { get => m_snapToGround; set => m_snapToGround = value; } public bool SnapChildrenToGround { get => m_snapChildrenToGround; set => m_snapChildrenToGround = value; } public bool IsProcessing { get => m_isProcessing; set => m_isProcessing = value; } public Vector3 IntersectionBoundsSize { get => m_intersectionBoundsSize; set => m_intersectionBoundsSize = value; } public bool CheckIntersectionBounds { get => m_checkIntersectionBounds; set => m_checkIntersectionBounds = value; } #endregion #region Methods protected override GameObject OnBake(GeNaSpline spline) { SpawnCalls.Clear(); Execute(); return null; } public void Load() { if (m_spawnerEntry == null) return; var spawner = m_spawnerEntry.Spawner; if (spawner == null) return; spawner.Load(); } protected override void OnSelect() { base.OnSelect(); if (Spawner == null) return; name = Spawner.name; Spawner.Load(); } protected override void OnAttach(GeNaSpline spline) { if (SpawnerData == null) return; m_spawnerEntry.SpawnCalls = GenerateSpawnCalls(); m_spawnerEntry.FlowRate = SpawnerData.SpawnRange; UpdateSpawnCallsGround(m_spawnerEntry.SpawnCalls); } public override void PreExecute() { if (SpawnerData == null) return; UpdateSpawnCallsGround(m_spawnerEntry.SpawnCalls); // Generate the spawn calls m_spawnerEntry.SpawnCalls = GenerateSpawnCalls(); name = Spawner.name; if (Spline != null) GeNaSpawnerInternal.SetupTempObject(Spline.transform); } public override void Execute() { if (SpawnerData == null) return; // Loop over each Spawn Call foreach (SpawnCall spawnCall in SpawnCalls) { // Loop over each Entity foreach (ResourceEntity entity in spawnCall.SpawnedEntities) { GameObject gameObject = entity.GameObject; if (gameObject != null) { if (gameObject.hideFlags == HideFlags.HideAndDontSave) continue; // Deactivate that entity gameObject.SetActive(false); } } } UpdateEntities(); } protected override void OnDelete() { if (m_undoRecord != null) m_undoRecord.Undo(); GeNaEvents.onSpawnFinished -= OnPostSpawn; } public void UpdateSpawnCallsGround(List spawnCalls) { if (m_spawnerEntry == null) return; GeNaSpawner spawner = m_spawnerEntry.Spawner; if (spawner == null) return; if (spawnCalls.Count == 0) return; ActivateAllEntities(false); Transform ground = GetTarget(out _); spawner.Load(); ActivateAllEntities(true); foreach (SpawnCall spawnCall in spawnCalls) { spawnCall.Ground = ground; spawnCall.Target = ground; spawnCall.SpawnRange = SpawnRange; spawnCall.ThrowDistance = ThrowDistance; GeNaSpawnerInternal.GenerateAabbTest(SpawnerData, out AabbTest aabbTest, spawnCall.Location); bool locationIsValid = GeNaSpawnerInternal.CheckLocationForSpawn(SpawnerData, aabbTest, null, false); spawnCall.CanSpawn = locationIsValid && spawnCall.Target == ground; } } public void Spawn() { if (m_isProcessing) return; if (!Spline.HasNodes) { GeNaDebug.LogWarning($"The Spline '{Spline.name}' does not contain any nodes!"); return; } if (SpawnerData == null) return; List spawnCalls = GenerateSpawnCalls(); if (spawnCalls.Count == 0) return; m_spawnerEntry.RecordUndo = true; m_spawnerEntry.SpawnCalls = spawnCalls; m_spawnerEntry.RootSpawnCall = spawnCalls.First(); m_spawnerEntry.Spawner.Load(); GeNaUtility.ScheduleSpawn(m_spawnerEntry); m_isProcessing = true; GeNaEvents.onSpawnFinished -= OnPostSpawn; GeNaEvents.onSpawnFinished += OnPostSpawn; } public void OnPostSpawn() { m_undoRecord = SpawnerData.LastUndoRecord; m_isProcessing = false; Spline.UpdateSpline(); GeNaEvents.onSpawnFinished -= OnPostSpawn; } public void Iterate() { if (SpawnerData == null) return; if (m_isProcessing) return; if (m_undoRecord != null) m_undoRecord.Undo(); // Clears the AabbManager var geNaManager = GeNaManager.GetInstance(); geNaManager.AabbManager.Clear(); Spawn(); GeNaEvents.onSpawnFinished -= OnPostSpawn; GeNaEvents.onSpawnFinished += OnPostSpawn; } public void UpdateEntities() { if (SpawnerData == null) return; // Loop over each spawned entity Dictionary foreach (SpawnCall spawnCall in SpawnCalls) { if (!spawnCall.IsActive) continue; spawnCall.AlignChildrenToRotation = m_alignChildrenToSpline; spawnCall.ConformChildrenToSlope = m_conformChildrenToSlope; spawnCall.SnapChildrenToGround = m_snapChildrenToGround; spawnCall.UpdateEntities(); } } public Transform GetTarget(Vector3 position, out RaycastHit hitInfo) { Transform target = default; // Sample ground at location Vector3 origin = position + Vector3.up * 2f; Vector3 direction = Vector3.down; Ray ray = new Ray(origin, direction); if (GeNaSpawnerInternal.Sample(SpawnerData, ray, out hitInfo)) target = hitInfo.transform; return target; } public Transform GetTarget(out RaycastHit hitInfo) { hitInfo = default; if (SpawnerData == null) return null; if (Spline == null) return null; List nodes = Spline.Nodes; if (nodes.Count == 0) return null; GeNaNode firstNode = nodes.First(); Vector3 nodePosition = firstNode.Position; return GetTarget(nodePosition, out hitInfo); } public void ActivateAllEntities(bool isActive) { foreach (var spawnCall in SpawnCalls) { if (isActive) spawnCall.EnableEntities(); else spawnCall.DisableEntities(); } } public List GenerateSpawnCalls() { List result = new List(); if (SpawnerData == null) return result; if (FlowRate <= 0.0f) return result; List nodes = Spline.Nodes; float length = Spline.Length; int spawnCallCount = Mathf.CeilToInt(length / FlowRate); result.Capacity = spawnCallCount; if (nodes.Count == 0) return result; float distance = 0f; int index = 0; List intersectionBounds = GeNaSpline.BuildIntersectionBounds(Spline, IntersectionBoundsSize); while (distance < length) { // Collect Sample at Distance GeNaSample geNaSample = Spline.GetSampleAtDistance(distance); if (geNaSample != null) { SpawnCall spawnCall = default; if (index < SpawnCalls.Count) { spawnCall = SpawnCalls[index++]; // if (spawnCall.IsEmpty || !spawnCall.Generated) // spawnCall = null; } if (spawnCall == null) { // Generate Spawn Call for entry spawnCall = new SpawnCall { Normal = Vector3.up }; } // Offset Location & Rotation Vector3 location = geNaSample.Location + (geNaSample.Scale.x * OffsetPosition.x * geNaSample.Right) + (geNaSample.Scale.y * OffsetPosition.y * geNaSample.Up); //Check if it's in intersection bounds if (CheckIntersectionBounds) { if (GeNaSpline.IsInIntersectionBounds(intersectionBounds, location)) { spawnCall.IsActive = false; } else { spawnCall.IsActive = true; } } else { spawnCall.IsActive = true; } if (m_isProcessing) { spawnCall.Rotation = Vector3.zero; spawnCall.SpawnedLocation = location; spawnCall.Location = location; } else { Vector3 rotation = new Vector3(0f, OffsetRotation.y, 0f); Quaternion forwardRotation = Quaternion.LookRotation(geNaSample.Forward, Vector3.up); Vector3 euler = forwardRotation.eulerAngles; // Align to Spline Mode? if (m_alignToSpline) { euler.x = euler.z = 0f; euler.y += OffsetRotation.y; rotation = euler; } spawnCall.SpawnedLocation = location; spawnCall.Location = location; spawnCall.Rotation = rotation; if (m_conformToSlope) { spawnCall.ConformToSlope(); } if (m_snapToGround) { spawnCall.SnapToGround(); spawnCall.Location += Vector3.up * OffsetPosition.y; } } // GeNaSpawnerInternal.SetSpawnOrigin(SpawnerData, spawnCall); result.Add(spawnCall); } // Offset Distance for next iteration distance += FlowRate; } for (int i = index; i < SpawnCalls.Count; i++) { SpawnCall spawnCall = SpawnCalls[i]; spawnCall.Dispose(); } if (SpawnerData != null) GeNaSpawnerInternal.GenerateRandomData(SpawnerData, result); return result; } protected override void OnDeselect() { } #endregion } }