namespace Dreamteck.Splines.Editor { using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEditor; using System.IO; public class ObjectSpawnTool : SplineTool { internal class SpawnCollection { public class SpawnObject { public GameObject instance; public GameObject source; public SpawnObject(GameObject instance, GameObject source) { this.instance = instance; this.source = source; } } internal SplineComputer spline; internal List objects = new List(); internal void Clear() { for (int i = 0; i < objects.Count; i++) Object.DestroyImmediate(objects[i].instance); objects.Clear(); } internal void Spawn(GameObject obj, Vector3 position, Quaternion rotation) { GameObject go = null; bool isPrefab = PrefabUtility.GetPrefabAssetType(obj) != PrefabAssetType.NotAPrefab; if (isPrefab) go = (GameObject)PrefabUtility.InstantiatePrefab(obj); else go = Object.Instantiate(obj, position, rotation); go.transform.parent = spline.transform; objects.Add(new SpawnObject(go, obj)); } internal void Destroy(int index) { Object.DestroyImmediate(objects[index].instance); objects.RemoveAt(index); } internal SpawnCollection(SplineComputer spline) { this.spline = spline; } } internal List collections = new List(); double clipFrom = 0.0, clipTo = 1.0; List objects = new List(); int spawnCount = 1; enum Iteration { Ordered, Random } Iteration iteration = Iteration.Ordered; private int offsetSeed = 0; private int rotationSeed = 0; private int scaleSeed = 0; private int orderSeed = 0; private float positionOffset = 0f; private Vector2 randomSize = Vector2.one; private Vector2 offset = Vector2.zero; private Vector3 minRotationOffset = Vector3.zero; private Vector3 maxRotationOffset = Vector3.zero; private Vector3 minScaleMultiplier = Vector3.one; private Vector3 maxScaleMultiplier = Vector3.one; private bool randomizeOffset = false; private bool useRandomOffsetRotation = false; private bool shellOffset = true; private bool applyRotation = true; private bool applyScale = false; bool uniform = false; SplineSample result = new SplineSample(); System.Random orderRandom, offsetRandom, rotationRandom, scaleRandom; public override string GetName() { return "Spawn Objects"; } protected override string GetPrefix() { return "ObjectSpawnTool"; } public override void Close() { base.Close(); for (int i = 0; i < splines.Count; i++) splines[i].onRebuild -= Rebuild; if (promptSave) { if (EditorUtility.DisplayDialog("Save changes?", "You are about to close the Object Spawn Tool, do you want to save the generated objects?", "Yes", "No")) Save(); else Cancel(); } else Cancel(); promptSave = false; #if UNITY_2019_1_OR_NEWER SceneView.duringSceneGui -= OnScene; #else SceneView.onSceneGUIDelegate -= OnScene; #endif } public override void Open(EditorWindow window) { base.Open(window); GetSplines(); collections.Clear(); for (int i = 0; i < splines.Count; i++) { collections.Add(new SpawnCollection(splines[i])); splines[i].onRebuild += Rebuild; } Rebuild(); #if UNITY_2019_1_OR_NEWER SceneView.duringSceneGui += OnScene; #else SceneView.onSceneGUIDelegate += OnScene; #endif } void OnScene(SceneView current) { for (int i = 0; i < collections.Count; i++) { if (collections[i].spline != null) DSSplineDrawer.DrawSplineComputer(collections[i].spline); } } protected override void OnSplineAdded(SplineComputer spline) { base.OnSplineAdded(spline); collections.Add(new SpawnCollection(spline)); spline.onRebuild += Rebuild; Rebuild(); } protected override void OnSplineRemoved(SplineComputer spline) { base.OnSplineRemoved(spline); for (int i = 0; i < collections.Count; i++) { if (collections[i].spline == spline) { collections[i].Clear(); collections.RemoveAt(i); spline.onRebuild -= Rebuild; Rebuild(); return; } } } public override void Draw(Rect windowRect) { base.Draw(windowRect); if (splines.Count == 0) { EditorGUILayout.HelpBox("No spline selected! Select an object with a SplineComputer component.", MessageType.Warning); return; } EditorGUI.BeginChangeCheck(); ClipUI(ref clipFrom, ref clipTo); uniform = EditorGUILayout.Toggle("Uniform Samples", uniform); EditorGUILayout.Space(); float labelWidth = EditorGUIUtility.labelWidth; float fieldWidth = EditorGUIUtility.fieldWidth; EditorGUIUtility.labelWidth = 0; EditorGUIUtility.fieldWidth = 0; EditorGUILayout.BeginVertical(); for (int i = 0; i < objects.Count; i++) { EditorGUILayout.BeginHorizontal(); objects[i] = (GameObject)EditorGUILayout.ObjectField(objects[i], typeof(GameObject), true); if (GUILayout.Button("x", GUILayout.Width(20))) { objects.RemoveAt(i); i--; Rebuild(); Repaint(); continue; } if (i > 0) { if (GUILayout.Button("▲", GUILayout.Width(20))) { GameObject temp = objects[i - 1]; objects[i - 1] = objects[i]; objects[i] = temp; Rebuild(); } } if (i < objects.Count - 1) { if (GUILayout.Button("▼", GUILayout.Width(20))) { GameObject temp = objects[i + 1]; objects[i + 1] = objects[i]; objects[i] = temp; Rebuild(); } } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndVertical(); GameObject newObj = null; newObj = (GameObject)EditorGUILayout.ObjectField("Add Object", newObj, typeof(GameObject), true); if (newObj != null) { objects.Add(newObj); Rebuild(); } EditorGUILayout.Space(); EditorGUIUtility.labelWidth = labelWidth; EditorGUIUtility.fieldWidth = fieldWidth; bool hasObj = false; for (int i = 0; i < objects.Count; i++) { if (objects[i] != null) { hasObj = true; break; } } if (hasObj) spawnCount = EditorGUILayout.IntField("Spawn count", spawnCount); else spawnCount = 0; iteration = (Iteration)EditorGUILayout.EnumPopup("Iteration", iteration); if (iteration == Iteration.Random) orderSeed = EditorGUILayout.IntField("Order Seed", orderSeed); EditorGUILayout.Space(); EditorGUILayout.LabelField("Transform", EditorStyles.boldLabel); applyRotation = EditorGUILayout.Toggle("Apply Rotation", applyRotation); if (applyRotation) { EditorGUI.indentLevel++; minRotationOffset = EditorGUILayout.Vector3Field("Min. Rotation Offset", minRotationOffset); maxRotationOffset = EditorGUILayout.Vector3Field("Max. Rotation Offset", maxRotationOffset); rotationSeed = EditorGUILayout.IntField("Rotation Seed", rotationSeed); EditorGUI.indentLevel--; } applyScale = EditorGUILayout.Toggle("Apply Scale", applyScale); if (applyScale) { EditorGUI.indentLevel++; minScaleMultiplier = EditorGUILayout.Vector3Field("Min. Scale Multiplier", minScaleMultiplier); maxScaleMultiplier = EditorGUILayout.Vector3Field("Max. Scale Multiplier", maxScaleMultiplier); scaleSeed = EditorGUILayout.IntField("Scale Seed", scaleSeed); EditorGUI.indentLevel--; } positionOffset = EditorGUILayout.Slider("Evaluate Offset", positionOffset, -1f, 1f); offset = EditorGUILayout.Vector2Field("Offset", offset); randomizeOffset = EditorGUILayout.Toggle("Randomize Offset", randomizeOffset); if (randomizeOffset) { randomSize = EditorGUILayout.Vector2Field("Size", randomSize); offsetSeed = EditorGUILayout.IntField("Offset Seed", offsetSeed); shellOffset = EditorGUILayout.Toggle("Shell", shellOffset); useRandomOffsetRotation = EditorGUILayout.Toggle("Apply offset rotation", useRandomOffsetRotation); } if (EditorGUI.EndChangeCheck()) { promptSave = true; Rebuild(); } EditorGUILayout.BeginHorizontal(); if(collections.Count > 0) { if (GUILayout.Button("Save")) { Save(); } if (GUILayout.Button("Cancel")) { Cancel(); } } else { if (GUILayout.Button("New")) Open(windowInstance); } EditorGUILayout.EndHorizontal(); } protected override void Save() { base.Save(); //register created object undo for each object in collections collections.Clear(); //Set scene dirty } protected override void Cancel() { base.Cancel(); foreach (SpawnCollection collection in collections) collection.Clear(); collections.Clear(); } void InitializeRandomization() { orderRandom = new System.Random(orderSeed); if (randomizeOffset) offsetRandom = new System.Random(offsetSeed); if(applyRotation) rotationRandom = new System.Random(rotationSeed); if(applyScale) scaleRandom = new System.Random(scaleSeed); } protected override void Rebuild() { base.Rebuild(); if (objects.Count == 0) return; InitializeRandomization(); foreach (SpawnCollection c in collections) { if(c == null) continue; if (c.spline == null || spawnCount <= 0) { c.Clear(); continue; } HandleCollection(c); } } void HandleCollection(SpawnCollection collection) { collection.Clear(); if (collection.spline == null) return; while(collection.objects.Count > spawnCount && collection.objects.Count >= 0) collection.Destroy(collection.objects.Count - 1); int orderIndex = 0; while (collection.objects.Count < spawnCount) { switch (iteration) { case Iteration.Ordered: collection.Spawn(objects[orderIndex], Vector3.zero, Quaternion.identity); orderIndex++; if (orderIndex >= objects.Count) orderIndex = 0; break; case Iteration.Random: collection.Spawn(objects[orderRandom.Next(objects.Count)], Vector3.zero, Quaternion.identity); break; } } float splineLength = 0f; if (uniform) splineLength = collection.spline.CalculateLength() * (float)(clipTo - clipFrom); for (int i = 0; i < spawnCount; i++) { double percent = 0.0; if(spawnCount > 1) percent = (double)i / (spawnCount - 1); double evaluate = 0.0; if (uniform) evaluate = collection.spline.Travel(clipFrom, splineLength * (float)percent, Spline.Direction.Forward); else evaluate = DMath.Lerp(clipFrom, clipTo, percent); //Handle uniform splines evaluate += positionOffset; if (evaluate > 1f) evaluate -= 1f; else if (evaluate < 0f) evaluate += 1f; collection.spline.Evaluate(evaluate, ref result); HandleObject(collection.objects[i]); } } void HandleObject(SpawnCollection.SpawnObject obj) { Transform instanceTransform = obj.instance.transform; Transform sourceTransform = obj.source.transform; Vector3 right = result.right; instanceTransform.position = result.position; instanceTransform.position += -right * offset.x + result.up * offset.y; Quaternion offsetRot = Quaternion.Euler(minRotationOffset); if (applyRotation) { offsetRot = Quaternion.Euler(Mathf.Lerp(minRotationOffset.x, maxRotationOffset.x, (float)rotationRandom.NextDouble()), Mathf.Lerp(minRotationOffset.y, maxRotationOffset.y, (float)rotationRandom.NextDouble()), Mathf.Lerp(minRotationOffset.z, maxRotationOffset.z, (float)rotationRandom.NextDouble())); instanceTransform.rotation = result.rotation * offsetRot; } if (randomizeOffset) { float distance = (float)offsetRandom.NextDouble(); float angleInRadians = (float)offsetRandom.NextDouble() * 360f * Mathf.Deg2Rad; Vector2 randomCircle = new Vector2(distance * Mathf.Cos(angleInRadians), distance * Mathf.Sin(angleInRadians)); if (shellOffset) randomCircle.Normalize(); else randomCircle = Vector2.ClampMagnitude(randomCircle, 1f); instanceTransform.position += randomCircle.x * right * randomSize.x * result.size * 0.5f + randomCircle.y * result.up * randomSize.y * result.size * 0.5f; if (useRandomOffsetRotation) instanceTransform.rotation = Quaternion.LookRotation(result.forward, instanceTransform.position - result.position) * offsetRot; } if (applyScale) { Vector3 scale = sourceTransform.localScale * result.size; scale.x *= Mathf.Lerp(minScaleMultiplier.x, maxScaleMultiplier.x, (float)scaleRandom.NextDouble()); scale.y *= Mathf.Lerp(minScaleMultiplier.y, maxScaleMultiplier.y, (float)scaleRandom.NextDouble()); scale.z *= Mathf.Lerp(minScaleMultiplier.z, maxScaleMultiplier.z, (float)scaleRandom.NextDouble()); instanceTransform.localScale = scale; } else instanceTransform.localScale = sourceTransform.localScale; } } }