void PxSingleActor::sweepTest( MatrixF *mat )
{
   NxVec3 nxCurrPos = getPosition();

   // If the position is zero, 
   // the parent hasn't been updated yet
   // and we don't even need to do the sweep test.
   // This is a fix for a problem that was happening
   // where on the add of the PxSingleActor, it would
   // set the position to a very small value because it would be getting a hit
   // even though the current position was 0.
   if ( nxCurrPos.isZero() )
      return;
   
   // Set up the flags and the query structure.
   NxU32 flags = NX_SF_STATICS | NX_SF_DYNAMICS;

   NxSweepQueryHit sweepResult;
   dMemset( &sweepResult, 0, sizeof( sweepResult ) );

   NxVec3 nxNewPos = mat->getPosition();

   // Get the velocity which will be our sweep direction and distance.
   NxVec3 nxDir = nxNewPos - nxCurrPos;
   if ( nxDir.isZero() )
      return;

   // Get the scene and do the sweep.
   mActor->wakeUp();
   mActor->linearSweep( nxDir, flags, NULL, 1, &sweepResult, NULL );

   if ( sweepResult.hitShape && sweepResult.t < nxDir.magnitude() )
   {
      nxDir.normalize();
      nxDir *= sweepResult.t;
      nxCurrPos += nxDir;

      mat->setPosition( Point3F( nxCurrPos.x, nxCurrPos.y, nxCurrPos.z ) );
   }
}
Beispiel #2
0
// Using swept code & direct position update (no physics engine)
// This function is the generic character controller logic, valid for all swept volumes
void SweepTest::MoveCharacter(
					void* user_data,
					void* user_data2,
					SweptVolume& volume,
					const NxVec3& direction,
					NxU32 nb_boxes, const NxExtendedBounds3* boxes, const void** box_user_data,
					NxU32 nb_capsules, const NxExtendedCapsule* capsules, const void** capsule_user_data,
					NxU32 groups, float min_dist,
					NxU32& collision_flags,
					const NxGroupsMask* groupsMask,
					bool constrainedClimbingMode
					 )
{
	mHitNonWalkable = false;
	NxU32 CollisionFlags = 0;
	const NxU32 MaxIter = mMaxIter;	// 1 for "collide and stop"
	const NxU32 MaxIterUp = MaxIter;
	const NxU32 MaxIterSides = MaxIter;
//	const NxU32 MaxIterDown = gWalkExperiment ? MaxIter : 1;
	const NxU32 MaxIterDown = 1;

	// ### this causes the artificial gap on top of chars
	float StepOffset = mStepOffset;	// Default step offset can be cancelled in some cases.

	// Save initial height
	Extended OriginalHeight = volume.mCenter[mUpDirection];
	Extended OriginalBottomPoint = OriginalHeight - volume.mHalfHeight;	// UBI

	// TEST! Disable auto-step when flying. Not sure this is really useful.
	if(direction[mUpDirection]>0.0f)
		StepOffset = 0.0f;

	// Decompose motion into 3 independent motions: up, side, down
	// - if the motion is purely down (gravity only), the up part is needed to fight accuracy issues. For example if the
	// character is already touching the geometry a bit, the down sweep test might have troubles. If we first move it above
	// the geometry, the problems disappear.
	// - if the motion is lateral (character moving forward under normal gravity) the decomposition provides the autostep feature
	// - if the motion is purely up, the down part can be skipped

	NxVec3 UpVector(0.0f, 0.0f, 0.0f);
	NxVec3 DownVector(0.0f, 0.0f, 0.0f);

	if(direction[mUpDirection]<0.0f)	DownVector[mUpDirection] = direction[mUpDirection];
	else								UpVector[mUpDirection] = direction[mUpDirection];

	NxVec3 SideVector = direction;
	SideVector[mUpDirection] = 0.0f;

	// If the side motion is zero, i.e. if the character is not really moving, disable auto-step.
	if(!SideVector.isZero())
		UpVector[mUpDirection] += StepOffset;

	// ==========[ Initial volume query ]===========================
	if(1)
	{
		NxVec3 MotionExtents = UpVector;
		MotionExtents.max(SideVector);
		MotionExtents.max(DownVector);

		NxExtendedBounds3 TemporalBox;
		volume.ComputeTemporalBox(*this, TemporalBox, volume.mCenter, MotionExtents);

		// Gather touched geoms
		UpdateTouchedGeoms(user_data, volume,
							nb_boxes, boxes, box_user_data,
							nb_capsules, capsules, capsule_user_data,
							groups, TemporalBox, groupsMask);
	}

	// ==========[ UP PASS ]===========================

	mCachedTriIndexIndex = 0;
	const bool PerformUpPass = true;
	NxU32 NbCollisions=0;

	if(PerformUpPass)
	{
		// Prevent user callback for up motion. This up displacement is artificial, and only needed for auto-stepping.
		// If we call the user for this, we might eventually apply upward forces to objects resting on top of us, even
		// if we visually don't move. This produces weird-looking motions.
		mValidateCallback = false;

		// In the walk-experiment we explicitely want to ban any up motions, to avoid characters climbing slopes they shouldn't climb.
		// So let's bypass the whole up pass.
		if(!mWalkExperiment)
		{
			// ### MaxIter here seems to "solve" the V bug
			if(DoSweepTest(user_data,
				user_data2,
				nb_boxes, boxes, box_user_data,
				nb_capsules, capsules, capsule_user_data,
				volume, UpVector, MaxIterUp, &NbCollisions, groups, min_dist, groupsMask))
			{
				if(NbCollisions)
				{
					CollisionFlags |= NXCC_COLLISION_UP;

					// Clamp step offset to make sure we don't undo more than what we did
					Extended Delta = volume.mCenter[mUpDirection] - OriginalHeight;
					if(Delta<StepOffset)
					{
						StepOffset=float(Delta);
					}
				}
			}
		}
	}

	// ==========[ SIDE PASS ]===========================

	mCachedTriIndexIndex = 1;
	mValidateCallback = true;
	const bool PerformSidePass = true;

	if(PerformSidePass)
	{
		NbCollisions=0;
		if(DoSweepTest(user_data,
			user_data2,
			nb_boxes, boxes, box_user_data,
			nb_capsules, capsules, capsule_user_data,
			volume, SideVector, MaxIterSides, &NbCollisions, groups, min_dist, groupsMask))
		{
			if(NbCollisions)	CollisionFlags |= NXCC_COLLISION_SIDES;
		}
	}

	// ==========[ DOWN PASS ]===========================

	mCachedTriIndexIndex = 2;
	const bool PerformDownPass = true;

	if(PerformDownPass)
	{
		NbCollisions=0;

		if(!SideVector.isZero())	// We disabled that before so we don't have to undo it in that case
			DownVector[mUpDirection] -= StepOffset;	// Undo our artificial up motion

		mValidTri = false;

		// min_dist actually makes a big difference :(
		// AAARRRGGH: if we get culled because of min_dist here, mValidTri never becomes valid!
		if(DoSweepTest(user_data,
			user_data2,
			nb_boxes, boxes, box_user_data,
			nb_capsules, capsules, capsule_user_data,
			volume, DownVector, MaxIterDown, &NbCollisions, groups, min_dist, groupsMask, true))
		{
			if(NbCollisions)
			{
				CollisionFlags |= NXCC_COLLISION_DOWN;
				if(mHandleSlope)	// PT: I think the following fix shouldn't be performed when mHandleSlope is false.
				{
					// PT: the following code is responsible for a weird capsule behaviour,
					// when colliding against a highly tesselated terrain:
					// - with a large direction vector, the capsule gets stuck against some part of the terrain
					// - with a slower direction vector (but in the same direction!) the capsule manages to move
					// I will keep that code nonetheless, since it seems to be useful for them.

					// constrainedClimbingMode
					if ( constrainedClimbingMode && mContactPointHeight > OriginalBottomPoint + StepOffset)
					{
						mHitNonWalkable = true;
						if(!mWalkExperiment)
							return;
					}
					//~constrainedClimbingMode
				}
			}
		}

		// TEST: do another down pass if we're on a non-walkable poly
		// ### kind of works but still not perfect
		// ### could it be because we zero the Y impulse later?
		// ### also check clamped response vectors
		if(mHandleSlope && mValidTri && direction[mUpDirection]<0.0f)
		{
			NxVec3 Normal;
		#ifdef USE_CONTACT_NORMAL_FOR_SLOPE_TEST
			Normal = mCN;
		#else
			mTouched.normal(Normal);
		#endif
		//	if(fabsf(Normal|NxVec3(0.0f, 1.0f, 0.0f))<cosf(45.0f*DEGTORAD))
			if(Normal[mUpDirection]>=0.0f && Normal[mUpDirection]<mSlopeLimit)
			{
				mHitNonWalkable = true;
				// Early exit if we're going to run this again anyway...
				if(!mWalkExperiment)	return;
		/*		CatchScene()->GetRenderer()->AddLine(mTouched.mVerts[0], mTouched.mVerts[1], ARGB_YELLOW);
				CatchScene()->GetRenderer()->AddLine(mTouched.mVerts[0], mTouched.mVerts[2], ARGB_YELLOW);
				CatchScene()->GetRenderer()->AddLine(mTouched.mVerts[1], mTouched.mVerts[2], ARGB_YELLOW);
		*/

				// ==========[ WALK EXPERIMENT ]===========================

				mNormalizeResponse=true;

				Extended Delta = volume.mCenter[mUpDirection] > OriginalHeight ? volume.mCenter[mUpDirection] - OriginalHeight : 0.0f;
				Delta += fabsf(direction[mUpDirection]);
				Extended Recover = Delta;

				NbCollisions=0;
				const Extended MD = Recover < min_dist ? Recover/float(MaxIter) : min_dist;

				NxVec3 RecoverPoint(0,0,0);
				RecoverPoint[mUpDirection]=-float(Recover);

				if(DoSweepTest(user_data,
					user_data2,
					nb_boxes, boxes, box_user_data,
					nb_capsules, capsules, capsule_user_data,
					volume, RecoverPoint, MaxIter, &NbCollisions, groups, float(MD), groupsMask))
				{
		//			if(NbCollisions)	CollisionFlags |= COLLISION_Y_DOWN;
					// PT: why did we do this ? Removed for now. It creates a bug (non registered event) when we land on a steep poly.
					// However this might have been needed when we were sliding on those polygons, and we didn't want the land anim to
					// start while we were sliding.
		//			if(NbCollisions)	CollisionFlags &= ~NXCC_COLLISION_DOWN;
				}
				mNormalizeResponse=false;
			}
		}
	}

	// Setup new collision flags
	collision_flags = CollisionFlags;
}
Beispiel #3
0
// This is the generic sweep test for all swept volumes, but not character-controller specific
bool SweepTest::DoSweepTest(void* user_data,
							void* user_data2,
							NxU32 nb_boxes, const NxExtendedBounds3* boxes, const void** box_user_data,
							NxU32 nb_capsules, const NxExtendedCapsule* capsules, const void** capsule_user_data,
							SweptVolume& swept_volume,
							const NxVec3& direction, NxU32 max_iter, NxU32* nb_collisions,
							NxU32 group_flags, float min_dist, const NxGroupsMask* groupsMask, bool down_pass)
{
	// Early exit when motion is zero. Since the motion is decomposed into several vectors
	// and this function is called for each of them, it actually happens quite often.
	if(direction.isZero())
		return false;

	bool HasMoved = false;
	mValidTri = false;

	NxExtendedVec3 CurrentPosition = swept_volume.mCenter;
	NxExtendedVec3 TargetPosition = swept_volume.mCenter;
	TargetPosition += direction;

	NxU32 NbCollisions = 0;
	while(max_iter--)
	{
		gNbIters++;
		// Compute current direction
		NxVec3 CurrentDirection = TargetPosition - CurrentPosition;

		// Make sure the new TBV is still valid
		{
			// Compute temporal bounding box. We could use a capsule or an OBB instead:
			// - the volume would be smaller
			// - but the query would be slower
			// Overall it's unclear whether it's worth it or not.
			// TODO: optimize this part ?
			NxExtendedBounds3 TemporalBox;
			swept_volume.ComputeTemporalBox(*this, TemporalBox, CurrentPosition, CurrentDirection);

			// Gather touched geoms
			UpdateTouchedGeoms(user_data, swept_volume,
								nb_boxes, boxes, box_user_data,
								nb_capsules, capsules, capsule_user_data,
								group_flags, TemporalBox, groupsMask);
		}

		const float Length = CurrentDirection.magnitude();
		if(Length<min_dist)	break;

		CurrentDirection /= Length;

		// From Quake2: "if velocity is against the original velocity, stop dead to avoid tiny occilations in sloping corners"
		if((CurrentDirection|direction) <= 0.0f)	break;

		// From this point, we're going to update the position at least once
		HasMoved = true;

		// Find closest collision
		SweptContact C;
		C.mDistance = Length + mSkinWidth;

		if(!CollideGeoms(this, swept_volume, mGeomStream, CurrentPosition, CurrentDirection, C))
		{
			// no collision found => move to desired position
			CurrentPosition = TargetPosition;
			break;
		}

		ASSERT(C.mGeom);	// If we reach this point, we must have touched a geom

		if(C.mGeom->mType==TOUCHED_USER_BOX || C.mGeom->mType==TOUCHED_USER_CAPSULE)
		{
			// We touched a user object, typically another CCT
			if(mValidateCallback)	UserHitCallback(user_data2, C, CurrentDirection, Length);

			// Trying to solve the following problem:
			// - by default, the CCT "friction" is infinite, i.e. a CCT will not slide on a slope (this is by design)
			// - this produces bad results when a capsule CCT stands on top of another capsule CCT, without sliding. Visually it looks
			//   like the character is standing on the other character's head, it looks bad. So, here, we would like to let the CCT
			//   slide away, i.e. we don't want friction.
			// So here we simply increase the number of iterations (== let the CCT slide) when the first down collision is with another CCT.
			if(down_pass && !NbCollisions)
				max_iter += 9;
		}
		else
		{
			// We touched a normal object
#ifdef USE_CONTACT_NORMAL_FOR_SLOPE_TEST
			mValidTri = true;
			mCN = C.mWorldNormal;
#else
			if(C.mIndex!=INVALID_ID)
			{
				mValidTri = true;
				mTouched = mWorldTriangles[C.mIndex];
			}
#endif
			{
				if(mValidateCallback)	ShapeHitCallback(user_data2, C, CurrentDirection, Length);
			}
		}

		NbCollisions++;
		mContactPointHeight = (float)C.mWorldPos[mUpDirection];	// UBI

		const float DynSkin = mSkinWidth;

		if(C.mDistance>DynSkin/*+0.01f*/)
			CurrentPosition += CurrentDirection*(C.mDistance-DynSkin);

		NxVec3 WorldNormal = C.mWorldNormal;
		if(mWalkExperiment)
		{
			// Make sure the auto-step doesn't bypass this !
			WorldNormal[mUpDirection]=0.0f;
			WorldNormal.normalize();
		}

		const float Bump = 0.0f;	// ### doesn't work when !=0 because of Quake2 hack!
		const float Friction = 1.0f;
		CollisionResponse(TargetPosition, CurrentPosition, CurrentDirection, WorldNormal, Bump, Friction, mNormalizeResponse);
	}

	if(nb_collisions)	*nb_collisions = NbCollisions;

	// Final box position that should be reflected in the graphics engine
	swept_volume.mCenter = CurrentPosition;

	// If we didn't move, don't update the box position at all (keeping possible lazy-evaluated structures valid)
	return HasMoved;
}