Skip to content

Mesh simplification, convex decomposition#407

Merged
eric-heiden merged 8 commits into
newton-physics:mainfrom
eric-heiden:simplify-meshes
Jul 15, 2025
Merged

Mesh simplification, convex decomposition#407
eric-heiden merged 8 commits into
newton-physics:mainfrom
eric-heiden:simplify-meshes

Conversation

@eric-heiden

@eric-heiden eric-heiden commented Jul 15, 2025

Copy link
Copy Markdown
Member

Description

  • Add ModelBuilder.simplify_meshes() which remeshes mesh shapes or computes convex decompositions using CoACD
  • Cache computation of the hash of meshes, this reduces the time of finalizing the meshes from 1000 G1 humanoids from >200 seconds to 200ms
  • Utilize Mesh.maxhullvert attribute when generating the convex hull of meshes

Work in progress on supporting convex decomposition and other mesh simplification algorithms in ModelBuilder.

OriginalConvex HullCOACD
image image image

The following example shows how convex decomposition via CoACD allows the sphere to fall into the torus which is otherwise not possible since MuJoCo uses the convex hull of a mesh for collision handling:
image

import numpy as np
import warp as wp

import newton
import newton.examples
import newton.utils

wp.config.enable_backward = False


def create_torus_mesh(major_radius=1.0, minor_radius=0.3, major_segments=32, minor_segments=16):
    """
    Generate a torus mesh for Newton physics simulation.

    Args:
        major_radius (float): The radius from the center of the torus to the center of the tube
        minor_radius (float): The radius of the tube cross-section
        major_segments (int): Number of segments around the major circumference
        minor_segments (int): Number of segments around the minor circumference

    Returns:
        newton.geometry.Mesh: A Newton Mesh object representing the torus
    """
    vertices = []
    indices = []

    # Generate vertices
    for i in range(major_segments + 1):
        major_angle = 2.0 * np.pi * i / major_segments
        cos_major = np.cos(major_angle)
        sin_major = np.sin(major_angle)

        for j in range(minor_segments + 1):
            minor_angle = 2.0 * np.pi * j / minor_segments
            cos_minor = np.cos(minor_angle)
            sin_minor = np.sin(minor_angle)

            # Calculate vertex position
            x = (major_radius + minor_radius * cos_minor) * cos_major
            y = (major_radius + minor_radius * cos_minor) * sin_major
            z = minor_radius * sin_minor

            vertices.append([x, y, z])

    # Generate triangle indices
    for i in range(major_segments):
        for j in range(minor_segments):
            # Calculate vertex indices for current quad
            v0 = i * (minor_segments + 1) + j
            v1 = i * (minor_segments + 1) + (j + 1)
            v2 = (i + 1) * (minor_segments + 1) + (j + 1)
            v3 = (i + 1) * (minor_segments + 1) + j

            # Add two triangles to form the quad (flipped winding)
            # First triangle
            indices.extend([v0, v2, v1])
            # Second triangle
            indices.extend([v0, v3, v2])

    # Create Newton Mesh object
    mesh = newton.geometry.Mesh(vertices, indices)
    return mesh


