Just tested and it takes about 11ms which isn't actually that slow in my eyes - depends of course how often you have to do this.
However,
Array.Copy would be fast but the main issue is that Quaternion and SerializableQuaternion are different types so you can not use it.
You can also not use Buffer.BlockCopy since the types are not primitives (float, int, etc).
But what you can do is using Marhsal.Copy.
For this to work you have to know your how your structs are layed out in bytes which luckily we happen to know from the Source Code of Quaternion
[StructLayout(LayoutKind.Sequential)]
[Unity.IL2CPP.CompilerServices.Il2CppEagerStaticClassConstruction]
public partial struct Quaternion : IEquatable<Quaternion>, IFormattable
{
// X component of the Quaternion. Don't modify this directly unless you know quaternions inside out.
public float x;
// Y component of the Quaternion. Don't modify this directly unless you know quaternions inside out.
public float y;
// Z component of the Quaternion. Don't modify this directly unless you know quaternions inside out.
public float z;
// W component of the Quaternion. Don't modify this directly unless you know quaternions inside out.
public float w;
....
so you only need to make sure that your type looks exactly the same byte wise (which seems already the case).
Now I am no marshal expert so there might even be a more direct way of doing this but you could have an in between step converting the Quternion[,,] to byte[] and from there to SerializableQuaternion[,,] like e.g.
private static void MarshalCopy<TFrom, TTo>(TFrom[,,] source, TTo[,,] destination) where TFrom : struct where TTo : struct
{
if (Marshal.SizeOf(typeof(TFrom)) != Marshal.SizeOf(typeof(TTo)))
{
throw new InvalidOperationException($"{typeof(TFrom).AssemblyQualifiedName} can not by marshaled into {typeof(TTo).AssemblyQualifiedName}");
}
var sourceHandle = GCHandle.Alloc(source, GCHandleType.Pinned);
var destinationHandle = GCHandle.Alloc(destination, GCHandleType.Pinned);
try
{
var sourcePointer = sourceHandle.AddrOfPinnedObject();
var bytes = new byte[source.Length * Marshal.SizeOf(typeof(TFrom))];
Marshal.Copy(sourcePointer, bytes, 0, bytes.Length);
var destinationPointer = destinationHandle.AddrOfPinnedObject();
Marshal.Copy(bytes, 0, destinationPointer, bytes.Length);
}
finally
{
if (sourceHandle.IsAllocated)
sourceHandle.Free();
if (destinationHandle.IsAllocated)
destinationHandle.Free();
}
}
and a little demo
var origin = new Quaternion[60, 60, 60];
for (var a = 0; a < origin.GetLength(0); a++)
{
for (var b = 0; b < origin.GetLength(1); b++)
{
for (var c = 0; c < origin.GetLength(2); c++)
{
origin[a, b, c] = Quaternion.Euler(UnityEngine.Random.Range(-360f, 360f), UnityEngine.Random.Range(-360f, 360f), UnityEngine.Random.Range(-360f, 360f));
}
}
}
var target = new SerializableQuaternion[origin.GetLength(0), origin.GetLength(1), origin.GetLength(2)];
var sw = new Stopwatch();
sw.Restart();
for (var a = 0; a < origin.GetLength(0); a++)
{
for (var b = 0; b < origin.GetLength(1); b++)
{
for (var c = 0; c < origin.GetLength(2); c++)
{
target[a, b, c] = origin[a, b, c];
}
}
}
sw.Stop();
UnityEngine.Debug.Log("Your way: " + sw.ElapsedMilliseconds + "ms");
sw.Restart();
var target2 = new SerializableQuaternion[origin.GetLength(0), origin.GetLength(1), origin.GetLength(2)];
MarshalCopy(origin, target2);
sw.Stop();
UnityEngine.Debug.Log("Alternative way: " + sw.ElapsedMilliseconds + "ms");
for (var a = 0; a < origin.GetLength(0); a++)
{
for (var b = 0; b < origin.GetLength(1); b++)
{
for (var c = 0; c < origin.GetLength(2); c++)
{
if ((Quaternion)target2[a, b, c] != target[a, b, c])
{
UnityEngine.Debug.LogError("Oh no error in copy!");
}
}
}
}
=>
Your way: 12ms
Alternative way: 1ms
and from another test run with 600, 60, 60
Your way: 123ms
Alternative way: 12ms
so I think we can say mine is faster by a factor of 10 ;)