hub/venv/lib/python3.7/site-packages/trimesh/sample.py

204 lines
5.6 KiB
Python
Raw Normal View History

"""
sample.py
------------
Randomly sample surface and volume of meshes.
"""
import numpy as np
from . import util
from . import transformations
def sample_surface(mesh, count):
"""
Sample the surface of a mesh, returning the specified
number of points
For individual triangle sampling uses this method:
http://mathworld.wolfram.com/TrianglePointPicking.html
Parameters
---------
mesh : trimesh.Trimesh
Geometry to sample the surface of
count : int
Number of points to return
Returns
---------
samples : (count, 3) float
Points in space on the surface of mesh
face_index : (count,) int
Indices of faces for each sampled point
"""
# len(mesh.faces) float, array of the areas
# of each face of the mesh
area = mesh.area_faces
# total area (float)
area_sum = np.sum(area)
# cumulative area (len(mesh.faces))
area_cum = np.cumsum(area)
face_pick = np.random.random(count) * area_sum
face_index = np.searchsorted(area_cum, face_pick)
# pull triangles into the form of an origin + 2 vectors
tri_origins = mesh.triangles[:, 0]
tri_vectors = mesh.triangles[:, 1:].copy()
tri_vectors -= np.tile(tri_origins, (1, 2)).reshape((-1, 2, 3))
# pull the vectors for the faces we are going to sample from
tri_origins = tri_origins[face_index]
tri_vectors = tri_vectors[face_index]
# randomly generate two 0-1 scalar components to multiply edge vectors by
random_lengths = np.random.random((len(tri_vectors), 2, 1))
# points will be distributed on a quadrilateral if we use 2 0-1 samples
# if the two scalar components sum less than 1.0 the point will be
# inside the triangle, so we find vectors longer than 1.0 and
# transform them to be inside the triangle
random_test = random_lengths.sum(axis=1).reshape(-1) > 1.0
random_lengths[random_test] -= 1.0
random_lengths = np.abs(random_lengths)
# multiply triangle edge vectors by the random lengths and sum
sample_vector = (tri_vectors * random_lengths).sum(axis=1)
# finally, offset by the origin to generate
# (n,3) points in space on the triangle
samples = sample_vector + tri_origins
return samples, face_index
def volume_mesh(mesh, count):
"""
Use rejection sampling to produce points randomly
distributed in the volume of a mesh.
Parameters
---------
mesh : trimesh.Trimesh
Geometry to sample
count : int
Number of points to return
Returns
---------
samples : (n, 3) float
Points in the volume of the mesh where n <= count
"""
points = (np.random.random((count, 3)) * mesh.extents) + mesh.bounds[0]
contained = mesh.contains(points)
samples = points[contained][:count]
return samples
def volume_rectangular(extents,
count,
transform=None):
"""
Return random samples inside a rectangular volume,
useful for sampling inside oriented bounding boxes.
Parameters
----------
extents : (3,) float
Side lengths of rectangular solid
count : int
Number of points to return
transform : (4, 4) float
Homogeneous transformation matrix
Returns
---------
samples : (count, 3) float
Points in requested volume
"""
samples = np.random.random((count, 3)) - .5
samples *= extents
if transform is not None:
samples = transformations.transform_points(samples,
transform)
return samples
def sample_surface_even(mesh, count, radius=None):
"""
Sample the surface of a mesh, returning samples which are
VERY approximately evenly spaced. This is accomplished by
sampling and then rejecting pairs that are too close together.
Note that since it is using rejection sampling it may return
fewer points than requested (i.e. n < count). If this is the
case a log.warning will be emitted.
Parameters
---------
mesh : trimesh.Trimesh
Geometry to sample the surface of
count : int
Number of points to return
radius : None or float
Removes samples below this radius
Returns
---------
samples : (n, 3) float
Points in space on the surface of mesh
face_index : (n,) int
Indices of faces for each sampled point
"""
from .points import remove_close
# guess radius from area
if radius is None:
radius = np.sqrt(mesh.area / (3 * count))
# get points on the surface
points, index = sample_surface(mesh, count * 3)
# remove the points closer than radius
points, mask = remove_close(points, radius)
# we got all the samples we expect
if len(points) >= count:
return points[:count], index[mask][:count]
# warn if we didn't get all the samples we expect
util.log.warning('only got {}/{} samples!'.format(
len(points), count))
return points, index[mask]
def sample_surface_sphere(count):
"""
Correctly pick random points on the surface of a unit sphere
Uses this method:
http://mathworld.wolfram.com/SpherePointPicking.html
Parameters
----------
count : int
Number of points to return
Returns
----------
points : (count, 3) float
Random points on the surface of a unit sphere
"""
# get random values 0.0-1.0
u, v = np.random.random((2, count))
# convert to two angles
theta = np.pi * 2 * u
phi = np.arccos((2 * v) - 1)
# convert spherical coordinates to cartesian
points = util.spherical_to_vector(
np.column_stack((theta, phi)))
return points