User Guide

In this section we describe how to set up NvCloth and guide you through some common features.

Setup

Some setup is required before we can start simulating. Here we give an overview of all the components needed. The sections below will provide more details about each component.

NvCloth library
Uses callbacks for things like memory allocations, which need to be initialized.
Factory
Interface object creating all other objects.
Solver
Manages simulation and time stepping.
Fabric
Contains information about cloth that can be shared between multiple instances.
Cloth
Contains the cloth instance data.

Initializing the Library

NvCloth uses user defined callbacks for memory allocation, error reporting, assert handling, and profile timings. The callbacks need to be passed to the library using nv::cloth::InitializeNvCloth() before the rest of the library can be used. The callbacks are straightforward to implement by providing classes with implementations for all the functions of physx::PxAllocatorCallback, physx::PxErrorCallback, physx::PxAssertHandler, and optionally physx::PxProfilerCallback. Note that the allocations returned by the PxAllocatorCallback need to be 16 byte aligned.

The library doesn’t need any deinitialization.

Factory

The Factory object lets you construct all the other components necessary for the simulation. There are different factories, one for each platform (e.g. CPU, CUDA, etc.) Components created with different platforms cannot be used together (e.g. a CPU cloth cannot be added to a GPU solver), but multiple factories (for different platforms) can be used at the same time. A factory is created as follows:

#include <NvCloth/Factory.h>

...

nv::cloth::Factory* factory = NvClothCreateFactoryCPU();
if(factory==nullptr)
{
       //error
}

...

//At cleanup:
NvClothDestroyFactory(factory); //This works for all different factories.

Different functions instead of NvClothCreateFactoryCPU() can be used to create factories for a different platform. Some platforms may need additional arguments when creating the factory, like CUDA:

//// CUDA
#include <NvCloth/Factory.h>
#include <cuda.h>

...

CUcontext cudaContext;
int deviceCount = 0;
CUresult result = cuDeviceGetCount(&deviceCount);
ASSERT(CUDA_SUCCESS == result);
ASSERT(deviceCount >= 1);

result = cuCtxCreate(&cudaContext, 0, 0); //Pick first device
ASSERT(CUDA_SUCCESS == result);

nv::cloth::Factory* factory = NvClothCreateFactoryCUDA(cudaContext);
//We need to call cuCtxDestroy(cudaContext); after destroying the factory.

And DX11:

//// DX11
#include <NvCloth/Factory.h>
#include <d3d11.h>

...

//Setup DX11 context
ID3D11Device* DXDevice;
ID3D11DeviceContext* DXDeviceContext;
nv::cloth::DxContextManagerCallback* GraphicsContextManager;
D3D_FEATURE_LEVEL featureLevels[] = {D3D_FEATURE_LEVEL_11_0};
D3D_FEATURE_LEVEL featureLevelResult;
HRESULT result = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, featureLevels, 1, D3D11_SDK_VERSION, &DXDevice, &featureLevelResult, &DXDeviceContext);
ASSERT(result == S_OK);
ASSERT(featureLevelResult == D3D_FEATURE_LEVEL_11_0);
GraphicsContextManager = new DxContextManagerCallbackImpl(DXDevice);
ASSERT(GraphicsContextManager != nullptr);

nv::cloth::Factory* factory = NvClothCreateFactoryDX11(GraphicsContextManager);
//We need to release all DX objects after destroying the factory.

Fabric & Cloth

The data used for cloth simulation is divided into two objects. The fabric object contains all reusable data like the constraint lengths and connections. The cloth object contains all instance data like particle positions. This way multiple cloths can share the same fabric data, reducing memory usage.

Fabric

Creating the fabric is the most complicated part of the setup. We have included helper functions as extension to simplify this step. We can fill the nv::cloth::ClothMeshDesc meshDesc struct and pass it to the NvClothCookFabricFromMesh function if we use the cooking extension:

#include <NvClothExt/ClothFabricCooker.h>

