Skip to content

Commit

Permalink
fix transforms of precomputed Shape data; cache MultiPoint of vertices
Browse files Browse the repository at this point in the history
  • Loading branch information
dfremont committed Dec 11, 2024
1 parent 93cc3ab commit 568a6e3
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 3 deletions.
33 changes: 31 additions & 2 deletions src/scenic/core/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,10 @@ def AABB(self):

@cached_property
def _transform(self):
"""Transform from input mesh to final mesh.
:meta private:
"""
if self.dimensions is not None:
scale = numpy.array(self.dimensions) / self._mesh.extents
else:
Expand All @@ -1024,9 +1028,33 @@ def _transform(self):
transform = compose_matrix(scale=scale, angles=angles, translate=self.position)
return transform

@cached_property
def _shapeTransform(self):
"""Transform from Shape mesh (scaled to unit dimensions) to final mesh.
:meta private:
"""
if self.dimensions is not None:
scale = numpy.array(self.dimensions)
else:
scale = self._mesh.extents
if self.rotation is not None:
angles = self.rotation._trimeshEulerAngles()
else:
angles = None

Check warning on line 1044 in src/scenic/core/regions.py

View check run for this annotation

Codecov / codecov/patch

src/scenic/core/regions.py#L1044

Added line #L1044 was not covered by tests
transform = compose_matrix(scale=scale, angles=angles, translate=self.position)
return transform

@cached_property
def _boundingPolygonHull(self):
assert not isLazy(self)
if self._shape:
raw = self._shape._multipoint
tr = self._shapeTransform
matrix = numpy.concatenate((tr[0:3, 0:3].flatten(), tr[0:3, 3]))
transformed = shapely.affinity.affine_transform(raw, matrix)
return transformed.convex_hull

return shapely.multipoints(self.mesh.vertices).convex_hull

@cached_property
Expand Down Expand Up @@ -1848,7 +1876,8 @@ def _circumradius(self):
if self._scaledShape:
return self._scaledShape._circumradius
if self._shape:
scale = max(self.dimensions) if self.dimensions else 1
dims = self.dimensions or self._mesh.extents
scale = max(dims)
return scale * self._shape._circumradius

return numpy.max(numpy.linalg.norm(self.mesh.vertices, axis=1))
Expand All @@ -1863,7 +1892,7 @@ def _interiorPoint(self):
if self._shape:
raw = self._shape._interiorPoint
homog = numpy.append(raw, [1])
return numpy.dot(self._transform, homog)[:3]
return numpy.dot(self._shapeTransform, homog)[:3]

return findMeshInteriorPoint(self.mesh, num_samples=self.num_samples)

Expand Down
5 changes: 5 additions & 0 deletions src/scenic/core/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from abc import ABC, abstractmethod

