packages feed

Box2D-0.1.0.0: upstream/src/pogo_joint.c

// SPDX-FileCopyrightText: 2023 Erin Catto
// SPDX-License-Identifier: MIT

#include "body.h"
#include "joint.h"
#include "physics_world.h"
#include "recording.h"
#include "solver.h"
#include "solver_set.h"

// needed for dll export
#include "box2d/box2d.h"

static const b2Vec2 b2_pogoAxis = { 0.0f, 1.0f };

void b2PogoJoint_SetRestLength( b2JointId jointId, float length )
{
	B2_CHECK_INPUT( b2IsValidFloat( length ) );

	b2World* world = b2GetWorld( jointId.world0 );
	B2_REC( world, PogoJointSetRestLength, jointId, length );
	
	b2JointSim* joint = b2GetJointSimCheckType( jointId, b2_pogoJoint );
	joint->pogoJoint.restLength = length;
}

float b2PogoJoint_GetRestLength( b2JointId jointId )
{
	b2JointSim* joint = b2GetJointSimCheckType( jointId, b2_pogoJoint );
	return joint->pogoJoint.restLength;
}

float b2PogoJoint_GetSpringHertz( b2JointId jointId )
{
	b2JointSim* joint = b2GetJointSimCheckType( jointId, b2_pogoJoint );
	return joint->pogoJoint.hertz;
}

void b2PogoJoint_SetSpringHertz( b2JointId jointId, float hertz )
{
	B2_CHECK_INPUT( b2IsValidFloat( hertz ) );

	 b2World* world = b2GetWorld( jointId.world0 );
	 B2_REC( world, PogoJointSetSpringHertz, jointId, hertz );

	b2JointSim* joint = b2GetJointSimCheckType( jointId, b2_pogoJoint );
	joint->pogoJoint.hertz = hertz;
}

void b2PogoJoint_SetSpringDampingRatio( b2JointId jointId, float dampingRatio )
{
	B2_CHECK_INPUT( b2IsValidFloat( dampingRatio ) );

	 b2World* world = b2GetWorld( jointId.world0 );
	 B2_REC( world, PogoJointSetSpringDampingRatio, jointId, dampingRatio );

	b2JointSim* joint = b2GetJointSimCheckType( jointId, b2_pogoJoint );
	joint->pogoJoint.dampingRatio = dampingRatio;
}

float b2PogoJoint_GetSpringDampingRatio( b2JointId jointId )
{
	b2JointSim* joint = b2GetJointSimCheckType( jointId, b2_pogoJoint );
	return joint->pogoJoint.dampingRatio;
}

float b2PogoJoint_GetCurrentLength( b2JointId jointId )
{
	b2World* world = b2GetWorld( jointId.world0 );
	b2JointSim* jointSim = b2GetJointSimCheckType( jointId, b2_pogoJoint );

	// Relative to body A so the difference stays in float precision far from the origin
	b2WorldTransform wxfA = b2GetBodyTransform( world, jointSim->bodyIdA );
	b2Transform transformA = b2ToRelativeTransform( wxfA, wxfA.p );
	b2Transform transformB = b2ToRelativeTransform( b2GetBodyTransform( world, jointSim->bodyIdB ), wxfA.p );

	b2Vec2 axis = b2RotateVector( jointSim->localFrameB.q, b2_pogoAxis );
	axis = b2RotateVector( transformB.q, axis );
	b2Vec2 pA = b2TransformPoint( transformA, jointSim->localFrameA.p );
	b2Vec2 pB = b2TransformPoint( transformB, jointSim->localFrameB.p );
	b2Vec2 d = b2Sub( pB, pA );
	float length = b2Dot( d, axis );
	return length;
}

float b2PogoJoint_GetImpulse( b2JointId jointId )
{
	b2JointSim* joint = b2GetJointSimCheckType( jointId, b2_pogoJoint );
	return joint->pogoJoint.impulse;
}

float b2PogoJoint_GetCurrentSpeed( b2JointId jointId )
{
	b2JointSim* joint = b2GetJointSimCheckType( jointId, b2_pogoJoint );
	return joint->pogoJoint.velocity;
}

