/NuGenBioChem/Visualization/Mathematics/MeshGeometry3D.cs

https://github.com/AnthonyNystrom/NuGenBioChem · C# · 259 lines · 170 code · 34 blank · 55 comment · 26 complexity · 10f27afe162dcc85418ed0e4ae21ff9b MD5 · raw file

  1. using System.Collections.Generic;
  2. namespace System.Windows.Media.Media3D
  3. {
  4. /// <summary>
  5. /// Extension methods for MeshGeometry3D
  6. /// </summary>
  7. public static class MeshGeometry3DMethods
  8. {
  9. #region Merge
  10. /// <summary>
  11. /// Adds the data of the given mesh
  12. /// </summary>
  13. /// <param name="thismesh">This mesh</param>
  14. /// <param name="mesh">Another mesh</param>
  15. public static void Merdge(this MeshGeometry3D thismesh, MeshGeometry3D mesh)
  16. {
  17. for (int i = 0; i < mesh.Positions.Count; i++) thismesh.Positions.Add(mesh.Positions[i]);
  18. for (int i = 0; i < mesh.Normals.Count; i++) thismesh.Normals.Add(mesh.Normals[i]);
  19. for (int i = 0; i < mesh.TextureCoordinates.Count; i++) thismesh.TextureCoordinates.Add(mesh.TextureCoordinates[i]);
  20. for (int i = 0; i < mesh.TriangleIndices.Count; i++) thismesh.TriangleIndices.Add(mesh.TriangleIndices[i]);
  21. }
  22. #endregion
  23. #region Transform
  24. /// <summary>
  25. /// Transforms the given mesh
  26. /// </summary>
  27. /// <param name="mesh">This mesh</param>
  28. /// <param name="transform">Transform</param>
  29. public static void Transform(this MeshGeometry3D mesh, Matrix3D transform)
  30. {
  31. Matrix3D rotationOnly = transform;
  32. rotationOnly.OffsetX = rotationOnly.OffsetY = rotationOnly.OffsetZ = 0;
  33. // Transform positions
  34. for(int i = 0; i < mesh.Positions.Count; i++)
  35. mesh.Positions[i] = transform.Transform(mesh.Positions[i]);
  36. // Transform normals
  37. for (int i = 0; i < mesh.Normals.Count; i++)
  38. mesh.Normals[i] = rotationOnly.Transform(mesh.Normals[i]);
  39. }
  40. #endregion
  41. #region Clear
  42. /// <summary>
  43. /// Clears data of the given mesh
  44. /// </summary>
  45. /// <param name="mesh">This mesh</param>
  46. public static void Clear(this MeshGeometry3D mesh)
  47. {
  48. mesh.Positions.Clear();
  49. mesh.Normals.Clear();
  50. mesh.TextureCoordinates.Clear();
  51. mesh.TriangleIndices.Clear();
  52. }
  53. #endregion
  54. #region Texture Coordinates
  55. /// <summary>
  56. /// Generates texture coordinates as if mesh were a cylinder.
  57. /// </summary>
  58. /// <param name="mesh">The mesh</param>
  59. /// <param name="cylinderAxis">The axis of rotation for the cylinder</param>
  60. /// <returns>The generated texture coordinates</returns>
  61. public static void GenerateCylindricalTextureCoordinates(this MeshGeometry3D mesh, Vector3D cylinderAxis)
  62. {
  63. Rect3D bounds = mesh.Bounds;
  64. int count = mesh.Positions.Count;
  65. PointCollection texcoords = new PointCollection(count);
  66. IEnumerable<Point3D> positions = TransformPoints(ref bounds, mesh.Positions, ref cylinderAxis);
  67. mesh.TextureCoordinates.Clear();
  68. foreach (Point3D vertex in positions)
  69. {
  70. mesh.TextureCoordinates.Add(new Point(
  71. GetUnitCircleCoordinate(-vertex.Z, vertex.X),
  72. 1.0 - GetPlanarCoordinate(vertex.Y, bounds.Y, bounds.SizeY)));
  73. }
  74. }
  75. /// <summary>
  76. /// Generates texture coordinates as if mesh were a sphere.
  77. /// </summary>
  78. /// <param name="mesh">The mesh</param>
  79. /// <param name="axisRotationSphere">The axis of rotation for the sphere</param>
  80. /// <returns>The generated texture coordinates</returns>
  81. public static void GenerateSphericalTextureCoordinates(this MeshGeometry3D mesh, Vector3D axisRotationSphere)
  82. {
  83. Rect3D bounds = mesh.Bounds;
  84. int count = mesh.Positions.Count;
  85. IEnumerable<Point3D> positions = TransformPoints(ref bounds, mesh.Positions, ref axisRotationSphere);
  86. mesh.TextureCoordinates.Clear();
  87. foreach (Point3D vertex in positions)
  88. {
  89. // Don't need to do 'vertex - center' since TransformPoints put us
  90. // at the origin
  91. Vector3D radius = new Vector3D(vertex.X, vertex.Y, vertex.Z);
  92. if (radius != new Vector3D()) radius.Normalize();
  93. mesh.TextureCoordinates.Add(new Point(
  94. GetUnitCircleCoordinate(-radius.Z, radius.X),
  95. 1.0 - (Math.Asin(radius.Y) / Math.PI + 0.5)));
  96. }
  97. }
  98. /// <summary>
  99. /// Generates texture coordinates as if mesh were a plane.
  100. /// </summary>
  101. /// <param name="mesh">The mesh</param>
  102. /// <param name="planeNormal">The normal of the plane</param>
  103. /// <returns>The generated texture coordinates</returns>
  104. public static void GeneratePlanarTextureCoordinates(this MeshGeometry3D mesh, Vector3D planeNormal)
  105. {
  106. Rect3D bounds = mesh.Bounds;
  107. int count = mesh.Positions.Count;
  108. IEnumerable<Point3D> positions = TransformPoints(ref bounds, mesh.Positions, ref planeNormal);
  109. mesh.TextureCoordinates.Clear();
  110. foreach (Point3D vertex in positions)
  111. {
  112. // The plane is looking along positive Y, so Z is really Y
  113. mesh.TextureCoordinates.Add(new Point(
  114. GetPlanarCoordinate(vertex.X, bounds.X, bounds.SizeX),
  115. GetPlanarCoordinate(vertex.Z, bounds.Z, bounds.SizeZ)));
  116. }
  117. }
  118. static double GetPlanarCoordinate(double end, double start, double width)
  119. {
  120. return (end - start) / width;
  121. }
  122. static double GetUnitCircleCoordinate(double y, double x)
  123. {
  124. return Math.Atan2(y, x) / (2.0 * Math.PI) + 0.5;
  125. }
  126. /// <summary>
  127. /// Finds the transform from 'meshOrientation' to '<0, 1, 0>' and transforms 'bounds' and 'points' by it.
  128. /// </summary>
  129. /// <param name="bounds">The bounds to transform</param>
  130. /// <param name="points">The vertices to transform</param>
  131. /// <param name="meshOrientation">The orientation of the mesh</param>
  132. /// <returns>The transformed points. If 'meshOrientation' is already '<0, 1, 0>' then this will equal 'points.'
  133. /// </returns>
  134. static IEnumerable<Point3D> TransformPoints(ref Rect3D bounds, Point3DCollection points, ref Vector3D meshOrientation)
  135. {
  136. Vector3D yAxis = new Vector3D(0, 1, 0);
  137. Vector3D xAxis = new Vector3D(1, 0, 0);
  138. if (meshOrientation == yAxis)
  139. {
  140. return points;
  141. }
  142. Vector3D rotAxis = Vector3D.CrossProduct(meshOrientation, yAxis);
  143. double rotAngle = Vector3D.AngleBetween(meshOrientation, yAxis);
  144. Quaternion q;
  145. if (rotAxis.X != 0 || rotAxis.Y != 0 || rotAxis.Z != 0)
  146. {
  147. q = new Quaternion(rotAxis, rotAngle);
  148. }
  149. else
  150. {
  151. q = new Quaternion(xAxis, rotAngle);
  152. }
  153. Vector3D center = new Vector3D(
  154. bounds.X + bounds.SizeX / 2,
  155. bounds.Y + bounds.SizeY / 2,
  156. bounds.Z + bounds.SizeZ / 2);
  157. Matrix3D t = Matrix3D.Identity;
  158. t.Translate(-center);
  159. t.Rotate(q);
  160. int count = points.Count;
  161. Point3D[] transformedPoints = new Point3D[count];
  162. for (int i = 0; i < count; i++)
  163. {
  164. transformedPoints[i] = t.Transform(points[i]);
  165. }
  166. // Finally, transform the bounds too
  167. bounds = bounds.Transform(t);
  168. return transformedPoints;
  169. }
  170. #endregion
  171. #region Saving (for debugging)
  172. /// <summary>
  173. /// Saves the mesh to wavefront(.obj) file format that allows
  174. /// to analyze the geometry with other very powerful tools
  175. /// </summary>
  176. /// <param name="mesh">Mesh geometry</param>
  177. /// <param name="path">Filename</param>
  178. public static void SaveToWavefrontObj(this MeshGeometry3D mesh, string path)
  179. {
  180. using (var writer = new IO.StreamWriter(path, false, Text.Encoding.ASCII))
  181. {
  182. var format = System.Globalization.CultureInfo.InvariantCulture;
  183. foreach (Point3D position in mesh.Positions)
  184. {
  185. writer.WriteLine("v {0} {1} {2}",
  186. position.X.ToString(format),
  187. position.Y.ToString(format),
  188. position.Z.ToString(format));
  189. }
  190. foreach (Vector3D normals in mesh.Normals)
  191. {
  192. writer.WriteLine("vn {0} {1} {2}",
  193. normals.X.ToString(format),
  194. normals.Y.ToString(format),
  195. normals.Z.ToString(format));
  196. }
  197. foreach (Point textureCoordinate in mesh.TextureCoordinates)
  198. {
  199. writer.WriteLine("vt {0} {1}",
  200. textureCoordinate.X.ToString(format),
  201. textureCoordinate.Y.ToString(format));
  202. }
  203. string formatString;
  204. if (mesh.TextureCoordinates.Count == mesh.Positions.Count)
  205. {
  206. if (mesh.Normals.Count == mesh.Positions.Count) formatString = "f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}";
  207. else formatString = "f {0}/{0} {1}/{1} {2}/{2}";
  208. }
  209. else
  210. {
  211. if (mesh.Normals.Count == mesh.Positions.Count) formatString = "f {0}//{0} {1}//{1} {2}//{2}";
  212. else formatString = "f {0} {1} {2}";
  213. }
  214. for (int i = 2; i < mesh.TriangleIndices.Count; i += 3)
  215. {
  216. writer.WriteLine(formatString,
  217. (mesh.TriangleIndices[i - 2] + 1).ToString(format),
  218. (mesh.TriangleIndices[i - 1] + 1).ToString(format),
  219. (mesh.TriangleIndices[i] + 1).ToString(format));
  220. }
  221. }
  222. }
  223. #endregion
  224. }
  225. }