aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs554
1 files changed, 392 insertions, 162 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs
index 4c195e1..e208d3a 100644
--- a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs
+++ b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs
@@ -45,7 +45,6 @@ public sealed class BSCharacter : BSPhysObject
45 private bool _selected; 45 private bool _selected;
46 private OMV.Vector3 _position; 46 private OMV.Vector3 _position;
47 private float _mass; 47 private float _mass;
48 private float _avatarDensity;
49 private float _avatarVolume; 48 private float _avatarVolume;
50 private OMV.Vector3 _force; 49 private OMV.Vector3 _force;
51 private OMV.Vector3 _velocity; 50 private OMV.Vector3 _velocity;
@@ -58,16 +57,12 @@ public sealed class BSCharacter : BSPhysObject
58 private bool _flying; 57 private bool _flying;
59 private bool _setAlwaysRun; 58 private bool _setAlwaysRun;
60 private bool _throttleUpdates; 59 private bool _throttleUpdates;
61 private bool _isColliding;
62 private bool _collidingObj;
63 private bool _floatOnWater; 60 private bool _floatOnWater;
64 private OMV.Vector3 _rotationalVelocity; 61 private OMV.Vector3 _rotationalVelocity;
65 private bool _kinematic; 62 private bool _kinematic;
66 private float _buoyancy; 63 private float _buoyancy;
67 64
68 // The friction and velocity of the avatar is modified depending on whether walking or not. 65 private BSVMotor _velocityMotor;
69 private OMV.Vector3 _appliedVelocity; // the last velocity applied to the avatar
70 private float _currentFriction; // the friction currently being used (changed by setVelocity).
71 66
72 private OMV.Vector3 _PIDTarget; 67 private OMV.Vector3 _PIDTarget;
73 private bool _usePID; 68 private bool _usePID;
@@ -83,34 +78,36 @@ public sealed class BSCharacter : BSPhysObject
83 _physicsActorType = (int)ActorTypes.Agent; 78 _physicsActorType = (int)ActorTypes.Agent;
84 _position = pos; 79 _position = pos;
85 80
86 // Old versions of ScenePresence passed only the height. If width and/or depth are zero,
87 // replace with the default values.
88 _size = size;
89 if (_size.X == 0f) _size.X = PhysicsScene.Params.avatarCapsuleDepth;
90 if (_size.Y == 0f) _size.Y = PhysicsScene.Params.avatarCapsuleWidth;
91
92 _flying = isFlying; 81 _flying = isFlying;
93 _orientation = OMV.Quaternion.Identity; 82 _orientation = OMV.Quaternion.Identity;
94 _velocity = OMV.Vector3.Zero; 83 _velocity = OMV.Vector3.Zero;
95 _appliedVelocity = OMV.Vector3.Zero;
96 _buoyancy = ComputeBuoyancyFromFlying(isFlying); 84 _buoyancy = ComputeBuoyancyFromFlying(isFlying);
97 _currentFriction = PhysicsScene.Params.avatarStandingFriction; 85 Friction = BSParam.AvatarStandingFriction;
98 _avatarDensity = PhysicsScene.Params.avatarDensity; 86 Density = BSParam.AvatarDensity / BSParam.DensityScaleFactor;
87
88 // Old versions of ScenePresence passed only the height. If width and/or depth are zero,
89 // replace with the default values.
90 _size = size;
91 if (_size.X == 0f) _size.X = BSParam.AvatarCapsuleDepth;
92 if (_size.Y == 0f) _size.Y = BSParam.AvatarCapsuleWidth;
99 93
100 // The dimensions of the avatar capsule are kept in the scale. 94 // The dimensions of the physical capsule are kept in the scale.
101 // Physics creates a unit capsule which is scaled by the physics engine. 95 // Physics creates a unit capsule which is scaled by the physics engine.
102 ComputeAvatarScale(_size); 96 Scale = ComputeAvatarScale(_size);
103 // set _avatarVolume and _mass based on capsule size, _density and Scale 97 // set _avatarVolume and _mass based on capsule size, _density and Scale
104 ComputeAvatarVolumeAndMass(); 98 ComputeAvatarVolumeAndMass();
99
100 SetupMovementMotor();
101
105 DetailLog("{0},BSCharacter.create,call,size={1},scale={2},density={3},volume={4},mass={5}", 102 DetailLog("{0},BSCharacter.create,call,size={1},scale={2},density={3},volume={4},mass={5}",
106 LocalID, _size, Scale, _avatarDensity, _avatarVolume, RawMass); 103 LocalID, _size, Scale, Density, _avatarVolume, RawMass);
107 104
108 // do actual create at taint time 105 // do actual creation in taint time
109 PhysicsScene.TaintedObject("BSCharacter.create", delegate() 106 PhysicsScene.TaintedObject("BSCharacter.create", delegate()
110 { 107 {
111 DetailLog("{0},BSCharacter.create,taint", LocalID); 108 DetailLog("{0},BSCharacter.create,taint", LocalID);
112 // New body and shape into PhysBody and PhysShape 109 // New body and shape into PhysBody and PhysShape
113 PhysicsScene.Shapes.GetBodyAndShape(true, PhysicsScene.World, this, null, null); 110 PhysicsScene.Shapes.GetBodyAndShape(true, PhysicsScene.World, this);
114 111
115 SetPhysicalProperties(); 112 SetPhysicalProperties();
116 }); 113 });
@@ -120,54 +117,216 @@ public sealed class BSCharacter : BSPhysObject
120 // called when this character is being destroyed and the resources should be released 117 // called when this character is being destroyed and the resources should be released
121 public override void Destroy() 118 public override void Destroy()
122 { 119 {
120 base.Destroy();
121
123 DetailLog("{0},BSCharacter.Destroy", LocalID); 122 DetailLog("{0},BSCharacter.Destroy", LocalID);
124 PhysicsScene.TaintedObject("BSCharacter.destroy", delegate() 123 PhysicsScene.TaintedObject("BSCharacter.destroy", delegate()
125 { 124 {
126 PhysicsScene.Shapes.DereferenceBody(PhysBody, true, null); 125 PhysicsScene.Shapes.DereferenceBody(PhysBody, null /* bodyCallback */);
127 PhysicsScene.Shapes.DereferenceShape(PhysShape, true, null); 126 PhysBody.Clear();
127 PhysicsScene.Shapes.DereferenceShape(PhysShape, null /* bodyCallback */);
128 PhysShape.Clear();
128 }); 129 });
129 } 130 }
130 131
131 private void SetPhysicalProperties() 132 private void SetPhysicalProperties()
132 { 133 {
133 BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, PhysBody.ptr); 134 PhysicsScene.PE.RemoveObjectFromWorld(PhysicsScene.World, PhysBody);
134 135
135 ZeroMotion(true); 136 ZeroMotion(true);
136 ForcePosition = _position; 137 ForcePosition = _position;
137 // Set the velocity and compute the proper friction 138
139 // Set the velocity
140 _velocityMotor.Reset();
141 _velocityMotor.SetTarget(_velocity);
142 _velocityMotor.SetCurrent(_velocity);
138 ForceVelocity = _velocity; 143 ForceVelocity = _velocity;
139 144
140 // This will enable or disable the flying buoyancy of the avatar. 145 // This will enable or disable the flying buoyancy of the avatar.
141 // Needs to be reset especially when an avatar is recreated after crossing a region boundry. 146 // Needs to be reset especially when an avatar is recreated after crossing a region boundry.
142 Flying = _flying; 147 Flying = _flying;
143 148
144 BulletSimAPI.SetRestitution2(PhysBody.ptr, PhysicsScene.Params.avatarRestitution); 149 PhysicsScene.PE.SetRestitution(PhysBody, BSParam.AvatarRestitution);
145 BulletSimAPI.SetMargin2(PhysShape.ptr, PhysicsScene.Params.collisionMargin); 150 PhysicsScene.PE.SetMargin(PhysShape, PhysicsScene.Params.collisionMargin);
146 BulletSimAPI.SetLocalScaling2(PhysShape.ptr, Scale); 151 PhysicsScene.PE.SetLocalScaling(PhysShape, Scale);
147 BulletSimAPI.SetContactProcessingThreshold2(PhysBody.ptr, PhysicsScene.Params.contactProcessingThreshold); 152 PhysicsScene.PE.SetContactProcessingThreshold(PhysBody, BSParam.ContactProcessingThreshold);
148 if (PhysicsScene.Params.ccdMotionThreshold > 0f) 153 if (BSParam.CcdMotionThreshold > 0f)
149 { 154 {
150 BulletSimAPI.SetCcdMotionThreshold2(PhysBody.ptr, PhysicsScene.Params.ccdMotionThreshold); 155 PhysicsScene.PE.SetCcdMotionThreshold(PhysBody, BSParam.CcdMotionThreshold);
151 BulletSimAPI.SetCcdSweptSphereRadius2(PhysBody.ptr, PhysicsScene.Params.ccdSweptSphereRadius); 156 PhysicsScene.PE.SetCcdSweptSphereRadius(PhysBody, BSParam.CcdSweptSphereRadius);
152 } 157 }
153 158
154 UpdatePhysicalMassProperties(RawMass); 159 UpdatePhysicalMassProperties(RawMass, false);
155 160
156 // Make so capsule does not fall over 161 // Make so capsule does not fall over
157 BulletSimAPI.SetAngularFactorV2(PhysBody.ptr, OMV.Vector3.Zero); 162 PhysicsScene.PE.SetAngularFactorV(PhysBody, OMV.Vector3.Zero);
158 163
159 BulletSimAPI.AddToCollisionFlags2(PhysBody.ptr, CollisionFlags.CF_CHARACTER_OBJECT); 164 PhysicsScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.CF_CHARACTER_OBJECT);
160 165
161 BulletSimAPI.AddObjectToWorld2(PhysicsScene.World.ptr, PhysBody.ptr); 166 PhysicsScene.PE.AddObjectToWorld(PhysicsScene.World, PhysBody);
162 167
163 // BulletSimAPI.ForceActivationState2(BSBody.ptr, ActivationState.ACTIVE_TAG); 168 // PhysicsScene.PE.ForceActivationState(PhysBody, ActivationState.ACTIVE_TAG);
164 BulletSimAPI.ForceActivationState2(PhysBody.ptr, ActivationState.DISABLE_DEACTIVATION); 169 PhysicsScene.PE.ForceActivationState(PhysBody, ActivationState.DISABLE_DEACTIVATION);
165 BulletSimAPI.UpdateSingleAabb2(PhysicsScene.World.ptr, PhysBody.ptr); 170 PhysicsScene.PE.UpdateSingleAabb(PhysicsScene.World, PhysBody);
166 171
167 // Do this after the object has been added to the world 172 // Do this after the object has been added to the world
168 BulletSimAPI.SetCollisionFilterMask2(PhysBody.ptr, 173 PhysBody.collisionType = CollisionType.Avatar;
169 (uint)CollisionFilterGroups.AvatarFilter, 174 PhysBody.ApplyCollisionMask(PhysicsScene);
170 (uint)CollisionFilterGroups.AvatarMask); 175 }
176
177 // The avatar's movement is controlled by this motor that speeds up and slows down
178 // the avatar seeking to reach the motor's target speed.
179 // This motor runs as a prestep action for the avatar so it will keep the avatar
180 // standing as well as moving. Destruction of the avatar will destroy the pre-step action.
181 private void SetupMovementMotor()
182 {
183 // Infinite decay and timescale values so motor only changes current to target values.
184 _velocityMotor = new BSVMotor("BSCharacter.Velocity",
185 0.2f, // time scale
186 BSMotor.Infinite, // decay time scale
187 BSMotor.InfiniteVector, // friction timescale
188 1f // efficiency
189 );
190 // _velocityMotor.PhysicsScene = PhysicsScene; // DEBUG DEBUG so motor will output detail log messages.
191
192 RegisterPreStepAction("BSCharactor.Movement", LocalID, delegate(float timeStep)
193 {
194 // TODO: Decide if the step parameters should be changed depending on the avatar's
195 // state (flying, colliding, ...). There is code in ODE to do this.
196
197 // COMMENTARY: when the user is making the avatar walk, except for falling, the velocity
198 // specified for the avatar is the one that should be used. For falling, if the avatar
199 // is not flying and is not colliding then it is presumed to be falling and the Z
200 // component is not fooled with (thus allowing gravity to do its thing).
201 // When the avatar is standing, though, the user has specified a velocity of zero and
202 // the avatar should be standing. But if the avatar is pushed by something in the world
203 // (raising elevator platform, moving vehicle, ...) the avatar should be allowed to
204 // move. Thus, the velocity cannot be forced to zero. The problem is that small velocity
205 // errors can creap in and the avatar will slowly float off in some direction.
206 // So, the problem is that, when an avatar is standing, we cannot tell creaping error
207 // from real pushing.
208 // The code below uses whether the collider is static or moving to decide whether to zero motion.
209
210 _velocityMotor.Step(timeStep);
211
212 // If we're not supposed to be moving, make sure things are zero.
213 if (_velocityMotor.ErrorIsZero() && _velocityMotor.TargetValue == OMV.Vector3.Zero)
214 {
215 // The avatar shouldn't be moving
216 _velocityMotor.Zero();
217
218 if (IsColliding)
219 {
220 // If we are colliding with a stationary object, presume we're standing and don't move around
221 if (!ColliderIsMoving)
222 {
223 DetailLog("{0},BSCharacter.MoveMotor,collidingWithStationary,zeroingMotion", LocalID);
224 ZeroMotion(true /* inTaintTime */);
225 }
226
227 // Standing has more friction on the ground
228 if (Friction != BSParam.AvatarStandingFriction)
229 {
230 Friction = BSParam.AvatarStandingFriction;
231 PhysicsScene.PE.SetFriction(PhysBody, Friction);
232 }
233 }
234 else
235 {
236 if (Flying)
237 {
238 // Flying and not collising and velocity nearly zero.
239 ZeroMotion(true /* inTaintTime */);
240 }
241 }
242
243 DetailLog("{0},BSCharacter.MoveMotor,taint,stopping,target={1},colliding={2}", LocalID, _velocityMotor.TargetValue, IsColliding);
244 }
245 else
246 {
247 // Supposed to be moving.
248 OMV.Vector3 stepVelocity = _velocityMotor.CurrentValue;
249
250 if (Friction != BSParam.AvatarFriction)
251 {
252 // Probably starting up walking. Set friction to moving friction.
253 Friction = BSParam.AvatarFriction;
254 PhysicsScene.PE.SetFriction(PhysBody, Friction);
255 }
256
257 // If falling, we keep the world's downward vector no matter what the other axis specify.
258 // The check for _velocity.Z < 0 makes jumping work (temporary upward force).
259 if (!Flying && !IsColliding)
260 {
261 if (_velocity.Z < 0)
262 stepVelocity.Z = _velocity.Z;
263 // DetailLog("{0},BSCharacter.MoveMotor,taint,overrideStepZWithWorldZ,stepVel={1}", LocalID, stepVelocity);
264 }
265
266 // 'stepVelocity' is now the speed we'd like the avatar to move in. Turn that into an instantanous force.
267 OMV.Vector3 moveForce = (stepVelocity - _velocity) * Mass;
268
269 // Should we check for move force being small and forcing velocity to zero?
270
271 // Add special movement force to allow avatars to walk up stepped surfaces.
272 moveForce += WalkUpStairs();
273
274 DetailLog("{0},BSCharacter.MoveMotor,move,stepVel={1},vel={2},mass={3},moveForce={4}", LocalID, stepVelocity, _velocity, Mass, moveForce);
275 PhysicsScene.PE.ApplyCentralImpulse(PhysBody, moveForce);
276 }
277 });
278 }
279
280 // Decide if the character is colliding with a low object and compute a force to pop the
281 // avatar up so it can walk up and over the low objects.
282 private OMV.Vector3 WalkUpStairs()
283 {
284 OMV.Vector3 ret = OMV.Vector3.Zero;
285
286 // This test is done if moving forward, not flying and is colliding with something.
287 // DetailLog("{0},BSCharacter.WalkUpStairs,IsColliding={1},flying={2},targSpeed={3},collisions={4}",
288 // LocalID, IsColliding, Flying, TargetSpeed, CollisionsLastTick.Count);
289 if (IsColliding && !Flying && TargetVelocitySpeed > 0.1f /* && ForwardSpeed < 0.1f */)
290 {
291 // The range near the character's feet where we will consider stairs
292 float nearFeetHeightMin = RawPosition.Z - (Size.Z / 2f) + 0.05f;
293 float nearFeetHeightMax = nearFeetHeightMin + BSParam.AvatarStepHeight;
294
295 // Look for a collision point that is near the character's feet and is oriented the same as the charactor is
296 foreach (KeyValuePair<uint, ContactPoint> kvp in CollisionsLastTick.m_objCollisionList)
297 {
298 // Don't care about collisions with the terrain
299 if (kvp.Key > PhysicsScene.TerrainManager.HighestTerrainID)
300 {
301 OMV.Vector3 touchPosition = kvp.Value.Position;
302 // DetailLog("{0},BSCharacter.WalkUpStairs,min={1},max={2},touch={3}",
303 // LocalID, nearFeetHeightMin, nearFeetHeightMax, touchPosition);
304 if (touchPosition.Z >= nearFeetHeightMin && touchPosition.Z <= nearFeetHeightMax)
305 {
306 // This contact is within the 'near the feet' range.
307 // The normal should be our contact point to the object so it is pointing away
308 // thus the difference between our facing orientation and the normal should be small.
309 OMV.Vector3 directionFacing = OMV.Vector3.UnitX * RawOrientation;
310 OMV.Vector3 touchNormal = OMV.Vector3.Normalize(kvp.Value.SurfaceNormal);
311 float diff = Math.Abs(OMV.Vector3.Distance(directionFacing, touchNormal));
312 if (diff < BSParam.AvatarStepApproachFactor)
313 {
314 // Found the stairs contact point. Push up a little to raise the character.
315 float upForce = (touchPosition.Z - nearFeetHeightMin) * Mass * BSParam.AvatarStepForceFactor;
316 ret = new OMV.Vector3(0f, 0f, upForce);
317
318 // Also move the avatar up for the new height
319 OMV.Vector3 displacement = new OMV.Vector3(0f, 0f, BSParam.AvatarStepHeight / 2f);
320 ForcePosition = RawPosition + displacement;
321 }
322 DetailLog("{0},BSCharacter.WalkUpStairs,touchPos={1},nearFeetMin={2},faceDir={3},norm={4},diff={5},ret={6}",
323 LocalID, touchPosition, nearFeetHeightMin, directionFacing, touchNormal, diff, ret);
324 }
325 }
326 }
327 }
328
329 return ret;
171 } 330 }
172 331
173 public override void RequestPhysicsterseUpdate() 332 public override void RequestPhysicsterseUpdate()
@@ -185,24 +344,31 @@ public sealed class BSCharacter : BSPhysObject
185 } 344 }
186 345
187 set { 346 set {
188 // When an avatar's size is set, only the height is changed.
189 _size = value; 347 _size = value;
190 ComputeAvatarScale(_size); 348 // Old versions of ScenePresence passed only the height. If width and/or depth are zero,
349 // replace with the default values.
350 if (_size.X == 0f) _size.X = BSParam.AvatarCapsuleDepth;
351 if (_size.Y == 0f) _size.Y = BSParam.AvatarCapsuleWidth;
352
353 Scale = ComputeAvatarScale(_size);
191 ComputeAvatarVolumeAndMass(); 354 ComputeAvatarVolumeAndMass();
192 DetailLog("{0},BSCharacter.setSize,call,size={1},scale={2},density={3},volume={4},mass={5}", 355 DetailLog("{0},BSCharacter.setSize,call,size={1},scale={2},density={3},volume={4},mass={5}",
193 LocalID, _size, Scale, _avatarDensity, _avatarVolume, RawMass); 356 LocalID, _size, Scale, Density, _avatarVolume, RawMass);
194 357
195 PhysicsScene.TaintedObject("BSCharacter.setSize", delegate() 358 PhysicsScene.TaintedObject("BSCharacter.setSize", delegate()
196 { 359 {
197 BulletSimAPI.SetLocalScaling2(PhysShape.ptr, Scale); 360 if (PhysBody.HasPhysicalBody && PhysShape.HasPhysicalShape)
198 UpdatePhysicalMassProperties(RawMass); 361 {
362 PhysicsScene.PE.SetLocalScaling(PhysShape, Scale);
363 UpdatePhysicalMassProperties(RawMass, true);
364 // Make sure this change appears as a property update event
365 PhysicsScene.PE.PushUpdate(PhysBody);
366 }
199 }); 367 });
200 368
201 } 369 }
202 } 370 }
203 371
204 public override OMV.Vector3 Scale { get; set; }
205
206 public override PrimitiveBaseShape Shape 372 public override PrimitiveBaseShape Shape
207 { 373 {
208 set { BaseShape = value; } 374 set { BaseShape = value; }
@@ -219,6 +385,10 @@ public sealed class BSCharacter : BSPhysObject
219 public override bool Selected { 385 public override bool Selected {
220 set { _selected = value; } 386 set { _selected = value; }
221 } 387 }
388 public override bool IsSelected
389 {
390 get { return _selected; }
391 }
222 public override void CrossingFailure() { return; } 392 public override void CrossingFailure() { return; }
223 public override void link(PhysicsActor obj) { return; } 393 public override void link(PhysicsActor obj) { return; }
224 public override void delink() { return; } 394 public override void delink() { return; }
@@ -236,7 +406,8 @@ public sealed class BSCharacter : BSPhysObject
236 // Zero some other properties directly into the physics engine 406 // Zero some other properties directly into the physics engine
237 PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate() 407 PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate()
238 { 408 {
239 BulletSimAPI.ClearAllForces2(PhysBody.ptr); 409 if (PhysBody.HasPhysicalBody)
410 PhysicsScene.PE.ClearAllForces(PhysBody);
240 }); 411 });
241 } 412 }
242 public override void ZeroAngularMotion(bool inTaintTime) 413 public override void ZeroAngularMotion(bool inTaintTime)
@@ -245,10 +416,13 @@ public sealed class BSCharacter : BSPhysObject
245 416
246 PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate() 417 PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate()
247 { 418 {
248 BulletSimAPI.SetInterpolationAngularVelocity2(PhysBody.ptr, OMV.Vector3.Zero); 419 if (PhysBody.HasPhysicalBody)
249 BulletSimAPI.SetAngularVelocity2(PhysBody.ptr, OMV.Vector3.Zero); 420 {
250 // The next also get rid of applied linear force but the linear velocity is untouched. 421 PhysicsScene.PE.SetInterpolationAngularVelocity(PhysBody, OMV.Vector3.Zero);
251 BulletSimAPI.ClearForces2(PhysBody.ptr); 422 PhysicsScene.PE.SetAngularVelocity(PhysBody, OMV.Vector3.Zero);
423 // The next also get rid of applied linear force but the linear velocity is untouched.
424 PhysicsScene.PE.ClearForces(PhysBody);
425 }
252 }); 426 });
253 } 427 }
254 428
@@ -263,29 +437,31 @@ public sealed class BSCharacter : BSPhysObject
263 public override OMV.Vector3 Position { 437 public override OMV.Vector3 Position {
264 get { 438 get {
265 // Don't refetch the position because this function is called a zillion times 439 // Don't refetch the position because this function is called a zillion times
266 // _position = BulletSimAPI.GetObjectPosition2(Scene.World.ptr, LocalID); 440 // _position = PhysicsScene.PE.GetObjectPosition(Scene.World, LocalID);
267 return _position; 441 return _position;
268 } 442 }
269 set { 443 set {
270 _position = value; 444 _position = value;
271 PositionSanityCheck();
272 445
273 PhysicsScene.TaintedObject("BSCharacter.setPosition", delegate() 446 PhysicsScene.TaintedObject("BSCharacter.setPosition", delegate()
274 { 447 {
275 DetailLog("{0},BSCharacter.SetPosition,taint,pos={1},orient={2}", LocalID, _position, _orientation); 448 DetailLog("{0},BSCharacter.SetPosition,taint,pos={1},orient={2}", LocalID, _position, _orientation);
276 BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); 449 PositionSanityCheck();
450 ForcePosition = _position;
277 }); 451 });
278 } 452 }
279 } 453 }
280 public override OMV.Vector3 ForcePosition { 454 public override OMV.Vector3 ForcePosition {
281 get { 455 get {
282 _position = BulletSimAPI.GetPosition2(PhysBody.ptr); 456 _position = PhysicsScene.PE.GetPosition(PhysBody);
283 return _position; 457 return _position;
284 } 458 }
285 set { 459 set {
286 _position = value; 460 _position = value;
287 PositionSanityCheck(); 461 if (PhysBody.HasPhysicalBody)
288 BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); 462 {
463 PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation);
464 }
289 } 465 }
290 } 466 }
291 467
@@ -297,17 +473,28 @@ public sealed class BSCharacter : BSPhysObject
297 { 473 {
298 bool ret = false; 474 bool ret = false;
299 475
476 // TODO: check for out of bounds
477 if (!PhysicsScene.TerrainManager.IsWithinKnownTerrain(RawPosition))
478 {
479 // The character is out of the known/simulated area.
480 // Force the avatar position to be within known. ScenePresence will use the position
481 // plus the velocity to decide if the avatar is moving out of the region.
482 RawPosition = PhysicsScene.TerrainManager.ClampPositionIntoKnownTerrain(RawPosition);
483 DetailLog("{0},BSCharacter.PositionSanityCheck,notWithinKnownTerrain,clampedPos={1}", LocalID, RawPosition);
484 return true;
485 }
486
300 // If below the ground, move the avatar up 487 // If below the ground, move the avatar up
301 float terrainHeight = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(_position); 488 float terrainHeight = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(RawPosition);
302 if (Position.Z < terrainHeight) 489 if (Position.Z < terrainHeight)
303 { 490 {
304 DetailLog("{0},BSCharacter.PositionAdjustUnderGround,call,pos={1},terrain={2}", LocalID, _position, terrainHeight); 491 DetailLog("{0},BSCharacter.PositionSanityCheck,adjustForUnderGround,pos={1},terrain={2}", LocalID, _position, terrainHeight);
305 _position.Z = terrainHeight + 2.0f; 492 _position.Z = terrainHeight + BSParam.AvatarBelowGroundUpCorrectionMeters;
306 ret = true; 493 ret = true;
307 } 494 }
308 if ((CurrentCollisionFlags & CollisionFlags.BS_FLOATS_ON_WATER) != 0) 495 if ((CurrentCollisionFlags & CollisionFlags.BS_FLOATS_ON_WATER) != 0)
309 { 496 {
310 float waterHeight = PhysicsScene.GetWaterLevelAtXYZ(_position); 497 float waterHeight = PhysicsScene.TerrainManager.GetWaterLevelAtXYZ(_position);
311 if (Position.Z < waterHeight) 498 if (Position.Z < waterHeight)
312 { 499 {
313 _position.Z = waterHeight; 500 _position.Z = waterHeight;
@@ -315,7 +502,6 @@ public sealed class BSCharacter : BSPhysObject
315 } 502 }
316 } 503 }
317 504
318 // TODO: check for out of bounds
319 return ret; 505 return ret;
320 } 506 }
321 507
@@ -332,7 +518,7 @@ public sealed class BSCharacter : BSPhysObject
332 PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.PositionSanityCheck", delegate() 518 PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.PositionSanityCheck", delegate()
333 { 519 {
334 DetailLog("{0},BSCharacter.PositionSanityCheck,taint,pos={1},orient={2}", LocalID, _position, _orientation); 520 DetailLog("{0},BSCharacter.PositionSanityCheck,taint,pos={1},orient={2}", LocalID, _position, _orientation);
335 BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); 521 ForcePosition = _position;
336 }); 522 });
337 ret = true; 523 ret = true;
338 } 524 }
@@ -345,10 +531,10 @@ public sealed class BSCharacter : BSPhysObject
345 public override float RawMass { 531 public override float RawMass {
346 get {return _mass; } 532 get {return _mass; }
347 } 533 }
348 public override void UpdatePhysicalMassProperties(float physMass) 534 public override void UpdatePhysicalMassProperties(float physMass, bool inWorld)
349 { 535 {
350 OMV.Vector3 localInertia = BulletSimAPI.CalculateLocalInertia2(PhysShape.ptr, physMass); 536 OMV.Vector3 localInertia = PhysicsScene.PE.CalculateLocalInertia(PhysShape, physMass);
351 BulletSimAPI.SetMassProps2(PhysBody.ptr, physMass, localInertia); 537 PhysicsScene.PE.SetMassProps(PhysBody, physMass, localInertia);
352 } 538 }
353 539
354 public override OMV.Vector3 Force { 540 public override OMV.Vector3 Force {
@@ -359,7 +545,8 @@ public sealed class BSCharacter : BSPhysObject
359 PhysicsScene.TaintedObject("BSCharacter.SetForce", delegate() 545 PhysicsScene.TaintedObject("BSCharacter.SetForce", delegate()
360 { 546 {
361 DetailLog("{0},BSCharacter.setForce,taint,force={1}", LocalID, _force); 547 DetailLog("{0},BSCharacter.setForce,taint,force={1}", LocalID, _force);
362 BulletSimAPI.SetObjectForce2(PhysBody.ptr, _force); 548 if (PhysBody.HasPhysicalBody)
549 PhysicsScene.PE.SetObjectForce(PhysBody, _force);
363 }); 550 });
364 } 551 }
365 } 552 }
@@ -376,6 +563,37 @@ public sealed class BSCharacter : BSPhysObject
376 563
377 public override OMV.Vector3 GeometricCenter { get { return OMV.Vector3.Zero; } } 564 public override OMV.Vector3 GeometricCenter { get { return OMV.Vector3.Zero; } }
378 public override OMV.Vector3 CenterOfMass { get { return OMV.Vector3.Zero; } } 565 public override OMV.Vector3 CenterOfMass { get { return OMV.Vector3.Zero; } }
566
567 // Sets the target in the motor. This starts the changing of the avatar's velocity.
568 public override OMV.Vector3 TargetVelocity
569 {
570 get
571 {
572 return m_targetVelocity;
573 }
574 set
575 {
576 DetailLog("{0},BSCharacter.setTargetVelocity,call,vel={1}", LocalID, value);
577 m_targetVelocity = value;
578 OMV.Vector3 targetVel = value;
579 if (_setAlwaysRun)
580 targetVel *= new OMV.Vector3(BSParam.AvatarAlwaysRunFactor, BSParam.AvatarAlwaysRunFactor, 0f);
581
582 PhysicsScene.TaintedObject("BSCharacter.setTargetVelocity", delegate()
583 {
584 _velocityMotor.Reset();
585 _velocityMotor.SetTarget(targetVel);
586 _velocityMotor.SetCurrent(_velocity);
587 _velocityMotor.Enabled = true;
588 });
589 }
590 }
591 public override OMV.Vector3 RawVelocity
592 {
593 get { return _velocity; }
594 set { _velocity = value; }
595 }
596 // Directly setting velocity means this is what the user really wants now.
379 public override OMV.Vector3 Velocity { 597 public override OMV.Vector3 Velocity {
380 get { return _velocity; } 598 get { return _velocity; }
381 set { 599 set {
@@ -383,6 +601,11 @@ public sealed class BSCharacter : BSPhysObject
383 // m_log.DebugFormat("{0}: set velocity = {1}", LogHeader, _velocity); 601 // m_log.DebugFormat("{0}: set velocity = {1}", LogHeader, _velocity);
384 PhysicsScene.TaintedObject("BSCharacter.setVelocity", delegate() 602 PhysicsScene.TaintedObject("BSCharacter.setVelocity", delegate()
385 { 603 {
604 _velocityMotor.Reset();
605 _velocityMotor.SetCurrent(_velocity);
606 _velocityMotor.SetTarget(_velocity);
607 _velocityMotor.Enabled = false;
608
386 DetailLog("{0},BSCharacter.setVelocity,taint,vel={1}", LocalID, _velocity); 609 DetailLog("{0},BSCharacter.setVelocity,taint,vel={1}", LocalID, _velocity);
387 ForceVelocity = _velocity; 610 ForceVelocity = _velocity;
388 }); 611 });
@@ -391,30 +614,11 @@ public sealed class BSCharacter : BSPhysObject
391 public override OMV.Vector3 ForceVelocity { 614 public override OMV.Vector3 ForceVelocity {
392 get { return _velocity; } 615 get { return _velocity; }
393 set { 616 set {
394 // Depending on whether the avatar is moving or not, change the friction 617 PhysicsScene.AssertInTaintTime("BSCharacter.ForceVelocity");
395 // to keep the avatar from slipping around
396 if (_velocity.Length() == 0)
397 {
398 if (_currentFriction != PhysicsScene.Params.avatarStandingFriction)
399 {
400 _currentFriction = PhysicsScene.Params.avatarStandingFriction;
401 BulletSimAPI.SetFriction2(PhysBody.ptr, _currentFriction);
402 }
403 }
404 else
405 {
406 if (_currentFriction != PhysicsScene.Params.avatarFriction)
407 {
408 _currentFriction = PhysicsScene.Params.avatarFriction;
409 BulletSimAPI.SetFriction2(PhysBody.ptr, _currentFriction);
410 }
411 }
412 _velocity = value;
413 // Remember the set velocity so we can suppress the reduction by friction, ...
414 _appliedVelocity = value;
415 618
416 BulletSimAPI.SetLinearVelocity2(PhysBody.ptr, _velocity); 619 _velocity = value;
417 BulletSimAPI.Activate2(PhysBody.ptr, true); 620 PhysicsScene.PE.SetLinearVelocity(PhysBody, _velocity);
621 PhysicsScene.PE.Activate(PhysBody, true);
418 } 622 }
419 } 623 }
420 public override OMV.Vector3 Torque { 624 public override OMV.Vector3 Torque {
@@ -439,13 +643,16 @@ public sealed class BSCharacter : BSPhysObject
439 public override OMV.Quaternion Orientation { 643 public override OMV.Quaternion Orientation {
440 get { return _orientation; } 644 get { return _orientation; }
441 set { 645 set {
442 _orientation = value; 646 // Orientation is set zillions of times when an avatar is walking. It's like
443 // m_log.DebugFormat("{0}: set orientation to {1}", LogHeader, _orientation); 647 // the viewer doesn't trust us.
444 PhysicsScene.TaintedObject("BSCharacter.setOrientation", delegate() 648 if (_orientation != value)
445 { 649 {
446 // _position = BulletSimAPI.GetPosition2(BSBody.ptr); 650 _orientation = value;
447 BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); 651 PhysicsScene.TaintedObject("BSCharacter.setOrientation", delegate()
448 }); 652 {
653 ForceOrientation = _orientation;
654 });
655 }
449 } 656 }
450 } 657 }
451 // Go directly to Bullet to get/set the value. 658 // Go directly to Bullet to get/set the value.
@@ -453,13 +660,17 @@ public sealed class BSCharacter : BSPhysObject
453 { 660 {
454 get 661 get
455 { 662 {
456 _orientation = BulletSimAPI.GetOrientation2(PhysBody.ptr); 663 _orientation = PhysicsScene.PE.GetOrientation(PhysBody);
457 return _orientation; 664 return _orientation;
458 } 665 }
459 set 666 set
460 { 667 {
461 _orientation = value; 668 _orientation = value;
462 BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); 669 if (PhysBody.HasPhysicalBody)
670 {
671 // _position = PhysicsScene.PE.GetPosition(BSBody);
672 PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation);
673 }
463 } 674 }
464 } 675 }
465 public override int PhysicsActorType { 676 public override int PhysicsActorType {
@@ -478,10 +689,14 @@ public sealed class BSCharacter : BSPhysObject
478 public override bool IsStatic { 689 public override bool IsStatic {
479 get { return false; } 690 get { return false; }
480 } 691 }
692 public override bool IsPhysicallyActive {
693 get { return true; }
694 }
481 public override bool Flying { 695 public override bool Flying {
482 get { return _flying; } 696 get { return _flying; }
483 set { 697 set {
484 _flying = value; 698 _flying = value;
699
485 // simulate flying by changing the effect of gravity 700 // simulate flying by changing the effect of gravity
486 Buoyancy = ComputeBuoyancyFromFlying(_flying); 701 Buoyancy = ComputeBuoyancyFromFlying(_flying);
487 } 702 }
@@ -500,27 +715,18 @@ public sealed class BSCharacter : BSPhysObject
500 get { return _throttleUpdates; } 715 get { return _throttleUpdates; }
501 set { _throttleUpdates = value; } 716 set { _throttleUpdates = value; }
502 } 717 }
503 public override bool IsColliding {
504 get { return (CollidingStep == PhysicsScene.SimulationStep); }
505 set { _isColliding = value; }
506 }
507 public override bool CollidingGround {
508 get { return (CollidingGroundStep == PhysicsScene.SimulationStep); }
509 set { CollidingGround = value; }
510 }
511 public override bool CollidingObj {
512 get { return _collidingObj; }
513 set { _collidingObj = value; }
514 }
515 public override bool FloatOnWater { 718 public override bool FloatOnWater {
516 set { 719 set {
517 _floatOnWater = value; 720 _floatOnWater = value;
518 PhysicsScene.TaintedObject("BSCharacter.setFloatOnWater", delegate() 721 PhysicsScene.TaintedObject("BSCharacter.setFloatOnWater", delegate()
519 { 722 {
520 if (_floatOnWater) 723 if (PhysBody.HasPhysicalBody)
521 CurrentCollisionFlags = BulletSimAPI.AddToCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_FLOATS_ON_WATER); 724 {
522 else 725 if (_floatOnWater)
523 CurrentCollisionFlags = BulletSimAPI.RemoveFromCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_FLOATS_ON_WATER); 726 CurrentCollisionFlags = PhysicsScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER);
727 else
728 CurrentCollisionFlags = PhysicsScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER);
729 }
524 }); 730 });
525 } 731 }
526 } 732 }
@@ -549,11 +755,16 @@ public sealed class BSCharacter : BSPhysObject
549 } 755 }
550 public override float ForceBuoyancy { 756 public override float ForceBuoyancy {
551 get { return _buoyancy; } 757 get { return _buoyancy; }
552 set { _buoyancy = value; 758 set {
759 PhysicsScene.AssertInTaintTime("BSCharacter.ForceBuoyancy");
760
761 _buoyancy = value;
553 DetailLog("{0},BSCharacter.setForceBuoyancy,taint,buoy={1}", LocalID, _buoyancy); 762 DetailLog("{0},BSCharacter.setForceBuoyancy,taint,buoy={1}", LocalID, _buoyancy);
554 // Buoyancy is faked by changing the gravity applied to the object 763 // Buoyancy is faked by changing the gravity applied to the object
555 float grav = PhysicsScene.Params.gravity * (1f - _buoyancy); 764 float grav = BSParam.Gravity * (1f - _buoyancy);
556 BulletSimAPI.SetGravity2(PhysBody.ptr, new OMV.Vector3(0f, 0f, grav)); 765 Gravity = new OMV.Vector3(0f, 0f, grav);
766 if (PhysBody.HasPhysicalBody)
767 PhysicsScene.PE.SetGravity(PhysBody, Gravity);
557 } 768 }
558 } 769 }
559 770
@@ -589,24 +800,33 @@ public sealed class BSCharacter : BSPhysObject
589 public override float APIDStrength { set { return; } } 800 public override float APIDStrength { set { return; } }
590 public override float APIDDamping { set { return; } } 801 public override float APIDDamping { set { return; } }
591 802
592 public override void AddForce(OMV.Vector3 force, bool pushforce) { 803 public override void AddForce(OMV.Vector3 force, bool pushforce)
804 {
805 // Since this force is being applied in only one step, make this a force per second.
806 OMV.Vector3 addForce = force / PhysicsScene.LastTimeStep;
807 AddForce(addForce, pushforce, false);
808 }
809 private void AddForce(OMV.Vector3 force, bool pushforce, bool inTaintTime) {
593 if (force.IsFinite()) 810 if (force.IsFinite())
594 { 811 {
595 _force.X += force.X; 812 OMV.Vector3 addForce = Util.ClampV(force, BSParam.MaxAddForceMagnitude);
596 _force.Y += force.Y; 813 // DetailLog("{0},BSCharacter.addForce,call,force={1}", LocalID, addForce);
597 _force.Z += force.Z; 814
598 // m_log.DebugFormat("{0}: AddForce. adding={1}, newForce={2}", LogHeader, force, _force); 815 PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.AddForce", delegate()
599 PhysicsScene.TaintedObject("BSCharacter.AddForce", delegate()
600 { 816 {
601 DetailLog("{0},BSCharacter.setAddForce,taint,addedForce={1}", LocalID, _force); 817 // Bullet adds this central force to the total force for this tick
602 BulletSimAPI.SetObjectForce2(PhysBody.ptr, _force); 818 // DetailLog("{0},BSCharacter.addForce,taint,force={1}", LocalID, addForce);
819 if (PhysBody.HasPhysicalBody)
820 {
821 PhysicsScene.PE.ApplyCentralForce(PhysBody, addForce);
822 }
603 }); 823 });
604 } 824 }
605 else 825 else
606 { 826 {
607 m_log.ErrorFormat("{0}: Got a NaN force applied to a Character", LogHeader); 827 m_log.WarnFormat("{0}: Got a NaN force applied to a character. LocalID={1}", LogHeader, LocalID);
828 return;
608 } 829 }
609 //m_lastUpdateSent = false;
610 } 830 }
611 831
612 public override void AddAngularForce(OMV.Vector3 force, bool pushforce) { 832 public override void AddAngularForce(OMV.Vector3 force, bool pushforce) {
@@ -614,24 +834,31 @@ public sealed class BSCharacter : BSPhysObject
614 public override void SetMomentum(OMV.Vector3 momentum) { 834 public override void SetMomentum(OMV.Vector3 momentum) {
615 } 835 }
616 836
617 private void ComputeAvatarScale(OMV.Vector3 size) 837 private OMV.Vector3 ComputeAvatarScale(OMV.Vector3 size)
618 { 838 {
619 // The 'size' given by the simulator is the mid-point of the avatar 839 OMV.Vector3 newScale;
620 // and X and Y are unspecified. 840
621 841 // Bullet's capsule total height is the "passed height + radius * 2";
622 OMV.Vector3 newScale = size; 842 // The base capsule is 1 diameter and 2 height (passed radius=0.5, passed height = 1)
623 // newScale.X = PhysicsScene.Params.avatarCapsuleWidth; 843 // The number we pass in for 'scaling' is the multiplier to get that base
624 // newScale.Y = PhysicsScene.Params.avatarCapsuleDepth; 844 // shape to be the size desired.
625 845 // So, when creating the scale for the avatar height, we take the passed height
626 // From the total height, remove the capsule half spheres that are at each end 846 // (size.Z) and remove the caps.
627 // The 1.15f came from ODE. Not sure what this factors in. 847 // Another oddity of the Bullet capsule implementation is that it presumes the Y
628 // newScale.Z = (size.Z * 1.15f) - (newScale.X + newScale.Y); 848 // dimension is the radius of the capsule. Even though some of the code allows
849 // for a asymmetrical capsule, other parts of the code presume it is cylindrical.
850
851 // Scale is multiplier of radius with one of "0.5"
852 newScale.X = size.X / 2f;
853 newScale.Y = size.Y / 2f;
629 854
630 // The total scale height is the central cylindar plus the caps on the two ends. 855 // The total scale height is the central cylindar plus the caps on the two ends.
631 newScale.Z = size.Z + (Math.Min(size.X, size.Y) * 2f); 856 newScale.Z = (size.Z + (Math.Min(size.X, size.Y) * 2)) / 2f;
857 // If smaller than the endcaps, just fake like we're almost that small
858 if (newScale.Z < 0)
859 newScale.Z = 0.1f;
632 860
633 // Convert diameters to radii and height to half height -- the way Bullet expects it. 861 return newScale;
634 Scale = newScale / 2f;
635 } 862 }
636 863
637 // set _avatarVolume and _mass based on capsule size, _density and Scale 864 // set _avatarVolume and _mass based on capsule size, _density and Scale
@@ -639,16 +866,16 @@ public sealed class BSCharacter : BSPhysObject
639 { 866 {
640 _avatarVolume = (float)( 867 _avatarVolume = (float)(
641 Math.PI 868 Math.PI
642 * Scale.X 869 * Size.X / 2f
643 * Scale.Y // the area of capsule cylinder 870 * Size.Y / 2f // the area of capsule cylinder
644 * Scale.Z // times height of capsule cylinder 871 * Size.Z // times height of capsule cylinder
645 + 1.33333333f 872 + 1.33333333f
646 * Math.PI 873 * Math.PI
647 * Scale.X 874 * Size.X / 2f
648 * Math.Min(Scale.X, Scale.Y) 875 * Math.Min(Size.X, Size.Y) / 2
649 * Scale.Y // plus the volume of the capsule end caps 876 * Size.Y / 2f // plus the volume of the capsule end caps
650 ); 877 );
651 _mass = _avatarDensity * _avatarVolume; 878 _mass = Density * BSParam.DensityScaleFactor * _avatarVolume;
652 } 879 }
653 880
654 // The physics engine says that properties have updated. Update same and inform 881 // The physics engine says that properties have updated. Update same and inform
@@ -657,27 +884,30 @@ public sealed class BSCharacter : BSPhysObject
657 { 884 {
658 _position = entprop.Position; 885 _position = entprop.Position;
659 _orientation = entprop.Rotation; 886 _orientation = entprop.Rotation;
660 _velocity = entprop.Velocity; 887
888 // Smooth velocity. OpenSimulator is VERY sensitive to changes in velocity of the avatar
889 // and will send agent updates to the clients if velocity changes by more than
890 // 0.001m/s. Bullet introduces a lot of jitter in the velocity which causes many
891 // extra updates.
892 if (!entprop.Velocity.ApproxEquals(_velocity, 0.1f))
893 _velocity = entprop.Velocity;
894
661 _acceleration = entprop.Acceleration; 895 _acceleration = entprop.Acceleration;
662 _rotationalVelocity = entprop.RotationalVelocity; 896 _rotationalVelocity = entprop.RotationalVelocity;
897
663 // Do some sanity checking for the avatar. Make sure it's above ground and inbounds. 898 // Do some sanity checking for the avatar. Make sure it's above ground and inbounds.
664 PositionSanityCheck(true); 899 if (PositionSanityCheck(true))
900 {
901 DetailLog("{0},BSCharacter.UpdateProperties,updatePosForSanity,pos={1}", LocalID, _position);
902 entprop.Position = _position;
903 }
665 904
666 // remember the current and last set values 905 // remember the current and last set values
667 LastEntityProperties = CurrentEntityProperties; 906 LastEntityProperties = CurrentEntityProperties;
668 CurrentEntityProperties = entprop; 907 CurrentEntityProperties = entprop;
669 908
670 if (entprop.Velocity != LastEntityProperties.Velocity)
671 {
672 // Changes in the velocity are suppressed in avatars.
673 // That's just the way they are defined.
674 OMV.Vector3 avVel = new OMV.Vector3(_appliedVelocity.X, _appliedVelocity.Y, entprop.Velocity.Z);
675 _velocity = avVel;
676 BulletSimAPI.SetLinearVelocity2(PhysBody.ptr, avVel);
677 }
678
679 // Tell the linkset about value changes 909 // Tell the linkset about value changes
680 Linkset.UpdateProperties(this); 910 // Linkset.UpdateProperties(UpdatedProperties.EntPropUpdates, this);
681 911
682 // Avatars don't report their changes the usual way. Changes are checked for in the heartbeat loop. 912 // Avatars don't report their changes the usual way. Changes are checked for in the heartbeat loop.
683 // base.RequestPhysicsterseUpdate(); 913 // base.RequestPhysicsterseUpdate();