示例#1
0
void InternalIndexPool::freeIndices(PxU32 num, const PxStrideIterator<const PxU32>& indexBuffer)
{
	PxU32 numAllocated = mIndexCount - mFreeList.size();
	if (num > numAllocated)
	{
		Ps::getFoundation().error(PxErrorCode::eDEBUG_WARNING, __FILE__, __LINE__, 
			"PxParticleExt::IndexPool::freeIndices: Provided number of indices exceeds number of actually allocated indices. Call faild.");
		return;
	}

#ifdef PX_CHECK
	for (PxU32 i = 0; i < num; ++i)
	{
		if (indexBuffer[i] < mIndexCount)
			continue;
			
		Ps::getFoundation().error(PxErrorCode::eDEBUG_WARNING, __FILE__, __LINE__, 
			"PxParticleExt::IndexPool::freeIndices: Provided indices which where not actually allocated before. Call failed.");
		return;
	}
#endif

	for (PxU32 i = 0; i < num; ++i)
		mFreeList.pushBack(indexBuffer[i]);
}
示例#2
0
PxU32 InternalIndexPool::allocateIndices(PxU32 num, const PxStrideIterator<PxU32>& indexBuffer)
{
	PxU32 numAllocated = mIndexCount - mFreeList.size();
	PxU32 numFree = mFreeList.capacity() - numAllocated;
	PxU32 numAccepted = PxMin(num, numFree);

	if (numAccepted < num)
		Ps::getFoundation().error(PxErrorCode::eDEBUG_WARNING, __FILE__, __LINE__, 
			"PxParticleExt::IndexPool::allocateIndices: Not all requested indices allocated; maximum reached.");

	PxU32 numAdded = 0;

	PxU32 numToAddFromFreeList = PxMin(numAccepted, mFreeList.size());
	PxU32 numToAddFromFullBlock = numAccepted - numToAddFromFreeList;
	
	//add from free list
	while (numToAddFromFreeList > 0)
	{
		indexBuffer[numAdded++] = mFreeList.popBack();
		numToAddFromFreeList--;
	}
	
	//add from full block
	while (numToAddFromFullBlock > 0)
	{
		indexBuffer[numAdded++] = mIndexCount++;
		numToAddFromFullBlock--;
	}
	
	PX_ASSERT(numAdded == numAccepted);
	return numAccepted;
}
bool Cm::CompleteBoxPruning(const PxBounds3* bounds, PxU32 nb, Ps::Array<PxU32>& pairs, const Axes& axes)
{
	pairs.clear();

	// Checkings
	if(!nb)
		return false;

	// Catch axes
	const PxU32 Axis0 = axes.mAxis0;
	const PxU32 Axis1 = axes.mAxis1;
	const PxU32 Axis2 = axes.mAxis2;

	PX_UNUSED(Axis1);
	PX_UNUSED(Axis2);

	// Allocate some temporary data
	float* PosList = reinterpret_cast<float*>(PX_ALLOC_TEMP(sizeof(float)*nb, "Cm::CompleteBoxPruning"));

	// 1) Build main list using the primary axis
	for(PxU32 i=0;i<nb;i++)	PosList[i] = bounds[i].minimum[Axis0];

	// 2) Sort the list
	/*static*/ RadixSortBuffered RS;	// Static for coherence
	const PxU32* Sorted = RS.Sort(PosList, nb).GetRanks();

	// 3) Prune the list
	const PxU32* const LastSorted = &Sorted[nb];
	const PxU32* RunningAddress = Sorted;
	PxU32 Index0, Index1;
	while(RunningAddress<LastSorted && Sorted<LastSorted)
	{
		Index0 = *Sorted++;

		while(RunningAddress<LastSorted && PosList[*RunningAddress++]<PosList[Index0]);

		const PxU32* RunningAddress2 = RunningAddress;

		while(RunningAddress2<LastSorted && PosList[Index1 = *RunningAddress2++]<=bounds[Index0].maximum[Axis0])
		{
			if(Index0!=Index1)
			{
				if(bounds[Index0].intersects(bounds[Index1]))
				{
					pairs.pushBack(Index0);
					pairs.pushBack(Index1);
				}
			}
		}
	}

	PX_FREE(PosList);

	return true;
}
static void accumulate(PvdHitType& query, Ps::Array<PvdHitType>& accumulated, const char* arrayName, Ps::Array<PvdSqHit>& dst, const SDKHitType* src, PxU32 nb, const PxQueryFilterData& fd)
{
	query.mFilterFlags = fd.flags;
	query.mHits = PvdReference(arrayName, dst.size(), nb);

	PX_ASSERT(PxU32(-1) != nb);
	for(PxU32 i=0; i<nb; i++)
		dst.pushBack(PvdSqHit(src[i]));

	accumulated.pushBack(query);
}
		void ProfileEventHandler::onCUDAProfileBuffer( PxU64 , PxF32  , const PxU8* cudaData, PxU32 bufLenInBytes, PxU32 bufferVersion )
		{
			if(bufferVersion == 1)
			{
				struct currentWarpProfileEvent
				{
					PxU16 block;
					PxU8  warp;
					PxU8  mpId;
					PxU8  hwWarpId;
					PxU8  userDataCfg;
					PxU16 eventId;
					PxU32 startTime;
					PxU32 endTime;
				};

				Ps::Array<Local::OverflowRecordPair> overflowPair;

				//Run through dataset.  We need to be able to correct rollover, meaning one of the timers
				//runs through PxU32.MAX_VALUE and resets to zero.
				PxU32 numEvents = bufLenInBytes/sizeof(currentWarpProfileEvent);
				const currentWarpProfileEvent* cudaEvents = reinterpret_cast<const currentWarpProfileEvent*> (cudaData);
				for (PxU32 i = 0; i < numEvents; i++)
				{
					const currentWarpProfileEvent& cudaEvent = cudaEvents[i];
					Local::OverflowRecord* record = NULL;
					for (PxU32 j = 0; j < overflowPair.size(); j++)
					{
						if(overflowPair[j].mpId == cudaEvent.mpId)
						{
							record = &overflowPair[j].mOverflowRecord;
							break;
						}
					}

					if(!record)
					{
						Local::OverflowRecordPair pair;
						pair.mpId = cudaEvent.mpId;						
						overflowPair.pushBack(pair);
						record = &overflowPair.back().mOverflowRecord;
					}
					
					//account for overflow
					PxU64 startTime = record->NextValue(cudaEvent.startTime);
					PxU64 endTime = record->NextValue(cudaEvent.endTime);

					CUDAProfileEventOccurence cudaEventOccurence = { cudaEvent.eventId, startTime, endTime };
					mCUDAEventOccurences.pushBack(cudaEventOccurence);
				}
			}		
		}
		void ProfileEventHandler::reportCudaCollection(PxBufferedProfilerCallback& callback)
		{
			if(mCUDAEventOccurences.empty())
				return;

			// sort the cuda occurrences - according to eventID and startTime
			Ps::sort(mCUDAEventOccurences.begin(), mCUDAEventOccurences.size(), SortCUDAProfileOccurences());
			PxU16 currentEventId = mCUDAEventOccurences[0].eventId;
			CUDAProfileEventOccurence currentEvent = mCUDAEventOccurences[0];
			Ps::Array<CUDAProfileEventOccurence> outOccurences;

			// now group the occurrences that do overlap
			// put the group occurrences into out array
			for (PxU32 i = 1; i < mCUDAEventOccurences.size(); i++)
			{
				const CUDAProfileEventOccurence& occurence = mCUDAEventOccurences[i];
				// occurrences are sorted by eventId, so once it changes, we have new group occurrence
				if(currentEventId != occurence.eventId)
				{
					outOccurences.pushBack(currentEvent);
					currentEvent = occurence;
					currentEventId = occurence.eventId;
				}
				else
				{
					// occurrences are sorted by start time, so we check for overlap and add occurrence or create new group
					if(currentEvent.endTime >= occurence.startTime)
					{
						currentEvent.endTime = PxMax(currentEvent.endTime, occurence.endTime);
					}
					else
					{
						outOccurences.pushBack(currentEvent);
						currentEvent = occurence;
					}
				}
			}
			outOccurences.pushBack(currentEvent);

			// fire the callback with grouped occurrences
			for (PxU32 i = outOccurences.size(); i--;)
			{
				const CUDAProfileEventOccurence& ev = outOccurences[i];				

				const char* name = mProfileZoneInterface->getProfileEventName(ev.eventId);
				const char* profileZoneName = mProfileZoneInterface->getName();

				PxBufferedProfilerEvent ce = { ev.startTime, ev.endTime, name, profileZoneName, ev.eventId, 0, 0, 0, 0 };

				callback.onEvent(ce);
			}
		}