class Example:
    def __init__(self, stage_path="example_torus.usda", num_envs=1):
        self.num_envs = num_envs

        # set numpy random seed
        self.seed = 123
        self.rng = np.random.default_rng(self.seed)

        # Create torus mesh
        torus_mesh = create_torus_mesh(major_radius=1.0, minor_radius=0.3, major_segments=32, minor_segments=16)

        builder = newton.ModelBuilder()
        builder.default_shape_cfg.ke = 1e4
        builder.default_shape_cfg.kd = 500.0

        # Add a torus body
        b = builder.add_body(xform=wp.transform(wp.vec3(0.0, 0.0, 2.0), wp.quat_identity()))
        builder.add_joint_free(b)
        builder.add_shape_mesh(
            body=b,
            mesh=torus_mesh,
            scale=wp.vec3(1.0, 1.0, 1.0),
        )

        # Add a sphere to fall on the torus
        b = builder.add_body(xform=wp.transform(wp.vec3(0.0, 0.0, 4.0), wp.quat_identity()))
        builder.add_shape_sphere(
            body=b,
            radius=0.2,
        )
        builder.add_joint_free(b)

        # Add a ground plane
        builder.add_ground_plane()

        builder.simplify_meshes(method="coacd", threshold=0.1)
        # builder.simplify_meshes()

        self.sim_time = 0.0
        fps = 60
        self.frame_dt = 1.0 / fps

        self.sim_substeps = 50
        self.sim_dt = self.frame_dt / self.sim_substeps

        # finalize model
        self.model = builder.finalize()

        self.control = self.model.control()

        self.solver = newton.solvers.MuJoCoSolver(
            self.model,
            use_mujoco=True,
            solver="newton",
            integrator="euler",
            iterations=20,
            ls_iterations=15,
            save_to_mjcf="converted_model.xml",
        )

        self.renderer = None
        if stage_path:
            self.renderer = newton.utils.SimRendererOpenGL(
                path=stage_path, model=self.model, scaling=1.0, show_joints=True
            )

        self.state_0, self.state_1 = self.model.state(), self.model.state()

        self.use_cuda_graph = not getattr(self.solver, "use_mujoco", False) and wp.get_device().is_cuda

        if self.use_cuda_graph:
            with wp.ScopedCapture() as capture:
                self.simulate()
            self.graph = capture.graph

    def simulate(self):
        if not isinstance(self.solver, newton.solvers.MuJoCoSolver):
            contacts = self.model.collide(self.state_0, rigid_contact_margin=100.0)
        else:
            contacts = None
        for _ in range(self.sim_substeps):
            self.state_0.clear_forces()
            self.solver.step(self.state_0, self.state_1, self.control, contacts, self.sim_dt)
            self.state_0, self.state_1 = self.state_1, self.state_0

    def step(self):
        with wp.ScopedTimer("step", active=False):
            if self.use_cuda_graph:
                wp.capture_launch(self.graph)
            else:
                self.simulate()
        self.sim_time += self.frame_dt

    def render(self):
        if self.renderer is None:
            return

        with wp.ScopedTimer("render", active=False):
            self.renderer.begin_frame(self.sim_time)
            self.renderer.render(self.state_0)
            self.renderer.end_frame()


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("--device", type=str, default=None, help="Override the default Warp device.")
    parser.add_argument(
        "--stage_path",
        type=lambda x: None if x == "None" else str(x),
        default="example_torus.usda",
        help="Path to the output USD file.",
    )
    parser.add_argument("--num_frames", type=int, default=1200, help="Total number of frames.")
    parser.add_argument("--num_envs", type=int, default=1, help="Total number of simulated environments.")

    args = parser.parse_known_args()[0]

    with wp.ScopedDevice(args.device):
        example = Example(stage_path=args.stage_path, num_envs=args.num_envs)

        for _ in range(args.num_frames):
            example.step()
            example.render()

        if example.renderer:
            example.renderer.save()

Newton Migration Guide

Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this MR.

  • The migration guide in docs/migration.rst is up-to date

Before your PR is "Ready for review"

  • All commits are signed-off to indicate that your contribution adheres to the Developer Certificate of Origin requirements
  • Necessary tests have been added and new examples are tested (see newton/tests/test_examples.py)
  • Documentation is up-to-date
  • Code passes formatting and linting checks with pre-commit run -a

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added mesh simplification method supporting "coacd" and remeshing options for model meshes.
    • Introduced caching for mesh remeshing and convex decomposition results to improve performance.
  • Refactor

    • Simplified the mesh class constructor and removed the ability to set a pre-computed convex hull directly.
    • Updated remeshing utilities to use explicit method typing and improved parameter handling.
    • Changed mesh handling in the MuJoCo solver to always use the original mesh, removing logic for pre-computed convex hulls.

Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2025

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

The changes simplify the handling of convex hulls in the Mesh class by removing the ability to set or pass a precomputed convex hull. The remeshing utilities are updated with stricter typing and improved parameter handling. Code in the MuJoCo solver is adjusted to always use the original mesh data, eliminating conditional logic for convex hulls. Additionally, a new mesh simplification method is added to the model builder supporting convex decomposition and remeshing.

Changes

