Spline-Importer/Runtime/SplineImporter.cs

201 lines
7.7 KiB
C#

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<SplineData>(splineImporter.splineData.text);
var splineContainer = splineImporter.GetComponent<SplineContainer>();
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<NavMeshLink>();
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<SplineContainer>();
SplineData splineData = new()
{
splines = new Spline[splineContainer.Splines.Count]
};
List<Spline> dataSplines = new();
foreach (var thisSpline in splineContainer.Splines)
{
List<ControlPoint> 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<Spline>();
}
#endif
}