private static unsafe void ProcessMaterial(ContentManager manager, ICollection <EntityChunk> chunks, MaterialInstance material, Model prefabModel) { //we need to futher group by VertexDeclaration var meshes = new Dictionary <VertexDeclaration, MeshData>(); //actually create the mesh foreach (var chunk in chunks) { foreach (var modelMesh in chunk.Model.Meshes) { //process only right material if (modelMesh.MaterialIndex == chunk.MaterialIndex) { MeshData mesh; if (!meshes.TryGetValue(modelMesh.Draw.VertexBuffers[0].Declaration, out mesh)) { mesh = new MeshData { VertexStride = modelMesh.Draw.VertexBuffers[0].Stride }; meshes.Add(modelMesh.Draw.VertexBuffers[0].Declaration, mesh); } //vertexes var vertexBufferRef = AttachedReferenceManager.GetAttachedReference(modelMesh.Draw.VertexBuffers[0].Buffer); byte[] vertexData; if (vertexBufferRef.Data != null) { vertexData = ((BufferData)vertexBufferRef.Data).Content; } else if (!string.IsNullOrEmpty(vertexBufferRef.Url)) { var dataAsset = manager.Load <Buffer>(vertexBufferRef.Url); vertexData = dataAsset.GetSerializationData().Content; } else { throw new Exception($"Failed to get Vertex BufferData for entity {chunk.Entity.Name}'s model."); } //transform the vertexes according to the entity var vertexDataCopy = vertexData.ToArray(); chunk.Entity.Transform.UpdateWorldMatrix(); //make sure matrix is computed var worldMatrix = chunk.Entity.Transform.WorldMatrix; var up = Vector3.Cross(worldMatrix.Right, worldMatrix.Forward); bool isScalingNegative = Vector3.Dot(worldMatrix.Up, up) < 0.0f; modelMesh.Draw.VertexBuffers[0].TransformBuffer(vertexDataCopy, ref worldMatrix); //add to the big single array var vertexes = vertexDataCopy .Skip(modelMesh.Draw.VertexBuffers[0].Offset) .Take(modelMesh.Draw.VertexBuffers[0].Count * modelMesh.Draw.VertexBuffers[0].Stride) .ToArray(); mesh.VertexData.AddRange(vertexes); //indices var indexBufferRef = AttachedReferenceManager.GetAttachedReference(modelMesh.Draw.IndexBuffer.Buffer); byte[] indexData; if (indexBufferRef.Data != null) { indexData = ((BufferData)indexBufferRef.Data).Content; } else if (!string.IsNullOrEmpty(indexBufferRef.Url)) { var dataAsset = manager.Load <Buffer>(indexBufferRef.Url); indexData = dataAsset.GetSerializationData().Content; } else { throw new Exception("Failed to get Indices BufferData for entity {chunk.Entity.Name}'s model."); } var indexSize = modelMesh.Draw.IndexBuffer.Is32Bit ? sizeof(uint) : sizeof(ushort); byte[] indices; if (isScalingNegative) { // Get reversed winding order modelMesh.Draw.GetReversedWindingOrder(out indices); indices = indices.Skip(modelMesh.Draw.IndexBuffer.Offset) .Take(modelMesh.Draw.IndexBuffer.Count * indexSize) .ToArray(); } else { // Get indices normally indices = indexData .Skip(modelMesh.Draw.IndexBuffer.Offset) .Take(modelMesh.Draw.IndexBuffer.Count * indexSize) .ToArray(); } // Convert indices to 32 bits if (indexSize == sizeof(ushort)) { var uintIndices = new byte[indices.Length * 2]; fixed(byte *psrc = indices) fixed(byte *pdst = uintIndices) { var src = (ushort *)psrc; var dst = (uint *)pdst; int numIndices = indices.Length / sizeof(ushort); for (var i = 0; i < numIndices; i++) { dst[i] = src[i]; } } indices = uintIndices; } // Offset indices by mesh.IndexOffset fixed(byte *pdst = indices) { var dst = (uint *)pdst; int numIndices = indices.Length / sizeof(uint); for (var i = 0; i < numIndices; i++) { // Offset indices dst[i] += (uint)mesh.IndexOffset; } } mesh.IndexOffset += modelMesh.Draw.VertexBuffers[0].Count; mesh.IndexData.AddRange(indices); } } } //Sort out material var matIndex = prefabModel.Materials.Count; prefabModel.Materials.Add(material); foreach (var meshData in meshes) { //todo need to take care of short index var vertexArray = meshData.Value.VertexData.ToArray(); var indexArray = meshData.Value.IndexData.ToArray(); var vertexCount = vertexArray.Length / meshData.Value.VertexStride; var indexCount = indexArray.Length / 4; var gpuMesh = new Mesh { Draw = new MeshDraw { PrimitiveType = PrimitiveType.TriangleList, DrawCount = indexCount, StartLocation = 0 }, MaterialIndex = matIndex }; var vertexBuffer = new BufferData(BufferFlags.VertexBuffer, new byte[vertexArray.Length]); var indexBuffer = new BufferData(BufferFlags.IndexBuffer, new byte[indexArray.Length]); var vertexBufferSerializable = vertexBuffer.ToSerializableVersion(); var indexBufferSerializable = indexBuffer.ToSerializableVersion(); Array.Copy(vertexArray, vertexBuffer.Content, vertexArray.Length); Array.Copy(indexArray, indexBuffer.Content, indexArray.Length); gpuMesh.Draw.VertexBuffers = new VertexBufferBinding[1]; gpuMesh.Draw.VertexBuffers[0] = new VertexBufferBinding(vertexBufferSerializable, meshData.Key, vertexCount); gpuMesh.Draw.IndexBuffer = new IndexBufferBinding(indexBufferSerializable, true, indexCount); prefabModel.Meshes.Add(gpuMesh); } }
private object ExportModel(ICommandContext commandContext, ContentManager contentManager) { // Read from model file var modelSkeleton = LoadSkeleton(commandContext, contentManager); // we get model skeleton to compare it to real skeleton we need to map to AdjustSkeleton(modelSkeleton); var model = LoadModel(commandContext, contentManager); if (!CheckInputSlots(commandContext, model)) { return(null); } // Apply materials foreach (var modelMaterial in Materials) { if (modelMaterial.MaterialInstance?.Material == null) { commandContext.Logger.Verbose($"The material [{modelMaterial.Name}] is null in the list of materials."); } model.Materials.Add(modelMaterial.MaterialInstance); } model.BoundingBox = BoundingBox.Empty; Skeleton skeleton; if (SkeletonUrl != null || !MergeMeshes) { if (SkeletonUrl != null) { // Load the skeleton skeleton = contentManager.Load <Skeleton>(SkeletonUrl); } else { skeleton = modelSkeleton; SkeletonUrl = Location + "_Skeleton_" + Guid.NewGuid(); contentManager.Save(SkeletonUrl, skeleton); } // Assign skeleton to model model.Skeleton = AttachedReferenceManager.CreateProxyObject <Skeleton>(AssetId.Empty, SkeletonUrl); } else { skeleton = null; } var skeletonMapping = new SkeletonMapping(skeleton, modelSkeleton); // Refresh skeleton updater with model skeleton var hierarchyUpdater = new SkeletonUpdater(modelSkeleton); hierarchyUpdater.UpdateMatrices(); // Move meshes in the new nodes foreach (var mesh in model.Meshes) { // Apply scale import on meshes if (!MathUtil.NearEqual(ScaleImport, 1.0f)) { var transformationMatrix = Matrix.Scaling(ScaleImport); for (int vbIdx = 0; vbIdx < mesh.Draw.VertexBuffers.Length; vbIdx++) { mesh.Draw.VertexBuffers[vbIdx].TransformBuffer(ref transformationMatrix); } } var skinning = mesh.Skinning; if (skinning != null) { // Update node mapping // Note: we only remap skinning matrices, but we could directly remap skinning bones instead for (int i = 0; i < skinning.Bones.Length; ++i) { var linkNodeIndex = skinning.Bones[i].NodeIndex; var newLinkNodeIndex = skeletonMapping.SourceToSource[linkNodeIndex]; var nodeIndex = mesh.NodeIndex; var newNodeIndex = skeletonMapping.SourceToSource[mesh.NodeIndex]; skinning.Bones[i].NodeIndex = skeletonMapping.SourceToTarget[linkNodeIndex]; // Adjust scale import if (!MathUtil.NearEqual(ScaleImport, 1.0f)) { skinning.Bones[i].LinkToMeshMatrix.TranslationVector = skinning.Bones[i].LinkToMeshMatrix.TranslationVector * ScaleImport; } // If it was remapped, we also need to update matrix if (nodeIndex != newNodeIndex) { // Update mesh part var transformMatrix = CombineMatricesFromNodeIndices(hierarchyUpdater.NodeTransformations, newNodeIndex, nodeIndex); transformMatrix.Invert(); skinning.Bones[i].LinkToMeshMatrix = Matrix.Multiply(transformMatrix, skinning.Bones[i].LinkToMeshMatrix); } if (newLinkNodeIndex != linkNodeIndex) { // Update link part var transformLinkMatrix = CombineMatricesFromNodeIndices(hierarchyUpdater.NodeTransformations, newLinkNodeIndex, linkNodeIndex); skinning.Bones[i].LinkToMeshMatrix = Matrix.Multiply(skinning.Bones[i].LinkToMeshMatrix, transformLinkMatrix); } } } // Check if there was a remap using model skeleton if (skeletonMapping.SourceToSource[mesh.NodeIndex] != mesh.NodeIndex) { // Transform vertices var transformationMatrix = CombineMatricesFromNodeIndices(hierarchyUpdater.NodeTransformations, skeletonMapping.SourceToSource[mesh.NodeIndex], mesh.NodeIndex); for (int vbIdx = 0; vbIdx < mesh.Draw.VertexBuffers.Length; vbIdx++) { mesh.Draw.VertexBuffers[vbIdx].TransformBuffer(ref transformationMatrix); } // Check if geometry is inverted, to know if we need to reverse winding order // TODO: What to do if there is no index buffer? We should create one... (not happening yet) if (mesh.Draw.IndexBuffer == null) { throw new InvalidOperationException(); } Matrix rotation; Vector3 scale, translation; if (transformationMatrix.Decompose(out scale, out rotation, out translation) && scale.X * scale.Y * scale.Z < 0) { mesh.Draw.ReverseWindingOrder(); } } // Update new node index using real asset skeleton mesh.NodeIndex = skeletonMapping.SourceToTarget[mesh.NodeIndex]; } // Apply custom model modifiers if (ModelModifiers != null) { foreach (var modifier in ModelModifiers) { modifier.Apply(commandContext, model); } } // Merge meshes with same parent nodes, material and skinning var meshesByNodes = model.Meshes.GroupBy(x => x.NodeIndex).ToList(); foreach (var meshesByNode in meshesByNodes) { // This logic to detect similar material is kept from old code; this should be reviewed/improved at some point foreach (var meshesPerDrawCall in meshesByNode.GroupBy(x => x, new AnonymousEqualityComparer <Mesh>((x, y) => x.MaterialIndex == y.MaterialIndex && // Same material ArrayExtensions.ArraysEqual(x.Skinning?.Bones, y.Skinning?.Bones) && // Same bones CompareParameters(model, x, y) && // Same parameters CompareShadowOptions(model, x, y), // Same shadow parameters x => 0)).ToList()) { if (meshesPerDrawCall.Count() == 1) { // Nothing to group, skip to next entry continue; } // Remove old meshes foreach (var mesh in meshesPerDrawCall) { model.Meshes.Remove(mesh); } // Add new combined mesh(es) var baseMesh = meshesPerDrawCall.First(); var newMeshList = meshesPerDrawCall.Select(x => x.Draw).ToList().GroupDrawData(Allow32BitIndex); foreach (var generatedMesh in newMeshList) { model.Meshes.Add(new Mesh(generatedMesh, baseMesh.Parameters) { MaterialIndex = baseMesh.MaterialIndex, Name = baseMesh.Name, Draw = generatedMesh, NodeIndex = baseMesh.NodeIndex, Skinning = baseMesh.Skinning, }); } } } // split the meshes if necessary model.Meshes = SplitExtensions.SplitMeshes(model.Meshes, Allow32BitIndex); // Refresh skeleton updater with asset skeleton hierarchyUpdater = new SkeletonUpdater(skeleton); hierarchyUpdater.UpdateMatrices(); // bounding boxes var modelBoundingBox = model.BoundingBox; var modelBoundingSphere = model.BoundingSphere; foreach (var mesh in model.Meshes) { var vertexBuffers = mesh.Draw.VertexBuffers; for (int vbIdx = 0; vbIdx < vertexBuffers.Length; vbIdx++) { // Compute local mesh bounding box (no node transformation) Matrix matrix = Matrix.Identity; mesh.BoundingBox = vertexBuffers[vbIdx].ComputeBounds(ref matrix, out mesh.BoundingSphere); // Compute model bounding box (includes node transformation) hierarchyUpdater.GetWorldMatrix(mesh.NodeIndex, out matrix); BoundingSphere meshBoundingSphere; var meshBoundingBox = vertexBuffers[vbIdx].ComputeBounds(ref matrix, out meshBoundingSphere); BoundingBox.Merge(ref modelBoundingBox, ref meshBoundingBox, out modelBoundingBox); BoundingSphere.Merge(ref modelBoundingSphere, ref meshBoundingSphere, out modelBoundingSphere); } // TODO: temporary Always try to compact mesh.Draw.CompactIndexBuffer(); } model.BoundingBox = modelBoundingBox; model.BoundingSphere = modelBoundingSphere; // Count unique meshes (they can be shared) var uniqueDrawMeshes = model.Meshes.Select(x => x.Draw).Distinct(); // Count unique vertex buffers and squish them together in a single buffer var uniqueVB = uniqueDrawMeshes.SelectMany(x => x.VertexBuffers).Distinct().ToList(); var vbMap = new Dictionary <VertexBufferBinding, VertexBufferBinding>(); var sizeVertexBuffer = uniqueVB.Select(x => x.Buffer.GetSerializationData().Content.Length).Sum(); var vertexBuffer = new BufferData(BufferFlags.VertexBuffer, new byte[sizeVertexBuffer]); var vertexBufferSerializable = vertexBuffer.ToSerializableVersion(); var vertexBufferNextIndex = 0; foreach (var vbBinding in uniqueVB) { var oldVertexBuffer = vbBinding.Buffer.GetSerializationData().Content; Array.Copy(oldVertexBuffer, 0, vertexBuffer.Content, vertexBufferNextIndex, oldVertexBuffer.Length); vbMap.Add(vbBinding, new VertexBufferBinding(vertexBufferSerializable, vbBinding.Declaration, vbBinding.Count, vbBinding.Stride, vertexBufferNextIndex)); vertexBufferNextIndex += oldVertexBuffer.Length; } // Count unique index buffers and squish them together in a single buffer var uniqueIB = uniqueDrawMeshes.Select(x => x.IndexBuffer).NotNull().Distinct().ToList(); var sizeIndexBuffer = 0; foreach (var ibBinding in uniqueIB) { // Make sure 32bit indices are properly aligned to 4 bytes in case the last alignment was 2 bytes if (ibBinding.Is32Bit && sizeIndexBuffer % 4 != 0) { sizeIndexBuffer += 2; } sizeIndexBuffer += ibBinding.Buffer.GetSerializationData().Content.Length; } var ibMap = new Dictionary <IndexBufferBinding, IndexBufferBinding>(); if (uniqueIB.Count > 0) { var indexBuffer = new BufferData(BufferFlags.IndexBuffer, new byte[sizeIndexBuffer]); var indexBufferSerializable = indexBuffer.ToSerializableVersion(); var indexBufferNextIndex = 0; foreach (var ibBinding in uniqueIB) { var oldIndexBuffer = ibBinding.Buffer.GetSerializationData().Content; // Make sure 32bit indices are properly aligned to 4 bytes in case the last alignment was 2 bytes if (ibBinding.Is32Bit && indexBufferNextIndex % 4 != 0) { indexBufferNextIndex += 2; } Array.Copy(oldIndexBuffer, 0, indexBuffer.Content, indexBufferNextIndex, oldIndexBuffer.Length); ibMap.Add(ibBinding, new IndexBufferBinding(indexBufferSerializable, ibBinding.Is32Bit, ibBinding.Count, indexBufferNextIndex)); indexBufferNextIndex += oldIndexBuffer.Length; } } // Assign new vertex and index buffer bindings foreach (var drawMesh in uniqueDrawMeshes) { for (int i = 0; i < drawMesh.VertexBuffers.Length; i++) { drawMesh.VertexBuffers[i] = vbMap[drawMesh.VertexBuffers[i]]; } if (drawMesh.IndexBuffer != null) { drawMesh.IndexBuffer = ibMap[drawMesh.IndexBuffer]; } } vbMap.Clear(); ibMap.Clear(); // Convert to Entity return(model); }
private object ExportModel(ICommandContext commandContext, ContentManager contentManager) { // Read from model file var modelSkeleton = LoadSkeleton(commandContext, contentManager); // we get model skeleton to compare it to real skeleton we need to map to AdjustSkeleton(modelSkeleton); var model = LoadModel(commandContext, contentManager); // Apply materials foreach (var modelMaterial in Materials) { if (modelMaterial.MaterialInstance?.Material == null) { commandContext.Logger.Warning($"The material [{modelMaterial.Name}] is null in the list of materials."); } model.Materials.Add(modelMaterial.MaterialInstance); } model.BoundingBox = BoundingBox.Empty; foreach (var mesh in model.Meshes) { if (TessellationAEN) { // TODO: Generate AEN model view commandContext.Logger.Error("TessellationAEN is not supported in {0}", ContextAsString); } } SkeletonMapping skeletonMapping; Skeleton skeleton; if (SkeletonUrl != null) { // Load skeleton and process it skeleton = contentManager.Load <Skeleton>(SkeletonUrl); // Assign skeleton to model model.Skeleton = AttachedReferenceManager.CreateProxyObject <Skeleton>(Guid.Empty, SkeletonUrl); } else { skeleton = null; } skeletonMapping = new SkeletonMapping(skeleton, modelSkeleton); // Refresh skeleton updater with model skeleton var hierarchyUpdater = new SkeletonUpdater(modelSkeleton); hierarchyUpdater.UpdateMatrices(); // Move meshes in the new nodes foreach (var mesh in model.Meshes) { // Apply scale import on meshes if (!MathUtil.NearEqual(ScaleImport, 1.0f)) { var transformationMatrix = Matrix.Scaling(ScaleImport); mesh.Draw.VertexBuffers[0].TransformBuffer(ref transformationMatrix); } var skinning = mesh.Skinning; if (skinning != null) { // Update node mapping // Note: we only remap skinning matrices, but we could directly remap skinning bones instead for (int i = 0; i < skinning.Bones.Length; ++i) { var linkNodeIndex = skinning.Bones[i].NodeIndex; var newLinkNodeIndex = skeletonMapping.SourceToSource[linkNodeIndex]; var nodeIndex = mesh.NodeIndex; var newNodeIndex = skeletonMapping.SourceToSource[mesh.NodeIndex]; skinning.Bones[i].NodeIndex = skeletonMapping.SourceToTarget[linkNodeIndex]; // Adjust scale import if (!MathUtil.NearEqual(ScaleImport, 1.0f)) { skinning.Bones[i].LinkToMeshMatrix.TranslationVector = skinning.Bones[i].LinkToMeshMatrix.TranslationVector * ScaleImport; } // If it was remapped, we also need to update matrix if (nodeIndex != newNodeIndex) { // Update mesh part var transformMatrix = CombineMatricesFromNodeIndices(hierarchyUpdater.NodeTransformations, newNodeIndex, nodeIndex); transformMatrix.Invert(); skinning.Bones[i].LinkToMeshMatrix = Matrix.Multiply(transformMatrix, skinning.Bones[i].LinkToMeshMatrix); } if (newLinkNodeIndex != linkNodeIndex) { // Update link part var transformLinkMatrix = CombineMatricesFromNodeIndices(hierarchyUpdater.NodeTransformations, newLinkNodeIndex, linkNodeIndex); skinning.Bones[i].LinkToMeshMatrix = Matrix.Multiply(skinning.Bones[i].LinkToMeshMatrix, transformLinkMatrix); } } } // Check if there was a remap using model skeleton if (skeletonMapping.SourceToSource[mesh.NodeIndex] != mesh.NodeIndex) { // Transform vertices var transformationMatrix = CombineMatricesFromNodeIndices(hierarchyUpdater.NodeTransformations, skeletonMapping.SourceToSource[mesh.NodeIndex], mesh.NodeIndex); mesh.Draw.VertexBuffers[0].TransformBuffer(ref transformationMatrix); // Check if geometry is inverted, to know if we need to reverse winding order // TODO: What to do if there is no index buffer? We should create one... (not happening yet) if (mesh.Draw.IndexBuffer == null) { throw new InvalidOperationException(); } Matrix rotation; Vector3 scale, translation; if (transformationMatrix.Decompose(out scale, out rotation, out translation) && scale.X * scale.Y * scale.Z < 0) { mesh.Draw.ReverseWindingOrder(); } } // Update new node index using real asset skeleton mesh.NodeIndex = skeletonMapping.SourceToTarget[mesh.NodeIndex]; } // Merge meshes with same parent nodes, material and skinning var meshesByNodes = model.Meshes.GroupBy(x => x.NodeIndex).ToList(); foreach (var meshesByNode in meshesByNodes) { // This logic to detect similar material is kept from old code; this should be reviewed/improved at some point foreach (var meshesPerDrawCall in meshesByNode.GroupBy(x => x, new AnonymousEqualityComparer <Mesh>((x, y) => x.MaterialIndex == y.MaterialIndex && // Same material ArrayExtensions.ArraysEqual(x.Skinning?.Bones, y.Skinning?.Bones) && // Same bones CompareParameters(model, x, y) && // Same parameters CompareShadowOptions(model, x, y), // Same shadow parameters x => 0)).ToList()) { if (meshesPerDrawCall.Count() == 1) { // Nothing to group, skip to next entry continue; } // Remove old meshes foreach (var mesh in meshesPerDrawCall) { model.Meshes.Remove(mesh); } // Add new combined mesh(es) var baseMesh = meshesPerDrawCall.First(); var newMeshList = meshesPerDrawCall.Select(x => x.Draw).ToList().GroupDrawData(Allow32BitIndex); foreach (var generatedMesh in newMeshList) { model.Meshes.Add(new Mesh(generatedMesh, baseMesh.Parameters) { MaterialIndex = baseMesh.MaterialIndex, Name = baseMesh.Name, Draw = generatedMesh, NodeIndex = baseMesh.NodeIndex, Skinning = baseMesh.Skinning, }); } } } // split the meshes if necessary model.Meshes = SplitExtensions.SplitMeshes(model.Meshes, Allow32BitIndex); // Refresh skeleton updater with asset skeleton hierarchyUpdater = new SkeletonUpdater(skeleton); hierarchyUpdater.UpdateMatrices(); // bounding boxes var modelBoundingBox = model.BoundingBox; var modelBoundingSphere = model.BoundingSphere; foreach (var mesh in model.Meshes) { var vertexBuffers = mesh.Draw.VertexBuffers; if (vertexBuffers.Length > 0) { // Compute local mesh bounding box (no node transformation) Matrix matrix = Matrix.Identity; mesh.BoundingBox = vertexBuffers[0].ComputeBounds(ref matrix, out mesh.BoundingSphere); // Compute model bounding box (includes node transformation) hierarchyUpdater.GetWorldMatrix(mesh.NodeIndex, out matrix); BoundingSphere meshBoundingSphere; var meshBoundingBox = vertexBuffers[0].ComputeBounds(ref matrix, out meshBoundingSphere); BoundingBox.Merge(ref modelBoundingBox, ref meshBoundingBox, out modelBoundingBox); BoundingSphere.Merge(ref modelBoundingSphere, ref meshBoundingSphere, out modelBoundingSphere); } // TODO: temporary Always try to compact mesh.Draw.CompactIndexBuffer(); } model.BoundingBox = modelBoundingBox; model.BoundingSphere = modelBoundingSphere; // merges all the Draw VB and IB together to produce one final VB and IB by entity. var sizeVertexBuffer = model.Meshes.SelectMany(x => x.Draw.VertexBuffers).Select(x => x.Buffer.GetSerializationData().Content.Length).Sum(); var sizeIndexBuffer = 0; foreach (var x in model.Meshes) { // Let's be aligned (if there was 16bit indices before, we might be off) if (x.Draw.IndexBuffer.Is32Bit && sizeIndexBuffer % 4 != 0) { sizeIndexBuffer += 2; } sizeIndexBuffer += x.Draw.IndexBuffer.Buffer.GetSerializationData().Content.Length; } var vertexBuffer = new BufferData(BufferFlags.VertexBuffer, new byte[sizeVertexBuffer]); var indexBuffer = new BufferData(BufferFlags.IndexBuffer, new byte[sizeIndexBuffer]); // Note: reusing same instance, to avoid having many VB with same hash but different URL var vertexBufferSerializable = vertexBuffer.ToSerializableVersion(); var indexBufferSerializable = indexBuffer.ToSerializableVersion(); var vertexBufferNextIndex = 0; var indexBufferNextIndex = 0; foreach (var drawMesh in model.Meshes.Select(x => x.Draw)) { // the index buffer var oldIndexBuffer = drawMesh.IndexBuffer.Buffer.GetSerializationData().Content; // Let's be aligned (if there was 16bit indices before, we might be off) if (drawMesh.IndexBuffer.Is32Bit && indexBufferNextIndex % 4 != 0) { indexBufferNextIndex += 2; } Array.Copy(oldIndexBuffer, 0, indexBuffer.Content, indexBufferNextIndex, oldIndexBuffer.Length); drawMesh.IndexBuffer = new IndexBufferBinding(indexBufferSerializable, drawMesh.IndexBuffer.Is32Bit, drawMesh.IndexBuffer.Count, indexBufferNextIndex); indexBufferNextIndex += oldIndexBuffer.Length; // the vertex buffers for (int index = 0; index < drawMesh.VertexBuffers.Length; index++) { var vertexBufferBinding = drawMesh.VertexBuffers[index]; var oldVertexBuffer = vertexBufferBinding.Buffer.GetSerializationData().Content; Array.Copy(oldVertexBuffer, 0, vertexBuffer.Content, vertexBufferNextIndex, oldVertexBuffer.Length); drawMesh.VertexBuffers[index] = new VertexBufferBinding(vertexBufferSerializable, vertexBufferBinding.Declaration, vertexBufferBinding.Count, vertexBufferBinding.Stride, vertexBufferNextIndex); vertexBufferNextIndex += oldVertexBuffer.Length; } } // Convert to Entity return(model); }
/// <summary> /// The method to override containing the actual command code. It is called by the <see cref="DoCommand" /> function /// </summary> /// <param name="commandContext">The command context.</param> /// <returns>Task{ResultStatus}.</returns> protected override async Task <ResultStatus> DoCommandOverride(ICommandContext commandContext) { var assetManager = new AssetManager(); while (Interlocked.Increment(ref spawnedFbxCommands) >= 2) { Interlocked.Decrement(ref spawnedFbxCommands); await Task.Delay(1, CancellationToken); } try { object exportedObject; if (ExportType == "animation") { // Read from model file var animationClip = LoadAnimation(commandContext, assetManager); exportedObject = animationClip; if (animationClip == null) { commandContext.Logger.Info("File {0} has an empty animation.", SourcePath); } else if (animationClip.Duration.Ticks == 0) { commandContext.Logger.Warning("File {0} has a 0 tick long animation.", SourcePath); } else { animationClip.RepeatMode = AnimationRepeatMode; animationClip.Optimize(); } } else if (ExportType == "model") { // Read from model file var model = LoadModel(commandContext, assetManager); // Apply materials foreach (var modelMaterial in Materials) { if (modelMaterial.MaterialInstance == null || modelMaterial.MaterialInstance.Material == null) { commandContext.Logger.Warning(string.Format("The material [{0}] is null in the list of materials.", modelMaterial.Name)); continue; } model.Materials.Add(modelMaterial.MaterialInstance); } model.BoundingBox = BoundingBox.Empty; var hierarchyUpdater = new ModelViewHierarchyUpdater(model.Hierarchy.Nodes); hierarchyUpdater.UpdateMatrices(); bool hasErrors = false; foreach (var mesh in model.Meshes) { if (TessellationAEN) { // TODO: Generate AEN model view commandContext.Logger.Error("TessellationAEN is not supported in {0}", ContextAsString); hasErrors = true; } } // split the meshes if necessary model.Meshes = SplitExtensions.SplitMeshes(model.Meshes, Allow32BitIndex); // merge the meshes if (Compact) { var indicesBlackList = new HashSet <int>(); if (PreservedNodes != null) { for (var index = 0; index < model.Hierarchy.Nodes.Length; ++index) { var node = model.Hierarchy.Nodes[index]; if (PreservedNodes.Contains(node.Name)) { indicesBlackList.Add(index); } } } // group meshes with same material and same root var sameMaterialMeshes = new List <GroupList <int, Mesh> >(); GroupFromIndex(model, 0, indicesBlackList, model.Meshes, sameMaterialMeshes); // remove meshes that cannot be merged var excludedMeshes = new List <Mesh>(); var finalMeshGroups = new List <GroupList <int, Mesh> >(); foreach (var meshList in sameMaterialMeshes) { var mergeList = new GroupList <int, Mesh> { Key = meshList.Key }; foreach (var mesh in meshList) { if (mesh.Skinning != null || indicesBlackList.Contains(mesh.NodeIndex)) { excludedMeshes.Add(mesh); } else { mergeList.Add(mesh); } } if (mergeList.Count <= 1) { excludedMeshes.AddRange(mergeList); } else { finalMeshGroups.Add(mergeList); } } var finalMeshes = new List <Mesh>(); finalMeshes.AddRange(excludedMeshes); foreach (var meshList in finalMeshGroups) { // transform the buffers foreach (var mesh in meshList) { var transformationMatrix = GetMatrixFromIndex(model.Hierarchy.Nodes, hierarchyUpdater, meshList.Key, mesh.NodeIndex); mesh.Draw.VertexBuffers[0].TransformBuffer(ref transformationMatrix); } // refine the groups base on several tests var newMeshGroups = new List <GroupList <int, Mesh> > { meshList }; // only regroup meshes if they share the same parameters newMeshGroups = RefineGroups(model, newMeshGroups, CompareParameters); // only regroup meshes if they share the shadow options newMeshGroups = RefineGroups(model, newMeshGroups, CompareShadowOptions); // add to the final meshes groups foreach (var sameParamsMeshes in newMeshGroups) { var baseMesh = sameParamsMeshes[0]; var newMeshList = sameParamsMeshes.Select(x => x.Draw).ToList().GroupDrawData(Allow32BitIndex); foreach (var generatedMesh in newMeshList) { finalMeshes.Add(new Mesh(generatedMesh, baseMesh.Parameters) { MaterialIndex = baseMesh.MaterialIndex, Name = baseMesh.Name, Draw = generatedMesh, NodeIndex = meshList.Key, Skinning = null, }); } } } // delete empty nodes (neither mesh nor bone attached) var keptNodes = new bool[model.Hierarchy.Nodes.Length]; for (var i = 0; i < keptNodes.Length; ++i) { keptNodes[i] = false; } foreach (var keepIndex in indicesBlackList) { var nodeIndex = keepIndex; while (nodeIndex != -1 && !keptNodes[nodeIndex]) { keptNodes[nodeIndex] = true; nodeIndex = model.Hierarchy.Nodes[nodeIndex].ParentIndex; } } foreach (var mesh in finalMeshes) { var nodeIndex = mesh.NodeIndex; while (nodeIndex != -1 && !keptNodes[nodeIndex]) { keptNodes[nodeIndex] = true; nodeIndex = model.Hierarchy.Nodes[nodeIndex].ParentIndex; } if (mesh.Skinning != null) { foreach (var bone in mesh.Skinning.Bones) { nodeIndex = bone.NodeIndex; while (nodeIndex != -1 && !keptNodes[nodeIndex]) { keptNodes[nodeIndex] = true; nodeIndex = model.Hierarchy.Nodes[nodeIndex].ParentIndex; } } } } var newNodes = new List <ModelNodeDefinition>(); var newMapping = new int[model.Hierarchy.Nodes.Length]; for (var i = 0; i < keptNodes.Length; ++i) { if (keptNodes[i]) { var parentIndex = model.Hierarchy.Nodes[i].ParentIndex; if (parentIndex != -1) { model.Hierarchy.Nodes[i].ParentIndex = newMapping[parentIndex]; // assume that the nodes are well ordered } newMapping[i] = newNodes.Count; newNodes.Add(model.Hierarchy.Nodes[i]); } } foreach (var mesh in finalMeshes) { mesh.NodeIndex = newMapping[mesh.NodeIndex]; if (mesh.Skinning != null) { for (var i = 0; i < mesh.Skinning.Bones.Length; ++i) { mesh.Skinning.Bones[i].NodeIndex = newMapping[mesh.Skinning.Bones[i].NodeIndex]; } } } model.Meshes = finalMeshes; model.Hierarchy.Nodes = newNodes.ToArray(); hierarchyUpdater = new ModelViewHierarchyUpdater(model.Hierarchy.Nodes); hierarchyUpdater.UpdateMatrices(); } // bounding boxes var modelBoundingBox = model.BoundingBox; var modelBoundingSphere = model.BoundingSphere; foreach (var mesh in model.Meshes) { var vertexBuffers = mesh.Draw.VertexBuffers; if (vertexBuffers.Length > 0) { // Compute local mesh bounding box (no node transformation) Matrix matrix = Matrix.Identity; mesh.BoundingBox = vertexBuffers[0].ComputeBounds(ref matrix, out mesh.BoundingSphere); // Compute model bounding box (includes node transformation) hierarchyUpdater.GetWorldMatrix(mesh.NodeIndex, out matrix); BoundingSphere meshBoundingSphere; var meshBoundingBox = vertexBuffers[0].ComputeBounds(ref matrix, out meshBoundingSphere); BoundingBox.Merge(ref modelBoundingBox, ref meshBoundingBox, out modelBoundingBox); BoundingSphere.Merge(ref modelBoundingSphere, ref meshBoundingSphere, out modelBoundingSphere); } // TODO: temporary Always try to compact mesh.Draw.CompactIndexBuffer(); } model.BoundingBox = modelBoundingBox; model.BoundingSphere = modelBoundingSphere; // merges all the Draw VB and IB together to produce one final VB and IB by entity. var sizeVertexBuffer = model.Meshes.SelectMany(x => x.Draw.VertexBuffers).Select(x => x.Buffer.GetSerializationData().Content.Length).Sum(); var sizeIndexBuffer = 0; foreach (var x in model.Meshes) { // Let's be aligned (if there was 16bit indices before, we might be off) if (x.Draw.IndexBuffer.Is32Bit && sizeIndexBuffer % 4 != 0) { sizeIndexBuffer += 2; } sizeIndexBuffer += x.Draw.IndexBuffer.Buffer.GetSerializationData().Content.Length; } var vertexBuffer = new BufferData(BufferFlags.VertexBuffer, new byte[sizeVertexBuffer]); var indexBuffer = new BufferData(BufferFlags.IndexBuffer, new byte[sizeIndexBuffer]); // Note: reusing same instance, to avoid having many VB with same hash but different URL var vertexBufferSerializable = vertexBuffer.ToSerializableVersion(); var indexBufferSerializable = indexBuffer.ToSerializableVersion(); var vertexBufferNextIndex = 0; var indexBufferNextIndex = 0; foreach (var drawMesh in model.Meshes.Select(x => x.Draw)) { // the index buffer var oldIndexBuffer = drawMesh.IndexBuffer.Buffer.GetSerializationData().Content; // Let's be aligned (if there was 16bit indices before, we might be off) if (drawMesh.IndexBuffer.Is32Bit && indexBufferNextIndex % 4 != 0) { indexBufferNextIndex += 2; } Array.Copy(oldIndexBuffer, 0, indexBuffer.Content, indexBufferNextIndex, oldIndexBuffer.Length); drawMesh.IndexBuffer = new IndexBufferBinding(indexBufferSerializable, drawMesh.IndexBuffer.Is32Bit, drawMesh.IndexBuffer.Count, indexBufferNextIndex); indexBufferNextIndex += oldIndexBuffer.Length; // the vertex buffers for (int index = 0; index < drawMesh.VertexBuffers.Length; index++) { var vertexBufferBinding = drawMesh.VertexBuffers[index]; var oldVertexBuffer = vertexBufferBinding.Buffer.GetSerializationData().Content; Array.Copy(oldVertexBuffer, 0, vertexBuffer.Content, vertexBufferNextIndex, oldVertexBuffer.Length); drawMesh.VertexBuffers[index] = new VertexBufferBinding(vertexBufferSerializable, vertexBufferBinding.Declaration, vertexBufferBinding.Count, vertexBufferBinding.Stride, vertexBufferNextIndex); vertexBufferNextIndex += oldVertexBuffer.Length; } } // If there were any errors while importing models if (hasErrors) { return(ResultStatus.Failed); } // Convert to Entity exportedObject = model; } else { commandContext.Logger.Error("Unknown export type [{0}] {1}", ExportType, ContextAsString); return(ResultStatus.Failed); } if (exportedObject != null) { assetManager.Save(Location, exportedObject); } commandContext.Logger.Info("The {0} has been successfully imported.", ContextAsString); return(ResultStatus.Successful); } catch (Exception ex) { commandContext.Logger.Error("Unexpected error while importing {0}", ex, ContextAsString); return(ResultStatus.Failed); } finally { Interlocked.Decrement(ref spawnedFbxCommands); } }
private object ExportModel(ICommandContext commandContext, AssetManager assetManager) { // Read from model file var modelSkeleton = LoadSkeleton(commandContext, assetManager); // we get model skeleton to compare it to real skeleton we need to map to var model = LoadModel(commandContext, assetManager); // Apply materials foreach (var modelMaterial in Materials) { if (modelMaterial.MaterialInstance?.Material == null) { commandContext.Logger.Warning($"The material [{modelMaterial.Name}] is null in the list of materials."); continue; } model.Materials.Add(modelMaterial.MaterialInstance); } model.BoundingBox = BoundingBox.Empty; foreach (var mesh in model.Meshes) { if (TessellationAEN) { // TODO: Generate AEN model view commandContext.Logger.Error("TessellationAEN is not supported in {0}", ContextAsString); } } SkeletonMapping skeletonMapping; Skeleton skeleton; if (SkeletonUrl != null) { // Load skeleton and process it skeleton = assetManager.Load<Skeleton>(SkeletonUrl); // Assign skeleton to model model.Skeleton = AttachedReferenceManager.CreateSerializableVersion<Skeleton>(Guid.Empty, SkeletonUrl); } else { skeleton = null; } skeletonMapping = new SkeletonMapping(skeleton, modelSkeleton); // Refresh skeleton updater with model skeleton var hierarchyUpdater = new SkeletonUpdater(modelSkeleton); hierarchyUpdater.UpdateMatrices(); // Move meshes in the new nodes foreach (var mesh in model.Meshes) { // Check if there was a remap using model skeleton if (skeletonMapping.SourceToSource[mesh.NodeIndex] != mesh.NodeIndex) { // Transform vertices var transformationMatrix = CombineMatricesFromNodeIndices(hierarchyUpdater.NodeTransformations, skeletonMapping.SourceToSource[mesh.NodeIndex], mesh.NodeIndex); mesh.Draw.VertexBuffers[0].TransformBuffer(ref transformationMatrix); // Check if geometry is inverted, to know if we need to reverse winding order // TODO: What to do if there is no index buffer? We should create one... (not happening yet) if (mesh.Draw.IndexBuffer == null) throw new InvalidOperationException(); Matrix rotation; Vector3 scale, translation; if (transformationMatrix.Decompose(out scale, out rotation, out translation) && scale.X * scale.Y * scale.Z < 0) { mesh.Draw.ReverseWindingOrder(); } } // Update new node index using real asset skeleton mesh.NodeIndex = skeletonMapping.SourceToTarget[mesh.NodeIndex]; } // Merge meshes with same parent nodes, material and skinning var meshesByNodes = model.Meshes.GroupBy(x => x.NodeIndex).ToList(); foreach (var meshesByNode in meshesByNodes) { // This logic to detect similar material is kept from old code; this should be reviewed/improved at some point foreach (var meshesPerDrawCall in meshesByNode.GroupBy(x => x, new AnonymousEqualityComparer<Mesh>((x, y) => x.MaterialIndex == y.MaterialIndex // Same material && ArrayExtensions.ArraysEqual(x.Skinning?.Bones, y.Skinning?.Bones) // Same bones && CompareParameters(model, x, y) // Same parameters && CompareShadowOptions(model, x, y), // Same shadow parameters x => 0)).ToList()) { if (meshesPerDrawCall.Count() == 1) { // Nothing to group, skip to next entry continue; } // Remove old meshes foreach (var mesh in meshesPerDrawCall) { model.Meshes.Remove(mesh); } // Add new combined mesh(es) var baseMesh = meshesPerDrawCall.First(); var newMeshList = meshesPerDrawCall.Select(x => x.Draw).ToList().GroupDrawData(Allow32BitIndex); foreach (var generatedMesh in newMeshList) { model.Meshes.Add(new Mesh(generatedMesh, baseMesh.Parameters) { MaterialIndex = baseMesh.MaterialIndex, Name = baseMesh.Name, Draw = generatedMesh, NodeIndex = baseMesh.NodeIndex, Skinning = baseMesh.Skinning, }); } } } // Remap skinning foreach (var skinning in model.Meshes.Select(x => x.Skinning).Where(x => x != null).Distinct()) { // Update node mapping // Note: we only remap skinning matrices, but we could directly remap skinning bones instead for (int i = 0; i < skinning.Bones.Length; ++i) { var nodeIndex = skinning.Bones[i].NodeIndex; var newNodeIndex = skeletonMapping.SourceToSource[nodeIndex]; skinning.Bones[i].NodeIndex = skeletonMapping.SourceToTarget[nodeIndex]; // If it was remapped, we also need to update matrix if (newNodeIndex != nodeIndex) { var transformationMatrix = CombineMatricesFromNodeIndices(hierarchyUpdater.NodeTransformations, newNodeIndex, nodeIndex); skinning.Bones[i].LinkToMeshMatrix = Matrix.Multiply(skinning.Bones[i].LinkToMeshMatrix, transformationMatrix); } } } // split the meshes if necessary model.Meshes = SplitExtensions.SplitMeshes(model.Meshes, Allow32BitIndex); // Refresh skeleton updater with asset skeleton hierarchyUpdater = new SkeletonUpdater(skeleton); hierarchyUpdater.UpdateMatrices(); // bounding boxes var modelBoundingBox = model.BoundingBox; var modelBoundingSphere = model.BoundingSphere; foreach (var mesh in model.Meshes) { var vertexBuffers = mesh.Draw.VertexBuffers; if (vertexBuffers.Length > 0) { // Compute local mesh bounding box (no node transformation) Matrix matrix = Matrix.Identity; mesh.BoundingBox = vertexBuffers[0].ComputeBounds(ref matrix, out mesh.BoundingSphere); // Compute model bounding box (includes node transformation) hierarchyUpdater.GetWorldMatrix(mesh.NodeIndex, out matrix); BoundingSphere meshBoundingSphere; var meshBoundingBox = vertexBuffers[0].ComputeBounds(ref matrix, out meshBoundingSphere); BoundingBox.Merge(ref modelBoundingBox, ref meshBoundingBox, out modelBoundingBox); BoundingSphere.Merge(ref modelBoundingSphere, ref meshBoundingSphere, out modelBoundingSphere); } // TODO: temporary Always try to compact mesh.Draw.CompactIndexBuffer(); } model.BoundingBox = modelBoundingBox; model.BoundingSphere = modelBoundingSphere; // merges all the Draw VB and IB together to produce one final VB and IB by entity. var sizeVertexBuffer = model.Meshes.SelectMany(x => x.Draw.VertexBuffers).Select(x => x.Buffer.GetSerializationData().Content.Length).Sum(); var sizeIndexBuffer = 0; foreach (var x in model.Meshes) { // Let's be aligned (if there was 16bit indices before, we might be off) if (x.Draw.IndexBuffer.Is32Bit && sizeIndexBuffer % 4 != 0) sizeIndexBuffer += 2; sizeIndexBuffer += x.Draw.IndexBuffer.Buffer.GetSerializationData().Content.Length; } var vertexBuffer = new BufferData(BufferFlags.VertexBuffer, new byte[sizeVertexBuffer]); var indexBuffer = new BufferData(BufferFlags.IndexBuffer, new byte[sizeIndexBuffer]); // Note: reusing same instance, to avoid having many VB with same hash but different URL var vertexBufferSerializable = vertexBuffer.ToSerializableVersion(); var indexBufferSerializable = indexBuffer.ToSerializableVersion(); var vertexBufferNextIndex = 0; var indexBufferNextIndex = 0; foreach (var drawMesh in model.Meshes.Select(x => x.Draw)) { // the index buffer var oldIndexBuffer = drawMesh.IndexBuffer.Buffer.GetSerializationData().Content; // Let's be aligned (if there was 16bit indices before, we might be off) if (drawMesh.IndexBuffer.Is32Bit && indexBufferNextIndex % 4 != 0) indexBufferNextIndex += 2; Array.Copy(oldIndexBuffer, 0, indexBuffer.Content, indexBufferNextIndex, oldIndexBuffer.Length); drawMesh.IndexBuffer = new IndexBufferBinding(indexBufferSerializable, drawMesh.IndexBuffer.Is32Bit, drawMesh.IndexBuffer.Count, indexBufferNextIndex); indexBufferNextIndex += oldIndexBuffer.Length; // the vertex buffers for (int index = 0; index < drawMesh.VertexBuffers.Length; index++) { var vertexBufferBinding = drawMesh.VertexBuffers[index]; var oldVertexBuffer = vertexBufferBinding.Buffer.GetSerializationData().Content; Array.Copy(oldVertexBuffer, 0, vertexBuffer.Content, vertexBufferNextIndex, oldVertexBuffer.Length); drawMesh.VertexBuffers[index] = new VertexBufferBinding(vertexBufferSerializable, vertexBufferBinding.Declaration, vertexBufferBinding.Count, vertexBufferBinding.Stride, vertexBufferNextIndex); vertexBufferNextIndex += oldVertexBuffer.Length; } } // Convert to Entity return model; }
private static void ProcessMaterial(ContentManager manager, ICollection <EntityChunk> chunks, MaterialInstance material, Rendering.Model prefabModel) { //we need to futher group by VertexDeclaration var meshes = new Dictionary <VertexDeclaration, MeshData>(); //actually create the mesh foreach (var chunk in chunks) { foreach (var modelMesh in chunk.Model.Meshes) { //process only right material if (modelMesh.MaterialIndex == chunk.MaterialIndex) { MeshData mesh; if (!meshes.TryGetValue(modelMesh.Draw.VertexBuffers[0].Declaration, out mesh)) { mesh = new MeshData { VertexStride = modelMesh.Draw.VertexBuffers[0].Stride }; meshes.Add(modelMesh.Draw.VertexBuffers[0].Declaration, mesh); } //vertexes var vertexBufferRef = AttachedReferenceManager.GetAttachedReference(modelMesh.Draw.VertexBuffers[0].Buffer); byte[] vertexData; if (vertexBufferRef.Data != null) { vertexData = ((BufferData)vertexBufferRef.Data).Content; } else if (!vertexBufferRef.Url.IsNullOrEmpty()) { var dataAsset = manager.Load <Graphics.Buffer>(vertexBufferRef.Url); vertexData = dataAsset.GetSerializationData().Content; } else { throw new Exception($"Failed to get Vertex BufferData for entity {chunk.Entity.Name}'s model."); } //transform the vertexes according to the entity var vertexDataCopy = vertexData.ToArray(); chunk.Entity.Transform.UpdateWorldMatrix(); //make sure matrix is computed modelMesh.Draw.VertexBuffers[0].TransformBuffer(vertexDataCopy, ref chunk.Entity.Transform.WorldMatrix); //add to the big single array var vertexes = vertexDataCopy .Skip(modelMesh.Draw.VertexBuffers[0].Offset) .Take(modelMesh.Draw.VertexBuffers[0].Count * modelMesh.Draw.VertexBuffers[0].Stride) .ToArray(); mesh.VertexData.AddRange(vertexes); //indices var indexBufferRef = AttachedReferenceManager.GetAttachedReference(modelMesh.Draw.IndexBuffer.Buffer); byte[] indexData; if (indexBufferRef.Data != null) { indexData = ((BufferData)indexBufferRef.Data).Content; } else if (!indexBufferRef.Url.IsNullOrEmpty()) { var dataAsset = manager.Load <Graphics.Buffer>(indexBufferRef.Url); indexData = dataAsset.GetSerializationData().Content; } else { throw new Exception("Failed to get Indices BufferData for entity {chunk.Entity.Name}'s model."); } var indexSize = modelMesh.Draw.IndexBuffer.Is32Bit ? sizeof(uint) : sizeof(ushort); var indices = indexData .Skip(modelMesh.Draw.IndexBuffer.Offset) .Take(modelMesh.Draw.IndexBuffer.Count * indexSize) .ToArray(); //todo this code is not optimal, use unsafe //must convert to 32bits if (indexSize == sizeof(ushort)) { var uintIndex = new List <byte>(); for (var i = 0; i < indices.Length; i += sizeof(ushort)) { var index = BitConverter.ToUInt16(indices, i); var bi = BitConverter.GetBytes((uint)index); uintIndex.Add(bi[0]); uintIndex.Add(bi[1]); uintIndex.Add(bi[2]); uintIndex.Add(bi[3]); } indices = uintIndex.ToArray(); } //need to offset the indices for (var i = 0; i < indices.Length; i += sizeof(uint)) { var index = BitConverter.ToUInt32(indices, i) + mesh.IndexOffset; var bi = BitConverter.GetBytes(index); indices[i + 0] = bi[0]; indices[i + 1] = bi[1]; indices[i + 2] = bi[2]; indices[i + 3] = bi[3]; } mesh.IndexOffset += modelMesh.Draw.VertexBuffers[0].Count; mesh.IndexData.AddRange(indices); } } } //Sort out material var matIndex = prefabModel.Materials.Count; prefabModel.Materials.Add(material); foreach (var meshData in meshes) { //todo need to take care of short index var vertexArray = meshData.Value.VertexData.ToArray(); var indexArray = meshData.Value.IndexData.ToArray(); var vertexCount = vertexArray.Length / meshData.Value.VertexStride; var indexCount = indexArray.Length / 4; var gpuMesh = new Mesh { Draw = new MeshDraw { PrimitiveType = PrimitiveType.TriangleList, DrawCount = indexCount, StartLocation = 0 }, MaterialIndex = matIndex }; var vertexBuffer = new BufferData(BufferFlags.VertexBuffer, new byte[vertexArray.Length]); var indexBuffer = new BufferData(BufferFlags.IndexBuffer, new byte[indexArray.Length]); var vertexBufferSerializable = vertexBuffer.ToSerializableVersion(); var indexBufferSerializable = indexBuffer.ToSerializableVersion(); Array.Copy(vertexArray, vertexBuffer.Content, vertexArray.Length); Array.Copy(indexArray, indexBuffer.Content, indexArray.Length); gpuMesh.Draw.VertexBuffers = new VertexBufferBinding[1]; gpuMesh.Draw.VertexBuffers[0] = new VertexBufferBinding(vertexBufferSerializable, meshData.Key, vertexCount); gpuMesh.Draw.IndexBuffer = new IndexBufferBinding(indexBufferSerializable, true, indexCount); prefabModel.Meshes.Add(gpuMesh); } }