File(s) Change Summary
newton/geometry/types.py Removed convex_hull parameter from Mesh constructor, deleted set_convex_hull method, updated compute_convex_hull signature, added _cached_hash, added vertices and indices properties with setters/getters, updated __hash__ to cache hash.
newton/geometry/utils.py Added RemeshingMethod type alias, updated signatures of remesh, remesh_mesh, and remesh_convex_hull with stricter typing and parameters, improved remeshing logic including maxhullvert handling, replaced Vec3 import source.
newton/solvers/mujoco/solver_mujoco.py Removed logic using precomputed convex hulls for mesh shapes; always uses original mesh vertices and indices in MuJoCo geometry creation.
newton/sim/builder.py Added simplify_meshes method to ModelBuilder supporting "coacd" convex decomposition and other remeshing methods, caching results by mesh hash; updated ShapeConfig.copy return type; minor flag handling and import adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Mesh
    participant Remeshing
    participant ModelBuilder
    participant MuJoCoSolver

    User->>Mesh: Create Mesh(vertices, indices, ...)
    Mesh-->>User: Mesh instance (no convex_hull argument)

    User->>Mesh: compute_convex_hull(replace=False)
    Mesh->>Remeshing: remesh_convex_hull(maxhullvert)
    Remeshing-->>Mesh: Convex hull mesh

    User->>Remeshing: remesh_mesh(mesh, method, ...)
    Remeshing->>Remeshing: remesh(vertices, faces, method, ...)
    Remeshing-->>Remeshing: (verts, faces)
    Remeshing-->>User: New Mesh

    User->>ModelBuilder: simplify_meshes(method, shape_indices, ...)
    ModelBuilder->>Remeshing: remesh_mesh or coacd decomposition
    Remeshing-->>ModelBuilder: Simplified meshes
    ModelBuilder-->>User: Updated model with simplified meshes

    User->>MuJoCoSolver: convert_to_mjc(model)
    MuJoCoSolver->>Mesh: Get vertices, indices
    MuJoCoSolver-->>User: MuJoCo model with mesh geoms (no convex hull fallback)
Loading

Possibly related issues

Suggested reviewers

  • AntoineRichard

📜 Recent review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e525c4 and cead5df.