b2Vec2 b2GetPogoJointForce( b2World* world, b2JointSim* base )
{
	b2Vec2 force = b2MulSV( world->inv_h * base->pogoJoint.impulse, base->pogoJoint.normal );
	return force;
}

void b2PreparePogoJoint( b2JointSim* base, b2StepContext* context )
{
	B2_ASSERT( base->type == b2_pogoJoint );

	b2World* world = context->world;

	// chase body id to the solver set where the body lives
	int idA = base->bodyIdA;
	int idB = base->bodyIdB;

	b2Body* bodyA = b2Array_Get( world->bodies, idA );
	b2Body* bodyB = b2Array_Get( world->bodies, idB );

	B2_ASSERT( bodyA->setIndex == b2_awakeSet || bodyB->setIndex == b2_awakeSet );

	b2SolverSet* setA = b2Array_Get( world->solverSets, bodyA->setIndex );
	b2SolverSet* setB = b2Array_Get( world->solverSets, bodyB->setIndex );

	int localIndexA = bodyA->localIndex;
	int localIndexB = bodyB->localIndex;

	b2BodySim* bodySimA = b2Array_Get( setA->bodySims, localIndexA );
	b2BodySim* bodySimB = b2Array_Get( setB->bodySims, localIndexB );

	float mA = bodySimA->invMass;
	float iA = bodySimA->invInertia;
	float mB = bodySimB->invMass;
	float iB = bodySimB->invInertia;

	base->invMassA = mA;
	base->invMassB = mB;
	base->invIA = iA;
	base->invIB = iB;

	b2PogoJoint* joint = &base->pogoJoint;
	joint->indexA = bodyA->setIndex == b2_awakeSet ? localIndexA : B2_NULL_INDEX;
	joint->indexB = bodyB->setIndex == b2_awakeSet ? localIndexB : B2_NULL_INDEX;

	// Compute joint anchor frames with world space rotation, relative to center of mass
	joint->frameA.q = b2MulRot( bodySimA->transform.q, base->localFrameA.q );
	joint->frameA.p = b2RotateVector( bodySimA->transform.q, b2Sub( base->localFrameA.p, bodySimA->localCenter ) );
	joint->frameB.q = b2MulRot( bodySimB->transform.q, base->localFrameB.q );
	joint->frameB.p = b2RotateVector( bodySimB->transform.q, b2Sub( base->localFrameB.p, bodySimB->localCenter ) );

	// Compute the initial center delta. Incremental position updates are relative to this.
	joint->deltaCenter = b2SubPos( bodySimB->center, bodySimA->center );

	b2Vec2 rA = joint->frameA.p;
	b2Vec2 rB = joint->frameB.p;

	// compute effective mass
	float crA = b2Cross( rA, joint->normal );
	float crB = b2Cross( rB, joint->normal );
	float k = mA + mB + iA * crA * crA + iB * crB * crB;
	joint->linearMass = k > 0.0f ? 1.0f / k : 0.0f;

	if ( context->enableWarmStarting == false )
	{
		joint->impulse = 0.0f;
	}
}

void b2WarmStartPogoJoint( b2JointSim* base, b2StepContext* context )
{
	B2_ASSERT( base->type == b2_pogoJoint );

	float mA = base->invMassA;
	float mB = base->invMassB;
	float iA = base->invIA;
	float iB = base->invIB;

	b2PogoJoint* joint = &base->pogoJoint;

	if ( joint->hertz == 0.0f )
	{
		joint->impulse = 0.0f;
		return;
	}

	// dummy state for static bodies
	b2BodyState dummyState = b2_identityBodyState;

	b2BodyState* stateA = joint->indexA == B2_NULL_INDEX ? &dummyState : context->states + joint->indexA;
	b2BodyState* stateB = joint->indexB == B2_NULL_INDEX ? &dummyState : context->states + joint->indexB;
	b2Vec2 rA = b2RotateVector( stateA->deltaRotation, joint->frameA.p );
	b2Vec2 rB = b2RotateVector( stateB->deltaRotation, joint->frameB.p );

	b2Vec2 linearImpulse = b2MulSV( joint->impulse, joint->normal );

	if ( stateA->flags & b2_dynamicFlag )
	{
		stateA->linearVelocity = b2MulSub( stateA->linearVelocity, mA, linearImpulse );
		stateA->angularVelocity -= iA * b2Cross( rA, linearImpulse );
	}

	if ( stateB->flags & b2_dynamicFlag )
	{
		stateB->linearVelocity = b2MulAdd( stateB->linearVelocity, mB, linearImpulse );
		stateB->angularVelocity += iB * b2Cross( rB, linearImpulse );
	}
}

