0.1.2 • Published 1 year ago

@c-frame/physx v0.1.2

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

physx

A-Frame physics using PhysX - based on an original implementation by Zach Capalbo

For examples of usage, see:

Examples

Installation

There is just one main JS module, physx.js, which triggers download of a specified additional wasm module.

The URL for the PhysX wasm module is specified on the physx component schema - but the default settings should work for most cases.

Installation via Script Tags

You can either download the module from the dist directory of this repo and include them like this:

<script src="physx.js"></script>

Or you can download via JSDelivr CDN (specifying the version number you want to use)

<script src="https://cdn.jsdelivr.net/gh/c-frame/physx@latest/dist/physx.min.js"></script>

Installation via npm

Not supported yet - if you want this, please raise an issue. PRs also welcome!

Build

Clone this repo, and run

npm install

To run for development purposes, run:

npm run dev or npm start

Examples can be viewed at /examples

To build (both development & production builds), run:

npm run dist

System physx

Implements the a physics system using an emscripten compiled PhysX engine.

If autoLoad is true, or when you call startPhysX(), the physx system will automatically load and initialize the physics system with reasonable defaults and a ground plane. All you have to do is add physx-body to the bodies that you want to be part of the simulation. The system will take try to take care of things like collision meshes, position updates, etc automatically. The simplest physics scene looks something like:

<a-scene physx="autoLoad: true">
 <a-assets><a-asset-item id="#mymodel" src="..."></a-asset-item></a-assets>

 <a-box physx-body="type: static" color="green" position="0 0 -3"></a-box>
 <a-sphere physx-body="type: dynamic" position="0.4 2 -3" color="blue"></a-sphere>
 <a-entity physx-body="type: dynamic" position="0 5 -3" gltf-model="#mymodel"></a-entity>
</a-scene>

If you want a little more control over how things behave, you can set the physx-material component on the objects in your simulation, or use physx-joints, physx-joint-constraints and physx-joint-drivers to add some complexity to your scene.

If you need more low-level control, the PhysX bindings are exposed through the PhysX property of the system. So for instance, if you wanted to make use of the PxCapsuleGeometry in your own component, you would call:

   let myGeometry = new this.el.sceneEl.PhysX.PxCapsuleGeometry(1.0, 2.0)

The system uses Zach Capalbo's fork of PhysX, built using the Docker Wrapper. To see what's exposed to JavaScript, see PxWebBindings.cpp

For a complete example of how to use this, you can see the aframe-vartiste-toolkit Physics Playground

It is also helpful to refer to the NVIDIA PhysX documentation

physx Schema

PropertyTypeDefaultDescription
delaynumber5000Amount of time to wait after loading before starting the physics. Can be useful if there is still some things loading or initializing elsewhere in the scene
throttlenumber10Throttle for running the physics simulation. On complex scenes, you can increase this to avoid dropping video frames
autoLoadbooleanfalseIf true, the PhysX will automatically be loaded and started. If false, you will have to call startPhysX() manually to load and start the physics engine
speednumber1Simulation speed multiplier. Increase or decrease to speed up or slow down simulation time
wasmUrlstringhttps://cdn.jsdelivr.net/gh/c-frame/physx/wasm/physx.release.wasmURL for the PhysX WASM bundle.
useDefaultScenebooleantrueIf true, sets up a default scene with a ground plane and bounding cylinder.
wrapBoundsbooleanfalseNYI
groundCollisionLayersstringWhich collision layers the ground belongs to
groundCollisionMaskstringWhich collision layers will collide with the ground
gravityvec3{ x: 0, y: -9.8, z: 0 }Global gravity vector
statsarray of stringsWhere to output performance stats (if any), panel, console, events (or some combination). - panel output stats to a panel similar to the A-Frame stats panel.-events generates physics-tick-timer events, which can be processed externally. -consoleoutputs stats to the console.

physx Methods

SignatureDescription
startPhysX ()Loads PhysX and starts the simulation

Component physx-material