void DeformableMesh::generateConstraintsFromTriangles()
{
	PxU32 numTriangles = mPrimitives.size() / 3;

	DeformableTriEdge e;
	Ps::Array<DeformableTriEdge> edges PX_DEBUG_EXP("defoMeshEdges");
	edges.reserve(numTriangles * 3);
	const PxU32 *i0 = mPrimitives.begin();

	for (PxU32 i = 0; i < numTriangles; i++, i0 += 3) 
	{
		const PxU32 *i1 = i0+1;
		const PxU32 *i2 = i0+2;
		e.set(mVertexToParticleMap[*i0], mVertexToParticleMap[*i1], mVertexToParticleMap[*i2], i); 
		edges.pushBack(e);
		e.set(mVertexToParticleMap[*i1], mVertexToParticleMap[*i2], mVertexToParticleMap[*i0], i); 
		edges.pushBack(e);
		e.set(mVertexToParticleMap[*i2], mVertexToParticleMap[*i0], mVertexToParticleMap[*i1], i); 
		edges.pushBack(e);
	}
	quickSortEdges(edges, 0, edges.size()-1);

	DeformableConstraint constraint;
	constraint.particleId[4] = -1;	// only used when torn
	constraint.particleId[5] = -1;	// only used when torn

	for(PxU32 i=0; i<edges.size(); )
	{
		const DeformableTriEdge &e0 = edges[i];
		PxU32 p0 = constraint.particleId[0] = e0.particleId[0];
		PxU32 p1 = constraint.particleId[1] = e0.particleId[1];
		PxVec3 d0 = mWeldedVertices[p1] - mWeldedVertices[p0]; 

		constraint.stretchingRestLength = d0.magnitude();
		constraint.particleId[2] = e0.third;
		constraint.particleId[3] = -1;			// for border edges -> no bending possible
		constraint.bendingRestLength = 0.0f;
		constraint.flags = 0;
		if (++i < edges.size()) {
			const DeformableTriEdge &e1 = edges[i];
			if (e0 == e1) {
				constraint.particleId[2] = e0.third;
				constraint.particleId[3] = e1.third;
				PxVec3 d1 = mWeldedVertices[e1.third] - mWeldedVertices[e0.third]; 
				constraint.bendingRestLength = d1.magnitude();
			}
			while (i < edges.size() && edges[i] == e0)
				++i;
		}
		mConstraints.pushBack(constraint);
	}
}
static void collectBatchedHits(const QueryResultT* results, Ps::Array<PvdHitType>& accumulated, Ps::Array<PvdSqHit>& pvdSqHits, PxU32 nb, PxU32 startIdx, const char* arrayName)
{
	for(PxU32 i=0; i<nb; i++)
	{
		const QueryResultT& result = results[i];
		if(result.queryStatus != PxBatchQueryStatus::eSUCCESS)
			continue;

		PvdHitType& query = accumulated[startIdx + i];
		const PxU32 nbAnyHits = result.getNbAnyHits();
		if(query.mHits.mCount != nbAnyHits)
		{
			query.mHits = PvdReference(arrayName, pvdSqHits.size(), nbAnyHits);

			for(PxU32 j=0; j<nbAnyHits; j++)
				pvdSqHits.pushBack(PvdSqHit(result.getAnyHit(j)));
		}
	}
}
void CharacterControllerManager::computeInteractions(PxF32 elapsedTime)
{
	PxU32 nbControllers = mControllers.size();
	Controller** controllers = mControllers.begin();

	PxBounds3* boxes = new PxBounds3[nbControllers];	// PT: TODO: get rid of alloc
	PxBounds3* runningBoxes = boxes;

	while(nbControllers--)
	{
		Controller* current = *controllers++;

		PxExtendedBounds3 extBox;
		current->getWorldBox(extBox);

		*runningBoxes++ = PxBounds3(toVec3(extBox.minimum), toVec3(extBox.maximum));	// ### LOSS OF ACCURACY
	}

	//

	const PxU32 nbEntities = runningBoxes - boxes;

	Ps::Array<PxU32> pairs;	// PT: TODO: get rid of alloc
	CompleteBoxPruning(boxes, nbEntities, pairs, Gu::Axes(physx::Gu::AXES_XZY));	// PT: TODO: revisit for variable up axis

	PxU32 nbPairs = pairs.size()>>1;
	const PxU32* indices = pairs.begin();
	while(nbPairs--)
	{
		const PxU32 index0 = *indices++;
		const PxU32 index1 = *indices++;
		Controller* ctrl0 = mControllers[index0];
		Controller* ctrl1 = mControllers[index1];
		InteractionCharacterCharacter(ctrl0, ctrl1, elapsedTime);
	}

	delete [] boxes;
}
		void ProfileEventHandler::reportEvents(Ps::Array<PxBufferedProfilerCallback*>& callbacks)
		{
			PX_ASSERT(mProfileZoneInterface);

			for (PxU32 callbackIndex = callbacks.size(); callbackIndex--; )
			{
				PxBufferedProfilerCallback& callback = *callbacks[callbackIndex];
				reportCollection(callback, mCrossThreadCollection);
				for (PxU32 i = mThreadCollections.size(); i--;)
				{
					reportCollection(callback, mThreadCollections[i]);
				}

				reportCudaCollection(callback);
			}

			clear();
		}