...

nv::cloth::ClothMeshDesc meshDesc;

//Fill meshDesc with data
meshDesc.setToDefault();
meshDesc.points.data = vertexArray;
meshDesc.points.stride = sizeof(vertexArray[0]);
meshDesc.points.count = vertexCount;
//etc. for quads, triangles and invMasses

physx::PxVec3 gravity(0.0f, -9.8f, 0.0f);
nv::cloth::Vector<int32_t>::Type phaseTypeInfo;
nv::cloth::Fabric* fabric = NvClothCookFabricFromMesh(factory, meshDesc, gravity, &phaseTypeInfo);

...

fabric->decRefCount();

phaseTypeInfo contains information used later for setting up the constraint phases of the cloth. We provide the gravity vector so the cooker can differentiate between horizontal and vertical constraints.

You have to release the fabric using the decRefCount() function. Cloths that depend on the fabric also keep a reference, so it is safe to decrease the refcount when the fabric is still in use. The fabric returned by NvClothCookFabricFromMesh (or by *Factory::createFabric) already has a reference count of 1. The fabric will destroy itself when its reference count reaches 0. All fabrics created by a factory need to be destroyed before that factory is destroyed (meaning indirectly that all cloths, which hold references to these fabrics, also need to be destroyed).

You can also manually provide all the cooked data using the Factory::createFabric function directly in case you have your own cooking code or if you do not cook at runtime.

Cloth

We can now create a cloth instance from the fabric created in the previous step. The cloth instance does not need to begin with the same particle positions the fabric was cooked in, so we need to provide the initial position of the particles:

physx::PxVec4* particlePositions = ...; // The w component is the inverse mass of the particle
                                        //  and can be to 0 to lock the particle / make it static.
nv::cloth::Cloth* cloth = factory->createCloth(nv::cloth::Range<physx::PxVec4>( particlePositions, & particlePositions + particleCount), *fabric);
// particlePositions can be freed here.

...

NV_CLOTH_DELETE(cloth);

Now we need to setup the phase configurations. Phase configurations control the order in which the constraints are solved (e.g. first horizontal then vertical etc.) and the constrain properties like stiffness:

nv::cloth::PhaseConfig* phases = new nv::cloth::PhaseConfig[fabric->getNumPhases()];
for(int i = 0; i < fabric->getNumPhases(); i++)
{
       phases[i].mPhaseIndex = i; // Set index to the corresponding set (constraint group)

       //Give phases different configs depending on type
       switch(phaseTypeInfo[i])
       {
               case nv::cloth::ClothFabricPhaseType::eINVALID:
                       //ERROR
                       break;
               case nv::cloth::ClothFabricPhaseType::eVERTICAL:
                       break;
               case nv::cloth::ClothFabricPhaseType::eHORIZONTAL:
                       break;
               case nv::cloth::ClothFabricPhaseType::eBENDING:
                       break;
               case nv::cloth::ClothFabricPhaseType::eSHEARING:
                       break;
       }

       //For this example we give very phase the same config
       phases[i].mStiffness = 1.0f;
       phases[i].mStiffnessMultiplier = 1.0f;
       phases[i].mCompressionLimit = 1.0f;
       phases[i].mStretchLimit = 1.0f;
}
cloth->setPhaseConfig(nv::cloth::Range<nv::cloth::PhaseConfig>(&phases[0], &phases[0] + fabric->getNumPhases()));
delete [] phases;

Note that there might be more phase of each type. Check if the gravity vector used for cooking is correct (and in the same space as the cooked mesh) when the vertical phases are missing.

There are more properties you can set on the cloth. They will be described below in the Usage section.

Solver

The solver represents something similar to a scene. It has a list of cloths that will be simulated together and it contains some simulation properties. Creating a solver is simple:

nv::cloth::Solver* solver = factory->createSolver();

...

NV_CLOTH_DELETE(solver);

We can add and remove cloths to/from the scene as follows:

