Skip to content

Commit

Permalink
feat(lib): add box mesh constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
jeertmans committed Oct 22, 2024
1 parent 3ad2a05 commit f141a6e
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
73 changes: 72 additions & 1 deletion differt/src/differt/geometry/triangle_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,12 @@ def plane(
rotate: Float[ArrayLike, " "] | None = None,
) -> Self:
"""
Create an plane mesh, made of two triangles.
Create a plane mesh, made of two triangles.
Note:
The mesh statisfies the guarantees
expected when setting
:attr:`assume_quads` to :data:`True`.
Args:
vertex_a: The center of the plane.
Expand Down Expand Up @@ -470,6 +475,72 @@ def plane(
triangles = jnp.array([[0, 1, 2], [0, 2, 3]], dtype=int)
return cls(vertices=vertices, triangles=triangles)

@classmethod
@jaxtyped(
typechecker=None
) # typing.Self is (currently) not compatible with jaxtyping and beartype
def box(
cls,
length: Float[ArrayLike, " "] = 1.0,
width: Float[ArrayLike, " "] = 1.0,
height: Float[ArrayLike, " "] = 1.0,
*,
with_top: bool = False,
) -> Self:
"""
Create a box mesh, with an optional opening on the top.
Note:
The mesh statisfies the guarantees
expected when setting
:attr:`assume_quads` to :data:`True`.
Args:
length: The length of the box (along x-axis).
width: The width of the box (along y-axis).
height: The height of the box (along z-axis).
with_top: Whether the top of part
of the box is included or not.
Returns:
A new box mesh.
"""
dx = jnp.array([length * 0.5, 0.0, 0.0])
dy = jnp.array([0.0, width * 0.5, 0.0])
dz = jnp.array([0.0, 0.0, height * 0.5])

vertices = jnp.stack((
+dx + dy + dz,
+dx + dy - dz,
-dx + dy - dz,
-dx + dy + dz,
-dx - dy - dz,
-dx - dy + dz,
+dx - dy - dz,
+dx - dy + dz,
))
triangles = jnp.array(
[
[0, 1, 2],
[0, 2, 3],
[3, 2, 4],
[3, 4, 5],
[5, 4, 6],
[5, 6, 7],
[7, 6, 1],
[7, 1, 0],
[1, 4, 2], # Bottom
[1, 6, 4],
],
dtype=int,
)
if with_top:
triangles = jnp.concatenate(
(triangles, jnp.asarray([[0, 3, 5], [0, 5, 7]])),
axis=0,
)
return cls(vertices=vertices, triangles=triangles)

@property
def is_empty(self) -> bool:
"""Whether this scene has no triangle."""
Expand Down
21 changes: 21 additions & 0 deletions differt/tests/geometry/test_triangle_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,27 @@ def test_plane(self, key: PRNGKeyArray) -> None:
):
_ = TriangleMesh.plane(center) # type: ignore[reportCallIssue]

@pytest.mark.parametrize(
("length", "width", "height"),
[(10.0, 5.0, 4.0)],
)
@pytest.mark.parametrize("with_top", [False, True])
def test_box(
self, length: float, width: float, height: float, with_top: bool
) -> None:
mesh = TriangleMesh.box(length, width, height, with_top=with_top)

if with_top:
assert mesh.num_triangles == 12
else:
assert mesh.num_triangles == 10

dx = length * 0.5
dy = width * 0.5
dz = height * 0.5

assert mesh.bounding_box.tolist() == [[-dx, -dy, -dz], [+dx, +dy, +dz]]

def test_rotate(self, two_buildings_mesh: TriangleMesh, key: PRNGKeyArray) -> None:
angle = jax.random.uniform(key, (), minval=0, maxval=2 * jnp.pi)

Expand Down

0 comments on commit f141a6e

Please sign in to comment.