📒 Files selected for processing (1)
  • newton/solvers/mujoco/solver_mujoco.py (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
newton/geometry/types.py (1)

156-156: Remove unused parameter.

The replace parameter is defined but never used in the method body, which can be confusing for callers.

-    def compute_convex_hull(self, replace: bool = False) -> "Mesh":
+    def compute_convex_hull(self) -> "Mesh":
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e8ea55 and 7ef570d.

📒 Files selected for processing (3)
  • newton/geometry/types.py (3 hunks)
  • newton/geometry/utils.py (3 hunks)
  • newton/solvers/mujoco/solver_mujoco.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
newton/solvers/mujoco/solver_mujoco.py (1)
Learnt from: Kenny-Vilella
PR: newton-physics/newton#398
File: newton/examples/example_mujoco.py:352-352
Timestamp: 2025-07-14T03:57:29.659Z
Learning: The use_mujoco option in newton/examples/example_mujoco.py is currently unsupported and causes crashes. The code automatically disables this option with a warning message when users attempt to enable it. This is intentionally kept as a placeholder for future implementation.
🧬 Code Graph Analysis (2)
newton/geometry/types.py (1)
newton/geometry/utils.py (1)
  • remesh_convex_hull (308-336)
newton/geometry/utils.py (1)
newton/geometry/types.py (1)
  • Mesh (67-178)
🔇 Additional comments (8)
newton/solvers/mujoco/solver_mujoco.py (1)

1478-1482: LGTM: Simplified mesh handling by removing convex hull fallback logic.

The changes correctly remove the conditional logic that previously checked for pre-computed convex hull meshes, now consistently using the original mesh data directly. This aligns with the overall design goal of removing cached convex hull handling.

newton/geometry/types.py (1)

128-128: Good addition for hash caching optimization.

The new _cached_hash attribute follows Python naming conventions and provides a foundation for optimizing hash computation performance.

newton/geometry/utils.py (6)

19-19: Good addition for type safety.

Adding Literal import supports the new RemeshingMethod type alias, improving type safety.


24-24: Improved import organization.

Consolidating the imports from ..core.types is cleaner and more maintainable.


338-338: Excellent type safety improvement.

The RemeshingMethod type alias provides clear constraints on valid remeshing methods and improves API usability.


340-340: Enhanced type annotations improve API clarity.

The explicit typing with RemeshingMethod and return type annotation makes the function interface clearer and more maintainable.


377-386: Well-documented function with clear interface.

The updated function signature, parameter typing, and comprehensive docstring significantly improve the API usability and maintainability.


389-393: Solid implementation of mesh updating logic.

The mesh vertex/index updating, index flattening, and conditional inertia recomputation provide a complete and flexible remeshing solution.

Comment thread newton/geometry/types.py
Comment thread newton/geometry/types.py Outdated
Comment thread newton/geometry/utils.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
newton/geometry/types.py (1)

186-186: Verify function signature compatibility.

The past review comment flagged that remesh_convex_hull doesn't accept the maxhullvert parameter. However, the enriched summary suggests this function was updated. Let's verify the current function signature.

#!/bin/bash
# Description: Check if remesh_convex_hull function accepts maxhullvert parameter
# Expected: Function signature should include maxhullvert parameter

# Search for the function definition
ast-grep --pattern 'def remesh_convex_hull($$$)'
🧹 Nitpick comments (1)
newton/sim/builder.py (1)

2177-2178: Add logging for empty decompositions and clarify type checking.

Two suggestions for improvement:

  1. Empty decomposition results (lines 2177-2178) should be logged as they might indicate an issue with the mesh.
  2. The type checking pattern on line 2206 is unusual and could be more explicit.
             if len(decomposition) == 0:
+                print(f"Warning: CoACD decomposition returned no results for shape {shape}")
                 continue

For better clarity, consider using a more explicit type check or enum membership test instead of RemeshingMethod.__args__ if possible.

Also applies to: 2206-2206

📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ef570d and bbc46ec.

📒 Files selected for processing (4)
  • newton/geometry/types.py (6 hunks)
  • newton/geometry/utils.py (7 hunks)
  • newton/sim/builder.py (4 hunks)
  • newton/solvers/mujoco/solver_mujoco.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • newton/solvers/mujoco/solver_mujoco.py
  • newton/geometry/utils.py
🧰 Additional context used
🪛 GitHub Actions: Pull Request
newton/geometry/types.py

[error] 1-1: Sphinx warning: duplicate object description of newton.Mesh.vertices, other instance in generated/core/newton.Mesh, use :no-index: for one of them


[error] 1-1: Sphinx warning: duplicate object description of newton.Mesh.indices, other instance in generated/core/newton.Mesh, use :no-index: for one of them

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
🔇 Additional comments (9)
newton/sim/builder.py (2)

25-25: LGTM!

The imports are correctly added to support the new mesh simplification functionality.

Also applies to: 66-66


177-181: Good type safety improvements.

The explicit int() casting ensures proper bitwise operations, and the specific return type annotation improves clarity.

newton/geometry/types.py (7)

21-21: LGTM: Import addition is correct.

The override import is properly added and used later in the __hash__ method.


100-104: LGTM: Type annotations enhance code clarity.

The type annotations properly specify the accepted parameter types and improve static type checking capabilities.


121-122: LGTM: Good encapsulation with private attributes.

Converting vertices and indices to private attributes with proper numpy array conversion is a solid design choice.


127-127: LGTM: Hash caching initialization.

The _cached_hash attribute initialization supports the performance improvement mentioned in the PR objectives.


187-201: LGTM: Well-designed replace parameter logic.

The implementation provides both in-place modification and new instance creation options, with proper attribute preservation for the new mesh case.


206-212: LGTM: Effective hash caching implementation.

The hash caching mechanism directly addresses the PR objective of reducing mesh computation time from 200+ seconds to 200 milliseconds. Cache invalidation on data modification is properly handled.


136-157: Ensure consistent dtype handling for vertices and resolve Sphinx documentation conflicts.

  • Remove dtype=np.float32 in the vertices setter so it preserves the input’s precision and matches the __init__ behavior:
    -        self._vertices = np.array(value, dtype=np.float32).reshape(-1, 3)
    +        self._vertices = np.array(value).reshape(-1, 3)
  • Retain dtype=np.int32 in the indices setter to enforce a valid integer type for mesh indices.
  • Investigate the Sphinx build warnings about duplicate descriptions for Mesh.vertices and Mesh.indices. To eliminate conflicts, consider one of the following:
    • Move both attribute descriptions into the class docstring under an Attributes section and remove the individual property docstrings.
    • Annotate the property docstrings with :noindex: (or similar) so Sphinx doesn’t generate duplicate entries.

Comment thread newton/sim/builder.py
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
@eric-heiden eric-heiden marked this pull request as ready for review July 15, 2025 01:24
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
@Milad-Rakhsha-NV

Copy link
Copy Markdown
Member

On the USD importing side (maybe as part of a different PR), does it make sense to choose mesh simplification based on what the asset specifies through UsdPhysics.MeshCollisionAPI GetApproximationAttr?

@eric-heiden

eric-heiden commented Jul 15, 2025

Copy link
Copy Markdown
Member Author

On the USD importing side (maybe as part of a different PR), does it make sense to choose mesh simplification based on what the asset specifies through UsdPhysics.MeshCollisionAPI GetApproximationAttr?

That's a good point! I will look into this. I think we can do this in a separate PR. I've created an issue for this here: #409.

@eric-heiden eric-heiden enabled auto-merge (squash) July 15, 2025 18:28
@eric-heiden eric-heiden merged commit 669b27b into newton-physics:main Jul 15, 2025
8 of 9 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Sep 20, 2025
4 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Oct 7, 2025
4 tasks
eric-heiden added a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants