city_retrofit/venv/lib/python3.7/site-packages/trimesh/smoothing.py

237 lines
7.3 KiB
Python
Raw Normal View History

import numpy as np
try:
from scipy.sparse.linalg import spsolve
from scipy.sparse import coo_matrix, eye
except ImportError:
pass
from . import triangles
def filter_laplacian(mesh,
lamb=0.5,
iterations=10,
implicit_time_integration=False,
volume_constraint=True,
laplacian_operator=None):
"""
Smooth a mesh in-place using laplacian smoothing.
Articles
1 - "Improved Laplacian Smoothing of Noisy Surface Meshes"
J. Vollmer, R. Mencl, and H. Muller
2 - "Implicit Fairing of Irregular Meshes using Diffusion
and Curvature Flow". M. Desbrun, M. Meyer,
P. Schroder, A.H.B. Caltech
Parameters
------------
mesh : trimesh.Trimesh
Mesh to be smoothed in place
lamb : float
Diffusion speed constant
If 0.0, no diffusion
If > 0.0, diffusion occurs
implicit_time_integration: boolean
if False: explicit time integration
-lamb <= 1.0 - Stability Limit (Article 1)
if True: implicit time integration
-lamb no limit (Article 2)
iterations : int
Number of passes to run filter
laplacian_operator : None or scipy.sparse.coo.coo_matrix
Sparse matrix laplacian operator
Will be autogenerated if None
"""
# if the laplacian operator was not passed create it here
if laplacian_operator is None:
laplacian_operator = laplacian_calculation(mesh)
# save initial volume
if volume_constraint:
vol_ini = mesh.volume
# get mesh vertices and faces as vanilla numpy array
vertices = mesh.vertices.copy().view(np.ndarray)
faces = mesh.faces.copy().view(np.ndarray)
# Set matrix for linear system of equations
if implicit_time_integration:
dlap = laplacian_operator.shape[0]
AA = eye(dlap) + lamb * (eye(dlap) - laplacian_operator)
# Number of passes
for _index in range(iterations):
# Classic Explicit Time Integration - Article 1
if not implicit_time_integration:
dot = laplacian_operator.dot(vertices) - vertices
vertices += lamb * dot
# Implicit Time Integration - Article 2
else:
vertices = spsolve(AA, vertices)
# volume constraint
if volume_constraint:
# find the volume with new vertex positions
vol_new = triangles.mass_properties(
vertices[faces], skip_inertia=True)["volume"]
# scale by volume ratio
vertices *= ((vol_ini / vol_new) ** (1.0 / 3.0))
# assign modified vertices back to mesh
mesh.vertices = vertices
return mesh
def filter_humphrey(mesh,
alpha=0.1,
beta=0.5,
iterations=10,
laplacian_operator=None):
"""
Smooth a mesh in-place using laplacian smoothing
and Humphrey filtering.
Articles
"Improved Laplacian Smoothing of Noisy Surface Meshes"
J. Vollmer, R. Mencl, and H. Muller
Parameters
------------
mesh : trimesh.Trimesh
Mesh to be smoothed in place
alpha : float
Controls shrinkage, range is 0.0 - 1.0
If 0.0, not considered
If 1.0, no smoothing
beta : float
Controls how aggressive smoothing is
If 0.0, no smoothing
If 1.0, full aggressiveness
iterations : int
Number of passes to run filter
laplacian_operator : None or scipy.sparse.coo.coo_matrix
Sparse matrix laplacian operator
Will be autogenerated if None
"""
# if the laplacian operator was not passed create it here
if laplacian_operator is None:
laplacian_operator = laplacian_calculation(mesh)
# get mesh vertices as vanilla numpy array
vertices = mesh.vertices.copy().view(np.ndarray)
# save original unmodified vertices
original = vertices.copy()
# run through iterations of filter
for _index in range(iterations):
vert_q = vertices.copy()
vertices = laplacian_operator.dot(vertices)
vert_b = vertices - (alpha * original + (1.0 - alpha) * vert_q)
vertices -= (beta * vert_b + (1.0 - beta) *
laplacian_operator.dot(vert_b))
# assign modified vertices back to mesh
mesh.vertices = vertices
return mesh
def filter_taubin(mesh,
lamb=0.5,
nu=0.5,
iterations=10,
laplacian_operator=None):
"""
Smooth a mesh in-place using laplacian smoothing
and taubin filtering.
Articles
"Improved Laplacian Smoothing of Noisy Surface Meshes"
J. Vollmer, R. Mencl, and H. Muller
Parameters
------------
mesh : trimesh.Trimesh
Mesh to be smoothed in place.
lamb : float
Controls shrinkage, range is 0.0 - 1.0
nu : float
Controls dilation, range is 0.0 - 1.0
Nu shall be between 0.0 < 1.0/lambda - 1.0/nu < 0.1
iterations : int
Number of passes to run the filter
laplacian_operator : None or scipy.sparse.coo.coo_matrix
Sparse matrix laplacian operator
Will be autogenerated if None
"""
# if the laplacian operator was not passed create it here
if laplacian_operator is None:
laplacian_operator = laplacian_calculation(mesh)
# get mesh vertices as vanilla numpy array
vertices = mesh.vertices.copy().view(np.ndarray)
# run through multiple passes of the filter
for index in range(iterations):
# do a sparse dot product on the vertices
dot = laplacian_operator.dot(vertices) - vertices
# alternate shrinkage and dilation
if index % 2 == 0:
vertices += lamb * dot
else:
vertices -= nu * dot
# assign updated vertices back to mesh
mesh.vertices = vertices
return mesh
def laplacian_calculation(mesh, equal_weight=True):
"""
Calculate a sparse matrix for laplacian operations.
Parameters
-------------
mesh : trimesh.Trimesh
Input geometry
equal_weight : bool
If True, all neighbors will be considered equally
If False, all neighbors will be weighted by inverse distance
Returns
----------
laplacian : scipy.sparse.coo.coo_matrix
Laplacian operator
"""
# get the vertex neighbors from the cache
neighbors = mesh.vertex_neighbors
# avoid hitting crc checks in loops
vertices = mesh.vertices.view(np.ndarray)
# stack neighbors to 1D arrays
col = np.concatenate(neighbors)
row = np.concatenate([[i] * len(n)
for i, n in enumerate(neighbors)])
if equal_weight:
# equal weights for each neighbor
data = np.concatenate([[1.0 / len(n)] * len(n)
for n in neighbors])
else:
# umbrella weights, distance-weighted
# use dot product of ones to replace array.sum(axis=1)
ones = np.ones(3)
# the distance from verticesex to neighbors
norms = [1.0 / np.sqrt(np.dot((vertices[i] - vertices[n]) ** 2, ones))
for i, n in enumerate(neighbors)]
# normalize group and stack into single array
data = np.concatenate([i / i.sum() for i in norms])
# create the sparse matrix
matrix = coo_matrix((data, (row, col)),
shape=[len(vertices)] * 2)
return matrix