solver->addCloth(cloth);

...

solver->removeCloth(cloth);

Advancing the simulation is broken up in a couple of function calls:

float deltaTime = 1.0f/60.0f;
solver->beginSimulation(deltaTime);
for(int i = 0; i < solver->getSimulationChunkCount(); i++)
{
       solver->simulateChunk(i);
}
solver->endSimulation();

The simulateChunk() calls can be called concurrently from multiple threads to increase performance.

Retrieving simulation data

We need to retrieve the new positions of the cloth particles after the simulation step to display the results:

nv::cloth::MappedRange<physx::PxVec4> particles = mCloth->getCurrentParticles();
for(int i = 0; i<particles.size(); i++)
{
       //do something with particles[i]
       //the xyz components are the current positions
       //the w component is the invMass.
}
//destructor of particles should be called before mCloth is destroyed.

Usage

Cloth instances have many properties that can be influence their behavior. This section shows how to use some of the most common properties.

Common cloth properties

We can set the gravity acceleration vector in global space:

cloth->setGravity(physx::PxVec3(0.0f, -9.8f, 0.0f));

This vector doesn’t have to be the same as provided to the cooker.

Sometimes it is desirable to dampen the particle motion:

cloth->setDamping(0.5f); //0.0f is default

Note that the effect of the damping varies between local and global space simulation, as it dampens the local space motion only (so everything is damped if global space simulation is used as local and global space are equal in that case). To create effects like air drag or underwater motion it is better to use the wind / air drag properties instead.

We can change the accuracy (and stiffness) of the cloth by increasing the solver frequency. The solver frequency is used to calculate how many solver iterations are executed per frame (Solver::begin/endSimulation call):

cloth->setSolverFrequency(60.0f); //default is 300

There will be at least 1 iteration executed per frame, regardless of the value set. Sometimes lowering the solver frequency helps to avoid instabilities and oscillations, while a higher solver frequency can help to increase stiffness and improve other behavior like collision detection. It is common to set this value to a multiple of the fps target for the application.

Tethers

Tethers are generated at the cooking stage and stored in the fabric. They improve behavior and reduce stretchiness by adding long range constraints between particles and fixed points.

We can change some properties of the tethers in the cloth instance. We can change the length of all tethers by setting the scale:

cloth->setTetherConstraintScale(1.2f); //Increase the length by 20%

We can make the tethers more spring like, or even disable them by changing the stiffness:

cloth->setTetherConstraintStiffness(0.0f); //Disable tethers
cloth->setTetherConstraintStiffness(0.5f); //Springy tethers
cloth->setTetherConstraintStiffness(1.0f); //Default value

The whole tether stage is skipped if the stiffness is set to 0 (increasing performance).

Collision detection

NvCloth provides a couple of different methods to add collision to the simulation. All collision primitives are defined in local space.

We can define up to 32 collision spheres per cloth:

physx::PxVec4 spheres[2] = {
       physx::PxVec4(0.0f, 0.0f, 0.0f, 1.0f),
       physx::PxVec4(0.0f, 5.0f, 0.0f, 1.0f)
};
nv::cloth::Range<const physx::PxVec4> sphereRange(spheres, spheres + 2);
cloth->setSpheres(sphereRange, 0, cloth->getNumSpheres());

