Skip to content

Commit e8c1dc3

Browse files
authored
Merge pull request vispy#2434 from asnt/vectorize_meshdata_vertex_normals
2 parents 356800a + 7cf17ad commit e8c1dc3

File tree

2 files changed

+58
-15
lines changed

2 files changed

+58
-15
lines changed

vispy/geometry/meshdata.py

+19-15
Original file line numberDiff line numberDiff line change
@@ -327,21 +327,25 @@ def get_vertex_normals(self, indexed=None):
327327
The normals.
328328
"""
329329
if self._vertex_normals is None:
330-
faceNorms = self.get_face_normals()
331-
vertFaces = self.get_vertex_faces()
332-
self._vertex_normals = np.empty(self._vertices.shape,
333-
dtype=np.float32)
334-
for vindex in range(self._vertices.shape[0]):
335-
faces = vertFaces[vindex]
336-
if len(faces) == 0:
337-
self._vertex_normals[vindex] = (0, 0, 0)
338-
continue
339-
norms = faceNorms[faces] # get all face normals
340-
norm = norms.sum(axis=0) # sum normals
341-
renorm = (norm**2).sum()**0.5
342-
if renorm > 0:
343-
norm /= renorm
344-
self._vertex_normals[vindex] = norm
330+
face_normals = self.get_face_normals().astype(np.float32)
331+
vertex_normals = np.zeros(self._vertices.shape, dtype=np.float32)
332+
333+
face_normals_repeated_on_face_vertices = np.repeat(face_normals,
334+
3,
335+
axis=0)
336+
# NOTE: Cannot use the following intuitive code as it does not
337+
# accumulate the values from the right hand side at repeated
338+
# indices on the left hand side (instead, the value is overwritten
339+
# at each occurence of an index):
340+
# vertex_normals[self._faces.ravel()] += face_normals_repeated_on_face_vertices
341+
# The following does the desired accumulation at repeated indices:
342+
np.add.at(vertex_normals, self._faces.ravel(),
343+
face_normals_repeated_on_face_vertices)
344+
345+
norms = (vertex_normals**2).sum(axis=1)**0.5
346+
nonzero_norms = norms > 0
347+
vertex_normals[nonzero_norms] /= norms[nonzero_norms][:, None]
348+
self._vertex_normals = vertex_normals
345349

346350
if indexed is None:
347351
return self._vertex_normals

vispy/geometry/tests/test_meshdata.py

+39
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,43 @@ def test_meshdata():
3131
assert_array_equal(square_edges, mesh.get_edges())
3232

3333

34+
def test_vertex_normals_indexed_none():
35+
dtype_float = np.float32
36+
dtype_int = np.int64
37+
vertices = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]],
38+
dtype=dtype_float)
39+
faces = np.array([[0, 2, 1], [0, 3, 2], [0, 1, 3]], dtype=dtype_int)
40+
mesh = MeshData(vertices=vertices, faces=faces)
41+
vertex_normals_unnormalized = np.array(
42+
[[-1, -1, -1], [0, -1, -1], [-1, 0, -1], [-1, -1, 0]],
43+
dtype=dtype_float)
44+
norms = np.sqrt((vertex_normals_unnormalized**2).sum(axis=1,
45+
keepdims=True))
46+
expected_vertex_normals = vertex_normals_unnormalized / norms
47+
48+
computed_vertex_normals = mesh.get_vertex_normals(indexed=None)
49+
50+
assert_array_equal(expected_vertex_normals, computed_vertex_normals)
51+
52+
53+
def test_vertex_normals_indexed_faces():
54+
dtype_float = np.float32
55+
dtype_int = np.int64
56+
vertices = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]],
57+
dtype=dtype_float)
58+
faces = np.array([[0, 2, 1], [0, 3, 2], [0, 1, 3]], dtype=dtype_int)
59+
mesh = MeshData(vertices=vertices, faces=faces)
60+
vertex_normals_unnormalized = np.array(
61+
[[-1, -1, -1], [0, -1, -1], [-1, 0, -1], [-1, -1, 0]],
62+
dtype=dtype_float)
63+
norms = np.sqrt((vertex_normals_unnormalized**2).sum(axis=1,
64+
keepdims=True))
65+
vertex_normals = vertex_normals_unnormalized / norms
66+
expected_vertex_normals = vertex_normals[faces]
67+
68+
computed_vertex_normals = mesh.get_vertex_normals(indexed="faces")
69+
70+
assert_array_equal(expected_vertex_normals, computed_vertex_normals)
71+
72+
3473
run_tests_if_main()

0 commit comments

Comments
 (0)