import numpy
import shapely
import trimesh
from trimesh.transformations import (
concatenate_matrices,
Expand Down Expand Up @@ -72,6 +73,10 @@ def _circumradius(self):
def _interiorPoint(self):
return findMeshInteriorPoint(self.mesh)

@cached_property
def _multipoint(self):
return shapely.multipoints(self.mesh.vertices)


###################################################################################################
# 3D Shape Classes
Expand Down
52 changes: 51 additions & 1 deletion tests/core/test_regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
from tests.utils import deprecationTest, sampleSceneFrom


def assertPolygonsEqual(p1, p2, prec=1e-6):
assert p1.difference(p2).area == pytest.approx(0, abs=prec)
assert p2.difference(p1).area == pytest.approx(0, abs=prec)


def sample_ignoring_rejections(region, num_samples):
samples = []
for _ in range(num_samples):
Expand Down Expand Up @@ -340,7 +345,23 @@ def test_mesh_intersects():
assert not r1.getSurfaceRegion().intersects(r2.getSurfaceRegion())


def test_mesh_circumradius(getAssetPath):
def test_mesh_boundingPolygon(getAssetPath, pytestconfig):
r = BoxRegion(dimensions=(8, 6, 2)).difference(BoxRegion(dimensions=(2, 2, 3)))
bp = r.boundingPolygon
poly = shapely.geometry.Polygon(
[(-4, 3), (4, 3), (4, -3), (-4, -3)], [[(-1, 1), (1, 1), (1, -1), (-1, -1)]]
)
assertPolygonsEqual(bp.polygons, poly)

shape = MeshShape(BoxRegion(dimensions=(1, 2, 3)).mesh)
o = Orientation.fromEuler(0, 0, math.pi / 4)
r = MeshVolumeRegion(shape.mesh, dimensions=(2, 4, 2), rotation=o, _shape=shape)
bp = r.boundingPolygon
sr2 = math.sqrt(2)
poly = shapely.geometry.Polygon([(-sr2, 2), (sr2, 2), (sr2, -2), (-sr2, -2)])
assertPolygonsEqual(bp.polygons, poly)

samples = 50 if pytestconfig.getoption("--fast") else 200
r1 = BoxRegion(dimensions=(1, 2, 3), position=(4, 5, 6))
bo = Orientation.fromEuler(math.pi / 4, math.pi / 4, math.pi / 4)
r2 = MeshVolumeRegion(r1.mesh, position=(15, 20, 5), rotation=bo, _scaledShape=r1)
Expand All @@ -349,6 +370,29 @@ def test_mesh_circumradius(getAssetPath):
shape = MeshShape.fromFile(planePath)
r4 = MeshVolumeRegion(shape.mesh, dimensions=(0.5, 2, 1.5), rotation=bo, _shape=shape)
for reg in (r1, r2, r3, r4):
bp = reg.boundingPolygon
pts = trimesh.sample.volume_mesh(reg.mesh, samples)
assert all(bp.containsPoint(pt) for pt in pts)


def test_mesh_circumradius(getAssetPath):
r1 = BoxRegion(dimensions=(1, 2, 3), position=(4, 5, 6))

bo = Orientation.fromEuler(math.pi / 4, math.pi / 4, math.pi / 4)
r2 = MeshVolumeRegion(r1.mesh, position=(15, 20, 5), rotation=bo, _scaledShape=r1)

planePath = getAssetPath("meshes/classic_plane.obj.bz2")
r3 = MeshVolumeRegion.fromFile(planePath, dimensions=(20, 20, 10))

shape = MeshShape.fromFile(planePath)
r4 = MeshVolumeRegion(shape.mesh, dimensions=(0.5, 2, 1.5), rotation=bo, _shape=shape)

r = BoxRegion(dimensions=(1, 2, 3)).difference(BoxRegion(dimensions=(0.5, 1, 1)))
shape = MeshShape(r.mesh)
scaled = MeshVolumeRegion(shape.mesh, dimensions=(6, 5, 4)).mesh
r5 = MeshVolumeRegion(scaled, position=(-10, -5, 30), rotation=bo, _shape=shape)

for reg in (r1, r2, r3, r4, r5):
pos = reg.position
d = 2.01 * reg._circumradius
assert SpheroidRegion(dimensions=(d, d, d), position=pos).containsRegion(reg)
Expand All @@ -374,6 +418,12 @@ def test_mesh_interiorPoint():
r3 = MeshVolumeRegion(shape.mesh, position=(-10, -5, 30), rotation=bo, _shape=shape)
regions.append(r3)

r = BoxRegion(dimensions=(1, 2, 3)).difference(BoxRegion(dimensions=(0.5, 1, 1)))
shape = MeshShape(r.mesh)
scaled = MeshVolumeRegion(shape.mesh, dimensions=(0.1, 0.1, 0.1)).mesh
r4 = MeshVolumeRegion(scaled, position=(-10, -5, 30), rotation=bo, _shape=shape)
regions.append(r4)

for reg in regions:
cp = reg._interiorPoint
# N.B. _containsPointExact can fail with embreex installed!
Expand Down

0 comments on commit 568a6e3

Please sign in to comment.