Browse Source

MeshTools: cache angles and cross product in generateSmoothNormals().

It allocates one more array, but that speeds up the calculation to twice
as fast. Before the benchmark was around 1 ms for flat normals and 12 ms
for smooth, now it's 6 ms for smooth. There is probably more I could do
(I feel like I could save at least one more normalization), however this
is good enough for now.
pull/229/head
Vladimír Vondruš 7 years ago
parent
commit
ba048cd2db
  1. 47
      src/Magnum/MeshTools/GenerateNormals.cpp
  2. 2
      src/Magnum/MeshTools/GenerateNormals.h

47
src/Magnum/MeshTools/GenerateNormals.cpp

@ -131,6 +131,29 @@ template<class T> void generateSmoothNormalsInto(const Containers::StridedArrayV
/* Now, triangleCount should be all zeros, we don't need it anymore and the /* Now, triangleCount should be all zeros, we don't need it anymore and the
underlying `normals` array is ready to get filled with real output. */ underlying `normals` array is ready to get filled with real output. */
/* Precalculate cross product and interior angles of each face --- the loop
below would otherwise calculate it for every vertex, which is at least
3x as much work */
Containers::Array<std::pair<Vector3, Math::Vector3<Rad>>> crossAngles{Math::NoInit, indices.size()/3};
for(std::size_t i = 0; i != crossAngles.size(); ++i) {
const Vector3 v0 = positions[indices[i*3 + 0]];
const Vector3 v1 = positions[indices[i*3 + 1]];
const Vector3 v2 = positions[indices[i*3 + 2]];
/* Cross product */
crossAngles[i].first = Math::cross(v2 - v1, v0 - v1);
/* Inner angle at each vertex of the triangle. The last one can be
calculated as a remainder to 180°. */
using namespace Math::Literals;
crossAngles[i].second[0] = Math::angle(
(v1 - v0).normalized(), (v2 - v0).normalized());
crossAngles[i].second[1] = Math::angle(
(v0 - v1).normalized(), (v2 - v1).normalized());
crossAngles[i].second[2] = Rad(180.0_degf)
- crossAngles[i].second[0] - crossAngles[i].second[1];
}
/* For every vertex v, calculate normals from all faces it belongs to and /* For every vertex v, calculate normals from all faces it belongs to and
average them */ average them */
for(std::size_t v = 0; v != positions.size(); ++v) { for(std::size_t v = 0; v != positions.size(); ++v) {
@ -146,23 +169,15 @@ template<class T> void generateSmoothNormalsInto(const Containers::StridedArrayV
/* Cross product is a vector in direction of the normal with length /* Cross product is a vector in direction of the normal with length
equal to size of the parallelogram */ equal to size of the parallelogram */
const Vector3 cross = Math::cross(positions[v2i] - positions[v1i], const std::pair<Vector3, Math::Vector3<Rad>>& crossAngle = crossAngles[triangleIds[t]];
positions[v0i] - positions[v1i]);
/* Angle between two sides of the triangle that share vertex `v`. /* Angle between two sides of the triangle that share vertex `v`.
The shared vertex can be one of the three, so three ways to The shared vertex can be one of the three. */
calculate the angle */ Rad angle;
Vector3 a{Math::NoInit}, b{Math::NoInit}; if(v == v0i) angle = crossAngle.second[0];
if(v == v0i) { else if(v == v1i) angle = crossAngle.second[1];
a = positions[v1i] - positions[v0i]; else if(v == v2i) angle = crossAngle.second[2];
b = positions[v2i] - positions[v0i]; else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
} else if(v == v1i) {
a = positions[v0i] - positions[v1i];
b = positions[v2i] - positions[v1i];
} else if(v == v2i) {
a = positions[v0i] - positions[v2i];
b = positions[v1i] - positions[v2i];
} else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
/* The normal is cross.normalized(), we need to multiply it it by /* The normal is cross.normalized(), we need to multiply it it by
surface area which is cross.length()/2. Since normalization is surface area which is cross.length()/2. Since normalization is
@ -172,7 +187,7 @@ template<class T> void generateSmoothNormalsInto(const Containers::StridedArrayV
that as well. Finally we need to weight by the angle, and in that as well. Finally we need to weight by the angle, and in
that case only the ratio is important as well, so it doesn't that case only the ratio is important as well, so it doesn't
matter if degrees or radians. */ matter if degrees or radians. */
normals[v] += cross*Float(Math::angle(a.normalized(), b.normalized())); normals[v] += crossAngle.first*Float(angle);
} }
/* Normalize the accumulated direction */ /* Normalize the accumulated direction */

2
src/Magnum/MeshTools/GenerateNormals.h

@ -121,7 +121,7 @@ extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array<Vector3> generateSmoot
A variant of @ref generateSmoothNormals() that fills existing memory instead of A variant of @ref generateSmoothNormals() that fills existing memory instead of
allocating a new array. The @p normals array is expected to have the same size allocating a new array. The @p normals array is expected to have the same size
as @p positions. Note that even with the output array this function isn't fully as @p positions. Note that even with the output array this function isn't fully
allocation-free --- it still allocates two additional internal arrays for allocation-free --- it still allocates three additional internal arrays for
adjacent face calculation. adjacent face calculation.
@see @ref generateFlatNormalsInto() @see @ref generateFlatNormalsInto()
*/ */

Loading…
Cancel
Save