Controls physics properties for individual shapes or rigid bodies. You can set this either on an entity with the phyx-body component, or on a shape or model contained in an entity with the physx-body component. If it's set on a physx-body, it will be the default material for all shapes in that body. If it's set on an element containing geometry or a model, it will be the material used for that shape only.

For instance, in the following scene fragment:

<a-entity id="bodyA" physx-body physx-material="staticFriction: 0.5">
  <a-box id="shape1" physx-material="staticFriction: 1.0"></a-box>
  <a-sphere id="shape2"></a-sphere>
</a-entity>
<a-cone id="bodyB" physx-body></a-cone>

shape1, which is part of the bodyA rigid body, will have static friction of 1.0, since it has a material set on it. shape2, which is also part of the bodyA rigid body, will have a static friction of 0.5, since that is the body default. bodyB will have the component default of 0.2, since it is a separate body.

physx-material Schema

PropertyTypeDefaultDescription
staticFrictionnumber0.2Static friction
dynamicFrictionnumber0.2Dynamic friction
restitutionnumber0.2Restitution, or "bounciness"
densitynumberDensity for the shape. If densities are specified for all shapes in a rigid body, then the rigid body's mass properties will be automatically calculated based on the different densities. However, if density information is not specified for every shape, then the mass defined in the overarching physx-body will be used instead.
collisionLayersarray1Which collision layers this shape is present on
collidesWithLayersarray 1, 2, 3, 4 Array containing all layers that this shape should collide with
collisionGroupnumber0If collisionGroup is greater than 0, this shape will not collide with any other shape with the same collisionGroup value
contactOffsetstringIf >= 0, this will set the PhysX contact offset, indicating how far away from the shape simulation contact events should begin.
restOffsetstringIf >= 0, this will set the PhysX rest offset

Component physx-body

Turns an entity into a PhysX rigid body. This is the main component for creating physics objects.

Types

There are 3 types of supported rigid bodies. The type can be set by using the type proeprty, but once initialized cannot be changed.

  • dynamic objects are objects that will have physics simulated on them. The entity's world position, scale, and rotation will be used as the starting condition for the simulation, however once the simulation starts the entity's position and rotation will be replaced each frame with the results of the simulation.
  • static objects are objects that cannot move. They can be used to create collidable objects for dynamic objects, or for anchor points for joints.
  • kinematic objects are objects that can be moved programmatically, but will not be moved by the simulation. They can however, interact with and collide with dynamic objects. Each frame, the entity's object3D will be used to set the position and rotation for the simulation object.

Shapes

When the component is initialized, and on the object3dset event, all visible meshes that are descendents of this entity will have shapes created for them. Each individual mesh will have its own convex hull automatically generated for it. This means you can have reasonably accurate collision meshes both from building up shapes with a-frame geometry primitives, and from importing 3D models.

Visible meshes can be excluded from this shape generation process by setting the physx-no-collision attribute on the corresponding a-entity element. Invisible meshes can be included into this shape generation process by settingt the physx-hidden-collision attribute on the corresponding a-entity element. This can be especially useful when using an external tool (like Blender V-HACD) to create a low-poly convex collision mesh for a high-poly or concave mesh. This leads to this pattern for such cases:

   <a-entity physx-body="type: dynamic">
     <a-entity gltf-model="HighPolyOrConcaveURL.gltf" physx-no-collision=""></a-entity>
     <a-entity gltf-model="LowPolyConvexURL.gltf" physx-hidden-collision="" visible="false"></a-entity>
   </a-entity>

Note, in such cases that if you are setting material properties on individual shapes, then the property should go on the collision mesh entity

physx-body Schema

PropertyTypeDefaultDescription
typestringdynamicdynamic, static, kinematic Type of the rigid body to create
massnumber1Total mass of the body
angularDampingnumber0If > 0, will set the rigid body's angular damping
linearDampingnumber0If > 0, will set the rigid body's linear damping
emitCollisionEventsbooleanfalseIf set to true, it will emit contactbegin and contactend events when collisions occur
highPrecisionbooleanfalseIf set to true, the object will receive extra attention by the simulation engine (at a performance cost).
shapeOffsetvec3{ x: 0, y: 0, z: 0 }

physx-body Methods

SignatureDescription
toggleGravity ()Turns gravity on and off

Component physx-joint-driver

Creates a driver which exerts force to return the joint to the specified (currently only the initial) position with the given velocity characteristics.

This can only be used on an entity with a physx-joint component. Currently only supports D6 joint type. E.g.

<a-box physx-body>
   <a-entity position="0.2 0.3 0.4" rotation="0 90 0"
             physx-joint="type: D6; target: #other-body"
             physx-joint-driver="axes: swing, twist; stiffness: 30; angularVelocity: 3 3 0">
   </a-entity>
</a-box>

physx-joint-driver Schema

PropertyTypeDefaultDescription
axesarray[]Which axes the joint should operate on. Should be some combination of x, y, z, twist, swing
stiffnessnumber1How stiff the drive should be
dampingnumber1Damping to apply to the drive
forceLimitnumber3.4028234663852886e+38Maximum amount of force used to get to the target position
useAccelerationbooleantrueIf true, will operate directly on body acceleration rather than on force
linearVelocityvec3{ x: 0, y: 0, z: 0 }Target linear velocity relative to the joint
angularVelocityvec3{ x: 0, y: 0, z: 0 }Targget angular velocity relative to the joint
lockOtherAxesbooleanfalseIf true, will automatically lock axes which are not being driven
slerpRotationbooleantrueIf true SLERP rotation mode. If false, will use SWING mode.

Component physx-joint-constraint

Adds a constraint to a physx-joint. Currently only D6 joints are supported.

Can only be used on an entity with the physx-joint component. You can set multiple constraints per joint. Note that in order to specify attributes of individual axes, you will need to use multiple constraints. For instance:

<a-box physx-body>
  <a-entity physx-joint="type: D6"
            physx-joint-constraint__xz="constrainedAxes: x,z; linearLimit: -1 20"
            physx-joint-constraint__y="constrainedAxes: y; linearLimit: 0 3; stiffness: 3"
            physx-joint-constraint__rotation="lockedAxes: twist; swing"></a-entity>
</a-box>

In the above example, the box will be able to move from -1 to 20 in both the x and z direction. It will be able to move from 0 to 3 in the y direction, but this will be a soft constraint, subject to spring forces if the box goes past in the y direction. All rotation will be locked. (Note that since no target is specified, it will use the scene default target, effectively jointed to joint's initial position in the world)

physx-joint-constraint Schema

PropertyTypeDefaultDescription
lockedAxesarray[]Which axes are explicitly locked by this constraint and can't be moved at all. Should be some combination of x, y, z, twist, swing
constrainedAxesarray[]Which axes are constrained by this constraint. These axes can be moved within the set limits. Should be some combination of x, y, z, twist, swing
freeAxesarray[]Which axes are explicitly freed by this constraint. These axes will not obey any limits set here. Should be some combination of x, y, z, twist, swing
linearLimitvec2Limit on linear movement. Only affects x, y, and z axes. First vector component is the minimum allowed position
limitConevec2Two angles specifying a cone in which the joint is allowed to swing, like a pendulum.
twistLimitvec2Minimum and maximum angles that the joint is allowed to twist
dampingnumber0Spring damping for soft constraints
restitutionnumber0Spring restitution for soft constraints
stiffnessnumber0If greater than 0, will make this joint a soft constraint, and use a spring force model

Component physx-joint

Creates a PhysX joint between an ancestor rigid body and a target rigid body.

The physx-joint is designed to be used either on or within an entity with the physx-body component. For instance:

<a-entity physx-body="type: dynamic">
  <a-entity physx-joint="target: #other-body" position="1 0 0"></a-entity>
</a-entity>

The position and rotation of the physx-joint will be used to create the corresponding PhysX joint object. Multiple joints can be created on a body, and multiple joints can target a body.

Stapler Example

Here's a simplified version of the stapler from the physics playground demo

<a-entity id="stapler">
  <a-entity id="stapler-top" physx-body="type: dynamic" class="grab-root">
    <a-entity class="clickable" propogate-grab="" gltf-part-plus="src: #asset-stapler; part: Top"></a-entity>
    <a-entity physx-joint="target: #stapler-bottom; type: Revolute; collideWithTarget: true" position="0 0.0254418 -3.7280"></a-entity>
  </a-entity>
  <a-entity id="stapler-bottom" gltf-part-plus="src: #asset-stapler; part: Bottom" physx-body="type: dynamic"></a-entity>
</a-entity>

Notice the joint is created between the top part of the stapler (which contains the joint) and the bottom part of the stapler at the position of the physx-joint component's entity. This will be the pivot point for the stapler's rotation.

Stapler with joint highlighted

physx-joint Schema

PropertyTypeDefaultDescription
typestringSphericalRigid body joint type to use. See the NVIDIA PhysX joint documentation for details on each type
targetselectorTarget object. If specified, must be an entity having the physx-body component. If no target is specified, a scene default target will be used, essentially joining the joint to its initial position in the world.
breakForcevec2{ x: -1, y: -1 }Force needed to break the constraint. First component is the linear force, second component is angular force. Set both components are >= 0
removeElOnBreakbooleanfalseIf true, removes the entity containing this component when the joint is broken.
collideWithTargetbooleanfalseIf false, collision will be disabled between the rigid body containing the joint and the target rigid body.
softFixedbooleanfalseWhen used with a D6 type, sets up a "soft" fixed joint. E.g., for grabbing things

Collision Events

If emitCollisionEvents is set of a physx-body then contactbegin and contactend events are emitted on that entity when it collides with other bodies.


Note the important restriction that SDK state changes are not permitted from an event callback (full details here). This means that you should never add / remove / modify bodies, materials, joints, etc. directly from a collision event callback.

If you need to make such changes in response to a collision event, you can use setTimeout() with a zero timer to delay the update until the physics processing for this tick has completed.


These events include a detail object with the following properties:

PropertyDescription
thisShapeA PxShape for the shape within this body involved in the collision. This can be used to look up the physx-body that owns the shape using this.shapeMap on the phsyx component on the scene. For a compound shape, there is currently no way to map this back to a specific object3D that corresponds to the shape in question.
otherShapeA PxShape for the shape within the other body involved in the collision. See row above for what can be done with this.
pointsThe set of contact points, a PxVec3Vector. Read length from points.size(), then access using points.get(index) for each index < size, which returns a JS object with properties x, y & z (like a THREE.Vector3, but not actually one).This will be null on a contactend event.
impulsesThe set of impulses at these contact points, a VectorPxReal. Read length from impulses.size(), then access using impulses.get(index) for each index < size, which returns a number.This will be null on a contactend event.
otherComponentThe physx-body component for the other object in the collision.

Statistics

The following statistics are available from PhysX. Each of these is refreshed every 100 ticks (i.e. every 100 frames).

StatisticMeaning
StaticThe number of static bodies being handled by the physics engine.
DynamicThe number of dynamic bodies being handled by the physics engine.
KinematicThe number of kinematic bodies being handled by the physics engine.
AfterThe number of milliseconds per tick after invoking the physics engine. Typically this is the time taken to synchronize the physics engine state into the scene, e.g. movements of dynamic bodies.Median = median value in the last 100 ticks90th % = 90th percentile value in the last 100 ticks99th % = maximum recorded value over the last 100 ticks.Note that unlike the physics implementations in aframe-physics-system, PhysX has no significant per-frame processing before invoking the physics engine, so the "after" statistic accounts for all the work done outside the PhysX WASM code.
EngineThe number of milliseconds per tick actually running the physics engine.Reported as Median / 90th / 99th percentiles, as above.
TotalThe total number of milliseconds of physics processing per tick: Engine + After. Reported as Median / 90th / 99th percentiles, as above.

Acknowledgements

This repository is based on an original implementation of A-Frame physics using PhysX by Zach Capalbo

Simplification into a standalone codebase by Lee Stemkoski