示例#11
0
PxU32 PxParticleExt::buildBoundsHash(PxU32* sortedParticleIndices,
									 ParticleBounds* particleBounds,
									 const PxStrideIterator<const PxVec3>& positionBuffer,
									 const PxU32 validParticleRange,
									 const PxU32* validParticleBitmap,
									 const PxU32 hashSize,
									 const PxU32 maxBounds,
									 const PxReal gridSpacing)
{
	// test if hash size is a multiple of 2
	PX_ASSERT((((hashSize - 1) ^ hashSize) + 1) == (2 * hashSize));
	PX_ASSERT(maxBounds <= hashSize);

	PxReal cellSizeInv = 1.0f / gridSpacing;

	Ps::Array<PxU32> particleToCellMap PX_DEBUG_EXP("buildBoundsHashCellMap"); 	
	particleToCellMap.resize(validParticleRange);

	// initialize cells
	Ps::Array<Cell> cells PX_DEBUG_EXP("buildBoundsCells");
	cells.resize(hashSize);
	PxMemSet(cells.begin(), sInvalidIndex, sizeof(Cell) * hashSize);

	// count number of particles per cell
	PxU32 entryCounter = 0;

	if (validParticleRange > 0)
	{
		for (PxU32 w = 0; w <= (validParticleRange-1) >> 5; w++)
			for (PxU32 b = validParticleBitmap[w]; b; b &= b-1)
			{
				PxU32 index = (w<<5|Ps::lowestSetBit(b));
				const PxVec3& position = positionBuffer[index];

				PxU32& cellIndex = particleToCellMap[index];
				cellIndex = sInvalidIndex;	// initialize to invalid in case we reach maxBounds	
				if (entryCounter < maxBounds)
				{
					CellCoords particleCoords;
					particleCoords.set(position, cellSizeInv);
					cellIndex = getEntry(particleCoords, hashSize, cells.begin());
					PX_ASSERT(cellIndex != sInvalidIndex);

					Cell& cell = cells[cellIndex];
					if (cell.size == sInvalidIndex)
					{
						// this is the first particle in this cell
						cell.coords = particleCoords;
						cell.aabb = PxBounds3(position, position);
						cell.size = 1;
						++entryCounter;
					}
					else
					{
						// the cell is already occupied
						cell.aabb.include(position);
						++cell.size;
					}
				}
			}
	}


	// accumulate start indices from cell size histogram and write to the user's particleBounds buffer
	PxU32 numBounds = 0;
	for (PxU32 i = 0, counter = 0; i < cells.size(); i++)
	{
		Cell& cell = cells[i];
		if (cell.size != sInvalidIndex)
		{
			cell.start = counter;
			counter += cell.size;
			
			PxParticleExt::ParticleBounds& cellBounds = particleBounds[numBounds++];
			PX_ASSERT(cell.aabb.isValid());
			cellBounds.bounds = cell.aabb;
			cellBounds.firstParticle = cell.start;
			cellBounds.numParticles = cell.size;
			
			cell.size = 0;
		}
	}

	// sort output particle indices by cell
	if (validParticleRange > 0)
	{
		for (PxU32 w = 0; w <= (validParticleRange-1) >> 5; w++)
			for (PxU32 b = validParticleBitmap[w]; b; b &= b-1)
			{
				PxU32 index = (w<<5|Ps::lowestSetBit(b));
				PxU32 cellIndex = particleToCellMap[index];
				if (cellIndex != sInvalidIndex)
				{
					Cell& cell = cells[cellIndex];
					PX_ASSERT(cell.start != sInvalidIndex && cell.size != sInvalidIndex);
					sortedParticleIndices[cell.start + cell.size] = index;
					++cell.size;
				}
			}
	}
	
	return numBounds;
}
示例#12
0
void InternalIndexPool::freeIndices()
{
	mIndexCount = 0;
	mFreeList.clear();	
}
bool Cm::BipartiteBoxPruning(const PxBounds3* bounds0, PxU32 nb0, const PxBounds3* bounds1, PxU32 nb1, Ps::Array<PxU32>& pairs, const Axes& axes)
{
	pairs.clear();
	// Checkings
	if(nb0 == 0 || nb1 == 0)
		return false;

	// Catch axes
	PxU32 Axis0 = axes.mAxis0;
	PxU32 Axis1 = axes.mAxis1;
	PxU32 Axis2 = axes.mAxis2;

	PX_UNUSED(Axis1);
	PX_UNUSED(Axis2);

	// Allocate some temporary data
	float* MinPosBounds0 = reinterpret_cast<float*>(PX_ALLOC_TEMP(sizeof(float)*nb0, "Gu::BipartiteBoxPruning"));
	float* MinPosBounds1 = reinterpret_cast<float*>(PX_ALLOC_TEMP(sizeof(float)*nb1, "Gu::BipartiteBoxPruning"));

	// 1) Build main lists using the primary axis
	for(PxU32 i=0;i<nb0;i++)	MinPosBounds0[i] = bounds0[i].minimum[Axis0];
	for(PxU32 i=0;i<nb1;i++)	MinPosBounds1[i] = bounds1[i].minimum[Axis0];

	// 2) Sort the lists
	//static RadixSort RS0, RS1;	// Static for coherence. Crashes on exit
	RadixSortBuffered RS0, RS1;	// Static for coherence.

	const PxU32* Sorted0 = RS0.Sort(MinPosBounds0, nb0).GetRanks();
	const PxU32* Sorted1 = RS1.Sort(MinPosBounds1, nb1).GetRanks();

	// 3) Prune the lists
	PxU32 Index0, Index1;

	const PxU32* const LastSorted0 = &Sorted0[nb0];
	const PxU32* const LastSorted1 = &Sorted1[nb1];
	const PxU32* RunningAddress0 = Sorted0;
	const PxU32* RunningAddress1 = Sorted1;

	while(RunningAddress1<LastSorted1 && Sorted0<LastSorted0)
	{
		Index0 = *Sorted0++;

		while(RunningAddress1<LastSorted1 && MinPosBounds1[*RunningAddress1]<MinPosBounds0[Index0])	RunningAddress1++;

		const PxU32* RunningAddress2_1 = RunningAddress1;

		while(RunningAddress2_1<LastSorted1 && MinPosBounds1[Index1 = *RunningAddress2_1++]<=bounds0[Index0].maximum[Axis0])
		{
			if(bounds0[Index0].intersects(bounds1[Index1]))
			{
				pairs.pushBack(Index0);
				pairs.pushBack(Index1);
			}
		}
	}

	////

	while(RunningAddress0<LastSorted0 && Sorted1<LastSorted1)
	{
		Index0 = *Sorted1++;

		while(RunningAddress0<LastSorted0 && MinPosBounds0[*RunningAddress0]<=MinPosBounds1[Index0])	RunningAddress0++;

		const PxU32* RunningAddress2_0 = RunningAddress0;

		while(RunningAddress2_0<LastSorted0 && MinPosBounds0[Index1 = *RunningAddress2_0++]<=bounds1[Index0].maximum[Axis0])
		{
			if(bounds0[Index1].intersects(bounds1[Index0]))
			{
				pairs.pushBack(Index1);
				pairs.pushBack(Index0);
			}
		}
	}

	PX_FREE(MinPosBounds1);
	PX_FREE(MinPosBounds0);

	return true;
}
void DeformableMesh::calculateInvMasses(Ps::Array<PxReal>& invMasses, PxReal mass) const
{
	PX_ASSERT(mVertexMasses.empty());
	PX_ASSERT(invMasses.size() == mVertexPositions.size());

	// clear output array
	for (PxU32 i = 0; i < invMasses.size(); i++)
		invMasses[i] = 0.0f;
	
	switch (mPrimitiveType)
	{
	case PxDeformablePrimitiveType::eTRIANGLE:
		{
			// first, we compute area associated with each vertex and temporarily store it in invMasses array
			PxReal totalArea = 0.0f;
			const PxU32* i0 = mPrimitives.begin();
			for (PxU32 i = 0; i < mPrimitives.size() / 3; ++i)
			{
				const PxU32* i1 = i0 + 1;
				const PxU32* i2 = i0 + 2;
				PxVec3 d0(mVertexPositions[*i0].x, mVertexPositions[*i0].y, mVertexPositions[*i0].z);
				PxVec3 d1(mVertexPositions[*i1].x, mVertexPositions[*i1].y, mVertexPositions[*i1].z);
				PxVec3 d2(mVertexPositions[*i2].x, mVertexPositions[*i2].y, mVertexPositions[*i2].z);
				d1 = d1 - d0;
				d2 = d2 - d0;
				PxVec3 n = d1.cross(d2);
				PxReal area = 0.5f * n.magnitude();
				invMasses[*i0] += area / 3.0f;
				invMasses[*i1] += area / 3.0f;
				invMasses[*i2] += area / 3.0f;
				totalArea += area;
				i0 += 3;
			}
			// distribute overall mass by associated area
			for (PxU32 i = 0; i < invMasses.size(); i++)
			{
				PxReal area = invMasses[i];
				invMasses[i] = (1.0f / mass) * (totalArea / area);
			}
		}
		break;
	case PxDeformablePrimitiveType::eTETRAHEDRON:
		{
			// first, we compute volume associated with each vertex and temporarily store it in invMasses array
			PxReal totalVolume = 0.0f;
			const PxU32* i0 = mPrimitives.begin();
			for (PxU32 i = 0; i < mPrimitives.size() / 4; i++)
			{
				const PxU32 *i1 = i0 + 1;
				const PxU32 *i2 = i0 + 2;
				const PxU32 *i3 = i0 + 3;
				PxVec3 d0(mVertexPositions[*i0].x, mVertexPositions[*i0].y, mVertexPositions[*i0].z);
				PxVec3 d1(mVertexPositions[*i1].x, mVertexPositions[*i1].y, mVertexPositions[*i1].z);
				PxVec3 d2(mVertexPositions[*i2].x, mVertexPositions[*i2].y, mVertexPositions[*i2].z);
				PxVec3 d3(mVertexPositions[*i3].x, mVertexPositions[*i3].y, mVertexPositions[*i3].z);
				d1 = d1 - d0;
				d2 = d2 - d0;
				d3 = d3 - d0;
				PxVec3 n = d1.cross(d2);
				PxReal volume = n.dot(d3) / 6.0f;
				invMasses[*i0] += volume / 4.0f;
				invMasses[*i1] += volume / 4.0f;
				invMasses[*i2] += volume / 4.0f;
				invMasses[*i3] += volume / 4.0f;
				totalVolume += volume;
				i0 += 4;
			}
			// distribute overall mass by associated volume
			for (PxU32 i = 0; i < invMasses.size(); i++)
			{
				PxReal volume = invMasses[i];
				invMasses[i] = (1.0f / mass) * (totalVolume / volume);
			}

		}
		break;
	default:
		PX_ASSERT(0);
		break;
	}	
}
void DeformableMesh::generateConstraintsFromTetrahedra()
{
	PxU32 edgeIndices[6][2] = { {0,1}, {0,2}, {0,3}, {1,2}, {1,3}, {2,3} };
	PxU32 *tetIndices;

	// - tetrahedra are assumed to be unique (thus no parent code here)

	PxU32 numTetrahedra = mPrimitives.size() / 4;

	DeformableTetraEdge e;
	Ps::Array<DeformableTetraEdge> edges PX_DEBUG_EXP("defoMeshEdges2");
	edges.reserve(numTetrahedra * 6);
	tetIndices = mPrimitives.begin();
	PxU32 i, j;
	for (i = 0; i < numTetrahedra; i++, tetIndices += 4) 
	{
		for(j = 0; j < 6; j++) 
		{
			PxU32 e0 = mVertexToParticleMap[tetIndices[edgeIndices[j][0]]];
			PxU32 e1 = mVertexToParticleMap[tetIndices[edgeIndices[j][1]]];
			e.set(e0, e1, i); edges.pushBack(e);	
		}
	}
	
	quickSortTetraEdges(edges, 0, edges.size()-1);

	mConstraints.resize(numTetrahedra);

	DeformableConstraint constraint;
	DeformableTetraEdge *tetEdges[6];
	tetIndices = mPrimitives.begin();
	
	bool warningIssued = false;
	for (i = 0; i < numTetrahedra; i++, tetIndices += 4) 
	{
		for (j = 0; j < 6; j++) 
		{
			PxU32 e0 = mVertexToParticleMap[tetIndices[edgeIndices[j][0]]];
			PxU32 e1 = mVertexToParticleMap[tetIndices[edgeIndices[j][1]]];
			DeformableTetraEdge goalEdge;
			goalEdge.set(e0, e1, i);
			tetEdges[j] = binarySearchTetraEdge(edges, goalEdge);
		}

		for (j = 0; j < 4; j++)
			constraint.particleId[j] = mVertexToParticleMap[tetIndices[j]];
		
		PxVec3 groundArea = (mWeldedVertices[tetIndices[1]] - mWeldedVertices[tetIndices[0]]).cross(mWeldedVertices[tetIndices[2]] - mWeldedVertices[tetIndices[0]]);
		constraint.restVolume = groundArea.dot(mWeldedVertices[tetIndices[3]] - mWeldedVertices[tetIndices[0]]);
		constraint.flags = 0;

		if (!warningIssued && constraint.restVolume < 0.0f)
		{
			Ps::getFoundation().error(PxErrorCode::eDEBUG_WARNING, __FILE__, __LINE__, "Soft body mesh tetrahedron %d has illegal winding order.", i);
			warningIssued = true;
		}

		for (j = 0; j < 6; j++) 
		{
			PxU32 e0 = mVertexToParticleMap[tetIndices[edgeIndices[j][0]]];
			PxU32 e1 = mVertexToParticleMap[tetIndices[edgeIndices[j][1]]];
			PxVec3 edgeVec = mWeldedVertices[e1] - mWeldedVertices[e0];
			if(tetEdges[j]) 
			{
				PX_ASSERT(tetEdges[j]->tetrahedron == i);
				constraint.restEdgeLengths[j] = edgeVec.magnitude();
			} 
			else 
				constraint.restEdgeLengths[j] = -edgeVec.magnitude();
		}

		mConstraints[i] = constraint;
	}
}
template<class Type> static void pushBackT(Ps::Array<Type>& array, const Type& item, PvdReference& ref, const char* arrayName)
{
	ref = PvdReference(arrayName, array.size(), 1);
	array.pushBack(item);
}
void DeformableMesh::weldMesh()
{
	mVertexToParticleMap.resize(mVertexPositions.size());

	if (mPrimitiveType == PxDeformablePrimitiveType::eTRIANGLE && (mFlags & PxDeformableMeshFlag::eWELD_VERTICES) != 0)
	{
		Ps::Array<int> order PX_DEBUG_EXP("defoMeshOrder");
		order.resize(mVertexPositions.size());
		for (PxU32 i = 0; i < order.size(); i++)
			order[i] = i;

		// sort vertices by their x coordinate
		Ps::sort(order.begin(), order.size(), WeldComparator(mVertexPositions));

		// generate permutation table which welds similar vertices
		for (PxU32 i = 0; i < mVertexPositions.size(); i++)
			mVertexToParticleMap[i] = PX_MAX_U32;

		int newNr = 0;
		PxReal mWeldingDistanceSq = mWeldingDistance * mWeldingDistance;
		for (PxU32 i = 0; i < mVertexPositions.size(); i++) 
		{
			int oldNr = order[i];
			if (mVertexToParticleMap[oldNr] != PX_MAX_U32)
				continue;
			
			mVertexToParticleMap[oldNr] = newNr;
			PxVec3 vi = mVertexPositions[oldNr];
			PxU32 j = i+1;
			while (j < mVertexPositions.size() && PxAbs(vi.x - mVertexPositions[order[j]].x) <= mWeldingDistance) 
			{
				oldNr = order[j];
				if ((vi-mVertexPositions[oldNr]).magnitudeSquared() <= mWeldingDistanceSq) 
				{
					if (mVertexToParticleMap[oldNr] == PX_MAX_U32)
						mVertexToParticleMap[oldNr] = newNr;
				}
				j++;
			}
			newNr++;
		}
		mNumWeldedVertices = newNr;
	}
	else
	{
		// Welding not enabled. We use an identity permutation.
		for (PxU32 i = 0; i < mVertexToParticleMap.size(); i++)
			mVertexToParticleMap[i] = i;

#if 0
		Ps::Array<int> order;
		order.resize(mVertexPositions.size());
		for (PxU32 i = 0; i < order.size(); i++)
			order[i] = i;
		Ps::sort(order.begin(), order.size(), WeldComparator(mVertexPositions));
		for (PxU32 i = 0; i < mVertexToParticleMap.size(); i++)
			mVertexToParticleMap[i] = order[i];
#endif

		mNumWeldedVertices = mVertexPositions.size();
	}

	mWeldedVertices.resize(mNumWeldedVertices);
	for (PxU32 i = 0; i < mVertexPositions.size(); i++)
		mWeldedVertices[mVertexToParticleMap[i]] = mVertexPositions[i];
}