From 1614e9ee900ee3ac2a0282354195709ef9815b62 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauldmccarthy@gmail.com>
Date: Sat, 15 Jul 2017 22:24:23 +0100
Subject: [PATCH] Mesh class calculates normals. Implementations are crap

---
 fsl/data/mesh.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 68 insertions(+), 2 deletions(-)

diff --git a/fsl/data/mesh.py b/fsl/data/mesh.py
index 2a9c47df7..82023debd 100644
--- a/fsl/data/mesh.py
+++ b/fsl/data/mesh.py
@@ -53,6 +53,12 @@ class TriangleMesh(object):
 
     ``indices``    A :meth:`M\times 3` ``numpy`` array containing
                    the vertex indices for :math:`M` triangles
+
+    ``normals``    A :math:`M\times 3` ``numpy`` array containing
+                   face normals.
+
+    ``vnormals``   A :math:`N\times 3` ``numpy`` array containing
+                   vertex normals.
     ============== ====================================================
 
 
@@ -101,8 +107,10 @@ class TriangleMesh(object):
         self.indices      = np.array(indices).reshape((-1, 3))
 
         self.__vertexData = {}
-        self.__loBounds   = self.vertices.min(axis=0)
-        self.__hiBounds   = self.vertices.max(axis=0)
+        self.__faceNormals = None
+        self.__vertNormals = None
+        self.__loBounds    = self.vertices.min(axis=0)
+        self.__hiBounds    = self.vertices.max(axis=0)
 
 
     def __repr__(self):
@@ -118,6 +126,64 @@ class TriangleMesh(object):
         return self.__repr__()
 
 
+    @property
+    def normals(self):
+        """Returns a ``(M, 3)`` array containing normals for every triangle
+        in the mesh.
+        """
+        if self.__faceNormals is not None:
+            return self.__faceNormals
+
+        v1 = self.vertices[self.indices[:, 0]]
+        v2 = self.vertices[self.indices[:, 1]]
+
+        fnormals = np.cross(v1, v2)
+
+        # TODO make fast, and make sure that this actually works.
+        for i in range(self.indices.shape[0]):
+
+            p0, p1, p2 = self.vertices[self.indices[i], :]
+            n          = fnormals[i, :]
+
+            if np.dot(n, np.cross(p1 - p0, p2 - p0)) > 0:
+                fnormals[i, :] *= -1
+
+        self.__faceNormals = fnormals
+
+        return self.__faceNormals
+
+
+    @property
+    def vnormals(self):
+        """Returns a ``(N, 3)`` array containing normals for every vertex
+        in the mesh.
+        """
+        if self.__vertNormals is not None:
+            return self.__vertNormals
+
+        # per-face normals
+        fnormals = self.normals
+
+        vnormals = np.zeros((self.vertices.shape[0], 3), dtype=np.float)
+
+        # TODO make fast
+        for i in range(self.indices.shape[0]):
+
+            v0, v1, v2 = self.indices[i]
+
+            vnormals[v0, :] += fnormals[i]
+            vnormals[v1, :] += fnormals[i]
+            vnormals[v2, :] += fnormals[i]
+
+        # normalise to unit length
+        lens = np.sqrt(np.sum(vnormals * vnormals, axis=1))
+        vnormals = (vnormals.T / lens).T
+
+        self.__vertNormals = vnormals
+
+        return self.__vertNormals
+
+
     def getBounds(self):
         """Returns a tuple of values which define a minimal bounding box that
         will contain all vertices in this ``TriangleMesh`` instance. The
-- 
GitLab