BulletSim: More aggressive as setting character velocity to zero

when should be standing.
Modify angular force routines to be the same pattern as linear force routines.
BulletSim vehicle turning is scaled like SL and is DIFFERENT THAN ODE!!
Fix some bugs in BSMotor dealing with the motor going to zero.
Add a bunch of parameters:  MaxLinearVelocity, MaxAngularVelocity,
MaxAddForceMagnitude, VehicleMaxLinearVelocity, VehicleMaxAngularVelocity,
and most of the values are defaulted to values that are larger
than in SL.
Use the new parameters in BSPrim, BSCharacter and BSDynamic.
user_profiles
Robert Adams 2013-01-20 22:35:42 -08:00
parent 3c4868f613
commit 52b341e2e2
5 changed files with 157 additions and 95 deletions

View File

@ -200,20 +200,36 @@ public sealed class BSCharacter : BSPhysObject
// TODO: Decide if the step parameters should be changed depending on the avatar's
// state (flying, colliding, ...). There is code in ODE to do this.
// COMMENTARY: when the user is making the avatar walk, except for falling, the velocity
// specified for the avatar is the one that should be used. For falling, if the avatar
// is not flying and is not colliding then it is presumed to be falling and the Z
// component is not fooled with (thus allowing gravity to do its thing).
// When the avatar is standing, though, the user has specified a velocity of zero and
// the avatar should be standing. But if the avatar is pushed by something in the world
// (raising elevator platform, moving vehicle, ...) the avatar should be allowed to
// move. Thus, the velocity cannot be forced to zero. The problem is that small velocity
// errors can creap in and the avatar will slowly float off in some direction.
// So, the problem is that, when an avatar is standing, we cannot tell creaping error
// from real pushing.OMV.Vector3.Zero;
// The code below keeps setting the velocity to zero hoping the world will keep pushing.
_velocityMotor.Step(timeStep);
// If we're not supposed to be moving, make sure things are zero.
if (_velocityMotor.ErrorIsZero() && _velocityMotor.TargetValue.ApproxEquals(OMV.Vector3.Zero, 0.01f))
if (_velocityMotor.ErrorIsZero() && _velocityMotor.TargetValue == OMV.Vector3.Zero && IsColliding)
{
if (_wasWalking)
// The avatar shouldn't be moving
_velocityMotor.Zero();
ZeroMotion(true /* inTaintTime */);
// Standing has more friction on the ground
if (_currentFriction != BSParam.AvatarStandingFriction)
{
_velocityMotor.Zero();
_velocity = OMV.Vector3.Zero;
PhysicsScene.PE.SetLinearVelocity(PhysBody, OMV.Vector3.Zero);
_currentFriction = BSParam.AvatarStandingFriction;
PhysicsScene.PE.SetFriction(PhysBody, _currentFriction);
// DetailLog("{0},BSCharacter.MoveMotor,taint,stopping,target={1}", LocalID, _velocityMotor.TargetValue);
}
DetailLog("{0},BSCharacter.MoveMotor,taint,stopping,target={1}", LocalID, _velocityMotor.TargetValue);
_wasWalking = false;
}
else
@ -242,7 +258,7 @@ public sealed class BSCharacter : BSPhysObject
// Add special movement force to allow avatars to walk up stepped surfaces.
moveForce += WalkUpStairs();
// DetailLog("{0},BSCharacter.MoveMotor,move,stepVel={1},vel={2},mass={3},moveForce={4}", LocalID, stepVelocity, _velocity, Mass, moveForce);
DetailLog("{0},BSCharacter.MoveMotor,move,stepVel={1},vel={2},mass={3},moveForce={4}", LocalID, stepVelocity, _velocity, Mass, moveForce);
PhysicsScene.PE.ApplyCentralImpulse(PhysBody, moveForce);
_wasWalking = true;
}

View File

