using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Smuggler { public static class Importer { public static Model ImportGLB(GraphicsDevice graphicsDevice, Stream stream) { var sharpModel = SharpGLTF.Schema2.ModelRoot.ReadGLB(stream); var vertexType = InferVertexType(sharpModel); if (vertexType == null) { return null; } var meshes = new List(); foreach (var mesh in sharpModel.LogicalMeshes) { var meshParts = new List(); foreach (var primitive in mesh.Primitives) { meshParts.Add( ImportMeshPart(graphicsDevice, vertexType, primitive) ); } meshes.Add(new Mesh(meshParts.ToArray())); } var model = new Model(meshes.ToArray()); return model; } private static System.Type InferVertexType(SharpGLTF.Schema2.ModelRoot model) { bool hasColor = false; bool hasNormal = false; bool hasTexture = false; if (model.LogicalMeshes.Count == 0) { return null; } if (model.LogicalMeshes[0].Primitives.Count == 0) { return null; } var samplePrimitive = model.LogicalMeshes[0].Primitives[0]; if (samplePrimitive.GetVertexAccessor("POSITION") == null) { System.Console.WriteLine("There is no Position attribute on this model's vertices. That seems weird!"); return null; } if (samplePrimitive.GetVertexAccessor("COLOR") != null) { hasColor = true; } if (samplePrimitive.GetVertexAccessor("NORMAL") != null) { hasNormal = true; } if (samplePrimitive.GetVertexAccessor("TEXCOORD_0") != null) { hasTexture = true; } if (hasColor && hasNormal && hasTexture) { // FIXME: Implement VertexPositionColorNormalTexture return null; } if (hasColor && hasTexture) { return typeof(VertexPositionColorTexture); } if (hasNormal && hasTexture) { return typeof(VertexPositionNormalTexture); } if (hasColor) { return typeof(VertexPositionColor); } if (hasTexture) { return typeof(VertexPositionTexture); } System.Console.WriteLine("Could not infer vertex type"); return null; } private static MeshPart ImportMeshPart(GraphicsDevice graphicsDevice, System.Type vertexType, SharpGLTF.Schema2.MeshPrimitive primitive) { var indices = Indices(primitive); var positions = Positions(primitive); var triangles = Triangles(primitive); var vertexBuffer = new VertexBuffer( graphicsDevice, vertexType, positions.Length, BufferUsage.None ); var indexBuffer = new IndexBuffer( graphicsDevice, IndexElementSize.ThirtyTwoBits, indices.Length, BufferUsage.None ); indexBuffer.SetData(indices); if (vertexType == typeof(VertexPositionColor)) { ImportVertexPositionColor(primitive, vertexBuffer, indices, positions); } else if (vertexType == typeof(VertexPositionColorTexture)) { ImportVertexPositionColorTexture(primitive, vertexBuffer, indices, positions); } else if (vertexType == typeof(VertexPositionNormalTexture)) { ImportVertexPositionNormalTexture(primitive, vertexBuffer, indices, positions); } else if (vertexType == typeof(VertexPositionTexture)) { ImportVertexPositionTexture(primitive, vertexBuffer, indices, positions); } var effect = new PBREffect(graphicsDevice); if (primitive.Material != null) { var normalChannel = primitive.Material.FindChannel("Normal"); if (normalChannel.HasValue) { if (normalChannel.Value.Texture != null) { effect.NormalTexture = Texture2D.FromStream( graphicsDevice, normalChannel.Value.Texture.PrimaryImage.Content.Open() ); } } //var occlusionChannel = primitive.Material.FindChannel("Occlusion"); //if (occlusionChannel.HasValue) //{ // if (occlusionChannel.Value.Texture != null) // { // effect.OcclusionTexture = Texture2D.FromStream( // graphicsDevice, // occlusionChannel.Value.Texture.PrimaryImage.Content.Open() // ); // } // effect.OcclusionStrength = occlusionChannel.Value.Parameter.X; //} //var emissiveChannel = primitive.Material.FindChannel("Emissive"); //if (emissiveChannel.HasValue) //{ // if (emissiveChannel.Value.Texture != null) // { // effect.EmissionTexture = Texture2D.FromStream( // graphicsDevice, // emissiveChannel.Value.Texture.PrimaryImage.Content.Open() // ); // } // var parameter = emissiveChannel.Value.Parameter; // effect.EmissiveFactor = new Vector3( // parameter.X, // parameter.Y, // parameter.Z // ); //} var albedoChannel = primitive.Material.FindChannel("BaseColor"); if (albedoChannel.HasValue) { if (albedoChannel.Value.Texture != null) { effect.AlbedoTexture = Texture2D.FromStream( graphicsDevice, albedoChannel.Value.Texture.PrimaryImage.Content.Open() ); } var parameter = albedoChannel.Value.Parameter; effect.Albedo = new Vector3( parameter.X, parameter.Y, parameter.Z ); } var metallicRoughnessChannel = primitive.Material.FindChannel("MetallicRoughness"); if (metallicRoughnessChannel.HasValue) { if (metallicRoughnessChannel.Value.Texture != null) { effect.MetallicRoughnessTexture = Texture2D.FromStream( graphicsDevice, metallicRoughnessChannel.Value.Texture.PrimaryImage.Content.Open() ); } var parameter = metallicRoughnessChannel.Value.Parameter; effect.Metallic = parameter.X; effect.Roughness = parameter.Y; } } effect.Albedo = new Vector3(0.5f, 0, 0); effect.AO = 1f; effect.Lights[0] = new PBRLight( new Vector3(-10f, 10f, -5f), new Vector3(300f, 300f, 300f) ); effect.Lights[1] = new PBRLight( new Vector3(10f, 10f, 5f), new Vector3(300f, 300f, 300f) ); effect.Lights[2] = new PBRLight( new Vector3(-10f, -10f, 5f), new Vector3(300f, 300f, 300f) ); effect.Lights[3] = new PBRLight( new Vector3(10f, -10f, 5f), new Vector3(300f, 300f, 300f) ); /* FIXME: how to load cube maps from GLTF? */ /* var diffuseChannel = primitive.Material.FindChannel("Diffuse"); if (diffuseChannel.HasValue) { } var specularChannel = primitive.Material.FindChannel("SpecularGlossiness"); if (specularChannel.HasValue) { } */ return new MeshPart( vertexBuffer, indexBuffer, positions, triangles, effect ); } /* Attribute Getters */ private static Vector3[] Positions(SharpGLTF.Schema2.MeshPrimitive primitive) { var positionAccessor = primitive.GetVertexAccessor("POSITION").AsVector3Array(); var positions = new Vector3[positionAccessor.Count]; for (int i = 0; i < positions.Length; i++) { var position = positionAccessor[i]; positions[i] = new Vector3(-position.X, -position.Z, position.Y); } return positions; } private static Color[] Colors(SharpGLTF.Schema2.MeshPrimitive primitive) { var colorAccessor = primitive.GetVertexAccessor("COLOR").AsColorArray(); var colors = new Color[colorAccessor.Count]; for (int i = 0; i < colors.Length; i++) { var color = colorAccessor[i]; colors[i] = new Color(color.X, color.Y, color.Z, color.W); } return colors; } private static Vector3[] Normals(SharpGLTF.Schema2.MeshPrimitive primitive) { var normalAccessor = primitive.GetVertexAccessor("NORMAL").AsVector3Array(); var normals = new Vector3[normalAccessor.Count]; for (int i = 0; i < normals.Length; i++) { var normal = normalAccessor[i]; normals[i] = new Vector3(-normal.X, -normal.Z, normal.Y); } return normals; } private static Vector2[] TexCoords(SharpGLTF.Schema2.MeshPrimitive primitive) { var texcoordAccessor = primitive.GetVertexAccessor("TEXCOORD_0").AsVector2Array(); var texcoords = new Vector2[texcoordAccessor.Count]; for (int i = 0; i < texcoords.Length; i++) { var texcoord = texcoordAccessor[i]; texcoords[i] = new Vector2(texcoord.X, texcoord.Y); } return texcoords; } private static Triangle[] Triangles(SharpGLTF.Schema2.MeshPrimitive primitive) { var triangles = new List(); foreach (var (a, b, c) in primitive.GetTriangleIndices()) { triangles.Add(new Triangle(a, b, c)); } return triangles.ToArray(); } private static uint[] Indices(SharpGLTF.Schema2.MeshPrimitive primitive) { var indexAccessor = primitive.GetIndexAccessor().AsIndicesArray(); var indices = new uint[indexAccessor.Count]; for (int i = 0; i < indices.Length; i++) { indices[i] = indexAccessor[i]; } return indices; } /* Vertex Builders */ private static void ImportVertexPositionColor( SharpGLTF.Schema2.MeshPrimitive primitive, VertexBuffer vertexBuffer, uint[] indices, Vector3[] positions ) { var colors = Colors(primitive); var vertices = new VertexPositionColor[positions.Length]; foreach (var index in indices) { var position = positions[index]; var color = colors[index]; vertices[index] = new VertexPositionColor( position, color ); } vertexBuffer.SetData(vertices); } private static void ImportVertexPositionColorTexture( SharpGLTF.Schema2.MeshPrimitive primitive, VertexBuffer vertexBuffer, uint[] indices, Vector3[] positions ) { var colors = Colors(primitive); var texcoords = TexCoords(primitive); var vertices = new VertexPositionColorTexture[positions.Length]; foreach (var index in indices) { var position = positions[index]; var color = colors[index]; var texcoord = texcoords[index]; vertices[index] = new VertexPositionColorTexture( position, color, texcoord ); } vertexBuffer.SetData(vertices); } private static void ImportVertexPositionNormalTexture( SharpGLTF.Schema2.MeshPrimitive primitive, VertexBuffer vertexBuffer, uint[] indices, Vector3[] positions ) { var normals = Normals(primitive); var texcoords = TexCoords(primitive); var vertices = new VertexPositionNormalTexture[positions.Length]; foreach (var index in indices) { var position = positions[index]; var normal = normals[index]; var texcoord = texcoords[index]; vertices[index] = new VertexPositionNormalTexture( position, normal, texcoord ); } vertexBuffer.SetData(vertices); } private static void ImportVertexPositionTexture( SharpGLTF.Schema2.MeshPrimitive primitive, VertexBuffer vertexBuffer, uint[] indices, Vector3[] positions ) { var texcoords = TexCoords(primitive); var vertices = new VertexPositionTexture[positions.Length]; foreach (var index in indices) { var position = positions[index]; var texcoord = texcoords[index]; vertices[index] = new VertexPositionTexture( position, texcoord ); } vertexBuffer.SetData(vertices); } } }