MSTRINGIFY( cbuffer SolvePositionsFromLinksKernelCB : register( b0 ) { unsigned int numNodes; float isolverdt; int padding0; int padding1; }; struct CollisionObjectIndices { int firstObject; int endObject; }; struct CollisionShapeDescription { float4x4 shapeTransform; float4 linearVelocity; float4 angularVelocity; int softBodyIdentifier; int collisionShapeType; // Shape information // Compressed from the union float radius; float halfHeight; float margin; float friction; int padding0; int padding1; }; // From btBroadphaseProxy.h static const int CAPSULE_SHAPE_PROXYTYPE = 10; // Node indices for each link StructuredBuffer g_vertexClothIdentifier : register( t0 ); StructuredBuffer g_vertexPreviousPositions : register( t1 ); StructuredBuffer g_perClothFriction : register( t2 ); StructuredBuffer g_clothDampingFactor : register( t3 ); StructuredBuffer g_perClothCollisionObjectIndices : register( t4 ); StructuredBuffer g_collisionObjectDetails : register( t5 ); RWStructuredBuffer g_vertexForces : register( u0 ); RWStructuredBuffer g_vertexVelocities : register( u1 ); RWStructuredBuffer g_vertexPositions : register( u2 ); // A buffer of local collision shapes // TODO: Iterate to support more than 16 groupshared CollisionShapeDescription localCollisionShapes[16]; [numthreads(128, 1, 1)] void SolveCollisionsAndUpdateVelocitiesKernel( uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint GI : SV_GroupIndex ) { int nodeID = DTid.x; float3 forceOnVertex = float3(0.f, 0.f, 0.f); int clothIdentifier = g_vertexClothIdentifier[nodeID]; float4 position = float4(g_vertexPositions[nodeID].xyz, 1.f); float4 previousPosition = float4(g_vertexPreviousPositions[nodeID].xyz, 1.f); float3 velocity; float clothFriction = g_perClothFriction[clothIdentifier]; float dampingFactor = g_clothDampingFactor[clothIdentifier]; float velocityCoefficient = (1.f - dampingFactor); CollisionObjectIndices collisionObjectIndices = g_perClothCollisionObjectIndices[clothIdentifier]; int numObjects = collisionObjectIndices.endObject - collisionObjectIndices.firstObject; if( numObjects > 0 ) { // We have some possible collisions to deal with // First load all of the collision objects into LDS int numObjects = collisionObjectIndices.endObject - collisionObjectIndices.firstObject; if( GTid.x < numObjects ) { localCollisionShapes[GTid.x] = g_collisionObjectDetails[ collisionObjectIndices.firstObject + GTid.x ]; } } // Safe as the vertices are padded so that not more than one soft body is in a group AllMemoryBarrierWithGroupSync(); // Annoyingly, even though I know the flow control is not varying, the compiler will not let me skip this if( numObjects > 0 ) { velocity = float3(0, 0, 0); // We have some possible collisions to deal with for( int collision = 0; collision < numObjects; ++collision ) { CollisionShapeDescription shapeDescription = localCollisionShapes[collision]; float colliderFriction = shapeDescription.friction; if( shapeDescription.collisionShapeType == CAPSULE_SHAPE_PROXYTYPE ) { // Colliding with a capsule float capsuleHalfHeight = localCollisionShapes[collision].halfHeight; float capsuleRadius = localCollisionShapes[collision].radius; float capsuleMargin = localCollisionShapes[collision].margin; float4x4 worldTransform = localCollisionShapes[collision].shapeTransform; float4 c1 = float4(0.f, -capsuleHalfHeight, 0.f, 1.f); float4 c2 = float4(0.f, +capsuleHalfHeight, 0.f, 1.f); float4 worldC1 = mul(worldTransform, c1); float4 worldC2 = mul(worldTransform, c2); float3 segment = (worldC2 - worldC1).xyz; // compute distance of tangent to vertex along line segment in capsule float distanceAlongSegment = -( dot( (worldC1 - position).xyz, segment ) / dot(segment, segment) ); float4 closestPoint = (worldC1 + float4(segment * distanceAlongSegment, 0.f)); float distanceFromLine = length(position - closestPoint); float distanceFromC1 = length(worldC1 - position); float distanceFromC2 = length(worldC2 - position); // Final distance from collision, point to push from, direction to push in // for impulse force float dist; float3 normalVector; if( distanceAlongSegment < 0 ) { dist = distanceFromC1; normalVector = normalize(position - worldC1).xyz; } else if( distanceAlongSegment > 1.f ) { dist = distanceFromC2; normalVector = normalize(position - worldC2).xyz; } else { dist = distanceFromLine; normalVector = normalize(position - closestPoint).xyz; } float3 colliderLinearVelocity = localCollisionShapes[collision].linearVelocity.xyz; float3 colliderAngularVelocity = localCollisionShapes[collision].angularVelocity.xyz; float3 velocityOfSurfacePoint = colliderLinearVelocity + cross(colliderAngularVelocity, position.xyz - worldTransform._m03_m13_m23); float minDistance = capsuleRadius + capsuleMargin; // In case of no collision, this is the value of velocity velocity = (position - previousPosition).xyz * velocityCoefficient * isolverdt; // Check for a collision if( dist < minDistance ) { // Project back to surface along normal position = position + float4((minDistance - dist)*normalVector*0.9, 0.f); velocity = (position - previousPosition).xyz * velocityCoefficient * isolverdt; float3 relativeVelocity = velocity - velocityOfSurfacePoint; float3 p1 = normalize(cross(normalVector, segment)); float3 p2 = normalize(cross(p1, normalVector)); // Full friction is sum of velocities in each direction of plane float3 frictionVector = p1*dot(relativeVelocity, p1) + p2*dot(relativeVelocity, p2); // Real friction is peak friction corrected by friction coefficients frictionVector = frictionVector * (colliderFriction*clothFriction); float approachSpeed = dot(relativeVelocity, normalVector); if( approachSpeed <= 0.0 ) forceOnVertex -= frictionVector; } } } } else { // Update velocity float3 difference = position.xyz - previousPosition.xyz; velocity = difference*velocityCoefficient*isolverdt; } g_vertexVelocities[nodeID] = float4(velocity, 0.f); // Update external force g_vertexForces[nodeID] = float4(forceOnVertex, 0.f); g_vertexPositions[nodeID] = float4(position.xyz, 0.f); } );