Skip to content

Instantly share code, notes, and snippets.

@EternalTamago
Last active October 16, 2024 16:01
Show Gist options
  • Save EternalTamago/e58f79a698e3aa92fe6cae78db8e3a9d to your computer and use it in GitHub Desktop.
Save EternalTamago/e58f79a698e3aa92fe6cae78db8e3a9d to your computer and use it in GitHub Desktop.
実行時にModelの頂点やインデックス情報を読み込む
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Xenko.Core.Mathematics;
using Xenko.Engine;
using Xenko.Graphics;
using Xenko.Input;
using Xenko.Rendering;
using MeshExtensions;
using Buffer = Xenko.Graphics.Buffer;
namespace LoadMesh
{
public class LoadMeshScript : AsyncScript
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct MyVertex : IVertex
{
Vector3 Position;
Vector3 Normal;
public static readonly VertexDeclaration Layout =
new VertexDeclaration(VertexElement.Position<Vector3>(), VertexElement.Normal<Vector3>());
public void FlipWinding()
{
throw new NotImplementedException();
}
public VertexDeclaration GetLayout()
{
return Layout;
}
}
public Model TargetModel;
public override async Task Execute()
{
var processedTargetModel = false;
var processedTargetUrl = false;
while(Game.IsRunning)
{
await Script.NextFrame();
// Modelから読み込む
if (!processedTargetModel && Input.IsKeyPressed(Keys.M))
{
AddEntity(new Vector3(-5, 5, 0), Content.GetMeshData<MyVertex>(TargetModel));
processedTargetModel = true;
}
// アセットURLから読み込む
if (!processedTargetUrl && Input.IsKeyPressed(Keys.U))
{
if (Content.TryGetAssetUrl(TargetModel, out var url))
{
AddEntity(new Vector3(5, 5, 0), Content.GetMeshData<MyVertex>(url));
}
processedTargetUrl = true;
}
}
}
public void AddEntity(Vector3 position, VertexAndIndexExtensions.MeshData<MyVertex, int> meshData)
{
// 取得したmeshDataからModelを再現する(Skeletonは考慮されていない)
if (meshData == null)
{
return;
}
var vertexBufferList = new List<Buffer>();
var indexBufferList = new List<Buffer>();
var is32Bit = meshData.IndexStride == sizeof(int);
foreach (var vertices in meshData.Vertices)
{
vertexBufferList.Add(Buffer.Vertex.New(GraphicsDevice, vertices));
}
foreach (var indices in meshData.Indices)
{
indexBufferList.Add(Buffer.Index.New(GraphicsDevice, indices));
}
var newModel = new Model();
foreach (var meshInfo in meshData.MeshInfos)
{
var vertexBufferBinginds = new VertexBufferBinding[meshInfo.VertexBufferInfos.Length];
for (int i = 0; i < vertexBufferBinginds.Length; ++i)
{
vertexBufferBinginds[i] = new VertexBufferBinding(vertexBufferList[meshInfo.VertexBufferInfos[i].BufferIndex], MyVertex.Layout, meshInfo.VertexBufferInfos[i].Count, meshData.VertexStride, meshInfo.VertexBufferInfos[i].Offset);
}
var indexBufferBinding = new IndexBufferBinding(indexBufferList[meshInfo.IndexBufferInfo.BufferIndex], is32Bit, meshInfo.IndexBufferInfo.Count, meshInfo.IndexBufferInfo.Offset);
var meshDraw = new MeshDraw
{
PrimitiveType = PrimitiveType.TriangleList,
VertexBuffers = vertexBufferBinginds,
IndexBuffer = indexBufferBinding,
StartLocation = 0,
DrawCount = meshInfo.IndexBufferInfo.Count,
};
newModel.Meshes.Add(new Mesh { Draw = meshDraw, });
}
var entity = new Entity(position);
entity.Add(new ModelComponent(newModel));
SceneSystem.SceneInstance.RootScene.Entities.Add(entity);
}
}
}
// [はじめに]
//
// Xenko(https://github.com/xenko3d/xenko)のソースコードの一部を元にしています。
// Xenkoのライセンスを以下に提示します。
//
// The MIT License (MIT)
//
// Copyright (c) 2018-2019 Xenko contributors (https://xenko.com)
// Copyright (c) 2011-2018 Silicon Studio Corp. (https://www.siliconstudio.co.jp)
//
// All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// [備考]
//
// CommandListかContentManagerのどちらかが必要。
//
// ContentManagerで読み込む場合は、ContentManagerで読み込めるアセットでなければならない。
// またContentManagerで読み込むときは、対象がModelアセットかProceduralModelアセットかで処理を分けている。
//
// 注意: Skeletonは考慮されていない。
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Xenko.Core;
using Xenko.Core.Annotations;
using Xenko.Core.Serialization;
using Xenko.Core.Serialization.Contents;
using Xenko.Graphics;
using Xenko.Graphics.Data;
using Xenko.Rendering;
using Xenko.Rendering.ProceduralModels;
using Buffer = Xenko.Graphics.Buffer;
namespace MeshExtensions
{
public static class VertexAndIndexExtensions
{
private class CopyInfo
{
public struct ElementInfo
{
public int SourceOffset;
public int OutputOffset;
public int Size;
}
public int Length;
public int SourceStride;
public int OutputStride;
public ElementInfo[] ElementInfos;
}
private static int CopyElements<T, U>(ref T[] destination, ref U[] source, CopyInfo copyInfo) where T : struct
{
int copied = 0;
GCHandle? sourceHandle = null;
GCHandle? outputHandle = null;
try
{
sourceHandle = GCHandle.Alloc(source, GCHandleType.Pinned);
outputHandle = GCHandle.Alloc(destination, GCHandleType.Pinned);
var sp = ((GCHandle)sourceHandle).AddrOfPinnedObject();
var op = ((GCHandle)outputHandle).AddrOfPinnedObject();
for (int i = 0; i < copyInfo.Length; ++i)
{
foreach (var elementInfo in copyInfo.ElementInfos)
{
Utilities.CopyMemory(
op + (i * copyInfo.OutputStride) + elementInfo.OutputOffset,
sp + (i * copyInfo.SourceStride) + elementInfo.SourceOffset,
elementInfo.Size
);
copied += elementInfo.Size;
}
}
}
finally
{
outputHandle?.Free();
sourceHandle?.Free();
}
return copied;
}
private static bool GenerateCopyInfo<T, U>(ref T[] destination, VertexDeclaration destinationLayout, ref U[] source, VertexDeclaration sourceLayout, out CopyInfo copyInfo)
where T : struct
where U : struct
{
copyInfo = null;
var sourceVertexOffsets = sourceLayout.EnumerateWithOffsets();
var sourceVertexStride = sourceLayout.CalculateSize();
var outputVertexOffsets = destinationLayout.EnumerateWithOffsets();
var outputVertexStride = destinationLayout.CalculateSize();
var elementInfos = outputVertexOffsets
.Join
(
sourceVertexOffsets,
o => o.VertexElement.SemanticAsText,
s => s.VertexElement.SemanticAsText,
(o, s) => new { o, s }
)
.Where(a => a.s.Size == a.o.Size)
.Select(a => new CopyInfo.ElementInfo { SourceOffset = a.s.Offset, OutputOffset = a.o.Offset, Size = a.s.Size })
.ToArray();
if (elementInfos.Length <= 0)
{
return false;
}
copyInfo = new CopyInfo
{
Length = Math.Min(destination.Length, source.Length),
SourceStride = sourceVertexStride,
OutputStride = outputVertexStride,
ElementInfos = elementInfos,
};
return true;
}
private static int CopyVertexElements<T, U>(ref T[] destination, ref U[] source)
where T : struct, IVertex
where U : struct, IVertex
{
if (!GenerateCopyInfo(ref destination, (new T()).GetLayout(), ref source, (new U()).GetLayout(), out var copyInfo))
{
return 0;
}
return CopyElements(ref destination, ref source, copyInfo);
}
public static T[] ToVertices<T>([NotNull] this byte[] source, VertexDeclaration sourceLayout) where T : struct, IVertex
{
var sourceVertexStride = sourceLayout.CalculateSize();
var length = source.Length / sourceVertexStride;
if (length <= 0)
{
return null;
}
T[] vertices = new T[length];
if (!GenerateCopyInfo(ref vertices, (new T()).GetLayout(), ref source, sourceLayout, out var copyInfo))
{
return null;
}
if (CopyElements(ref vertices, ref source, copyInfo) == 0)
{
return null;
}
return vertices;
}
public static T[] ToIndices<T>([NotNull] this byte[] source, bool is32Bit) where T : struct
{
if (typeof(T) != typeof(ushort) && typeof(T) != typeof(int))
{
return null;
}
var sourceIndexStride = is32Bit ? sizeof(int) : sizeof(ushort);
var outputIndexStride = typeof(T) == typeof(int) ? sizeof(int) : sizeof(ushort);
if (sourceIndexStride > outputIndexStride)
{
return null;
}
var length = source.Length / sourceIndexStride;
if (length <= 0)
{
return null;
}
T[] indices = new T[length];
var copyInfo = new CopyInfo
{
Length = length,
SourceStride = sourceIndexStride,
OutputStride = outputIndexStride,
ElementInfos = new[]
{
new CopyInfo.ElementInfo
{
SourceOffset = 0,
OutputOffset = 0,
Size = sourceIndexStride,
},
},
};
if (CopyElements(ref indices, ref source, copyInfo) == 0)
{
return null;
}
return indices;
}
public static byte[] GetBufferBytes([NotNull] this Buffer buffer, CommandList commandList, ContentManager contentManager)
{
// CommandListが存在するならバッファの現在の状態を読み込む
if (commandList != null)
{
return buffer.GetData<byte>(commandList);
}
// ContentManagerからバッファデータを読み込む(バッファの現在の状態と同じとは限らない?)
if (contentManager != null)
{
// この処理はProceduralModelアセットから生成されたBufferに対しては失敗する
// Modelアセットと違ってContentにBufferデータが存在しない
var result = contentManager.TryGetAssetUrl(buffer, out var url); // "<ModelUrl>/gen/Buffer_1"
if (!result)
{
return null;
}
BufferData bufferData = null;
using (var stream = contentManager.OpenAsStream(url, Xenko.Core.IO.StreamFlags.Seekable))
{
var streamReader = new BinarySerializationReader(stream);
var chunkHeader = ChunkHeader.Read(streamReader);
if (chunkHeader != null)
{
streamReader.NativeStream.Seek(chunkHeader.OffsetToObject, SeekOrigin.Begin);
streamReader.SerializeExtended(ref bufferData, ArchiveMode.Deserialize);
}
}
return bufferData?.Content;
}
return null;
}
public static bool ConfirmContentType([NotNull] this ContentManager contentManager, string url, Type type)
{
if (!contentManager.Exists(url))
{
return false;
}
using (var stream = contentManager.OpenAsStream(url, Xenko.Core.IO.StreamFlags.Seekable))
{
var streamReader = new BinarySerializationReader(stream);
var chunkHeader = ChunkHeader.Read(streamReader);
if (chunkHeader != null && chunkHeader.Type == type.AssemblyQualifiedName)
{
return true;
}
else
{
return false;
}
}
}
public static GeometricMeshData<VertexPositionNormalTexture> GetProceduralModelData([NotNull] this ContentManager contentManager, string url)
{
if (!contentManager.ConfirmContentType(url, typeof(ProceduralModelDescriptor)))
{
return null;
}
var proceduralModel = contentManager.Load<ProceduralModelDescriptor>(url);
var type = proceduralModel.Type;
var createPrimitiveMeshData = type.GetType().GetMethod("CreatePrimitiveMeshData", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance);
var geometricMeshData = (GeometricMeshData<VertexPositionNormalTexture>)createPrimitiveMeshData.Invoke(type, null);
return geometricMeshData;
}
public static GeometricMeshData<VertexPositionNormalTexture> GetProceduralModelData([NotNull] this ContentManager contentManager, Model model)
{
var result = contentManager.TryGetAssetUrl(model, out var url);
if (!result)
{
return null;
}
return contentManager.GetProceduralModelData(url);
}
public class BufferBindingInfo
{
public int BufferIndex;
public int Offset;
public int Count;
}
public class MeshInfo
{
public BufferBindingInfo[] VertexBufferInfos;
public BufferBindingInfo IndexBufferInfo;
}
public class MeshData<V, I>
where V : struct, IVertex
where I : struct
{
public V[][] Vertices;
public I[][] Indices;
public int VertexStride;
public int IndexStride;
public MeshInfo[] MeshInfos;
}
public static MeshData<V, int> ToMeshData<V>([NotNull] this GeometricMeshData<VertexPositionNormalTexture> geometricMeshData)
where V : struct, IVertex
{
var source = geometricMeshData.Vertices;
var vertices = new V[geometricMeshData.Vertices.Length];
CopyVertexElements(ref vertices, ref source);
var meshData = new MeshData<V, int>
{
VertexStride = new V().GetLayout().VertexStride,
Vertices = new[] { vertices, },
IndexStride = sizeof(int),
Indices = new[] { geometricMeshData.Indices },
MeshInfos = new[]
{
new MeshInfo
{
VertexBufferInfos = new []
{
new BufferBindingInfo
{
BufferIndex = 0,
Offset = 0,
Count = geometricMeshData.Vertices.Length,
},
},
IndexBufferInfo = new BufferBindingInfo
{
BufferIndex = 0,
Offset = 0,
Count = geometricMeshData.Indices.Length,
},
}
},
};
return meshData;
}
private static MeshData<V, I> GetMeshDataInternal<V, I>([NotNull] this Model model, CommandList commandList, ContentManager contentManager)
where V : struct, IVertex
where I : struct
{
var meshData = new MeshData<V, I>();
meshData.VertexStride = (new V()).GetLayout().CalculateSize();
meshData.IndexStride = typeof(I) == typeof(int) ? sizeof(int) : sizeof(ushort);
var vertexBufferList = new Dictionary<Buffer, int>();
var indexBufferList = new Dictionary<Buffer, int>();
var verticesList = new List<V[]>();
var indicesList = new List<I[]>();
var meshInfos = new List<MeshInfo>();
for (int i = 0; i < model.Meshes.Count; ++i)
{
var mesh = model.Meshes[i];
// 頂点
var vertexBufferInfos = new List<BufferBindingInfo>();
foreach (var vertexBufferBinding in mesh.Draw.VertexBuffers)
{
var vertexBuffer = vertexBufferBinding.Buffer;
if (!vertexBufferList.TryGetValue(vertexBuffer, out var vertexBufferIndex))
{
vertexBufferIndex = vertexBufferList.Count;
vertexBufferList.Add(vertexBuffer, vertexBufferIndex);
var vertices = vertexBufferBinding.Buffer.GetBufferBytes(commandList, contentManager).ToVertices<V>(vertexBufferBinding.Declaration);
if (vertices == null)
{
throw new Exception();
}
verticesList.Add(vertices);
}
var bufferInfo = new BufferBindingInfo
{
BufferIndex = vertexBufferIndex,
Offset = (vertexBufferBinding.Offset / vertexBufferBinding.Stride) * meshData.VertexStride,
Count = vertexBufferBinding.Count,
};
vertexBufferInfos.Add(bufferInfo);
}
// インデックス
var indexBufferBinding = mesh.Draw.IndexBuffer;
var indexStride = indexBufferBinding.Is32Bit ? sizeof(int) : sizeof(ushort);
var indexBuffer = indexBufferBinding.Buffer;
if (!indexBufferList.TryGetValue(indexBuffer, out var indexBufferIndex))
{
indexBufferIndex = indexBufferList.Count;
indexBufferList.Add(indexBuffer, indexBufferIndex);
var indices = indexBufferBinding.Buffer.GetBufferBytes(commandList, contentManager).ToIndices<I>(indexBufferBinding.Is32Bit);
if (indices == null)
{
throw new Exception();
}
indicesList.Add(indices);
}
// MeshInfoの追加
var meshInfo = new MeshInfo
{
VertexBufferInfos = vertexBufferInfos.ToArray(),
IndexBufferInfo = new BufferBindingInfo
{
BufferIndex = indexBufferIndex,
Offset = (indexBufferBinding.Offset / indexStride) * meshData.IndexStride,
Count = indexBufferBinding.Count,
},
};
meshInfos.Add(meshInfo);
}
meshData.MeshInfos = meshInfos.ToArray();
meshData.Vertices = verticesList.ToArray();
meshData.Indices = indicesList.ToArray();
return meshData;
}
public static MeshData<V, int> GetMeshData<V>([NotNull] this Model model, CommandList commandList, ContentManager contentManager)
where V : struct, IVertex
{
// commandListがnullでmodelの元がProceduralModelアセットの場合
if (commandList == null && contentManager != null)
{
if (!contentManager.TryGetAssetUrl(model, out var url))
{
return null;
}
if (contentManager.ConfirmContentType(url, typeof(ProceduralModelDescriptor)))
{
var geometricMeshData = contentManager.GetProceduralModelData(model);
return geometricMeshData.ToMeshData<V>();
}
}
// その他の場合
return model.GetMeshDataInternal<V, int>(commandList, contentManager);
}
public static MeshData<V, int> GetMeshData<V>([NotNull] this ContentManager contentManager, string url)
where V : struct, IVertex
{
if (!contentManager.Exists(url))
{
return null;
}
var model = contentManager.IsLoaded(url) ? contentManager.Get<Model>(url) : contentManager.Load<Model>(url);
return model.GetMeshData<V>(null, contentManager);
}
public static MeshData<V, int> GetMeshData<V>([NotNull] this ContentManager contentManager, Model model, CommandList commandList = null)
where V : struct, IVertex
{
return model?.GetMeshData<V>(commandList, contentManager);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment