using System; using UnityEditor; using UnityEngine; using UnityEngine.Splines; using System.IO; using System.Text; using Unity.Mathematics; using System.Collections.Generic; using System.Linq; using Unity.AI.Navigation; using UnityEngine.AI; namespace FrameJosh.SplineImporter { #if UNITY_EDITOR using static SplineImporter; using static SplineData; [CustomEditor(typeof(SplineImporter))] public class SplineImporterEditor : Editor { public override void OnInspectorGUI() { DrawDefaultInspector(); if (GUILayout.Button("Import Spline")) { var splineImporter = (SplineImporter)target; splineImporter.name = splineImporter.splineData.name; var splineData = JsonUtility.FromJson(splineImporter.splineData.text); var splineContainer = splineImporter.GetComponent(); foreach (var thisSpline in splineContainer.Splines) splineContainer.RemoveSpline(thisSpline); #if COM_UNITY_AI_NAVIGATION Undo.IncrementCurrentGroup(); var children = (from Transform child in splineImporter.transform select child.gameObject).ToList(); children.ForEach(Undo.DestroyObjectImmediate); #endif foreach (var thisDataSpline in splineData.splines) { var thisSpline = splineContainer.AddSpline(); thisSpline.Closed = thisDataSpline.closed; foreach (var thisControlPoint in thisDataSpline.controlPoints) { var position = thisControlPoint.position; var handleL = thisControlPoint.handleL; var handleR = thisControlPoint.handleR; var rotation = Quaternion.LookRotation((float3)handleR - position, Vector3.up) * Quaternion.AngleAxis(-thisControlPoint.tilt, Vector3.forward); float3x3 rotationMatrix = new(rotation); thisSpline.Add(new BezierKnot { Position = position * splineImporter.scale, Rotation = rotation, TangentIn = math.mul((float3)handleL - position, rotationMatrix) * splineImporter.scale, TangentOut = math.mul((float3)handleR - position, rotationMatrix) * splineImporter.scale }, TangentMode.Broken); } #if COM_UNITY_AI_NAVIGATION if (thisDataSpline.closed) continue; var go = new GameObject { transform = { parent = splineImporter.transform } }; var link = go.AddComponent(); var startPoint = thisDataSpline.controlPoints[0].position * splineImporter.scale; if (NavMesh.SamplePosition(startPoint, out var startHit, 2f, NavMesh.AllAreas)) startPoint = startHit.position; var endPoint = thisDataSpline.controlPoints[^1].position * splineImporter.scale; if (NavMesh.SamplePosition(endPoint, out var endHit, 2f, NavMesh.AllAreas)) endPoint = endHit.position; link.startPoint = startPoint; link.endPoint = endPoint; link.area = NavMesh.GetAreaFromName(splineImporter.area); Undo.RegisterCreatedObjectUndo(go, $"Create link from {startPoint} to {endPoint}"); #endif } } if (GUILayout.Button("Export Spline")) { var splineImporter = (SplineImporter)target; if (!splineImporter.splineData) { var path = "Assets" + EditorUtility.SaveFilePanel("Save .JSON", "", "New Spline.json", "json")[ Application.dataPath.Length..]; if (path.Length > 0) { File.WriteAllBytes(path, Encoding.ASCII.GetBytes("")); AssetDatabase.Refresh(); var textAsset = AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset)) as TextAsset; splineImporter.splineData = textAsset; } else return; } var splineContainer = splineImporter.GetComponent(); SplineData splineData = new() { splines = new Spline[splineContainer.Splines.Count] }; List dataSplines = new(); foreach (var thisSpline in splineContainer.Splines) { List controlPoints = new(); foreach (var thisBezierKnot in thisSpline.Knots) { Position position = thisBezierKnot.Position; float3x3 rotationMatrix = new(Quaternion.Inverse(thisBezierKnot.Rotation)); Position handleL = math.mul(thisBezierKnot.TangentIn, rotationMatrix) + thisBezierKnot.Position; Position handleR = math.mul(thisBezierKnot.TangentOut, rotationMatrix) + thisBezierKnot.Position; controlPoints.Add(new ControlPoint { position = position, handleL = handleL, handleR = handleR }); } dataSplines.Add(new Spline { controlPoints = controlPoints.ToArray(), closed = thisSpline.Closed }); } splineData.splines = dataSplines.ToArray(); File.WriteAllText(AssetDatabase.GetAssetPath(splineImporter.splineData), JsonUtility.ToJson(splineData, true)); AssetDatabase.Refresh(); } } } [RequireComponent(typeof(SplineContainer))] #endif public class SplineImporter : MonoBehaviour { #if UNITY_EDITOR public TextAsset splineData; public float3 scale = new(1f, 1f, 1f); public string area = "Walkable"; #endif } #if UNITY_EDITOR [Serializable] public class SplineData { [Serializable] public struct Position { public float x; public float y; public float z; public static implicit operator Position(float3 vector) { return new Position { x = vector.x, y = vector.z, z = vector.y }; } public static implicit operator float3(Position vector) { return new float3(vector.x, vector.z, vector.y); } } [Serializable] public struct ControlPoint { public Position position; public Position handleL; public Position handleR; public float tilt; } [Serializable] public struct Spline { public ControlPoint[] controlPoints; public bool closed; } public Spline[] splines = Array.Empty(); } #endif }