PhysX

Overview

This walkthough will illustrate the use of the PhysX APIs available in the Aether Engine SDK as used in the Physics demo.

Physics entry point

The Aether Engine physics API should feel familiar to developers who have previously used the PhysX API, and indeed the physics features of the engine can be found in the aether::physx namespace. We can create a physics system to integrate with the Aether Engine ECS:

using c_physx = aether::physx::physx_c;
struct c_trivial {
struct colour agent_colour = {1.0f, 0.0f, 0.0f};
uint64_t id;
float size;
};
AETHER_SERDE_DERIVE_TRIVIAL(c_trivial);
using component_types = std::tuple<c_physx, c_trivial>;
using octree_traits = aether::octree_traits<morton_code<3, 21>>;
using user_cell_state = aether::ecs<octree_traits, component_types>;
struct physx_update_system {
using accessed_components = std::tuple<c_physx, c_trivial>;
using ecs_type =
aether::constrained_ecs<user_cell_state, accessed_components>;
void operator()(
const aether_cell_state<octree_traits> &aether_state,
ecs_type &store, float delta_time
) {
aether::physx::physx_state *physx_state = store.user_data.get();
// Advance the simulation.
// This will be called once per tick, per cell
physx_state->scene->simulate(delta_time);
physx_state->scene->fetchResults(true);
}
};

Here c_physx is the component for providing PhysX integration, provided by Aether. c_trivial is a simple struct of our design to allow us to render the physics controlled agents. Now given we have an physics system, we can initialise it for each cell in initialise_cell:

void user_cell_state_impl::initialise_cell(const aether_state_type &aether_state) {
store.user_data = std::make_unique<aether::physx::physx_state>(aether::physx::physx_state::basic_scene(new event_callback(store)));
store.add_system<physx_update_system>();
aether::physx::physx_state *physx_state = store.user_data.get();
// Lets add a physics aware plane
physx::PxMaterial* material =
physx_state->physics->createMaterial(0.0f,0.0f,1.0f);
auto plane = physx::PxCreatePlane(
*physx_state->physics, physx::PxPlane(0,0,1,150), *material
);
material->release();
physx_state->scene->addActor(*plane);
}

For more information about using event_callback to receive events from PhysX, see Physics.

Notice how here we are creating a plane in initialise_cell - this will cause the plane object to exist once for each cell in the simulation, and will not be shared across cells. This is fine for static meshes (e.g. ground floors) that are expected to remain in a consistent state, but will not work for any entities that we expect to move around and could become inconsistent between workers. This includes the bouncing boxes in our demo, let’s look at how we can resolve that and have physics that works across multiple cells. Naturally we will want to avoid initialise_cell, and instead focus on initialise_world.

void user_cell_state_impl::initialise_world(const aether_state_type &aether_state) {
aether::physx::physx_state *physx_state = store.user_data.get();
// Create a material and physics shape
physx::PxReal extent(1.0f);
physx::PxMaterial* material =
physx_state->physics->createMaterial(0.0f, 0.0f, 1.0f);
physx::PxShape* actor_shape =
physx_state->physics->createShape(
physx::PxBoxGeometry(extent, extent, extent),
*material
);
// Position our actor
physx::PxTransform actor_position =
physx::PxTransform(physx::PxVec3(0.0f, 0.0f, 0.0f));
// Create a physics enabled actor
physx::PxRigidDynamic* actor =
physx_state->physics->createRigidDynamic(actor_position);
actor->attachShape(*actor_shape);
physx::PxRigidBodyExt::updateMassAndInertia(*actor, 10.0f);
actor->setLinearVelocity(physx::PxVec3(0.0f, 1.0f, 0.0f));
// Add our new agent to the ECS
auto update_set = state.create_update_set();
auto agent = update_set.new_entity_local();
auto physx_component = agent.create_component<c_physx>();
physx_component->add_actor(*actor);
physx_component->add_to_simulation();
// Add a helpful component for rendering
auto trivial = agent.create_component<c_trivial>();
trivial->id = 0;
trivial->size = 1.0f;
trivial->agent_colour.r = 1.0f;
// Cleanup
material->release();
actor_shape->release();
}

Currently it is not possible to share materials between actors - it is important to create a unique material for each actor in the simulation.

Of course, we need to have some way of advancing the simulation on each tick. However as we’ve integrated our physics system with the ECS, this is very straightforward:

void user_cell_state_impl::cell_tick(const aether_state_type &aether_state, float delta_time) {
store.tick(aether_state, delta_time);
}

The Demo

The physics demo uses exactly the above setup to simulate cubes bouncing whilst constrained inside a cubic bounding box. The physx_update_systemhas been described above and needs no further work. In our initialise_world call we can initialise 500 boxes rather than the single box previously. The setup for a single actor is identical to what we have previously, just wrapped in a for loop. Finally, rather than creating a single plane in initialise_cell we create the six bounding planes of the bounding box.