The last two arguments define which range of spheres defined previously is replaced (the parameters work the same across all Cloth::set’CollisionShape’ functions). This can be useful if only a couple of collision primitives changed since previous frame. Here we use the range [0, cloth->getNumSpheres()[ to ensure we replace all spheres that might have been defined previously. To insert the spheres at the beginning we could use [0, 0[ and to insert at the end we use [cloth->getNumSpheres(), cloth->getNumSpheres()[.

We can connect spheres to create capsules:

uint32_t capsuleIndices[2];
capsuleIndices[0]       = 0;
capsuleIndices[1]       = 1;

cloth->setCapsules(nv::cloth::Range<uint32_t>(capsuleIndices, capsuleIndices + 2), 0, cloth->getNumCapsules());

This connects sphere 0 and 1. Indices always need to be provided in pairs. Also note that the last two arguments specify indices of pairs. So cloth->getNumCapsules() will return 1 after the above snippet is executed.

We can also define up to 32 collision planes:

physx::PxVec4 planes[2] = {
       physx::PxVec4(physx::PxVec3(0.5f, 0.4f, 0.0f).getNormalized(), 3.0f),
       physx::PxVec4(physx::PxVec3(0.0f, 0.4f, 0.5f).getNormalized(), 3.0f)
};

nv::cloth::Range<const physx::PxVec4> planesR(planes, planes + 2);
cloth->setPlanes(planesR, 0, cloth->getNumPlanes());

This on its own will not make the cloth collide with anything as we first need to tell the solver that each plane is one convex shape:

uint32_t indices[2];
for(int i = 0; i < 2; i++)
       indices[i] = 1 << i;
nv::cloth::Range<uint32_t> indiceR(indices, indices + 2);
mCloth->setConvexes(indiceR, 0, mCloth->getNumConvexes());

The value stored in the indices array is a bit mask telling the solver which planes are part of the convex shape. Plane i is indicated by bit 1<<i. We can easily construct convex shapes consisting of more planes by setting more bits (e.g. (1<<i) | (1<<j)).

We can also use arbitrary triangle meshes for collision:

physx::PxVec3* triangles = ...; //Get triangle data from somewhere
                                //We can't use indexed meshes/vertex sharing,
                                // each triangle is defined with its own 3 vertexes
nv::cloth::Range<const physx::PxVec3> triangleR(&triangles[0], &triangles[0] + triangleCount * 3);
cloth->setTriangles(triangleR, 0, cloth>getNumTriangles());

Note that the range arguments passed to setTriangles are counting whole triangles (3 PxVec3 vertexes each).

We can set the friction coefficient used for collision between cloth and collision shapes like this:

cloth->setFriction(0.5);

Local space simulation

Using local space simulation gives you more control over the simulation allowing you to improve the stability and response of the simulation. We separate the global/render coordinate system from the particle simulation coordinate system in order to use local space simulation. Graphics transforms should be adjusted to render the cloth in the correct location again.

We can use the following methods to let the simulation know that the cloth coordinate system has moved relative to the global coordinates:

cloth->setTranslation(physx::PxVec3(x,y,z));
cloth->setRotation(physx::PxQuat(qx,qy,qz,qw));

This will not change the particle positions, but will apply impulses so that the cloth reacts properly to the movement of the coordinate system. Air drag and lift also reacts accordingly.

We can use an inertia multiplier to control the strength of these impulses. Fast movements can cause problems like tunneling through collision shapes, self-intersection, instabilities and stretchy cloth. Use smaller inertia multipliers if these issues are noticeable. We can set these multipliers as follows:

//All values should be between 0.0 and 1.0
cloth->setLinearInertia(physx::PxVec3(x,y,z));
cloth->setAngularInertia(physx::PxVec3(ax,ay,az));
cloth->setCentrifugalInertia(physx::PxVec3(cx,cy,cz));

If we want to move the cloth without applying any forces (in case of a world origin shift for example) we can teleport:

cloth->teleport(physx::PxVec3(deltaX, deltaY, deltaZ));

Or we can reset the inertia effects after using the setTranslation/setPosition functions:

//Clear any pending inertia
cloth->clearInertia();

Drag lift and wind

Cloth is simulated in a vacuum by default. We can set the drag and lift coefficients to make the simulation more natural:

cloth->setDragCoefficient(0.5f);
cloth->setLiftCoefficient(0.6f);

We can also add wind to the simulation:

cloth->setWindVelocity(physx::PxVec3(x,y,z));

It is a good idea to vary this parameter continuously to simulate gusts, making the simulation more lively.