diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cba4791c7aaba330fa3a9c7d3579c9ad6e5387e3..63c63cafd6b800500622fe1c18b293e8b5d6d9a1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,18 @@ This document contains the ``fslpy`` release history in reverse chronological order. + +2.8.0 (Under development) +------------------------- + + +Fixed +^^^^^ + + +* Improved the algorithm used by the :func:`.mesh.needsFixing` function. + + 2.7.0 (Wednesday 6th November 2019) ----------------------------------- diff --git a/fsl/data/mesh.py b/fsl/data/mesh.py index c15e49ced5f972e39e01af4c520053ecdb6b809a..383afcb17d0f31fe329886212a2a81d814f85c4e 100644 --- a/fsl/data/mesh.py +++ b/fsl/data/mesh.py @@ -758,15 +758,19 @@ def needsFixing(vertices, indices, fnormals, loBounds, hiBounds): ivert = np.argmin(dists) vert = vertices[ivert] - # Pick a triangle that - # this vertex is in and - # ges its face normal - itri = np.where(indices == ivert)[0][0] - n = fnormals[itri, :] + # Get all the triangles + # that this vertex is in + # and their face normals + itris = np.where(indices == ivert)[0] + norms = fnormals[itris, :] - # Make sure the angle between the + # Calculate the angle between each # normal, and a vector from the - # vertex to the camera is positive - # If it isn't, we need to flip the - # triangle winding order. - return np.dot(n, affine.normalise(camera - vert)) < 0 + # vertex to the camera. If more than + # 50% of the angles are negative + # (== more than 90 degrees == the + # face is facing away from the + # camera), assume that we need to + # flip the triangle winding order. + angles = np.dot(norms, affine.normalise(camera - vert)) + return ((angles >= 0).sum() / len(itris)) < 0.5 diff --git a/tests/test_mesh.py b/tests/test_mesh.py index c5841df9d38338c601cc9e2a6ee4c5552d18f7b4..f5ae5ead39be0dc3621a847c2083636cbf23e6a7 100644 --- a/tests/test_mesh.py +++ b/tests/test_mesh.py @@ -245,7 +245,7 @@ def test_needsFixing(): verts = np.array(CUBE_VERTICES) tris_cw = np.array(CUBE_TRIANGLES_CW) tris_ccw = np.array(CUBE_TRIANGLES_CCW) - fnormals = np.array(CUBE_CCW_VERTEX_NORMALS) + fnormals = np.array(CUBE_CCW_FACE_NORMALS) blo = verts.min(axis=0) bhi = verts.max(axis=0) mesh = fslmesh.Mesh(tris_cw, vertices=verts, fixWinding=True) @@ -254,6 +254,28 @@ def test_needsFixing(): assert fslmesh.needsFixing(verts, tris_cw, -fnormals, blo, bhi) assert np.all(np.isclose(mesh.indices, tris_ccw)) + # regression: needsFixing used to use the first triangle + # of the nearest vertex to the camera. But this will fail + # if that triangle is facing away from the camera. + verts = np.array([ + [ -1, -1, -1], # vertex 0 will be nearest the camera + [ 0.5, -0.5, 0], + [ 1, -1, 0], + [ 1, 1, 1], + [ 0, -1, 1]]) + tris = np.array([ + [0, 4, 1], # first triangle will be facing away from the camera + [0, 1, 2], + [1, 3, 2], + [0, 2, 4], + [2, 3, 4], + [1, 4, 3]]) + mesh = fslmesh.Mesh(tris, vertices=verts, fixWinding=True) + fnormals = fslmesh.calcFaceNormals(verts, tris) + blo = verts.min(axis=0) + bhi = verts.max(axis=0) + assert not fslmesh.needsFixing(verts, tris, fnormals, blo, bhi) + def test_trimesh_no_trimesh():