@ -231,6 +231,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin
break;
case Vehicle.ANGULAR_MOTOR_DIRECTION:
m_angularMotorDirection = new Vector3(pValue, pValue, pValue);
m_angularMotor.Zero();
m_angularMotor.SetTarget(m_angularMotorDirection);
break;
case Vehicle.LINEAR_FRICTION_TIMESCALE:
@ -264,6 +265,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin
pValue.Y = ClampInRange(-12.56f, pValue.Y, 12.56f);
pValue.Z = ClampInRange(-12.56f, pValue.Z, 12.56f);
m_angularMotorDirection = new Vector3(pValue.X, pValue.Y, pValue.Z);
m_angularMotor.Zero();
m_angularMotor.SetTarget(m_angularMotorDirection);
break;
case Vehicle.LINEAR_FRICTION_TIMESCALE:
@ -945,10 +947,10 @@ namespace OpenSim.Region.Physics.BulletSPlugin
// ==================================================================
// Clamp high or low velocities
float newVelocityLengthSq = VehicleVelocity.LengthSquared();
if (newVelocityLengthSq > 1000f)
if (newVelocityLengthSq > BSParam.VehicleMaxLinearVelocity)
{
VehicleVelocity /= VehicleVelocity.Length();
VehicleVelocity *= 1000f;
VehicleVelocity *= BSParam.VehicleMaxLinearVelocity;
}
else if (newVelocityLengthSq < 0.001f)
VehicleVelocity = Vector3.Zero;
@ -1190,63 +1192,33 @@ namespace OpenSim.Region.Physics.BulletSPlugin
// set directly on the vehicle.
private void MoveAngular(float pTimestep)
{
// The user wants this many radians per second angular change?
Vector3 angularMotorContribution = m_angularMotor.Step(pTimestep);
angularMotorContribution = m_angularMotor.CurrentValue;
VehicleRotationalVelocity = Vector3.Zero;
ComputeAngularTurning(pTimestep);
ComputeAngularVerticalAttraction();
ComputeAngularDeflection();
ComputeAngularBanking();
// ==================================================================
// From http://wiki.secondlife.com/wiki/LlSetVehicleFlags :
// This flag prevents linear deflection parallel to world z-axis. This is useful
// for preventing ground vehicles with large linear deflection, like bumper cars,
// from climbing their linear deflection into the sky.
// That is, NO_DEFLECTION_UP says angular motion should not add any pitch or roll movement
// TODO: This is here because this is where ODE put it but documentation says it
// is a linear effect. Where should this check go?
if ((m_flags & (VehicleFlag.NO_DEFLECTION_UP)) != 0)
{
angularMotorContribution.X = 0f;
angularMotorContribution.Y = 0f;
VDetailLog("{0}, MoveAngular,noDeflectionUp,angularMotorContrib={1}", Prim.LocalID, angularMotorContribution);
}
Vector3 verticalAttractionContribution = ComputeAngularVerticalAttraction();
Vector3 deflectionContribution = ComputeAngularDeflection();
Vector3 bankingContribution = ComputeAngularBanking();
// ==================================================================
m_lastVertAttractor = verticalAttractionContribution;
m_lastAngularVelocity = angularMotorContribution
+ verticalAttractionContribution
+ deflectionContribution
+ bankingContribution;
// All of the above computation are made relative to vehicle coordinates.
// Convert to world coordinates.
m_lastAngularVelocity *= VehicleOrientation;
// TODO: Should this be applied as an angular force (torque)?
VehicleRotationalVelocity *= VehicleOrientation;
// ==================================================================
// Apply the correction velocity.
// TODO: Should this be applied as an angular force (torque)?
if (!m_lastAngularVelocity.ApproxEquals(Vector3.Zero, 0.01f))
{
VehicleRotationalVelocity = m_lastAngularVelocity;
VDetailLog("{0}, MoveAngular,done,nonZero,angMotorContrib={1},vertAttrContrib={2},bankContrib={3},deflectContrib={4},totalContrib={5}",
Prim.LocalID,
angularMotorContribution, verticalAttractionContribution,
bankingContribution, deflectionContribution,
m_lastAngularVelocity
);
}
else
if (VehicleRotationalVelocity.ApproxEquals(Vector3.Zero, 0.01f))
{
// The vehicle is not adding anything angular wise.
VehicleRotationalVelocity = Vector3.Zero;
VDetailLog("{0}, MoveAngular,done,zero", Prim.LocalID);
}
else
{
VDetailLog("{0}, MoveAngular,done,nonZero,angVel={1}", Prim.LocalID, VehicleRotationalVelocity);
}
// ==================================================================
//Offset section
@ -1280,6 +1252,30 @@ namespace OpenSim.Region.Physics.BulletSPlugin
}
}
private void ComputeAngularTurning(float pTimestep)
{
// The user wants this many radians per second angular change?
Vector3 angularMotorContribution = m_angularMotor.Step(pTimestep);
// ==================================================================
// From http://wiki.secondlife.com/wiki/LlSetVehicleFlags :
// This flag prevents linear deflection parallel to world z-axis. This is useful
// for preventing ground vehicles with large linear deflection, like bumper cars,
// from climbing their linear deflection into the sky.
// That is, NO_DEFLECTION_UP says angular motion should not add any pitch or roll movement
// TODO: This is here because this is where ODE put it but documentation says it
// is a linear effect. Where should this check go?
if ((m_flags & (VehicleFlag.NO_DEFLECTION_UP)) != 0)
{
angularMotorContribution.X = 0f;
angularMotorContribution.Y = 0f;
}
VehicleRotationalVelocity += angularMotorContribution;
VDetailLog("{0}, MoveAngular,angularTurning,angularMotorContrib={1}", Prim.LocalID, angularMotorContribution);
}
// From http://wiki.secondlife.com/wiki/Linden_Vehicle_Tutorial:
// Some vehicles, like boats, should always keep their up-side up. This can be done by
// enabling the "vertical attractor" behavior that springs the vehicle's local z-axis to
@ -1288,13 +1284,13 @@ namespace OpenSim.Region.Physics.BulletSPlugin
// and then set the VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY to control the damping. An
// efficiency of 0.0 will cause the spring to wobble around its equilibrium, while an
// efficiency of 1.0 will cause the spring to reach its equilibrium with exponential decay.
public Vector3 ComputeAngularVerticalAttraction()
public void ComputeAngularVerticalAttraction()
{
Vector3 ret = Vector3.Zero;
// If vertical attaction timescale is reasonable
if (enableAngularVerticalAttraction && m_verticalAttractionTimescale < m_verticalAttractionCutoff)
{
Vector3 vertContribution = Vector3.Zero;
// Take a vector pointing up and convert it from world to vehicle relative coords.
Vector3 verticalError = Vector3.UnitZ * VehicleOrientation;
@ -1308,37 +1304,36 @@ namespace OpenSim.Region.Physics.BulletSPlugin
// Y error means needed rotation around X axis and visa versa.
// Since the error goes from zero to one, the asin is the corresponding angle.
ret.X = (float)Math.Asin(verticalError.Y);
vertContribution.X = (float)Math.Asin(verticalError.Y);
// (Tilt forward (positive X) needs to tilt back (rotate negative) around Y axis.)
ret.Y = -(float)Math.Asin(verticalError.X);
vertContribution.Y = -(float)Math.Asin(verticalError.X);
// If verticalError.Z is negative, the vehicle is upside down. Add additional push.
if (verticalError.Z < 0f)
{
ret.X += PIOverFour;
ret.Y += PIOverFour;
vertContribution.X += PIOverFour;
vertContribution.Y += PIOverFour;
}
// 'ret' is now the necessary velocity to correct tilt in one second.
// 'vertContrbution' is now the necessary angular correction to correct tilt in one second.
// Correction happens over a number of seconds.
Vector3 unscaledContrib = ret;
ret /= m_verticalAttractionTimescale;
Vector3 unscaledContrib = vertContribution; // DEBUG DEBUG
vertContribution /= m_verticalAttractionTimescale;
VehicleRotationalVelocity += vertContribution;
VDetailLog("{0}, MoveAngular,verticalAttraction,,verticalError={1},unscaled={2},eff={3},ts={4},vertAttr={5}",
Prim.LocalID, verticalError, unscaledContrib, m_verticalAttractionEfficiency, m_verticalAttractionTimescale, ret);
Prim.LocalID, verticalError, unscaledContrib, m_verticalAttractionEfficiency, m_verticalAttractionTimescale, vertContribution);
}
return ret;
}
// Return the angular correction to correct the direction the vehicle is pointing to be
// Angular correction to correct the direction the vehicle is pointing to be
// the direction is should want to be pointing.
// The vehicle is moving in some direction and correct its orientation to it is pointing
// in that direction.
// TODO: implement reference frame.
public Vector3 ComputeAngularDeflection()
public void ComputeAngularDeflection()
{
Vector3 ret = Vector3.Zero;
// Since angularMotorUp and angularDeflection are computed independently, they will calculate
// approximately the same X or Y correction. When added together (when contributions are combined)
// this creates an over-correction and then wabbling as the target is overshot.
@ -1346,6 +1341,8 @@ namespace OpenSim.Region.Physics.BulletSPlugin
if (enableAngularDeflection && m_angularDeflectionEfficiency != 0 && VehicleForwardSpeed > 0.2)
{
Vector3 deflectContribution = Vector3.Zero;
// The direction the vehicle is moving
Vector3 movingDirection = VehicleVelocity;
movingDirection.Normalize();
@ -1371,18 +1368,19 @@ namespace OpenSim.Region.Physics.BulletSPlugin
// ret = m_angularDeflectionCorrectionMotor(1f, deflectionError);
// Scale the correction by recovery timescale and efficiency
ret = (-deflectionError) * m_angularDeflectionEfficiency;
ret /= m_angularDeflectionTimescale;
deflectContribution = (-deflectionError) * m_angularDeflectionEfficiency;
deflectContribution /= m_angularDeflectionTimescale;
VehicleRotationalVelocity += deflectContribution;
VDetailLog("{0}, MoveAngular,Deflection,movingDir={1},pointingDir={2},deflectError={3},ret={4}",
Prim.LocalID, movingDirection, pointingDirection, deflectionError, ret);
Prim.LocalID, movingDirection, pointingDirection, deflectionError, deflectContribution);
VDetailLog("{0}, MoveAngular,Deflection,fwdSpd={1},defEff={2},defTS={3}",
Prim.LocalID, VehicleForwardSpeed, m_angularDeflectionEfficiency, m_angularDeflectionTimescale);
}
return ret;
}
// Return an angular change to rotate the vehicle around the Z axis when the vehicle
// Angular change to rotate the vehicle around the Z axis when the vehicle
// is tipped around the X axis.
// From http://wiki.secondlife.com/wiki/Linden_Vehicle_Tutorial:
// The vertical attractor feature must be enabled in order for the banking behavior to
@ -1413,12 +1411,12 @@ namespace OpenSim.Region.Physics.BulletSPlugin
// world z-axis is determined by the VEHICLE_BANKING_TIMESCALE. So if you want the vehicle to
// bank quickly then give it a banking timescale of about a second or less, otherwise you can
// make a sluggish vehicle by giving it a timescale of several seconds.
public Vector3 ComputeAngularBanking()
public void ComputeAngularBanking()
{
Vector3 ret = Vector3.Zero;
if (enableAngularBanking && m_bankingEfficiency != 0 && m_verticalAttractionTimescale < m_verticalAttractionCutoff)
{
Vector3 bankingContribution = Vector3.Zero;
// Rotate a UnitZ vector (pointing up) to how the vehicle is oriented.
// As the vehicle rolls to the right or left, the Y value will increase from
// zero (straight up) to 1 or -1 (full tilt right or left)
@ -1435,15 +1433,16 @@ namespace OpenSim.Region.Physics.BulletSPlugin
mixedYawAngle = ClampInRange(-20f, mixedYawAngle, 20f);
// Build the force vector to change rotation from what it is to what it should be
ret.Z = -mixedYawAngle;
bankingContribution.Z = -mixedYawAngle;
// Don't do it all at once.
ret /= m_bankingTimescale;
bankingContribution /= m_bankingTimescale;
VehicleRotationalVelocity += bankingContribution;
VDetailLog("{0}, MoveAngular,Banking,rollComp={1},speed={2},rollComp={3},yAng={4},mYAng={5},ret={6}",
Prim.LocalID, rollComponents, VehicleForwardSpeed, rollComponents, yawAngle, mixedYawAngle, ret);
Prim.LocalID, rollComponents, VehicleForwardSpeed, rollComponents, yawAngle, mixedYawAngle, bankingContribution);
}
return ret;
}
// This is from previous instantiations of XXXDynamics.cs.

