SDF Mesh floating up in +Z direction? (Isaac Sim 5.1, ROS2 Jazzy)

Hi, I’m having an issue with a CloudSat model in Isaac Sim 5.1.

I downloaded the GLB from NASA:

I converted it to USD and added a rigid body to the root prim:

/World/CloudSat__B_

The main mesh is located at:

/World/CloudSat__B_/Layer_0/Mesh_0

I’m moving the satellite using a Python Script Node that subscribes to a ROS 2 nav_msgs/msg/Odometry topic. The orbit is calculated outside Isaac Sim, and the script applies the resulting velocity during each physics step. Gravity is disabled because this is an orbital simulation.

It generally works as expected, it moves within the path of LEO, but the problem is that it randomly rotates and jankily moves in the Z direction. As soon as I add a collider to the CloudSat mesh, though, the satellite starts moving upward in the Z direction. I have tried SDF Mesh and Convex Hull and seem to get similar behavior.

I checked the incoming odometry and confirmed that both values remain zero:

position.z = 0.0
linear_velocity.z = 0.0

In one test, the Isaac world transform became approximately:

X = 6,778,000 m
Y = 48.9 m
Z = 43.2 m

The large X coordinate comes from converting the orbital position from kilometres to metres. I’m wondering if being that far from the world origin could be causing physics precision problems. The Z axis is supposed to remain 0, as I am not orbiting in the Z direction.

I also saw this warning when adding the collider:

setCollider: /World/CloudSat__B_/Layer_0/Mesh_0 is a part of a rigid body.
Resetting approximation shape from none (trimesh) to convexHull

The SDF option sometimes appears red in the Properties panel, although I haven’t found a clear SDF cooking error in the console.

Has anyone seen this behavior with an imported GLB/USD model? Could it be caused by the model’s inherited scale or transforms, the large distance from the origin, or an incorrectly generated collider?

I’ve tested it without my ROS nodes, and it just floats up?

I disabled the ground plane and that seemed to fix the floating up behavior.

However, it keeps rotating randomly?

Hi, short answer: this isn’t a bug in the SDF collider — it’s floating-point precision.
PhysX runs in single precision (float32), and you’re simulating at ~6,778,000 m from the origin. At that magnitude a float32 can only represent position to roughly 0.5 m granularity, so poses, contacts, penetration depths and velocities are all corrupted. That noise is what produces the janky Z motion and random rotation, and it’s why the collider makes things worse.

Why it floats specifically in +Z
The default ground plane sits at Z=0 with a +Z normal. With the precision noise at those large coordinates, your satellite’s collider registers false penetrations against that plane, and PhysX pushes it apart along the plane normal (+Z) every step → continuous upward drift. That matches exactly what you found by removing the ground plane. In an orbital scene there should be no ground plane at all, so removing it is correct.

The remaining “random rotation” is a body-type issue. Your pose comes from an external source (the nav_msgs/Odometry propagator), but the prim is a dynamic rigid body, so PhysX is still integrating it. With gravity disabled and no damping, any residual/precision-induced velocity never settles and the body tumbles. For an object whose motion is authored externally, make it kinematic instead:

  • Set physics:kinematicEnabled = true on the rigid body (or drive it as a plain Xform and set the transform directly in your Odometry callback). Kinematic bodies are moved by you, not integrated by PhysX — no gravity, no residual velocity, no contact push-back. This alone should remove both the floating and the tumbling.

About the collider warning (“Resetting approximation from none (trimesh) to convexHull”): that’s expected. A raw triangle-mesh collider is only valid on static/kinematic bodies; on a dynamic body PhysX falls back to convex hull. Concave dynamic collision needs the SDF approximation (PhysxSDFMeshCollisionAPI) plus GPU dynamics enabled — which is probably why the SDF option shows as invalid/red in your setup. For a single odometry-driven satellite you most likely don’t need mesh collision at all; a convex hull
(or no collider) is sufficient.

