You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
11 KiB
302 lines
11 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using UnityEngine.Splines;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace UnityEditor.Splines
|
|
{
|
|
static class CopyPaste
|
|
{
|
|
// JSONUtility needs a root object to serialize
|
|
[Serializable]
|
|
class CopyPasteBuffer
|
|
{
|
|
public SerializedSpline[] Splines;
|
|
public SerializedLink[] Links;
|
|
}
|
|
|
|
[Serializable]
|
|
struct SerializedKnot
|
|
{
|
|
public BezierKnot Knot;
|
|
public TangentMode Mode;
|
|
public float Tension;
|
|
|
|
public SerializedKnot(SelectableKnot knot)
|
|
{
|
|
Knot = knot.GetBezierKnot(false);
|
|
Mode = knot.Mode;
|
|
Tension = knot.Tension;
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
class SerializedSpline
|
|
{
|
|
public float4x4 Transform;
|
|
public bool Closed;
|
|
public SerializedKnot[] Knots;
|
|
}
|
|
|
|
[Serializable]
|
|
class SerializedLink
|
|
{
|
|
public SplineKnotIndex[] Indices;
|
|
}
|
|
|
|
public static bool IsSplineCopyBuffer(string contents)
|
|
{
|
|
if (string.IsNullOrEmpty(contents))
|
|
return false;
|
|
|
|
|
|
var buffer = new CopyPasteBuffer();
|
|
try
|
|
{
|
|
EditorJsonUtility.FromJsonOverwrite(contents, buffer);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return buffer.Splines?.Length > 0;
|
|
}
|
|
|
|
static int CompareKnot(SelectableKnot a, SelectableKnot b)
|
|
{
|
|
var compareTarget = (int)math.sign(a.SplineInfo.Object.GetInstanceID() - b.SplineInfo.Object.GetInstanceID());
|
|
if (compareTarget != 0)
|
|
return compareTarget;
|
|
|
|
var compareSpline = (int)math.sign(a.SplineInfo.Index - b.SplineInfo.Index);
|
|
if (compareSpline != 0)
|
|
return compareSpline;
|
|
|
|
return (int)math.sign(a.KnotIndex - b.KnotIndex);
|
|
}
|
|
|
|
public static string Copy(IEnumerable<SelectableKnot> selection)
|
|
{
|
|
SerializedKnot[] ToArray(SelectableKnot[] original)
|
|
{
|
|
var result = new SerializedKnot[original.Length];
|
|
for (int i = 0; i < result.Length; ++i)
|
|
result[i] = new SerializedKnot(original[i]);
|
|
return result;
|
|
}
|
|
|
|
void Flatten(List<SelectableKnot[]> arrays, List<SelectableKnot> results)
|
|
{
|
|
results.Clear();
|
|
foreach (var knotArray in arrays)
|
|
results.AddRange(knotArray);
|
|
}
|
|
|
|
Dictionary<SelectableKnot, SplineKnotIndex> knotToSerializedIndex = new Dictionary<SelectableKnot, SplineKnotIndex>();
|
|
List<SerializedSpline> splines = new List<SerializedSpline>();
|
|
List<SelectableKnot> originalKnots = new List<SelectableKnot>(selection);
|
|
|
|
var connectedKnots = GetConnectedKnots(originalKnots);
|
|
foreach (var connectedKnotArray in connectedKnots)
|
|
{
|
|
// Skip Orphan Knots
|
|
if (connectedKnotArray.Length < 2)
|
|
continue;
|
|
|
|
var splineInfo = connectedKnotArray[0].SplineInfo;
|
|
splines.Add(new SerializedSpline
|
|
{
|
|
Closed = splineInfo.Spline.Closed && connectedKnotArray.Length == splineInfo.Spline.Count,
|
|
Knots = ToArray(connectedKnotArray),
|
|
Transform = splineInfo.LocalToWorld
|
|
});
|
|
|
|
for (int i = 0; i < connectedKnotArray.Length; ++i)
|
|
knotToSerializedIndex.Add(connectedKnotArray[i], new SplineKnotIndex(splines.Count - 1, i));
|
|
}
|
|
|
|
// Add the links
|
|
List<SplineKnotIndex> indices = new List<SplineKnotIndex>();
|
|
List<SerializedLink> links = new List<SerializedLink>();
|
|
List<SelectableKnot> knots = new List<SelectableKnot>();
|
|
|
|
// Update the original knots array with the removal of orphan knots
|
|
Flatten(connectedKnots, originalKnots);
|
|
|
|
foreach (var originalKnot in originalKnots)
|
|
{
|
|
EditorSplineUtility.GetKnotLinks(originalKnot, knots);
|
|
indices.Clear();
|
|
foreach (var knot in knots)
|
|
{
|
|
if (knotToSerializedIndex.TryGetValue(knot, out var index))
|
|
{
|
|
indices.Add(index);
|
|
|
|
//Remove the pair to ensure we don't get duplicates for every knot in the same link
|
|
knotToSerializedIndex.Remove(knot);
|
|
}
|
|
}
|
|
|
|
// Only serialized the link if at least 2 copied knots were linked together
|
|
if (indices.Count >= 2)
|
|
links.Add(new SerializedLink {Indices = indices.ToArray()});
|
|
}
|
|
|
|
if (splines.Count == 0)
|
|
return string.Empty;
|
|
|
|
CopyPasteBuffer buffer = new CopyPasteBuffer
|
|
{
|
|
Splines = splines.ToArray(),
|
|
Links = links.ToArray(),
|
|
};
|
|
|
|
return EditorJsonUtility.ToJson(buffer);
|
|
}
|
|
|
|
static List<SelectableKnot[]> GetConnectedKnots(List<SelectableKnot> knots)
|
|
{
|
|
if (knots.Count == 0)
|
|
return new List<SelectableKnot[]>();
|
|
|
|
knots.Sort(CompareKnot);
|
|
|
|
List<SelectableKnot[]> results = new List<SelectableKnot[]>();
|
|
List<SelectableKnot> connected = new List<SelectableKnot> { knots[0] };
|
|
|
|
for (int i = 1; i < knots.Count; ++i)
|
|
{
|
|
var previous = connected[^1];
|
|
var current = knots[i];
|
|
|
|
// Check if adjacent and on the same spline as previous
|
|
if (!previous.SplineInfo.Equals(current.SplineInfo)
|
|
|| previous.KnotIndex + 1 != current.KnotIndex)
|
|
{
|
|
results.Add(connected.ToArray());
|
|
connected.Clear();
|
|
}
|
|
|
|
connected.Add(current);
|
|
}
|
|
|
|
results.Add(connected.ToArray());
|
|
|
|
// Merge ends if the spline is closed and first and last knots are connected
|
|
for (int i = 0; i < results.Count; ++i)
|
|
{
|
|
var firstKnot = results[i][0];
|
|
if (firstKnot.KnotIndex == 0 && firstKnot.SplineInfo.Spline.Closed)
|
|
{
|
|
// Look for the last knot on the same spline
|
|
for (int j = i + 1; j < results.Count; ++j)
|
|
{
|
|
var lastKnot = results[j][^1];
|
|
|
|
// Early exit if not on the same spline
|
|
if (!lastKnot.SplineInfo.Equals(firstKnot.SplineInfo))
|
|
break;
|
|
|
|
if (lastKnot.KnotIndex == lastKnot.SplineInfo.Spline.Count - 1)
|
|
{
|
|
// combine both arrays
|
|
var a = results[j];
|
|
var b = results[i];
|
|
|
|
var newArray = new SelectableKnot[a.Length + b.Length];
|
|
Array.Copy(a, newArray, a.Length);
|
|
Array.Copy(b, 0, newArray, a.Length, b.Length);
|
|
results[i] = newArray;
|
|
results.RemoveAt(j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// Paste will create all new splines in the first active ISplineContainer in the selection.
|
|
// Duplicate will try to create new splines in the same container that the knots were copied from.
|
|
public static void Paste(string copyPasteBuffer)
|
|
{
|
|
ISplineContainer target = Selection.GetFiltered<ISplineContainer>(SelectionMode.TopLevel).FirstOrDefault() ??
|
|
ObjectFactory.CreateGameObject("New Spline", typeof(SplineContainer)).GetComponent<SplineContainer>();
|
|
|
|
Paste(copyPasteBuffer, target);
|
|
}
|
|
|
|
public static void Paste(string copyPasteBuffer, ISplineContainer target)
|
|
{
|
|
if (target == null)
|
|
throw new ArgumentNullException(nameof(target));
|
|
|
|
if (string.IsNullOrEmpty(copyPasteBuffer))
|
|
return;
|
|
|
|
var buffer = new CopyPasteBuffer();
|
|
try
|
|
{
|
|
EditorJsonUtility.FromJsonOverwrite(copyPasteBuffer, buffer);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
//If the copy buffer wasn't for a spline copy buffer, we just don't do anything
|
|
return;
|
|
}
|
|
|
|
var selection = new List<SelectableKnot>();
|
|
|
|
var inverse = (target is Component component)
|
|
? component.transform.localToWorldMatrix.inverse
|
|
: Matrix4x4.identity;
|
|
|
|
var branches = new List<Spline>(target.Splines);
|
|
int splineIndexOffset = branches.Count;
|
|
|
|
foreach (var serialized in buffer.Splines)
|
|
{
|
|
var knots = serialized.Knots;
|
|
var spline = new Spline(knots.Length);
|
|
spline.Closed = serialized.Closed;
|
|
var trs = serialized.Transform;
|
|
var index = branches.Count;
|
|
var info = new SplineInfo(target, index);
|
|
branches.Add(spline);
|
|
for (int i = 0, c = knots.Length; i < c; ++i)
|
|
{
|
|
spline.Add(knots[i].Knot.Transform(math.mul(inverse, trs)), knots[i].Mode, knots[i].Tension);
|
|
selection.Add(new SelectableKnot(info, i));
|
|
}
|
|
}
|
|
|
|
if (target is Object obj)
|
|
Undo.RecordObject(obj, "Paste Knots");
|
|
|
|
target.Splines = branches;
|
|
|
|
foreach (var link in buffer.Links)
|
|
{
|
|
var firstIndex = link.Indices[0];
|
|
firstIndex.Spline += splineIndexOffset;
|
|
|
|
for (int i = 1; i < link.Indices.Length; ++i)
|
|
{
|
|
var indexPair = link.Indices[i];
|
|
indexPair.Spline += splineIndexOffset;
|
|
target.KnotLinkCollection.Link(firstIndex, indexPair);
|
|
}
|
|
}
|
|
|
|
SplineSelection.Clear();
|
|
SplineSelection.AddRange(selection);
|
|
SceneView.RepaintAll();
|
|
}
|
|
}
|
|
}
|