# LlPushObject/Havok4Implementation

From Second Life Wiki

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