Recommended setup for orbital sims:

  1. Remove the ground plane (no ground in orbit).
  2. Make the satellite kinematic (or a driven Xform) since its pose comes from ROS odometry — don’t let PhysX integrate it at absolute orbital coordinates.
  3. Don’t feed orbital-scale coordinates into PhysX. If you need real PhysX dynamics (e.g., contacts between bodies), keep the simulated objects within a few km of the origin and apply the large orbital offset only at the visualization/USD layer — a “floating origin” / rebasing approach. Simulating dynamics directly at 6.78×10⁶ m will always be unstable in single precision, and you may also hit PhysX’s large-coordinate culling (the “bodies disappearing at large coordinates” you saw referenced).
  4. Also double-check the imported model’s units/scale (metersPerUnit) and any inherited non-uniform scale on Mesh_0 from the GLB→USD conversion — a non-uniform scale can independently break SDF/convex generation.

Hope this helps.

My understanding of floating origin is to set change the origin of the world to be where the viewport/camera is set?

I think this is how Kerbal Space Program does it? How frequent, where, and when would the origin be rebased?

In Isaac Sim 5.1, which public C++ interface and headers provide access to the active PxScene* so a Kit extension can safely call PxScene::shiftOrigin() after a physics step?

I am thinking about doing this via an extension, but I’m not sure and I’d rather use actiongraph nodes?

I inspected all Python interfaces available from omni.physx in Isaac Sim 5.1, including PhysX, IPhysxSimulation, IPhysxStageUpdate, PhysXSceneQuery, and IPhysxStatistics. I also tested likely method names such as shift_origin, shiftOrigin, get_physx_scene, get_native_scene, and get_scene_pointer; all returned false. The installed _physx.pyi contains physics-step callbacks and scene simulation/writeback methods, but no origin shift or native scene access. Is there a public C++ interface/header that exposes the active PxScene*?

Yes, the first person viewpoint, or player character, does not move from the origin. The World is removed around the player instead. There is actually no absolute motion and therefore no “rebasing” based on some guesswork threshold distance from the origin.

KSP used the rebasing, or shifting, approach (They incorrectly described it as “floating origin” at a 2013 Unity conference).

Good to know, thank you. This helps alot!

An approach that I was thinking of doing was using the midpoint between two orbiting bodies, but I am guessing this isnt a good idea, as I will eventually be simulating >2 orbiting bodies with propagation.

I can’t exactly elaborate on the project I’m working on, but I am working on using this on a 1:1 scale earth model for a NASA proposal for a space robotics competition.

No probs. RE orbiting, I Simulate full-scale Earth and Solar System in Unity using a combination of single and half precision. So I can verify that, if your character stays at the origin, it can relative orbit Earth successfully. Here is video evidence: https://youtu.be/Bc_N9jGG9Ug.

And w.r.t your questions on Isaac Sim:

  1. Is PxScene::shiftOrigin() exposed anywhere in Isaac Sim 5.1?

    No. It isn’t exposed in the Python API, the omni.physx bindings, or any Isaac Sim / Isaac Lab utility — there’s no origin-shift or floating-origin code in the shipping source. PxScene::shiftOrigin(const PxVec3&) exists at the PhysX SDK level, but you can only reach it by obtaining the raw PxScene* yourself (see #2). Note that shiftOrigin() only translates PhysX-internal state; it does not touch USD, Fabric, or rendering.

  2. Can a C++ extension obtain the active PxScene* through a supported Omni PhysX interface?

    Yes — this is the supported route and it’s used in NVIDIA’s own PhysX OmniGraph nodes: omni::physx::IPhysx::getPhysXPtr(const pxr::SdfPath& path, omni::physx::PhysXType type) returns raw PhysX SDK pointers. For example, the shipping code does getPhysXPtr(shapePath, omni::physx::ePTShape) → PxShape* and getPhysXPtr(SdfPath(), omni::physx::ePTPhysics) → PxPhysics*. Acquire IPhysx via the omni.physx C++ interface, pass your PhysicsScene prim path with the scene type (ePTScene) and cast to physx::PxScene*. Caveats: the pointer is invalidated on stage close / physics re-parse; build against the exact PhysX SDK headers for your Isaac Sim build; and confirm the ePTScene enumerant name in the 5.1 headers.

Hi, I am closing this topic as all the questions had been addressed. If you need further help with your orbital simulation, please open a new topic.