Difference between revisions of "LlPushObject/Havok4Implementation"
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: | ||
<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 | |||
// (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); | |||
} | |||
}</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);
}
}