LlPushObject/Havok4Implementation

From Second Life Wiki
Jump to navigation Jump to search
 // 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);
 	}
 }