View File

@ -149,6 +149,7 @@ public class BSVMotor : BSMotor
Vector3 correction = Vector3.Zero;
Vector3 error = TargetValue - CurrentValue;
LastError = error;
if (!ErrorIsZero(error))
{
correction = StepError(timeStep, error);
@ -188,9 +189,15 @@ public class BSVMotor : BSMotor
else
{
// Difference between what we have and target is small. Motor is done.
CurrentValue = TargetValue = Vector3.Zero;
MDetailLog("{0}, BSVMotor.Step,zero,{1},origTgt={2},origCurr={3},ret={4}",
BSScene.DetailLogZero, UseName, origCurrVal, origTarget, CurrentValue);
if (TargetValue.ApproxEquals(Vector3.Zero, ErrorZeroThreshold))
{
// The target can step down to nearly zero but not get there. If close to zero
// it is really zero.
TargetValue = Vector3.Zero;
}
CurrentValue = TargetValue;
MDetailLog("{0}, BSVMotor.Step,zero,{1},origTgt={2},origCurr={3},currTgt={4},currCurr={5}",
BSScene.DetailLogZero, UseName, origCurrVal, origTarget, TargetValue, CurrentValue);
}
return correction;
@ -205,9 +212,8 @@ public class BSVMotor : BSMotor
{
if (!Enabled) return Vector3.Zero;
LastError = error;
Vector3 returnCorrection = Vector3.Zero;
if (!ErrorIsZero())
if (!ErrorIsZero(error))
{
// correction = error / secondsItShouldTakeToCorrect
Vector3 correctionAmount;
@ -309,6 +315,7 @@ public class BSFMotor : BSMotor
float correction = 0f;
float error = TargetValue - CurrentValue;
LastError = error;
if (!ErrorIsZero(error))
{
correction = StepError(timeStep, error);
@ -346,6 +353,12 @@ public class BSFMotor : BSMotor
else
{
// Difference between what we have and target is small. Motor is done.
if (Util.InRange<float>(TargetValue, -ErrorZeroThreshold, ErrorZeroThreshold))
{
// The target can step down to nearly zero but not get there. If close to zero
// it is really zero.
TargetValue = 0f;
}
CurrentValue = TargetValue;
MDetailLog("{0}, BSFMotor.Step,zero,{1},origTgt={2},origCurr={3},ret={4}",
BSScene.DetailLogZero, UseName, origCurrVal, origTarget, CurrentValue);
@ -358,9 +371,8 @@ public class BSFMotor : BSMotor
{
if (!Enabled) return 0f;
LastError = error;
float returnCorrection = 0f;
if (!ErrorIsZero())
if (!ErrorIsZero(error))
{
// correction = error / secondsItShouldTakeToCorrect
float correctionAmount;

View File

@ -45,6 +45,9 @@ public static class BSParam
public static float MinimumObjectMass { get; private set; }
public static float MaximumObjectMass { get; private set; }
public static float MaxLinearVelocity { get; private set; }
public static float MaxAngularVelocity { get; private set; }
public static float MaxAddForceMagnitude { get; private set; }
public static float LinearDamping { get; private set; }
public static float AngularDamping { get; private set; }
@ -79,6 +82,8 @@ public static class BSParam
public static float AvatarStepApproachFactor { get; private set; }
public static float AvatarStepForceFactor { get; private set; }
public static float VehicleMaxLinearVelocity { get; private set; }
public static float VehicleMaxAngularVelocity { get; private set; }
public static float VehicleAngularDamping { get; private set; }
public static float VehicleDebuggingEnabled { get; private set; }
@ -103,7 +108,6 @@ public static class BSParam
public const float MaxDensity = 22587f;
public const float MinRestitution = 0f;
public const float MaxRestitution = 1f;
public const float MaxAddForceMagnitude = 20f;
// ===========================================================================
public delegate void ParamUser(BSScene scene, IConfig conf, string paramName, float val);
@ -247,6 +251,22 @@ public static class BSParam
(s,cf,p,v) => { MaximumObjectMass = cf.GetFloat(p, v); },
(s) => { return (float)MaximumObjectMass; },
(s,p,l,v) => { MaximumObjectMass = v; } ),
new ParameterDefn("MaxLinearVelocity", "Maximum velocity magnitude that can be assigned to an object",
1000.0f,
(s,cf,p,v) => { MaxLinearVelocity = cf.GetFloat(p, v); },
(s) => { return (float)MaxLinearVelocity; },
(s,p,l,v) => { MaxLinearVelocity = v; } ),
new ParameterDefn("MaxAngularVelocity", "Maximum rotational velocity magnitude that can be assigned to an object",
1000.0f,
(s,cf,p,v) => { MaxAngularVelocity = cf.GetFloat(p, v); },
(s) => { return (float)MaxAngularVelocity; },
(s,p,l,v) => { MaxAngularVelocity = v; } ),
// LL documentation says thie number should be 20f
new ParameterDefn("MaxAddForceMagnitude", "Maximum force that can be applied by llApplyImpulse (SL says 20f)",
200.0f,
(s,cf,p,v) => { MaxAddForceMagnitude = cf.GetFloat(p, v); },
(s) => { return (float)MaxAddForceMagnitude; },
(s,p,l,v) => { MaxAddForceMagnitude = v; } ),
new ParameterDefn("PID_D", "Derivitive factor for motion smoothing",
2200f,
@ -423,6 +443,16 @@ public static class BSParam
(s) => { return AvatarStepForceFactor; },
(s,p,l,v) => { AvatarStepForceFactor = v; } ),
new ParameterDefn("VehicleMaxLinearVelocity", "Maximum velocity magnitude that can be assigned to a vehicle",
1000.0f,
(s,cf,p,v) => { VehicleMaxLinearVelocity = cf.GetFloat(p, v); },
(s) => { return (float)VehicleMaxLinearVelocity; },
(s,p,l,v) => { VehicleMaxLinearVelocity = v; } ),
new ParameterDefn("VehicleMaxAngularVelocity", "Maximum rotational velocity magnitude that can be assigned to a vehicle",
12.0f,
(s,cf,p,v) => { VehicleMaxAngularVelocity = cf.GetFloat(p, v); },
(s) => { return (float)VehicleMaxAngularVelocity; },
(s,p,l,v) => { VehicleMaxAngularVelocity = v; } ),
new ParameterDefn("VehicleAngularDamping", "Factor to damp vehicle angular movement per second (0.0 - 1.0)",
0.95f,
(s,cf,p,v) => { VehicleAngularDamping = cf.GetFloat(p, v); },

View File

@ -989,10 +989,10 @@ public sealed class BSPrim : BSPhysObject
}
set {
_rotationalVelocity = value;
Util.ClampV(_rotationalVelocity, BSParam.MaxAngularVelocity);
// m_log.DebugFormat("{0}: RotationalVelocity={1}", LogHeader, _rotationalVelocity);
PhysicsScene.TaintedObject("BSPrim.setRotationalVelocity", delegate()
{
DetailLog("{0},BSPrim.SetRotationalVel,taint,rotvel={1}", LocalID, _rotationalVelocity);
ForceRotationalVelocity = _rotationalVelocity;
});
}
@ -1005,6 +1005,7 @@ public sealed class BSPrim : BSPhysObject
_rotationalVelocity = value;
if (PhysBody.HasPhysicalBody)
{
DetailLog("{0},BSPrim.ForceRotationalVel,taint,rotvel={1}", LocalID, _rotationalVelocity);
PhysicsScene.PE.SetAngularVelocity(PhysBody, _rotationalVelocity);
ActivateIfPhysical(false);
}
@ -1193,10 +1194,14 @@ public sealed class BSPrim : BSPhysObject
public override float APIDDamping { set { return; } }
public override void AddForce(OMV.Vector3 force, bool pushforce) {
// Per documentation, max force is limited.
OMV.Vector3 addForce = Util.ClampV(force, BSParam.MaxAddForceMagnitude);
// Since this force is being applied in only one step, make this a force per second.
OMV.Vector3 addForce = force / PhysicsScene.LastTimeStep;
AddForce(addForce, pushforce, false);
addForce /= PhysicsScene.LastTimeStep;
AddForce(addForce, pushforce, false /* inTaintTime */);
}
// Applying a force just adds this to the total force on the object.
// This added force will only last the next simulation tick.
public void AddForce(OMV.Vector3 force, bool pushforce, bool inTaintTime) {
@ -1205,9 +1210,9 @@ public sealed class BSPrim : BSPhysObject
{
if (force.IsFinite())
{
OMV.Vector3 addForce = Util.ClampV(force, BSParam.MaxAddForceMagnitude);
// DetailLog("{0},BSPrim.addForce,call,force={1}", LocalID, addForce);
OMV.Vector3 addForce = force;
PhysicsScene.TaintedObject(inTaintTime, "BSPrim.AddForce", delegate()
{
// Bullet adds this central force to the total force for this tick