diff options
author | Melanie | 2012-09-02 22:15:55 +0100 |
---|---|---|
committer | Melanie | 2012-09-02 22:15:55 +0100 |
commit | e2e8b0905950a42fa254d8448027332567470d1e (patch) | |
tree | 03023da2f0e01d943231d4fd517b299266fd0645 /OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs | |
parent | Merge branch 'master' into careminster (diff) | |
parent | BulletSim: update the SOs and DLLs (diff) | |
download | opensim-SC-e2e8b0905950a42fa254d8448027332567470d1e.zip opensim-SC-e2e8b0905950a42fa254d8448027332567470d1e.tar.gz opensim-SC-e2e8b0905950a42fa254d8448027332567470d1e.tar.bz2 opensim-SC-e2e8b0905950a42fa254d8448027332567470d1e.tar.xz |
Merge branch 'master' into careminster
Diffstat (limited to 'OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs')
-rw-r--r-- | OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs | 124 |
1 files changed, 77 insertions, 47 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs index 1b23a36..747ae71 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs | |||
@@ -34,13 +34,12 @@ using OpenSim.Region.Physics.Manager; | |||
34 | 34 | ||
35 | namespace OpenSim.Region.Physics.BulletSPlugin | 35 | namespace OpenSim.Region.Physics.BulletSPlugin |
36 | { | 36 | { |
37 | public class BSCharacter : PhysicsActor | 37 | public class BSCharacter : BSPhysObject |
38 | { | 38 | { |
39 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | 39 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
40 | private static readonly string LogHeader = "[BULLETS CHAR]"; | 40 | private static readonly string LogHeader = "[BULLETS CHAR]"; |
41 | 41 | ||
42 | private BSScene _scene; | 42 | public BSScene Scene { get; private set; } |
43 | public BSScene Scene { get { return _scene; } } | ||
44 | private String _avName; | 43 | private String _avName; |
45 | // private bool _stopped; | 44 | // private bool _stopped; |
46 | private Vector3 _size; | 45 | private Vector3 _size; |
@@ -74,11 +73,8 @@ public class BSCharacter : PhysicsActor | |||
74 | private bool _kinematic; | 73 | private bool _kinematic; |
75 | private float _buoyancy; | 74 | private float _buoyancy; |
76 | 75 | ||
77 | private BulletBody m_body; | 76 | public override BulletBody Body { get; set; } |
78 | public BulletBody Body { | 77 | public override BSLinkset Linkset { get; set; } |
79 | get { return m_body; } | ||
80 | set { m_body = value; } | ||
81 | } | ||
82 | 78 | ||
83 | private int _subscribedEventsMs = 0; | 79 | private int _subscribedEventsMs = 0; |
84 | private int _nextCollisionOkTime = 0; | 80 | private int _nextCollisionOkTime = 0; |
@@ -95,7 +91,7 @@ public class BSCharacter : PhysicsActor | |||
95 | { | 91 | { |
96 | _localID = localID; | 92 | _localID = localID; |
97 | _avName = avName; | 93 | _avName = avName; |
98 | _scene = parent_scene; | 94 | Scene = parent_scene; |
99 | _position = pos; | 95 | _position = pos; |
100 | _size = size; | 96 | _size = size; |
101 | _flying = isFlying; | 97 | _flying = isFlying; |
@@ -104,10 +100,12 @@ public class BSCharacter : PhysicsActor | |||
104 | _buoyancy = ComputeBuoyancyFromFlying(isFlying); | 100 | _buoyancy = ComputeBuoyancyFromFlying(isFlying); |
105 | // The dimensions of the avatar capsule are kept in the scale. | 101 | // The dimensions of the avatar capsule are kept in the scale. |
106 | // Physics creates a unit capsule which is scaled by the physics engine. | 102 | // Physics creates a unit capsule which is scaled by the physics engine. |
107 | _scale = new Vector3(_scene.Params.avatarCapsuleRadius, _scene.Params.avatarCapsuleRadius, size.Z); | 103 | _scale = new Vector3(Scene.Params.avatarCapsuleRadius, Scene.Params.avatarCapsuleRadius, size.Z); |
108 | _density = _scene.Params.avatarDensity; | 104 | _density = Scene.Params.avatarDensity; |
109 | ComputeAvatarVolumeAndMass(); // set _avatarVolume and _mass based on capsule size, _density and _scale | 105 | ComputeAvatarVolumeAndMass(); // set _avatarVolume and _mass based on capsule size, _density and _scale |
110 | 106 | ||
107 | Linkset = new BSLinkset(Scene, this); | ||
108 | |||
111 | ShapeData shapeData = new ShapeData(); | 109 | ShapeData shapeData = new ShapeData(); |
112 | shapeData.ID = _localID; | 110 | shapeData.ID = _localID; |
113 | shapeData.Type = ShapeData.PhysicsShapeType.SHAPE_AVATAR; | 111 | shapeData.Type = ShapeData.PhysicsShapeType.SHAPE_AVATAR; |
@@ -118,19 +116,19 @@ public class BSCharacter : PhysicsActor | |||
118 | shapeData.Mass = _mass; | 116 | shapeData.Mass = _mass; |
119 | shapeData.Buoyancy = _buoyancy; | 117 | shapeData.Buoyancy = _buoyancy; |
120 | shapeData.Static = ShapeData.numericFalse; | 118 | shapeData.Static = ShapeData.numericFalse; |
121 | shapeData.Friction = _scene.Params.avatarFriction; | 119 | shapeData.Friction = Scene.Params.avatarFriction; |
122 | shapeData.Restitution = _scene.Params.avatarRestitution; | 120 | shapeData.Restitution = Scene.Params.avatarRestitution; |
123 | 121 | ||
124 | // do actual create at taint time | 122 | // do actual create at taint time |
125 | _scene.TaintedObject("BSCharacter.create", delegate() | 123 | Scene.TaintedObject("BSCharacter.create", delegate() |
126 | { | 124 | { |
127 | DetailLog("{0},BSCharacter.create", _localID); | 125 | DetailLog("{0},BSCharacter.create", _localID); |
128 | BulletSimAPI.CreateObject(parent_scene.WorldID, shapeData); | 126 | BulletSimAPI.CreateObject(Scene.WorldID, shapeData); |
129 | 127 | ||
130 | // Set the buoyancy for flying. This will be refactored when all the settings happen in C# | 128 | // Set the buoyancy for flying. This will be refactored when all the settings happen in C# |
131 | BulletSimAPI.SetObjectBuoyancy(_scene.WorldID, LocalID, _buoyancy); | 129 | BulletSimAPI.SetObjectBuoyancy(Scene.WorldID, LocalID, _buoyancy); |
132 | 130 | ||
133 | m_body = new BulletBody(LocalID, BulletSimAPI.GetBodyHandle2(_scene.World.Ptr, LocalID)); | 131 | Body = new BulletBody(LocalID, BulletSimAPI.GetBodyHandle2(Scene.World.Ptr, LocalID)); |
134 | // avatars get all collisions no matter what (makes walking on ground and such work) | 132 | // avatars get all collisions no matter what (makes walking on ground and such work) |
135 | BulletSimAPI.AddToCollisionFlags2(Body.Ptr, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); | 133 | BulletSimAPI.AddToCollisionFlags2(Body.Ptr, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); |
136 | }); | 134 | }); |
@@ -139,12 +137,12 @@ public class BSCharacter : PhysicsActor | |||
139 | } | 137 | } |
140 | 138 | ||
141 | // called when this character is being destroyed and the resources should be released | 139 | // called when this character is being destroyed and the resources should be released |
142 | public void Destroy() | 140 | public override void Destroy() |
143 | { | 141 | { |
144 | DetailLog("{0},BSCharacter.Destroy", LocalID); | 142 | DetailLog("{0},BSCharacter.Destroy", LocalID); |
145 | _scene.TaintedObject("BSCharacter.destroy", delegate() | 143 | Scene.TaintedObject("BSCharacter.destroy", delegate() |
146 | { | 144 | { |
147 | BulletSimAPI.DestroyObject(_scene.WorldID, _localID); | 145 | BulletSimAPI.DestroyObject(Scene.WorldID, _localID); |
148 | }); | 146 | }); |
149 | } | 147 | } |
150 | 148 | ||
@@ -173,9 +171,9 @@ public class BSCharacter : PhysicsActor | |||
173 | 171 | ||
174 | ComputeAvatarVolumeAndMass(); | 172 | ComputeAvatarVolumeAndMass(); |
175 | 173 | ||
176 | _scene.TaintedObject("BSCharacter.setSize", delegate() | 174 | Scene.TaintedObject("BSCharacter.setSize", delegate() |
177 | { | 175 | { |
178 | BulletSimAPI.SetObjectScaleMass(_scene.WorldID, LocalID, _scale, _mass, true); | 176 | BulletSimAPI.SetObjectScaleMass(Scene.WorldID, LocalID, _scale, _mass, true); |
179 | }); | 177 | }); |
180 | 178 | ||
181 | } | 179 | } |
@@ -204,17 +202,17 @@ public class BSCharacter : PhysicsActor | |||
204 | 202 | ||
205 | public override Vector3 Position { | 203 | public override Vector3 Position { |
206 | get { | 204 | get { |
207 | // _position = BulletSimAPI.GetObjectPosition(_scene.WorldID, _localID); | 205 | // _position = BulletSimAPI.GetObjectPosition(Scene.WorldID, _localID); |
208 | return _position; | 206 | return _position; |
209 | } | 207 | } |
210 | set { | 208 | set { |
211 | _position = value; | 209 | _position = value; |
212 | PositionSanityCheck(); | 210 | PositionSanityCheck(); |
213 | 211 | ||
214 | _scene.TaintedObject("BSCharacter.setPosition", delegate() | 212 | Scene.TaintedObject("BSCharacter.setPosition", delegate() |
215 | { | 213 | { |
216 | DetailLog("{0},BSCharacter.SetPosition,taint,pos={1},orient={2}", LocalID, _position, _orientation); | 214 | DetailLog("{0},BSCharacter.SetPosition,taint,pos={1},orient={2}", LocalID, _position, _orientation); |
217 | BulletSimAPI.SetObjectTranslation(_scene.WorldID, _localID, _position, _orientation); | 215 | BulletSimAPI.SetObjectTranslation(Scene.WorldID, _localID, _position, _orientation); |
218 | }); | 216 | }); |
219 | } | 217 | } |
220 | } | 218 | } |
@@ -227,16 +225,35 @@ public class BSCharacter : PhysicsActor | |||
227 | bool ret = false; | 225 | bool ret = false; |
228 | 226 | ||
229 | // If below the ground, move the avatar up | 227 | // If below the ground, move the avatar up |
230 | float terrainHeight = Scene.GetTerrainHeightAtXYZ(_position); | 228 | float terrainHeight = Scene.TerrainManager.GetTerrainHeightAtXYZ(_position); |
231 | if (_position.Z < terrainHeight) | 229 | if (Position.Z < terrainHeight) |
232 | { | 230 | { |
233 | DetailLog("{0},BSCharacter.PositionAdjustUnderGround,call,pos={1},orient={2}", LocalID, _position, _orientation); | 231 | DetailLog("{0},BSCharacter.PositionAdjustUnderGround,call,pos={1},terrain={2}", LocalID, _position, terrainHeight); |
234 | _position.Z = terrainHeight + 2.0f; | 232 | _position.Z = terrainHeight + 2.0f; |
235 | ret = true; | 233 | ret = true; |
236 | } | 234 | } |
237 | 235 | ||
238 | // TODO: check for out of bounds | 236 | // TODO: check for out of bounds |
237 | return ret; | ||
238 | } | ||
239 | 239 | ||
240 | // A version of the sanity check that also makes sure a new position value is | ||
241 | // pushed back to the physics engine. This routine would be used by anyone | ||
242 | // who is not already pushing the value. | ||
243 | private bool PositionSanityCheck2() | ||
244 | { | ||
245 | bool ret = false; | ||
246 | if (PositionSanityCheck()) | ||
247 | { | ||
248 | // The new position value must be pushed into the physics engine but we can't | ||
249 | // just assign to "Position" because of potential call loops. | ||
250 | Scene.TaintedObject("BSCharacter.PositionSanityCheck", delegate() | ||
251 | { | ||
252 | DetailLog("{0},BSCharacter.PositionSanityCheck,taint,pos={1},orient={2}", LocalID, _position, _orientation); | ||
253 | BulletSimAPI.SetObjectTranslation(Scene.WorldID, _localID, _position, _orientation); | ||
254 | }); | ||
255 | ret = true; | ||
256 | } | ||
240 | return ret; | 257 | return ret; |
241 | } | 258 | } |
242 | 259 | ||
@@ -245,6 +262,10 @@ public class BSCharacter : PhysicsActor | |||
245 | return _mass; | 262 | return _mass; |
246 | } | 263 | } |
247 | } | 264 | } |
265 | |||
266 | // used when we only want this prim's mass and not the linkset thing | ||
267 | public override float MassRaw { get {return _mass; } } | ||
268 | |||
248 | public override Vector3 Force { | 269 | public override Vector3 Force { |
249 | get { return _force; } | 270 | get { return _force; } |
250 | set { | 271 | set { |
@@ -277,10 +298,10 @@ public class BSCharacter : PhysicsActor | |||
277 | set { | 298 | set { |
278 | _velocity = value; | 299 | _velocity = value; |
279 | // m_log.DebugFormat("{0}: set velocity = {1}", LogHeader, _velocity); | 300 | // m_log.DebugFormat("{0}: set velocity = {1}", LogHeader, _velocity); |
280 | _scene.TaintedObject("BSCharacter.setVelocity", delegate() | 301 | Scene.TaintedObject("BSCharacter.setVelocity", delegate() |
281 | { | 302 | { |
282 | DetailLog("{0},BSCharacter.setVelocity,taint,vel={1}", LocalID, _velocity); | 303 | DetailLog("{0},BSCharacter.setVelocity,taint,vel={1}", LocalID, _velocity); |
283 | BulletSimAPI.SetObjectVelocity(_scene.WorldID, _localID, _velocity); | 304 | BulletSimAPI.SetObjectVelocity(Scene.WorldID, _localID, _velocity); |
284 | }); | 305 | }); |
285 | } | 306 | } |
286 | } | 307 | } |
@@ -303,10 +324,10 @@ public class BSCharacter : PhysicsActor | |||
303 | set { | 324 | set { |
304 | _orientation = value; | 325 | _orientation = value; |
305 | // m_log.DebugFormat("{0}: set orientation to {1}", LogHeader, _orientation); | 326 | // m_log.DebugFormat("{0}: set orientation to {1}", LogHeader, _orientation); |
306 | _scene.TaintedObject("BSCharacter.setOrientation", delegate() | 327 | Scene.TaintedObject("BSCharacter.setOrientation", delegate() |
307 | { | 328 | { |
308 | // _position = BulletSimAPI.GetObjectPosition(_scene.WorldID, _localID); | 329 | // _position = BulletSimAPI.GetObjectPosition(Scene.WorldID, _localID); |
309 | BulletSimAPI.SetObjectTranslation(_scene.WorldID, _localID, _position, _orientation); | 330 | BulletSimAPI.SetObjectTranslation(Scene.WorldID, _localID, _position, _orientation); |
310 | }); | 331 | }); |
311 | } | 332 | } |
312 | } | 333 | } |
@@ -343,11 +364,11 @@ public class BSCharacter : PhysicsActor | |||
343 | set { _throttleUpdates = value; } | 364 | set { _throttleUpdates = value; } |
344 | } | 365 | } |
345 | public override bool IsColliding { | 366 | public override bool IsColliding { |
346 | get { return (_collidingStep == _scene.SimulationStep); } | 367 | get { return (_collidingStep == Scene.SimulationStep); } |
347 | set { _isColliding = value; } | 368 | set { _isColliding = value; } |
348 | } | 369 | } |
349 | public override bool CollidingGround { | 370 | public override bool CollidingGround { |
350 | get { return (_collidingGroundStep == _scene.SimulationStep); } | 371 | get { return (_collidingGroundStep == Scene.SimulationStep); } |
351 | set { _collidingGround = value; } | 372 | set { _collidingGround = value; } |
352 | } | 373 | } |
353 | public override bool CollidingObj { | 374 | public override bool CollidingObj { |
@@ -369,10 +390,10 @@ public class BSCharacter : PhysicsActor | |||
369 | public override float Buoyancy { | 390 | public override float Buoyancy { |
370 | get { return _buoyancy; } | 391 | get { return _buoyancy; } |
371 | set { _buoyancy = value; | 392 | set { _buoyancy = value; |
372 | _scene.TaintedObject("BSCharacter.setBuoyancy", delegate() | 393 | Scene.TaintedObject("BSCharacter.setBuoyancy", delegate() |
373 | { | 394 | { |
374 | DetailLog("{0},BSCharacter.setBuoyancy,taint,buoy={1}", LocalID, _buoyancy); | 395 | DetailLog("{0},BSCharacter.setBuoyancy,taint,buoy={1}", LocalID, _buoyancy); |
375 | BulletSimAPI.SetObjectBuoyancy(_scene.WorldID, LocalID, _buoyancy); | 396 | BulletSimAPI.SetObjectBuoyancy(Scene.WorldID, LocalID, _buoyancy); |
376 | }); | 397 | }); |
377 | } | 398 | } |
378 | } | 399 | } |
@@ -416,7 +437,7 @@ public class BSCharacter : PhysicsActor | |||
416 | _force.Y += force.Y; | 437 | _force.Y += force.Y; |
417 | _force.Z += force.Z; | 438 | _force.Z += force.Z; |
418 | // m_log.DebugFormat("{0}: AddForce. adding={1}, newForce={2}", LogHeader, force, _force); | 439 | // m_log.DebugFormat("{0}: AddForce. adding={1}, newForce={2}", LogHeader, force, _force); |
419 | _scene.TaintedObject("BSCharacter.AddForce", delegate() | 440 | Scene.TaintedObject("BSCharacter.AddForce", delegate() |
420 | { | 441 | { |
421 | DetailLog("{0},BSCharacter.setAddForce,taint,addedForce={1}", LocalID, _force); | 442 | DetailLog("{0},BSCharacter.setAddForce,taint,addedForce={1}", LocalID, _force); |
422 | BulletSimAPI.AddObjectForce2(Body.Ptr, _force); | 443 | BulletSimAPI.AddObjectForce2(Body.Ptr, _force); |
@@ -448,6 +469,12 @@ public class BSCharacter : PhysicsActor | |||
448 | }); | 469 | }); |
449 | } | 470 | } |
450 | } | 471 | } |
472 | |||
473 | public override void ZeroMotion() | ||
474 | { | ||
475 | return; | ||
476 | } | ||
477 | |||
451 | // Stop collision events | 478 | // Stop collision events |
452 | public override void UnSubscribeEvents() { | 479 | public override void UnSubscribeEvents() { |
453 | _subscribedEventsMs = 0; | 480 | _subscribedEventsMs = 0; |
@@ -481,7 +508,7 @@ public class BSCharacter : PhysicsActor | |||
481 | 508 | ||
482 | // The physics engine says that properties have updated. Update same and inform | 509 | // The physics engine says that properties have updated. Update same and inform |
483 | // the world that things have changed. | 510 | // the world that things have changed. |
484 | public void UpdateProperties(EntityProperties entprop) | 511 | public override void UpdateProperties(EntityProperties entprop) |
485 | { | 512 | { |
486 | _position = entprop.Position; | 513 | _position = entprop.Position; |
487 | _orientation = entprop.Rotation; | 514 | _orientation = entprop.Rotation; |
@@ -491,30 +518,33 @@ public class BSCharacter : PhysicsActor | |||
491 | // Avatars don't report their changes the usual way. Changes are checked for in the heartbeat loop. | 518 | // Avatars don't report their changes the usual way. Changes are checked for in the heartbeat loop. |
492 | // base.RequestPhysicsterseUpdate(); | 519 | // base.RequestPhysicsterseUpdate(); |
493 | 520 | ||
494 | DetailLog("{0},BSCharacter.UpdateProperties,call,pos={1},orient={2},vel={3},accel={4},rotVel={5}", | 521 | // Do some sanity checking for the avatar. Make sure it's above ground and inbounds. |
495 | LocalID, entprop.Position, entprop.Rotation, entprop.Velocity, | 522 | PositionSanityCheck2(); |
496 | entprop.Acceleration, entprop.RotationalVelocity); | 523 | |
524 | float heightHere = Scene.TerrainManager.GetTerrainHeightAtXYZ(_position); // only for debug | ||
525 | DetailLog("{0},BSCharacter.UpdateProperties,call,pos={1},orient={2},vel={3},accel={4},rotVel={5},terrain={6}", | ||
526 | LocalID, _position, _orientation, _velocity, _acceleration, _rotationalVelocity, heightHere); | ||
497 | } | 527 | } |
498 | 528 | ||
499 | // Called by the scene when a collision with this object is reported | 529 | // Called by the scene when a collision with this object is reported |
500 | // The collision, if it should be reported to the character, is placed in a collection | 530 | // The collision, if it should be reported to the character, is placed in a collection |
501 | // that will later be sent to the simulator when SendCollisions() is called. | 531 | // that will later be sent to the simulator when SendCollisions() is called. |
502 | CollisionEventUpdate collisionCollection = null; | 532 | CollisionEventUpdate collisionCollection = null; |
503 | public void Collide(uint collidingWith, ActorTypes type, Vector3 contactPoint, Vector3 contactNormal, float pentrationDepth) | 533 | public override void Collide(uint collidingWith, BSPhysObject collidee, ActorTypes type, Vector3 contactPoint, Vector3 contactNormal, float pentrationDepth) |
504 | { | 534 | { |
505 | // m_log.DebugFormat("{0}: Collide: ms={1}, id={2}, with={3}", LogHeader, _subscribedEventsMs, LocalID, collidingWith); | 535 | // m_log.DebugFormat("{0}: Collide: ms={1}, id={2}, with={3}", LogHeader, _subscribedEventsMs, LocalID, collidingWith); |
506 | 536 | ||
507 | // The following makes IsColliding() and IsCollidingGround() work | 537 | // The following makes IsColliding() and IsCollidingGround() work |
508 | _collidingStep = _scene.SimulationStep; | 538 | _collidingStep = Scene.SimulationStep; |
509 | if (collidingWith == BSScene.TERRAIN_ID || collidingWith == BSScene.GROUNDPLANE_ID) | 539 | if (collidingWith == BSScene.TERRAIN_ID || collidingWith == BSScene.GROUNDPLANE_ID) |
510 | { | 540 | { |
511 | _collidingGroundStep = _scene.SimulationStep; | 541 | _collidingGroundStep = Scene.SimulationStep; |
512 | } | 542 | } |
513 | // DetailLog("{0},BSCharacter.Collison,call,with={1}", LocalID, collidingWith); | 543 | // DetailLog("{0},BSCharacter.Collison,call,with={1}", LocalID, collidingWith); |
514 | 544 | ||
515 | // throttle collisions to the rate specified in the subscription | 545 | // throttle collisions to the rate specified in the subscription |
516 | if (_subscribedEventsMs != 0) { | 546 | if (_subscribedEventsMs != 0) { |
517 | int nowTime = _scene.SimulationNowTime; | 547 | int nowTime = Scene.SimulationNowTime; |
518 | if (nowTime >= _nextCollisionOkTime) { | 548 | if (nowTime >= _nextCollisionOkTime) { |
519 | _nextCollisionOkTime = nowTime + _subscribedEventsMs; | 549 | _nextCollisionOkTime = nowTime + _subscribedEventsMs; |
520 | 550 | ||
@@ -525,7 +555,7 @@ public class BSCharacter : PhysicsActor | |||
525 | } | 555 | } |
526 | } | 556 | } |
527 | 557 | ||
528 | public void SendCollisions() | 558 | public override void SendCollisions() |
529 | { | 559 | { |
530 | /* | 560 | /* |
531 | if (collisionCollection != null && collisionCollection.Count > 0) | 561 | if (collisionCollection != null && collisionCollection.Count > 0) |