using System; using System.Collections.Generic; namespace Oni.Motoko { internal static class GeometryDaeReader { #region private struct Vertex private struct Vertex : IEquatable { public readonly int PositionIndex; public readonly int TexcoordIndex; public readonly int NormalIndex; public Vertex(int pointIndex, int uvIndex, int normalIndex) { PositionIndex = pointIndex; TexcoordIndex = uvIndex; NormalIndex = normalIndex; } public static bool operator ==(Vertex v1, Vertex v2) => v1.Equals(v2); public static bool operator !=(Vertex v1, Vertex v2) => !v1.Equals(v2); public bool Equals(Vertex v) => PositionIndex == v.PositionIndex && TexcoordIndex == v.TexcoordIndex && NormalIndex == v.NormalIndex; public override bool Equals(object obj) => obj is Vertex && Equals((Vertex)obj); public override int GetHashCode() => PositionIndex ^ TexcoordIndex ^ NormalIndex; } #endregion public static Geometry Read(Dae.Geometry daeGeometry) { return Read(daeGeometry, false, false, 0.0f); } public static IEnumerable Read(Dae.Node node, TextureImporter3 textureImporter) { Dae.FaceConverter.Triangulate(node); foreach (var daeGeometryInstance in node.GeometryInstances) { var daeGeometry = daeGeometryInstance.Target; var geometry = Read(daeGeometry, false, false, 0.0f); geometry.Name = node.Name; if (textureImporter != null && daeGeometryInstance.Materials.Count > 0) geometry.TextureName = textureImporter.AddMaterial(daeGeometryInstance.Materials[0].Target); yield return geometry; } } public static Geometry Read(Dae.Geometry daeGeometry, bool generateNormals, bool flatNormals, float shellOffset) { if (daeGeometry.Primitives.Count > 1) throw new NotSupportedException(string.Format("Geometry {0}: Multiple primitive groups per mesh are not supported", daeGeometry.Name)); var primitives = daeGeometry.Primitives[0]; if (primitives.PrimitiveType == Dae.MeshPrimitiveType.Lines || primitives.PrimitiveType == Dae.MeshPrimitiveType.LineStrips) throw new NotSupportedException(string.Format("Geometry {0}: Line primitives are not supported", daeGeometry.Name)); var positionIndex = new Dictionary(); var positions = new List(); int[] positionIndices = null; var normalIndex = new Dictionary(); var normals = new List(); int[] normalIndices = null; var texCoordIndex = new Dictionary(); var texCoords = new List(); int[] texCoordIndices = null; foreach (var input in primitives.Inputs) { switch (input.Semantic) { case Dae.Semantic.Position: positionIndices = RemoveDuplicates(input, positions, positionIndex, Dae.Source.ReadVector3); break; case Dae.Semantic.Normal: if (!generateNormals) normalIndices = RemoveDuplicates(input, normals, normalIndex, Dae.Source.ReadVector3); break; case Dae.Semantic.TexCoord: texCoordIndices = RemoveDuplicates(input, texCoords, texCoordIndex, Dae.Source.ReadTexCoord); break; } } if (texCoordIndices == null) Console.WriteLine("Geometry {0} does not have texture coordinates", daeGeometry.Name); if (normalIndices == null) generateNormals = true; Vector3[] generatedNormals = null; if (generateNormals || shellOffset != 0.0f) generatedNormals = GenerateNormals(positions, positionIndices, flatNormals); if (generateNormals) { normals = new List(generatedNormals); normalIndices = positionIndices; } int[] shellIndices = null; if (shellOffset != 0.0f) { var shellNormals = generatedNormals; if (flatNormals) shellNormals = GenerateNormals(positions, positionIndices, false); shellIndices = GenerateShell(positions, positionIndices, shellNormals, shellOffset); } var triangles = new int[(shellIndices == null) ? positionIndices.Length : positionIndices.Length + shellIndices.Length]; var vertices = new List(); var vertexIndex = new Dictionary(); for (int i = 0; i < positionIndices.Length; i++) { var vertex = new Vertex( positionIndices[i], (texCoordIndices != null) ? texCoordIndices[i] : -1, (normalIndices != null) ? normalIndices[i] : -1); if (!vertexIndex.TryGetValue(vertex, out triangles[i])) { triangles[i] = vertices.Count; vertices.Add(vertex); vertexIndex.Add(vertex, triangles[i]); } } if (shellIndices != null) { for (int i = 0; i < shellIndices.Length; i++) { var vertex = new Vertex(shellIndices[i], -1, -1); int j = i + positionIndices.Length; if (!vertexIndex.TryGetValue(vertex, out triangles[j])) { triangles[j] = vertices.Count; vertices.Add(vertex); vertexIndex.Add(vertex, triangles[j]); } } } if (vertices.Count > 2048) Console.Error.WriteLine("Warning: Geometry {0} has too many vertices ({1})", daeGeometry.Name, vertices.Count); var geometry = new Geometry { Points = new Vector3[vertices.Count], Normals = new Vector3[vertices.Count], TexCoords = new Vector2[vertices.Count], Triangles = triangles }; for (int i = 0; i < vertices.Count; i++) { geometry.Points[i] = positions[vertices[i].PositionIndex]; if (vertices[i].NormalIndex != -1) geometry.Normals[i] = normals[vertices[i].NormalIndex]; if (vertices[i].TexcoordIndex != -1) geometry.TexCoords[i] = texCoords[vertices[i].TexcoordIndex]; } return geometry; } private static int[] RemoveDuplicates( Dae.IndexedInput input, List list, Dictionary index, Func elementReader) { var indices = new int[input.Indices.Count]; for (int i = 0; i < indices.Length; i++) { var v = elementReader(input.Source, input.Indices[i]); if (!index.TryGetValue(v, out indices[i])) { indices[i] = list.Count; list.Add(v); index.Add(v, indices[i]); } } return indices; } private static Vector3[] GenerateNormals(List positions, int[] triangleList, bool flatNormals) { var autoNormals = new Vector3[positions.Count]; if (!flatNormals) { for (int i = 0; i < triangleList.Length; i += 3) { Vector3 p0 = positions[triangleList[i + 0]]; Vector3 p1 = positions[triangleList[i + 1]]; Vector3 p2 = positions[triangleList[i + 2]]; Vector3 e1 = p1 - p0; Vector3 e2 = p2 - p0; Vector3 faceNormal = Vector3.Cross(e1, e2); float weight = FMath.Atan2(faceNormal.Length(), Vector3.Dot(e1, e2)); faceNormal = Vector3.Normalize(faceNormal) * weight; for (int j = 0; j < 3; j++) autoNormals[triangleList[i + j]] += faceNormal; } for (int i = 0; i < autoNormals.Length; i++) autoNormals[i].Normalize(); } return autoNormals; } private static int[] GenerateShell(List positions, int[] positionIndices, Vector3[] normals, float offset) { int positionCount = positions.Count; for (int i = 0; i < positionCount; i++) positions.Add(positions[i] + normals[i] * offset); var shellIndices = new int[positionIndices.Length]; for (int i = 0; i < positionIndices.Length; i += 3) { shellIndices[i + 0] = positionIndices[i + 2] + positionCount; shellIndices[i + 1] = positionIndices[i + 1] + positionCount; shellIndices[i + 2] = positionIndices[i + 0] + positionCount; } return shellIndices; } } }