MagicPhysX — A Cross-Platform Physics Engine for .NET

Yoshifumi Kawai
6 min readJul 4, 2023

I’m excited to announce the release of a new library, MagicPhysX! It’s a physics engine designed for use with .NET, and as its name suggests, it serves as a C# binding for NVIDIA PhysX.

Potential applications include:

  • 3D aspects of GUI applications
  • Incorporating a physics engine into a custom game engine
  • Simulations for deep learning
  • Server-side physics in real-time communications

There are other PhysX bindings for .NET out there, but due to their being generated with C++/CLI, they often only work on Windows, or they’re based on an outdated 4.x version. MagicPhysX, on the other hand, is based on the latest PhysX 5 and operates on all platforms, including Windows, MacOS, and Linux (win-x64, osx-x64, osx-arm64, linux-x64, linux-arm64). This is made possible by leveraging the cross-platform compiling capabilities of Rust and the automatic C# binding generation of Cysharp/csbindgen.

Let’s first discuss the internal architecture. MagicPhysX uses physx-rs developed by Embark Studios as its build source.

Embark Studios is a company formed by the team that developed the Frostbite game engine (used in Battlefield) at EA DICE. They are currently creating a game engine with Rust. In the process, they have been actively releasing the Rust libraries they have developed as open source software. You can find a list of these on the Embark Studios Open Source page. It’s definitely worth checking out!

The PhysX library is written in C++, and was not designed with use in other languages in mind. As a result, in order to adapt it for other languages, you need to first create a bridge in C++, and then prepare the bindings, which is essentially doing the job twice. This is no exception, even in the case of Rust. Moreover, the source code of PhysX is quite large, so the amount of work involved is considerable.

As I introduced in the previous article, “csbindgen — Generate C# native code bridge automatically or modern approaches to native code invocation from C#”, there are automation projects like SWIG for automatic generation from C++, or cxx and autocxx for Rust. However, due to the complexity of C++ itself, it’s difficult to automatically produce the desired results.

In the session “An unholy fusion of Rust and C++ in physx-rs” at the Stockholm Rust Meetup in October 2019, there is a description of the candidate methods and the actual adopted method for bringing PhysX to Rust. In simple terms, the final method was to prepare a custom generator that specializes in PhysX, analyzes the code, and generates a C API. In other words, physx-rs has created a C API for PhysX that can also be used as a binding method for other languages!

Furthermore, csbindgen has the ability to automatically generate C# from the functions in the “extern C” within the rs file. This has become a build pipeline that can bring PhysX from C++ to C# via Rust.

Because of this structure, the API of MagicPhysX is identical to the API of PhysX.

using MagicPhysX; // for enable Extension Methods.
using static MagicPhysX.NativeMethods; // recommend to use C API.

// create foundation(allocator, logging, etc...)
var foundation = physx_create_foundation();

// create physics system
var physics = physx_create_physics(foundation);

// create physics scene settings
var sceneDesc = PxSceneDesc_new(PxPhysics_getTolerancesScale(physics));

// you can create PhysX primitive(PxVec3, etc...) by C# struct
sceneDesc.gravity = new PxVec3 { x = 0.0f, y = -9.81f, z = 0.0f };

var dispatcher = phys_PxDefaultCpuDispatcherCreate(1, null, PxDefaultCpuDispatcherWaitForWorkMode.WaitForWork, 0);
sceneDesc.cpuDispatcher = (PxCpuDispatcher*)dispatcher;
sceneDesc.filterShader = get_default_simulation_filter_shader();

// create physics scene
var scene = physics->CreateSceneMut(&sceneDesc);

var material = physics->CreateMaterialMut(0.5f, 0.5f, 0.6f);

// create plane and add to scene
var plane = PxPlane_new_1(0.0f, 1.0f, 0.0f, 0.0f);
var groundPlane = physics->PhysPxCreatePlane(&plane, material);
scene->AddActorMut((PxActor*)groundPlane, null);

// create sphere and add to scene
var sphereGeo = PxSphereGeometry_new(10.0f);
var vec3 = new PxVec3 { x = 0.0f, y = 40.0f, z = 100.0f };
var transform = PxTransform_new_1(&vec3);
var identity = PxTransform_new_2(PxIDENTITY.PxIdentity);
var sphere = physics->PhysPxCreateDynamic(&transform, (PxGeometry*)&sphereGeo, material, 10.0f, &identity);
PxRigidBody_setAngularDamping_mut((PxRigidBody*)sphere, 0.5f);
scene->AddActorMut((PxActor*)sphere, null);