void b2SolvePogoJoint( b2JointSim* base, b2StepContext* context, bool useBias )
{
	B2_UNUSED( useBias );

	B2_ASSERT( base->type == b2_pogoJoint );

	float mA = base->invMassA;
	float mB = base->invMassB;
	float iA = base->invIA;
	float iB = base->invIB;

	// dummy state for static bodies
	b2BodyState dummyState = b2_identityBodyState;

	b2PogoJoint* joint = &base->pogoJoint;
	if ( joint->hertz == 0.0f )
	{
		joint->impulse = 0.0f;
		return;
	}

	b2BodyState* stateA = joint->indexA == B2_NULL_INDEX ? &dummyState : context->states + joint->indexA;
	b2BodyState* stateB = joint->indexB == B2_NULL_INDEX ? &dummyState : context->states + joint->indexB;

	b2Vec2 vA = stateA->linearVelocity;
	float wA = stateA->angularVelocity;
	b2Vec2 vB = stateB->linearVelocity;
	float wB = stateB->angularVelocity;

	b2Vec2 rA = b2RotateVector( stateA->deltaRotation, joint->frameA.p );
	b2Vec2 rB = b2RotateVector( stateB->deltaRotation, joint->frameB.p );

	float bias = 0.0f;
	if ( useBias )
	{
		// This is a custom pogo position correction designed to avoid feeding velocity into
		// the mover body. Otherwise the mover can make huge jumps going up stairs. This still
		// works with continuous collision.

		b2Vec2 dcA = stateA->deltaPosition;
		b2Vec2 dcB = stateB->deltaPosition;
		b2Vec2 d = b2Add( b2Add( b2Sub( dcB, dcA ), b2Sub( rB, rA ) ), joint->deltaCenter );

		// The pogo axis is usually fixed as the world y-axis. Different than the ground normal.
		b2Vec2 pogoAxis = b2RotateVector( joint->frameB.q, b2_pogoAxis );
		float c = b2Dot( pogoAxis, d ) - joint->restLength;

		// This could be divided by dot(joint->normal, pogoAxis) to account for the constraint
		// direction, but I'd rather diminish the pogo recovery rate than risk making it huge.
		joint->velocity = b2SpringDamper( joint->hertz, joint->dampingRatio, c, joint->velocity, context->h );
		bias = -joint->velocity;
	}

	b2Vec2 vr = b2Sub( b2Add( vB, b2CrossSV( wB, rB ) ), b2Add( vA, b2CrossSV( wA, rA ) ) );
	float cdot = b2Dot( joint->normal, vr );

	float maxTensionImpulse = context->h * joint->maxTensionForce;
	float maxCompressionImpulse = context->h * joint->maxCompressionForce;
	float oldImpulse = joint->impulse;
	float impulse = -joint->linearMass * (cdot + bias);
	joint->impulse = b2ClampFloat( joint->impulse + impulse, -maxTensionImpulse, maxCompressionImpulse );
	impulse = joint->impulse - oldImpulse;

	b2Vec2 P = b2MulSV( impulse, joint->normal );
	vA = b2MulSub( vA, mA, P );
	wA -= iA * b2Cross( rA, P );
	vB = b2MulAdd( vB, mB, P );
	wB += iB * b2Cross( rB, P );

	if ( stateA->flags & b2_dynamicFlag )
	{
		stateA->linearVelocity = vA;
		stateA->angularVelocity = wA;
	}

	if ( stateB->flags & b2_dynamicFlag )
	{
		stateB->linearVelocity = vB;
		stateB->angularVelocity = wB;
	}
}