LlPushObject/Havok4Implementation

From Second Life Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
// This is not quite pseudo code for how the llPushObject() effect is computed.
// Really it is simplified C++ code that doesn't quite reflect our true API
// (for instance, the LLObject class doesn't actually exist), but it is a
// fairly accurate outline of what is going on.  This code probably doesn't
// compile.  Fix any syntax errors you find -- it's a wiki! -- Andrew
//
void llPushObject( 
		const LLObject& pusher, 
		const LLObject& pushee, 
		const LLVector3& linear_impulse, 
		const LLVector3& angular_impulse, 
		bool use_pushee_local_frame )
{
	LLPhysicsBody& pushee_body = pushee.getPhysicsBody(); 
	if (!pushee_body || pushee_body.isKeyframed() )
	{
		// some objects don't have collidable bodies (trees, grass)
		// and keyframed (non-dynamic) objects can't move
		return;
	}

	if ( pushee.isAvatar()
		&& pushee.isGod()
		&& pusher.getOwnerID() != pushee.getID() )
	{
		// avatars in admin mode cannot be pushed 
		// unless they are the pusher's owner
		return;
	}

	// How much effect an llPushObject() call has depends on whether
	// the pusher had enough "script energy" to execute.  If the pusher
	// only has half of the required energy then it will push, but the
	// effect of the push is reduced to half of what it would be, and the
	// pusher is left with zero energy at the end.
	//
	// The "script energy" system was implemented waaaaay back in the beginning
	// and was done for a few reasons:  (1) as a pre-emptive goo and grief
	// throttle for problems that we expected to encounter (certain operations
	// cost energy, and hence an one object's effectiveness is throttled by its
	// energy budget) and (2) to provide a sort of balance/tradeoff when
	// designing in-world objects that had some sort of gaming application (big
	// guns should have more potency than small guns, hence the larger the
	// object the more energy).
	//
	// In any case, the energy bugdet is recovered at some fixed rate over time
	// but the holding tank of each object depends on its mass, up to some
	// maximum.
	//
	// Way back in in 2003 or so we added a distance^3 term to the cost of
	// llPushObject() designed prevent huge impulses from pushing people
	// around.  Unfortunately the original implementation was severely flawed
	// in a particular way -- the term was additive instead multiplicative.
	// The cube power gets big fast, but not all that big or fast.  If the
	// original push magnitude dwarfed the cubed term then you still ended up
	// with a giant effect.  In other words: 
	//
	// 10^26 * (energy_recovered_during_last_frame) = Very_Big_Number
	//
	F32 distance = (pushee.getPosition() - pusher.getPosition()).length();
	F32 distance_term = distance * distance * distance * SCRIPT_ENERGY_PER_METER_AWAY;

	// How to fix this?  We can't change the energy consumption since
	// lots of Residents have tuned their pushers just right.  But we
	// don't want arbitrarily high impulses over arbitrarily large 
	// distances.  So here's what we'll do:
	//
	// (1) clamp obnoxiously large impulses (after the energy consumption)
	// (2) actually attenuate the impulse by distances that are beyond
	//     some threshold

	F32 pusher_mass = pusher.getMass();

	// (2) we compute the distance attenuation factor early since it applies to
	// both the linear and angular cases.  This attenuates pushes that are
	// beyond (max_distance + 1) 
	F32 PUSH_ATTENUATION_DISTANCE = 17.f;
	F32 PUSH_ATTENUATION_SCALE = 5.f;
	F32 distance_attenuation = 1.f;
	if (distance > PUSH_ATTENUATION_DISTANCE)
	{
		// +1 below to avoid the asymptotic part of 1/x
		F32 normalized_units = 1.f + (distance - PUSH_ATTENUATION_DISTANCE) / PUSH_ATTENUATION_SCALE;
		distance_attenuation = 1.f / normalized_units;
	}

	// linear
	LLVector3 applied_linear_impulse = linear_impulse;
	{
		// consume energy according to the intended impulses
		F32 impulse_length = linear_impulse.length();
	
		// NOTE: the pusher_mass factor in the desired_energy calculation 
		// is just wrong, it should be the pushee_mass, but now that it
		// has been wrong for years we can't fix it!
		F32 desired_energy = impulse_length * SCRIPT_ENERGY_PER_MOMENTUM * pusher_mass;
		if (desired_energy > 0.f)
		{
			desired_energy += distance_term;
		}
	
		// LLObject::consumeEnergy() returns 1 if there is enough energy,
		// else returns the fraction of energy available.
		F32 scaling_factor = pusher.consumeEnergy(desired_energy);
	
		// (1) clamp obnoxiously large impulses
		// the max push possible is what it would take to levitate the
		// smallest object that has the max script energy
		F32 max_push_impulse_length = MAX_ENERGY_MASS * fabsf(GRAVITY) * PHYSICS_TIMESTEP;
		if (impulse_length > max_push_impulse_length)
		{
			// reduce the scaling factor by the ratio
			scaling_factor *= max_push_impulse_length / impulse_length;
		}
	
		// (2) attenuate pushes that are from very far away
		scaling_factor *= distance_attenuation;
	
		applied_linear_impulse *= scaling_factor;
	}

	// angular
	LLVector3 applied_angular_impulse = angular_impulse;
	{
		// NOTE: same comments here as to the incorrectness of the mass units
		// with an extra problem: the angular energy consumption should be
		// proportional to the actual change of angular momentum (i.e. should
		// involve the inertia tensor) so the linear mass factor here is not
		// only from the wrong object but is also the wrong property.  Oh well.
		F32 desired_energy = angular_impulse.length() * SCRIPT_ENERGY_PER_MOMENTUM * pusher_mass;
		if (desired_energy > 0.f)
		{
			desired_energy += distance_term;
		}
		
		// consume energy
		F32 scaling_factor = distance_attenuation * pusher.consumeEnergy(desired_energy);

		// (1) clamp obnoxiously large impulses
		F32 impulse_length = angular_impulse.length();
		if (impulse_length > max_push_impulse_length)
		{
			scaling_factor *= max_push_impulse_length / impulse_length;
		}
	
		// (2) attenuate pushes that are from very far away
		scaling_factor *= distance_attenuation;

		applied_angular_impulse *= scaling_factor;
	}
	
	if ( use_pushee_local_frame )
	{
		// rotate the directions of the impulses into the pushee's local frame
		LLQuaternion rotation = pushee.getRotation();
		applied_linear_impulse *= rotation;
		applied_angular_impulse *= rotation;
	}

	if (pushee.isAvatar())
	{
		// NOTE: we ignore the angular part since the avatar cannot yet be spun
		// from external or attachment forces
		if (pusher.isAttachment()
			&& pusher.getAvatarID() == pushee.getID())
		{
			// this is an attachment pushing its avatar, so the push must go
			// through the avatar controller which will guard against too fast
			// velocities and do the right thing for the current movement mode
			pushee.getAvatarController().accumulateAttachmentImpulse(applied_linear_impulse);
		}
		else
		{
			// this is an attachment pushing another avatar, so the push must
			// go through the avatar controller which will gaurd against too
			// fast velocities and do the right thing for current movement mode
			pushee.getAvatarController().applyExternalImpulse(applied_linear_impulse);
		}
	}
	else
	{
		pushee_body.applyImpulse(applied_linear_impulse);
		pushee_body.applyAngularImpulse(applied_angular_impulse);
	}
}