# Source code for pytorch3d.loss.mesh_laplacian_smoothing

```# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# LICENSE file in the root directory of this source tree.

import torch
from pytorch3d.ops import cot_laplacian

[docs]def mesh_laplacian_smoothing(meshes, method: str = "uniform"):
r"""
Computes the laplacian smoothing objective for a batch of meshes.
This function supports three variants of Laplacian smoothing,
namely with uniform weights("uniform"), with cotangent weights ("cot"),
and cotangent curvature ("cotcurv").For more details read [1, 2].

Args:
meshes: Meshes object with a batch of meshes.
method: str specifying the method for the laplacian.
Returns:
loss: Average laplacian smoothing loss across the batch.
Returns 0 if meshes contains no meshes or all empty meshes.

Consider a mesh M = (V, F), with verts of shape Nx3 and faces of shape Mx3.
The Laplacian matrix L is a NxN tensor such that LV gives a tensor of vectors:
for a uniform Laplacian, LuV[i] points to the centroid of its neighboring
vertices, a cotangent Laplacian LcV[i] is known to be an approximation of
the surface normal, while the curvature variant LckV[i] scales the normals
by the discrete mean curvature. For vertex i, assume S[i] is the set of
neighboring vertices to i, a_ij and b_ij are the "outside" angles in the
two triangles connecting vertex v_i and its neighboring vertex v_j
for j in S[i], as seen in the diagram below.

.. code-block:: python

a_ij
/\
/  \
/    \
/      \
v_i /________\ v_j
\        /
\      /
\    /
\  /
\/
b_ij

The definition of the Laplacian is LV[i] = sum_j w_ij (v_j - v_i)
For the uniform variant,    w_ij = 1 / |S[i]|
For the cotangent variant,
w_ij = (cot a_ij + cot b_ij) / (sum_k cot a_ik + cot b_ik)
For the cotangent curvature, w_ij = (cot a_ij + cot b_ij) / (4 A[i])
where A[i] is the sum of the areas of all triangles containing vertex v_i.

There is a nice trigonometry identity to compute cotangents. Consider a triangle
with side lengths A, B, C and angles a, b, c.

.. code-block:: python

c
/|\
/ | \
/  |  \
B /  H|   \ A
/    |    \
/     |     \
/a_____|_____b\
C

Then cot a = (B^2 + C^2 - A^2) / 4 * area
We know that area = CH/2, and by the law of cosines we have

A^2 = B^2 + C^2 - 2BC cos a => B^2 + C^2 - A^2 = 2BC cos a

Putting these together, we get:

B^2 + C^2 - A^2     2BC cos a
_______________  =  _________ = (B/H) cos a = cos a / sin a = cot a
4 * area            2CH

 Desbrun et al, "Implicit fairing of irregular meshes using diffusion
and curvature flow", SIGGRAPH 1999.

 Nealan et al, "Laplacian Mesh Optimization", Graphite 2006.
"""

if meshes.isempty():
)

N = len(meshes)
verts_packed = meshes.verts_packed()  # (sum(V_n), 3)
faces_packed = meshes.faces_packed()  # (sum(F_n), 3)
num_verts_per_mesh = meshes.num_verts_per_mesh()  # (N,)
verts_packed_idx = meshes.verts_packed_to_mesh_idx()  # (sum(V_n),)
weights = num_verts_per_mesh.gather(0, verts_packed_idx)  # (sum(V_n),)
weights = 1.0 / weights.float()

# We don't want to backprop through the computation of the Laplacian;
# just treat it as a magic constant matrix that is used to transform
# verts into normals
if method == "uniform":
L = meshes.laplacian_packed()
elif method in ["cot", "cotcurv"]:
L, inv_areas = cot_laplacian(verts_packed, faces_packed)
if method == "cot":
norm_w = torch.sparse.sum(L, dim=1).to_dense().view(-1, 1)
idx = norm_w > 0
# pyre-fixme: `/` is not supported for operand types `float` and
#  `Tensor`.
norm_w[idx] = 1.0 / norm_w[idx]
else:
L_sum = torch.sparse.sum(L, dim=1).to_dense().view(-1, 1)
norm_w = 0.25 * inv_areas
else:
raise ValueError("Method should be one of {uniform, cot, cotcurv}")

if method == "uniform":
loss = L.mm(verts_packed)
elif method == "cot":
# pyre-fixme: `norm_w` is undefined, or not always defined.
loss = L.mm(verts_packed) * norm_w - verts_packed
elif method == "cotcurv":
# pyre-fixme: `norm_w` may not be initialized here.
loss = (L.mm(verts_packed) - L_sum * verts_packed) * norm_w
loss = loss.norm(dim=1)

loss = loss * weights
return loss.sum() / N
```