Difference between revisions of "LlPushObject/Havok4Implementation"

From Second Life Wiki
Jump to navigation Jump to search
(New page: // 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 LLOb...)
 
(swapped inactive <cpp> tag with <source> tags)
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
  // This is not quite pseudo code for how the llPushObject() effect is computed.
<source lang="cpp">// 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
// 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
// (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
// fairly accurate outline of what is going on.  This code probably doesn't
  // compile.  Fix any bugs you find -- it's a wiki! -- Andrew
// compile.  Fix any syntax errors you find -- it's a wiki! -- Andrew
  //
//
  void llPushObject(  
void llPushObject(  
  const LLObject& pusher,  
const LLObject& pusher,  
  const LLObject& pushee,  
const LLObject& pushee,  
  const LLVector3& linear_impulse,  
const LLVector3& linear_impulse,  
  const LLVector3& angular_impulse,  
const LLVector3& angular_impulse,  
  bool use_pushee_local_frame )
bool use_pushee_local_frame )
  {
{
  LLPhysicsBody& pushee_body = pushee.getPhysicsBody();  
LLPhysicsBody& pushee_body = pushee.getPhysicsBody();  
  if (!pushee_body || pushee_body.isKeyframed() )
if (!pushee_body || pushee_body.isKeyframed() )
  {
{
  // some objects don't have collidable bodies (trees, grass)
// some objects don't have collidable bodies (trees, grass)
  // and keyframed (non-dynamic) objects can't move
// and keyframed (non-dynamic) objects can't move
  return;
return;
  }
}
 
 
  if ( pushee.isAvatar()
if ( pushee.isAvatar()
  && pushee.isGod()
&& pushee.isGod()
  && pusher.getOwnerID() != pushee.getID() )
&& pusher.getOwnerID() != pushee.getID() )
  {
{
  // avatars in admin mode cannot be pushed  
// avatars in admin mode cannot be pushed  
  // unless they are the pusher's owner
// unless they are the pusher's owner
  return;
return;
  }
}
 
 
  // How much effect an llPushObject() call has depends on whether
// How much effect an llPushObject() call has depends on whether
  // the pusher had enough "script energy" to execute.  If the pusher
// the pusher had enough "script energy" to execute.  If the pusher
  // only has half of the required energy then it will push, but the
// 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
// effect of the push is reduced to half of what it would be, and the
  // pusher is left with zero energy at the end.
// pusher is left with zero energy at the end.
  //
//
  // The "script energy" system was implemented waaaaay back in the beginning
// 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
// 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
// throttle for problems that we expected to encounter (certain operations
  // cost energy, and hence an one object's effectiveness is throttled by its
// 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
// energy budget) and (2) to provide a sort of balance/tradeoff when
  // designing in-world objects that had some sort of gaming application (big
// designing in-world objects that had some sort of gaming application (big
  // guns should have more potency than small guns, hence the larger the
// guns should have more potency than small guns, hence the larger the
  // object the more energy).
// object the more energy).
  //
//
  // In any case, the energy bugdet is recovered at some fixed rate over time
// 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
// but the holding tank of each object depends on its mass, up to some
  // maximum.
// maximum.
  //
//
  // Way back in in 2003 or so we added a distance^3 term to the cost of
// 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
// llPushObject() designed prevent huge impulses from pushing people
  // around.  Unfortunately the original implementation was severely flawed
// around.  Unfortunately the original implementation was severely flawed
  // in a particular way -- the term was additive instead multiplicative.
// 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
// 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
// original push magnitude dwarfed the cubed term then you still ended up
  // with a giant effect.  In other words:  
// with a giant effect.  In other words:  
  //
//
  // 10^26 * (energy_recovered_during_last_frame) = Very_Big_Number
// 10^26 * (energy_recovered_during_last_frame) = Very_Big_Number
  //
//
  F32 distance = (pushee.getPosition() - pusher.getPosition()).length();
F32 distance = (pushee.getPosition() - pusher.getPosition()).length();
  F32 distance_term = distance * distance * distance * SCRIPT_ENERGY_PER_METER_AWAY;
F32 distance_term = distance * distance * distance * SCRIPT_ENERGY_PER_METER_AWAY;
 
 
  // How to fix this?  We can't change the energy consumption since
// How to fix this?  We can't change the energy consumption since
  // lots of Residents have tuned their pushers just right.  But we
// lots of Residents have tuned their pushers just right.  But we
  // don't want arbitrarily high impulses over arbitrarily large  
// don't want arbitrarily high impulses over arbitrarily large  
  // distances.  So here's what we'll do:
// distances.  So here's what we'll do:
  //
//
  // (1) clamp obnoxiously large impulses (after the energy consumption)
// (1) clamp obnoxiously large impulses (after the energy consumption)
  // (2) actually attenuate the impulse by distances that are beyond
// (2) actually attenuate the impulse by distances that are beyond
  //    some threshold
//    some threshold
 
 
  F32 pusher_mass = pusher.getMass();
F32 pusher_mass = pusher.getMass();
 
 
  // (2) we compute the distance attenuation factor early since it applies to
// (2) we compute the distance attenuation factor early since it applies to
  // both the linear and angular cases.  This attenuates pushes that are
// both the linear and angular cases.  This attenuates pushes that are
  // beyond (max_distance + 1)  
// beyond (max_distance + 1)  
  F32 PUSH_ATTENUATION_DISTANCE = 17.f;
F32 PUSH_ATTENUATION_DISTANCE = 17.f;
  F32 PUSH_ATTENUATION_SCALE = 5.f;
F32 PUSH_ATTENUATION_SCALE = 5.f;
  F32 distance_attenuation = 1.f;
F32 distance_attenuation = 1.f;
  if (distance > PUSH_ATTENUATION_DISTANCE)
if (distance > PUSH_ATTENUATION_DISTANCE)
  {
{
  // +1 below to avoid the asymptotic part of 1/x
// +1 below to avoid the asymptotic part of 1/x
  F32 normalized_units = 1.f + (distance - PUSH_ATTENUATION_DISTANCE) / PUSH_ATTENUATION_SCALE;
F32 normalized_units = 1.f + (distance - PUSH_ATTENUATION_DISTANCE) / PUSH_ATTENUATION_SCALE;
  distance_attenuation = 1.f / normalized_units;
distance_attenuation = 1.f / normalized_units;
  }
}
 
 
  // linear
// linear
  LLVector3 applied_linear_impulse = linear_impulse;
LLVector3 applied_linear_impulse = linear_impulse;
  {
{
  // consume energy according to the intended impulses
// consume energy according to the intended impulses
  F32 impulse_length = linear_impulse.length();
F32 impulse_length = linear_impulse.length();
 
  // NOTE: the pusher_mass factor in the desired_energy calculation  
// NOTE: the pusher_mass factor in the desired_energy calculation  
  // is just wrong, it should be the pushee_mass, but now that it
// is just wrong, it should be the pushee_mass, but now that it
  // has been wrong for years we can't fix it!
// has been wrong for years we can't fix it!
  F32 desired_energy = impulse_length * SCRIPT_ENERGY_PER_MOMENTUM * pusher_mass;
F32 desired_energy = impulse_length * SCRIPT_ENERGY_PER_MOMENTUM * pusher_mass;
  if (desired_energy > 0.f)
if (desired_energy > 0.f)
  {
{
  desired_energy += distance_term;
desired_energy += distance_term;
  }
}
 
  // LLObject::consumeEnergy() returns 1 if there is enough energy,
// LLObject::consumeEnergy() returns 1 if there is enough energy,
  // else returns the fraction of energy available.
// else returns the fraction of energy available.
  F32 scaling_factor = pusher.consumeEnergy(desired_energy);
F32 scaling_factor = pusher.consumeEnergy(desired_energy);
 
  // (1) clamp obnoxiously large impulses
// (1) clamp obnoxiously large impulses
  // the max push possible is what it would take to levitate the
// the max push possible is what it would take to levitate the
  // smallest object that has the max script energy
// smallest object that has the max script energy
  F32 max_push_impulse_length = MAX_ENERGY_MASS * fabsf(GRAVITY) * PHYSICS_TIMESTEP;
F32 max_push_impulse_length = MAX_ENERGY_MASS * fabsf(GRAVITY) * PHYSICS_TIMESTEP;
  if (impulse_length > max_push_impulse_length)
if (impulse_length > max_push_impulse_length)
  {
{
  // reduce the scaling factor by the ratio
// reduce the scaling factor by the ratio
  scaling_factor *= max_push_impulse_length / impulse_length;
scaling_factor *= max_push_impulse_length / impulse_length;
  }
}
 
  // (2) attenuate pushes that are from very far away
// (2) attenuate pushes that are from very far away
  scaling_factor *= distance_attenuation;
scaling_factor *= distance_attenuation;
 
  applied_linear_impulse *= scaling_factor;
applied_linear_impulse *= scaling_factor;
  }
}
 
 
  // angular
// angular
  LLVector3 applied_angular_impulse = angular_impulse;
LLVector3 applied_angular_impulse = angular_impulse;
  {
{
  // NOTE: same comments here as to the incorrectness of the mass units
// NOTE: same comments here as to the incorrectness of the mass units
  // with an extra problem: the angular energy consumption should be
// with an extra problem: the angular energy consumption should be
  // proportional to the actual change of angular momentum (i.e. should
// proportional to the actual change of angular momentum (i.e. should
  // involve the inertia tensor) so the linear mass factor here is not
// 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.
// 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;
F32 desired_energy = angular_impulse.length() * SCRIPT_ENERGY_PER_MOMENTUM * pusher_mass;
  if (desired_energy > 0.f)
if (desired_energy > 0.f)
  {
{
  desired_energy += distance_term;
desired_energy += distance_term;
  }
}
 
  // consume energy
// consume energy
  F32 scaling_factor = distance_attenuation * pusher.consumeEnergy(desired_energy);
F32 scaling_factor = distance_attenuation * pusher.consumeEnergy(desired_energy);
 
 
  // (1) clamp obnoxiously large impulses
// (1) clamp obnoxiously large impulses
  F32 impulse_length = angular_impulse.length();
F32 impulse_length = angular_impulse.length();
  if (impulse_length > max_push_impulse_length)
if (impulse_length > max_push_impulse_length)
  {
{
  scaling_factor *= max_push_impulse_length / impulse_length;
scaling_factor *= max_push_impulse_length / impulse_length;
  }
}
 
  // (2) attenuate pushes that are from very far away
// (2) attenuate pushes that are from very far away
  scaling_factor *= distance_attenuation;
scaling_factor *= distance_attenuation;
 
 
  applied_angular_impulse *= scaling_factor;
applied_angular_impulse *= scaling_factor;
  }
}
 
  if ( use_pushee_local_frame )
if ( use_pushee_local_frame )
  {
{
  // rotate the directions of the impulses into the pushee's local frame
// rotate the directions of the impulses into the pushee's local frame
  LLQuaternion rotation = pushee.getRotation();
LLQuaternion rotation = pushee.getRotation();
  applied_linear_impulse *= rotation;
applied_linear_impulse *= rotation;
  applied_angular_impulse *= rotation;
applied_angular_impulse *= rotation;
  }
}
 
 
  if (pushee.isAvatar())
if (pushee.isAvatar())
  {
{
  // NOTE: we ignore the angular part since the avatar cannot yet be spun
// NOTE: we ignore the angular part since the avatar cannot yet be spun
  // from external or attachment forces
// from external or attachment forces
  if (pusher.isAttachment()
if (pusher.isAttachment()
  && pusher.getAvatarID() == pushee.getID())
&& pusher.getAvatarID() == pushee.getID())
  {
{
  // this is an attachment pushing its avatar, so the push must go
// this is an attachment pushing its avatar, so the push must go
  // through the avatar controller which will guard against too fast
// through the avatar controller which will guard against too fast
  // velocities and do the right thing for the current movement mode
// velocities and do the right thing for the current movement mode
  pushee.getAvatarController().accumulateAttachmentImpulse(applied_linear_impulse);
pushee.getAvatarController().accumulateAttachmentImpulse(applied_linear_impulse);
  }
}
  else
else
  {
{
  // this is an attachment pushing another avatar, so the push must
// this is an attachment pushing another avatar, so the push must
  // go through the avatar controller which will gaurd against too
// go through the avatar controller which will gaurd against too
  // fast velocities and do the right thing for current movement mode
// fast velocities and do the right thing for current movement mode
  pushee.getAvatarController().applyExternalImpulse(applied_linear_impulse);
pushee.getAvatarController().applyExternalImpulse(applied_linear_impulse);
  }
}
  }
}
  else
else
  {
{
  pushee_body.applyImpulse(applied_linear_impulse);
pushee_body.applyImpulse(applied_linear_impulse);
  pushee_body.applyAngularImpulse(applied_angular_impulse);
pushee_body.applyAngularImpulse(applied_angular_impulse);
  }
}
  }
}</source>

Latest revision as of 18:23, 14 May 2015

// 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);
	}
}