// simulate scene
for (int i = 0; i < 200; i++)
{
// 30fps update
scene->SimulateMut(1.0f / 30.0f, null, null, 0, true);
uint error = 0;
scene->FetchResultsMut(true, &error);

// output to console(frame-count: position-y)
var pose = PxRigidActor_getGlobalPose((PxRigidActor*)sphere);
Console.WriteLine($"{i:000}: {pose.p.y}");
}

// release resources
PxScene_release_mut(scene);
PxDefaultCpuDispatcher_release_mut(dispatcher);
PxPhysics_release_mut(physics);

In other words, it’s not exactly user-friendly in its current form. If you want to create a full-fledged application, not just run a portion of it, you’ll likely need to prepare a high-level framework that aligns with C#. We have prepared such samples within MagicPhysX. With it, the code above can be simplified significantly.

using MagicPhysX.Toolkit;
using System.Numerics;

unsafe
{
using var physics = new PhysicsSystem(enablePvd: false);
using var scene = physics.CreateScene();

var material = physics.CreateMaterial(0.5f, 0.5f, 0.6f);

var plane = scene.AddStaticPlane(0.0f, 1.0f, 0.0f, 0.0f, new Vector3(0, 0, 0), Quaternion.Identity, material);
var sphere = scene.AddDynamicSphere(1.0f, new Vector3(0.0f, 10.0f, 0.0f), Quaternion.Identity, 10.0f, material);

for (var i = 0; i < 200; i++)
{
scene.Update(1.0f / 30.0f);

var position = sphere.transform.position;
Console.WriteLine($"{i:D2} : x={position.X:F6}, y={position.Y:F6}, z={position.Z:F6}");
}
}

However, as these are only samples, you’ll need to create the necessary parts yourself, using the samples as references.

You might think it’s impossible to confirm whether the physics engine is behaving correctly without a visualization like you would have with a Unity editor. However, PhysX provides a tool called PhysX Visual Debugger. With the appropriate settings, it’s possible to link this tool with MagicPhysX.

At Cysharp, we’re developing libraries like MagicOnion and LogicLooper to run game logic on the server side. It’s only natural to want to run even physics engine-requiring games on a regular .NET server, in line with our approach…(?)

In a dedicated server setup for Unreal Engine (UE) or Unity, you end up building and hosting a headless UE/Unity application for the server. However, since these aren’t really server frameworks, they aren’t particularly easy to work with. Issues can arise from compatibility with libraries for regular servers, differences in life cycles, and subpar performance as a runtime, among other things.

This is why it’s better to use a server-oriented framework like MagicOnion, but nothing can be done about the physics engine. Until now…?

While I’d like to make that claim, in reality, it would be somewhat (quite) challenging! Questions like, “How will we handle colliders?” or “The APIs are different (Unity’s physics engine is PhysX, but the APIs aren’t a 1:1 match, so there are differences in the details)” make it difficult to align behaviors. Also, with this configuration, you’d want to run not only on the server but also on the client. The debuggability differs too much otherwise.

In summary, if your game requires behavior to some extent that leans towards a physics engine, then it’s essential to have a “physics engine unification.” Unfortunately, MagicPhysX is not that. Initially, we aimed for that, intending it to be a library that could seamlessly integrate, providing almost identical behavior and API as Unity. However, please note that this is not the current state of things. We also don’t have plans to develop a compatible API based on the initial intention.

This library project has seen its share of detours. Initially, we planned to adopt Bullet Physics. We had decided on the library name first, thinking “MagicBullet” sounded pretty cool. Then we decided to use Jolt Physics, and got it to a working state by creating some bindings, but in the end, we decided to use PhysX in the interest of achieving “physics engine unification.”

I’m glad we managed to bring it to fruition (and demonstrate the practicality of csbindgen!), but it’s a bit disappointing that we couldn’t achieve the “physics engine unification”. The initial vision was supposed to be far more revolutionary…!

However, I believe that simply bringing PhysX 5 to .NET on a cross-platform basis is a challenging and novel feat in itself. So, if you get a chance, please do give it a try.

--

--

Yoshifumi Kawai

a.k.a. neuecc. Creator of UniRx, UniTask, MessagePack for C#, MagicOnion etc. Microsoft MVP for C#. CEO/CTO of Cysharp Inc. Live and work in Tokyo, Japan.