aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/PhysicsModules/BulletS/BSScene.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/PhysicsModules/BulletS/BSScene.cs')
-rw-r--r--OpenSim/Region/PhysicsModules/BulletS/BSScene.cs1281
1 files changed, 1281 insertions, 0 deletions
diff --git a/OpenSim/Region/PhysicsModules/BulletS/BSScene.cs b/OpenSim/Region/PhysicsModules/BulletS/BSScene.cs
new file mode 100644
index 0000000..8a19944
--- /dev/null
+++ b/OpenSim/Region/PhysicsModules/BulletS/BSScene.cs
@@ -0,0 +1,1281 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyrightD
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27using System;
28using System.Collections.Generic;
29using System.Linq;
30using System.Reflection;
31using System.Runtime.InteropServices;
32using System.Text;
33using System.Threading;
34using OpenSim.Framework;
35using OpenSim.Framework.Monitoring;
36using OpenSim.Region.Framework;
37using OpenSim.Region.CoreModules;
38using Logging = OpenSim.Region.CoreModules.Framework.Statistics.Logging;
39using OpenSim.Region.Physics.Manager;
40using Nini.Config;
41using log4net;
42using OpenMetaverse;
43
44namespace OpenSim.Region.Physics.BulletSPlugin
45{
46public sealed class BSScene : PhysicsScene, IPhysicsParameters
47{
48 internal static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
49 internal static readonly string LogHeader = "[BULLETS SCENE]";
50
51 // The name of the region we're working for.
52 public string RegionName { get; private set; }
53
54 public string BulletSimVersion = "?";
55
56 // The handle to the underlying managed or unmanaged version of Bullet being used.
57 public string BulletEngineName { get; private set; }
58 public BSAPITemplate PE;
59
60 // If the physics engine is running on a separate thread
61 public Thread m_physicsThread;
62
63 public Dictionary<uint, BSPhysObject> PhysObjects;
64 public BSShapeCollection Shapes;
65
66 // Keeping track of the objects with collisions so we can report begin and end of a collision
67 public HashSet<BSPhysObject> ObjectsWithCollisions = new HashSet<BSPhysObject>();
68 public HashSet<BSPhysObject> ObjectsWithNoMoreCollisions = new HashSet<BSPhysObject>();
69
70 // All the collision processing is protected with this lock object
71 public Object CollisionLock = new Object();
72
73 // Properties are updated here
74 public Object UpdateLock = new Object();
75 public HashSet<BSPhysObject> ObjectsWithUpdates = new HashSet<BSPhysObject>();
76
77 // Keep track of all the avatars so we can send them a collision event
78 // every tick so OpenSim will update its animation.
79 private HashSet<BSPhysObject> AvatarsInScene = new HashSet<BSPhysObject>();
80 private Object AvatarsInSceneLock = new Object();
81
82 // let my minuions use my logger
83 public ILog Logger { get { return m_log; } }
84
85 public IMesher mesher;
86 public uint WorldID { get; private set; }
87 public BulletWorld World { get; private set; }
88
89 // All the constraints that have been allocated in this instance.
90 public BSConstraintCollection Constraints { get; private set; }
91
92 // Simulation parameters
93 //internal float m_physicsStepTime; // if running independently, the interval simulated by default
94
95 internal int m_maxSubSteps;
96 internal float m_fixedTimeStep;
97
98 internal float m_simulatedTime; // the time simulated previously. Used for physics framerate calc.
99
100 internal long m_simulationStep = 0; // The current simulation step.
101 public long SimulationStep { get { return m_simulationStep; } }
102 // A number to use for SimulationStep that is probably not any step value
103 // Used by the collision code (which remembers the step when a collision happens) to remember not any simulation step.
104 public static long NotASimulationStep = -1234;
105
106 internal float LastTimeStep { get; private set; } // The simulation time from the last invocation of Simulate()
107
108 internal float NominalFrameRate { get; set; } // Parameterized ideal frame rate that simulation is scaled to
109
110 // Physical objects can register for prestep or poststep events
111 public delegate void PreStepAction(float timeStep);
112 public delegate void PostStepAction(float timeStep);
113 public event PreStepAction BeforeStep;
114 public event PostStepAction AfterStep;
115
116 // A value of the time 'now' so all the collision and update routines do not have to get their own
117 // Set to 'now' just before all the prims and actors are called for collisions and updates
118 public int SimulationNowTime { get; private set; }
119
120 // True if initialized and ready to do simulation steps
121 private bool m_initialized = false;
122
123 // Flag which is true when processing taints.
124 // Not guaranteed to be correct all the time (don't depend on this) but good for debugging.
125 public bool InTaintTime { get; private set; }
126
127 // Pinned memory used to pass step information between managed and unmanaged
128 internal int m_maxCollisionsPerFrame;
129 internal CollisionDesc[] m_collisionArray;
130
131 internal int m_maxUpdatesPerFrame;
132 internal EntityProperties[] m_updateArray;
133
134 /// <summary>
135 /// Used to control physics simulation timing if Bullet is running on its own thread.
136 /// </summary>
137 private ManualResetEvent m_updateWaitEvent;
138
139 public const uint TERRAIN_ID = 0; // OpenSim senses terrain with a localID of zero
140 public const uint GROUNDPLANE_ID = 1;
141 public const uint CHILDTERRAIN_ID = 2; // Terrain allocated based on our mega-prim childre start here
142
143 public float SimpleWaterLevel { get; set; }
144 public BSTerrainManager TerrainManager { get; private set; }
145
146 public ConfigurationParameters Params
147 {
148 get { return UnmanagedParams[0]; }
149 }
150 public Vector3 DefaultGravity
151 {
152 get { return new Vector3(0f, 0f, Params.gravity); }
153 }
154 // Just the Z value of the gravity
155 public float DefaultGravityZ
156 {
157 get { return Params.gravity; }
158 }
159
160 // When functions in the unmanaged code must be called, it is only
161 // done at a known time just before the simulation step. The taint
162 // system saves all these function calls and executes them in
163 // order before the simulation.
164 public delegate void TaintCallback();
165 private struct TaintCallbackEntry
166 {
167 public String originator;
168 public String ident;
169 public TaintCallback callback;
170 public TaintCallbackEntry(string pIdent, TaintCallback pCallBack)
171 {
172 originator = BSScene.DetailLogZero;
173 ident = pIdent;
174 callback = pCallBack;
175 }
176 public TaintCallbackEntry(string pOrigin, string pIdent, TaintCallback pCallBack)
177 {
178 originator = pOrigin;
179 ident = pIdent;
180 callback = pCallBack;
181 }
182 }
183 private Object _taintLock = new Object(); // lock for using the next object
184 private List<TaintCallbackEntry> _taintOperations;
185 private Dictionary<string, TaintCallbackEntry> _postTaintOperations;
186 private List<TaintCallbackEntry> _postStepOperations;
187
188 // A pointer to an instance if this structure is passed to the C++ code
189 // Used to pass basic configuration values to the unmanaged code.
190 internal ConfigurationParameters[] UnmanagedParams;
191
192 // Sometimes you just have to log everything.
193 public Logging.LogWriter PhysicsLogging;
194 private bool m_physicsLoggingEnabled;
195 private string m_physicsLoggingDir;
196 private string m_physicsLoggingPrefix;
197 private int m_physicsLoggingFileMinutes;
198 private bool m_physicsLoggingDoFlush;
199 private bool m_physicsPhysicalDumpEnabled;
200 public int PhysicsMetricDumpFrames { get; set; }
201 // 'true' of the vehicle code is to log lots of details
202 public bool VehicleLoggingEnabled { get; private set; }
203 public bool VehiclePhysicalLoggingEnabled { get; private set; }
204
205 #region Construction and Initialization
206 public BSScene(string engineType, string identifier)
207 {
208 m_initialized = false;
209
210 // The name of the region we're working for is passed to us. Keep for identification.
211 RegionName = identifier;
212
213 // Set identifying variables in the PhysicsScene interface.
214 EngineType = engineType;
215 Name = EngineType + "/" + RegionName;
216 }
217
218 // Old version of initialization that assumes legacy sized regions (256x256)
219 public override void Initialise(IMesher meshmerizer, IConfigSource config)
220 {
221 m_log.ErrorFormat("{0} WARNING WARNING WARNING! BulletSim initialized without region extent specification. Terrain will be messed up.");
222 Vector3 regionExtent = new Vector3( Constants.RegionSize, Constants.RegionSize, Constants.RegionSize);
223 Initialise(meshmerizer, config, regionExtent);
224
225 }
226
227 public override void Initialise(IMesher meshmerizer, IConfigSource config, Vector3 regionExtent)
228 {
229 mesher = meshmerizer;
230 _taintOperations = new List<TaintCallbackEntry>();
231 _postTaintOperations = new Dictionary<string, TaintCallbackEntry>();
232 _postStepOperations = new List<TaintCallbackEntry>();
233 PhysObjects = new Dictionary<uint, BSPhysObject>();
234 Shapes = new BSShapeCollection(this);
235
236 m_simulatedTime = 0f;
237 LastTimeStep = 0.1f;
238
239 // Allocate pinned memory to pass parameters.
240 UnmanagedParams = new ConfigurationParameters[1];
241
242 // Set default values for physics parameters plus any overrides from the ini file
243 GetInitialParameterValues(config);
244
245 // Force some parameters to values depending on other configurations
246 // Only use heightmap terrain implementation if terrain larger than legacy size
247 if ((uint)regionExtent.X > Constants.RegionSize || (uint)regionExtent.Y > Constants.RegionSize)
248 {
249 m_log.WarnFormat("{0} Forcing terrain implementation to heightmap for large region", LogHeader);
250 BSParam.TerrainImplementation = (float)BSTerrainPhys.TerrainImplementation.Heightmap;
251 }
252
253 // Get the connection to the physics engine (could be native or one of many DLLs)
254 PE = SelectUnderlyingBulletEngine(BulletEngineName);
255
256 // Enable very detailed logging.
257 // By creating an empty logger when not logging, the log message invocation code
258 // can be left in and every call doesn't have to check for null.
259 if (m_physicsLoggingEnabled)
260 {
261 PhysicsLogging = new Logging.LogWriter(m_physicsLoggingDir, m_physicsLoggingPrefix, m_physicsLoggingFileMinutes, m_physicsLoggingDoFlush);
262 PhysicsLogging.ErrorLogger = m_log; // for DEBUG. Let's the logger output its own error messages.
263 }
264 else
265 {
266 PhysicsLogging = new Logging.LogWriter();
267 }
268
269 // Allocate memory for returning of the updates and collisions from the physics engine
270 m_collisionArray = new CollisionDesc[m_maxCollisionsPerFrame];
271 m_updateArray = new EntityProperties[m_maxUpdatesPerFrame];
272
273 // The bounding box for the simulated world. The origin is 0,0,0 unless we're
274 // a child in a mega-region.
275 // Bullet actually doesn't care about the extents of the simulated
276 // area. It tracks active objects no matter where they are.
277 Vector3 worldExtent = regionExtent;
278
279 World = PE.Initialize(worldExtent, Params, m_maxCollisionsPerFrame, ref m_collisionArray, m_maxUpdatesPerFrame, ref m_updateArray);
280
281 Constraints = new BSConstraintCollection(World);
282
283 TerrainManager = new BSTerrainManager(this, worldExtent);
284 TerrainManager.CreateInitialGroundPlaneAndTerrain();
285
286 // Put some informational messages into the log file.
287 m_log.InfoFormat("{0} Linksets implemented with {1}", LogHeader, (BSLinkset.LinksetImplementation)BSParam.LinksetImplementation);
288
289 InTaintTime = false;
290 m_initialized = true;
291
292 // If the physics engine runs on its own thread, start same.
293 if (BSParam.UseSeparatePhysicsThread)
294 {
295 // The physics simulation should happen independently of the heartbeat loop
296 m_physicsThread
297 = WorkManager.StartThread(
298 BulletSPluginPhysicsThread,
299 string.Format("{0} ({1})", BulletEngineName, RegionName),
300 ThreadPriority.Normal,
301 true,
302 true);
303 }
304 }
305
306 // All default parameter values are set here. There should be no values set in the
307 // variable definitions.
308 private void GetInitialParameterValues(IConfigSource config)
309 {
310 ConfigurationParameters parms = new ConfigurationParameters();
311 UnmanagedParams[0] = parms;
312
313 BSParam.SetParameterDefaultValues(this);
314
315 if (config != null)
316 {
317 // If there are specifications in the ini file, use those values
318 IConfig pConfig = config.Configs["BulletSim"];
319 if (pConfig != null)
320 {
321 BSParam.SetParameterConfigurationValues(this, pConfig);
322
323 // There are two Bullet implementations to choose from
324 BulletEngineName = pConfig.GetString("BulletEngine", "BulletUnmanaged");
325
326 // Very detailed logging for physics debugging
327 // TODO: the boolean values can be moved to the normal parameter processing.
328 m_physicsLoggingEnabled = pConfig.GetBoolean("PhysicsLoggingEnabled", false);
329 m_physicsLoggingDir = pConfig.GetString("PhysicsLoggingDir", ".");
330 m_physicsLoggingPrefix = pConfig.GetString("PhysicsLoggingPrefix", "physics-%REGIONNAME%-");
331 m_physicsLoggingFileMinutes = pConfig.GetInt("PhysicsLoggingFileMinutes", 5);
332 m_physicsLoggingDoFlush = pConfig.GetBoolean("PhysicsLoggingDoFlush", false);
333 m_physicsPhysicalDumpEnabled = pConfig.GetBoolean("PhysicsPhysicalDumpEnabled", false);
334 // Very detailed logging for vehicle debugging
335 VehicleLoggingEnabled = pConfig.GetBoolean("VehicleLoggingEnabled", false);
336 VehiclePhysicalLoggingEnabled = pConfig.GetBoolean("VehiclePhysicalLoggingEnabled", false);
337
338 // Do any replacements in the parameters
339 m_physicsLoggingPrefix = m_physicsLoggingPrefix.Replace("%REGIONNAME%", RegionName);
340 }
341 else
342 {
343 // Nothing in the configuration INI file so assume unmanaged and other defaults.
344 BulletEngineName = "BulletUnmanaged";
345 m_physicsLoggingEnabled = false;
346 VehicleLoggingEnabled = false;
347 }
348
349 // The material characteristics.
350 BSMaterials.InitializeFromDefaults(Params);
351 if (pConfig != null)
352 {
353 // Let the user add new and interesting material property values.
354 BSMaterials.InitializefromParameters(pConfig);
355 }
356 }
357 }
358
359 // A helper function that handles a true/false parameter and returns the proper float number encoding
360 float ParamBoolean(IConfig config, string parmName, float deflt)
361 {
362 float ret = deflt;
363 if (config.Contains(parmName))
364 {
365 ret = ConfigurationParameters.numericFalse;
366 if (config.GetBoolean(parmName, false))
367 {
368 ret = ConfigurationParameters.numericTrue;
369 }
370 }
371 return ret;
372 }
373
374 // Select the connection to the actual Bullet implementation.
375 // The main engine selection is the engineName up to the first hypen.
376 // So "Bullet-2.80-OpenCL-Intel" specifies the 'bullet' class here and the whole name
377 // is passed to the engine to do its special selection, etc.
378 private BSAPITemplate SelectUnderlyingBulletEngine(string engineName)
379 {
380 // For the moment, do a simple switch statement.
381 // Someday do fancyness with looking up the interfaces in the assembly.
382 BSAPITemplate ret = null;
383
384 string selectionName = engineName.ToLower();
385 int hyphenIndex = engineName.IndexOf("-");
386 if (hyphenIndex > 0)
387 selectionName = engineName.ToLower().Substring(0, hyphenIndex - 1);
388
389 switch (selectionName)
390 {
391 case "bullet":
392 case "bulletunmanaged":
393 ret = new BSAPIUnman(engineName, this);
394 break;
395 case "bulletxna":
396 ret = new BSAPIXNA(engineName, this);
397 // Disable some features that are not implemented in BulletXNA
398 m_log.InfoFormat("{0} Disabling some physics features not implemented by BulletXNA", LogHeader);
399 m_log.InfoFormat("{0} Disabling ShouldUseBulletHACD", LogHeader);
400 BSParam.ShouldUseBulletHACD = false;
401 m_log.InfoFormat("{0} Disabling ShouldUseSingleConvexHullForPrims", LogHeader);
402 BSParam.ShouldUseSingleConvexHullForPrims = false;
403 m_log.InfoFormat("{0} Disabling ShouldUseGImpactShapeForPrims", LogHeader);
404 BSParam.ShouldUseGImpactShapeForPrims = false;
405 m_log.InfoFormat("{0} Setting terrain implimentation to Heightmap", LogHeader);
406 BSParam.TerrainImplementation = (float)BSTerrainPhys.TerrainImplementation.Heightmap;
407 break;
408 }
409
410 if (ret == null)
411 {
412 m_log.ErrorFormat("{0} COULD NOT SELECT BULLET ENGINE: '[BulletSim]PhysicsEngine' must be either 'BulletUnmanaged-*' or 'BulletXNA-*'", LogHeader);
413 }
414 else
415 {
416 m_log.InfoFormat("{0} Selected bullet engine {1} -> {2}/{3}", LogHeader, engineName, ret.BulletEngineName, ret.BulletEngineVersion);
417 }
418
419 return ret;
420 }
421
422 public override void Dispose()
423 {
424 // m_log.DebugFormat("{0}: Dispose()", LogHeader);
425
426 // make sure no stepping happens while we're deleting stuff
427 m_initialized = false;
428
429 lock (PhysObjects)
430 {
431 foreach (KeyValuePair<uint, BSPhysObject> kvp in PhysObjects)
432 {
433 kvp.Value.Destroy();
434 }
435 PhysObjects.Clear();
436 }
437
438 // Now that the prims are all cleaned up, there should be no constraints left
439 if (Constraints != null)
440 {
441 Constraints.Dispose();
442 Constraints = null;
443 }
444
445 if (Shapes != null)
446 {
447 Shapes.Dispose();
448 Shapes = null;
449 }
450
451 if (TerrainManager != null)
452 {
453 TerrainManager.ReleaseGroundPlaneAndTerrain();
454 TerrainManager.Dispose();
455 TerrainManager = null;
456 }
457
458 // Anything left in the unmanaged code should be cleaned out
459 PE.Shutdown(World);
460
461 // Not logging any more
462 PhysicsLogging.Close();
463 }
464 #endregion // Construction and Initialization
465
466 #region Prim and Avatar addition and removal
467
468 public override PhysicsActor AddAvatar(string avName, Vector3 position, Vector3 velocity, Vector3 size, bool isFlying)
469 {
470 m_log.ErrorFormat("{0}: CALL TO AddAvatar in BSScene. NOT IMPLEMENTED", LogHeader);
471 return null;
472 }
473
474 public override PhysicsActor AddAvatar(uint localID, string avName, Vector3 position, Vector3 velocity, Vector3 size, bool isFlying)
475 {
476 // m_log.DebugFormat("{0}: AddAvatar: {1}", LogHeader, avName);
477
478 if (!m_initialized) return null;
479
480 BSCharacter actor = new BSCharacter(localID, avName, this, position, velocity, size, isFlying);
481 lock (PhysObjects)
482 PhysObjects.Add(localID, actor);
483
484 // TODO: Remove kludge someday.
485 // We must generate a collision for avatars whether they collide or not.
486 // This is required by OpenSim to update avatar animations, etc.
487 lock (AvatarsInSceneLock)
488 AvatarsInScene.Add(actor);
489
490 return actor;
491 }
492
493 public override void RemoveAvatar(PhysicsActor actor)
494 {
495 // m_log.DebugFormat("{0}: RemoveAvatar", LogHeader);
496
497 if (!m_initialized) return;
498
499 BSCharacter bsactor = actor as BSCharacter;
500 if (bsactor != null)
501 {
502 try
503 {
504 lock (PhysObjects)
505 PhysObjects.Remove(bsactor.LocalID);
506 // Remove kludge someday
507 lock (AvatarsInSceneLock)
508 AvatarsInScene.Remove(bsactor);
509 }
510 catch (Exception e)
511 {
512 m_log.WarnFormat("{0}: Attempt to remove avatar that is not in physics scene: {1}", LogHeader, e);
513 }
514 bsactor.Destroy();
515 // bsactor.dispose();
516 }
517 else
518 {
519 m_log.ErrorFormat("{0}: Requested to remove avatar that is not a BSCharacter. ID={1}, type={2}",
520 LogHeader, actor.LocalID, actor.GetType().Name);
521 }
522 }
523
524 public override void RemovePrim(PhysicsActor prim)
525 {
526 if (!m_initialized) return;
527
528 BSPhysObject bsprim = prim as BSPhysObject;
529 if (bsprim != null)
530 {
531 DetailLog("{0},RemovePrim,call", bsprim.LocalID);
532 // m_log.DebugFormat("{0}: RemovePrim. id={1}/{2}", LogHeader, bsprim.Name, bsprim.LocalID);
533 try
534 {
535 lock (PhysObjects) PhysObjects.Remove(bsprim.LocalID);
536 }
537 catch (Exception e)
538 {
539 m_log.ErrorFormat("{0}: Attempt to remove prim that is not in physics scene: {1}", LogHeader, e);
540 }
541 bsprim.Destroy();
542 // bsprim.dispose();
543 }
544 else
545 {
546 m_log.ErrorFormat("{0}: Attempt to remove prim that is not a BSPrim type.", LogHeader);
547 }
548 }
549
550 public override PhysicsActor AddPrimShape(string primName, PrimitiveBaseShape pbs, Vector3 position,
551 Vector3 size, Quaternion rotation, bool isPhysical, uint localID)
552 {
553 // m_log.DebugFormat("{0}: AddPrimShape2: {1}", LogHeader, primName);
554
555 if (!m_initialized) return null;
556
557 // DetailLog("{0},BSScene.AddPrimShape,call", localID);
558
559 BSPhysObject prim = new BSPrimLinkable(localID, primName, this, position, size, rotation, pbs, isPhysical);
560 lock (PhysObjects) PhysObjects.Add(localID, prim);
561 return prim;
562 }
563
564 // This is a call from the simulator saying that some physical property has been updated.
565 // The BulletSim driver senses the changing of relevant properties so this taint
566 // information call is not needed.
567 public override void AddPhysicsActorTaint(PhysicsActor prim) { }
568
569 #endregion // Prim and Avatar addition and removal
570
571 #region Simulation
572
573 // Call from the simulator to send physics information to the simulator objects.
574 // This pushes all the collision and property update events into the objects in
575 // the simulator and, since it is on the heartbeat thread, there is an implicit
576 // locking of those data structures from other heartbeat events.
577 // If the physics engine is running on a separate thread, the update information
578 // will be in the ObjectsWithCollions and ObjectsWithUpdates structures.
579 public override float Simulate(float timeStep)
580 {
581 if (!BSParam.UseSeparatePhysicsThread)
582 {
583 DoPhysicsStep(timeStep);
584 }
585 return SendUpdatesToSimulator(timeStep);
586 }
587
588 // Call the physics engine to do one 'timeStep' and collect collisions and updates
589 // into ObjectsWithCollisions and ObjectsWithUpdates data structures.
590 private void DoPhysicsStep(float timeStep)
591 {
592 // prevent simulation until we've been initialized
593 if (!m_initialized) return;
594
595 LastTimeStep = timeStep;
596
597 int updatedEntityCount = 0;
598 int collidersCount = 0;
599
600 int beforeTime = Util.EnvironmentTickCount();
601 int simTime = 0;
602
603 int numTaints = _taintOperations.Count;
604 InTaintTime = true; // Only used for debugging so locking is not necessary.
605
606 // update the prim states while we know the physics engine is not busy
607 ProcessTaints();
608
609 // Some of the physical objects requre individual, pre-step calls
610 // (vehicles and avatar movement, in particular)
611 TriggerPreStepEvent(timeStep);
612
613 // the prestep actions might have added taints
614 numTaints += _taintOperations.Count;
615 ProcessTaints();
616
617 InTaintTime = false; // Only used for debugging so locking is not necessary.
618
619 // The following causes the unmanaged code to output ALL the values found in ALL the objects in the world.
620 // Only enable this in a limited test world with few objects.
621 if (m_physicsPhysicalDumpEnabled)
622 PE.DumpAllInfo(World);
623
624 // step the physical world one interval
625 m_simulationStep++;
626 int numSubSteps = 0;
627 try
628 {
629 numSubSteps = PE.PhysicsStep(World, timeStep, m_maxSubSteps, m_fixedTimeStep, out updatedEntityCount, out collidersCount);
630
631 }
632 catch (Exception e)
633 {
634 m_log.WarnFormat("{0},PhysicsStep Exception: nTaints={1}, substeps={2}, updates={3}, colliders={4}, e={5}",
635 LogHeader, numTaints, numSubSteps, updatedEntityCount, collidersCount, e);
636 DetailLog("{0},PhysicsStepException,call, nTaints={1}, substeps={2}, updates={3}, colliders={4}",
637 DetailLogZero, numTaints, numSubSteps, updatedEntityCount, collidersCount);
638 updatedEntityCount = 0;
639 collidersCount = 0;
640 }
641
642 // Make the physics engine dump useful statistics periodically
643 if (PhysicsMetricDumpFrames != 0 && ((m_simulationStep % PhysicsMetricDumpFrames) == 0))
644 PE.DumpPhysicsStatistics(World);
645
646 // Get a value for 'now' so all the collision and update routines don't have to get their own.
647 SimulationNowTime = Util.EnvironmentTickCount();
648
649 // Send collision information to the colliding objects. The objects decide if the collision
650 // is 'real' (like linksets don't collide with themselves) and the individual objects
651 // know if the simulator has subscribed to collisions.
652 lock (CollisionLock)
653 {
654 if (collidersCount > 0)
655 {
656 lock (PhysObjects)
657 {
658 for (int ii = 0; ii < collidersCount; ii++)
659 {
660 uint cA = m_collisionArray[ii].aID;
661 uint cB = m_collisionArray[ii].bID;
662 Vector3 point = m_collisionArray[ii].point;
663 Vector3 normal = m_collisionArray[ii].normal;
664 float penetration = m_collisionArray[ii].penetration;
665 SendCollision(cA, cB, point, normal, penetration);
666 SendCollision(cB, cA, point, -normal, penetration);
667 }
668 }
669 }
670 }
671
672 // If any of the objects had updated properties, tell the managed objects about the update
673 // and remember that there was a change so it will be passed to the simulator.
674 lock (UpdateLock)
675 {
676 if (updatedEntityCount > 0)
677 {
678 lock (PhysObjects)
679 {
680 for (int ii = 0; ii < updatedEntityCount; ii++)
681 {
682 EntityProperties entprop = m_updateArray[ii];
683 BSPhysObject pobj;
684 if (PhysObjects.TryGetValue(entprop.ID, out pobj))
685 {
686 if (pobj.IsInitialized)
687 pobj.UpdateProperties(entprop);
688 }
689 }
690 }
691 }
692 }
693
694 // Some actors want to know when the simulation step is complete.
695 TriggerPostStepEvent(timeStep);
696
697 simTime = Util.EnvironmentTickCountSubtract(beforeTime);
698 if (PhysicsLogging.Enabled)
699 {
700 DetailLog("{0},DoPhysicsStep,complete,frame={1}, nTaints={2}, simTime={3}, substeps={4}, updates={5}, colliders={6}, objWColl={7}",
701 DetailLogZero, m_simulationStep, numTaints, simTime, numSubSteps,
702 updatedEntityCount, collidersCount, ObjectsWithCollisions.Count);
703 }
704
705 // The following causes the unmanaged code to output ALL the values found in ALL the objects in the world.
706 // Only enable this in a limited test world with few objects.
707 if (m_physicsPhysicalDumpEnabled)
708 PE.DumpAllInfo(World);
709
710 // The physics engine returns the number of milliseconds it simulated this call.
711 // These are summed and normalized to one second and divided by 1000 to give the reported physics FPS.
712 // Multiply by a fixed nominal frame rate to give a rate similar to the simulator (usually 55).
713 m_simulatedTime += (float)numSubSteps * m_fixedTimeStep * 1000f * NominalFrameRate;
714 }
715
716 // Called by a BSPhysObject to note that it has changed properties and this information
717 // should be passed up to the simulator at the proper time.
718 // Note: this is called by the BSPhysObject from invocation via DoPhysicsStep() above so
719 // this is is under UpdateLock.
720 public void PostUpdate(BSPhysObject updatee)
721 {
722 lock (UpdateLock)
723 {
724 ObjectsWithUpdates.Add(updatee);
725 }
726 }
727
728 // The simulator thinks it is physics time so return all the collisions and position
729 // updates that were collected in actual physics simulation.
730 private float SendUpdatesToSimulator(float timeStep)
731 {
732 if (!m_initialized) return 5.0f;
733
734 DetailLog("{0},SendUpdatesToSimulator,collisions={1},updates={2},simedTime={3}",
735 BSScene.DetailLogZero, ObjectsWithCollisions.Count, ObjectsWithUpdates.Count, m_simulatedTime);
736 // Push the collisions into the simulator.
737 lock (CollisionLock)
738 {
739 if (ObjectsWithCollisions.Count > 0)
740 {
741 foreach (BSPhysObject bsp in ObjectsWithCollisions)
742 if (!bsp.SendCollisions())
743 {
744 // If the object is done colliding, see that it's removed from the colliding list
745 ObjectsWithNoMoreCollisions.Add(bsp);
746 }
747 }
748
749 // This is a kludge to get avatar movement updates.
750 // The simulator expects collisions for avatars even if there are have been no collisions.
751 // The event updates avatar animations and stuff.
752 // If you fix avatar animation updates, remove this overhead and let normal collision processing happen.
753 // Note that we get a copy of the list to search because SendCollision() can take a while.
754 HashSet<BSPhysObject> tempAvatarsInScene;
755 lock (AvatarsInSceneLock)
756 {
757 tempAvatarsInScene = new HashSet<BSPhysObject>(AvatarsInScene);
758 }
759 foreach (BSPhysObject actor in tempAvatarsInScene)
760 {
761 if (!ObjectsWithCollisions.Contains(actor)) // don't call avatars twice
762 actor.SendCollisions();
763 }
764 tempAvatarsInScene = null;
765
766 // Objects that are done colliding are removed from the ObjectsWithCollisions list.
767 // Not done above because it is inside an iteration of ObjectWithCollisions.
768 // This complex collision processing is required to create an empty collision
769 // event call after all real collisions have happened on an object. This allows
770 // the simulator to generate the 'collision end' event.
771 if (ObjectsWithNoMoreCollisions.Count > 0)
772 {
773 foreach (BSPhysObject po in ObjectsWithNoMoreCollisions)
774 ObjectsWithCollisions.Remove(po);
775 ObjectsWithNoMoreCollisions.Clear();
776 }
777 }
778
779 // Call the simulator for each object that has physics property updates.
780 HashSet<BSPhysObject> updatedObjects = null;
781 lock (UpdateLock)
782 {
783 if (ObjectsWithUpdates.Count > 0)
784 {
785 updatedObjects = ObjectsWithUpdates;
786 ObjectsWithUpdates = new HashSet<BSPhysObject>();
787 }
788 }
789 if (updatedObjects != null)
790 {
791 foreach (BSPhysObject obj in updatedObjects)
792 {
793 obj.RequestPhysicsterseUpdate();
794 }
795 updatedObjects.Clear();
796 }
797
798 // Return the framerate simulated to give the above returned results.
799 // (Race condition here but this is just bookkeeping so rare mistakes do not merit a lock).
800 float simTime = m_simulatedTime;
801 m_simulatedTime = 0f;
802 return simTime;
803 }
804
805 // Something has collided
806 private void SendCollision(uint localID, uint collidingWith, Vector3 collidePoint, Vector3 collideNormal, float penetration)
807 {
808 if (localID <= TerrainManager.HighestTerrainID)
809 {
810 return; // don't send collisions to the terrain
811 }
812
813 BSPhysObject collider;
814 // NOTE that PhysObjects was locked before the call to SendCollision().
815 if (!PhysObjects.TryGetValue(localID, out collider))
816 {
817 // If the object that is colliding cannot be found, just ignore the collision.
818 DetailLog("{0},BSScene.SendCollision,colliderNotInObjectList,id={1},with={2}", DetailLogZero, localID, collidingWith);
819 return;
820 }
821
822 // Note: the terrain is not in the physical object list so 'collidee' can be null when Collide() is called.
823 BSPhysObject collidee = null;
824 PhysObjects.TryGetValue(collidingWith, out collidee);
825
826 // DetailLog("{0},BSScene.SendCollision,collide,id={1},with={2}", DetailLogZero, localID, collidingWith);
827
828 if (collider.IsInitialized)
829 {
830 if (collider.Collide(collidingWith, collidee, collidePoint, collideNormal, penetration))
831 {
832 // If a collision was 'good', remember to send it to the simulator
833 lock (CollisionLock)
834 {
835 ObjectsWithCollisions.Add(collider);
836 }
837 }
838 }
839
840 return;
841 }
842
843 public void BulletSPluginPhysicsThread()
844 {
845 Thread.CurrentThread.Priority = ThreadPriority.Highest;
846 m_updateWaitEvent = new ManualResetEvent(false);
847
848 while (m_initialized)
849 {
850 int beginSimulationRealtimeMS = Util.EnvironmentTickCount();
851
852 if (BSParam.Active)
853 DoPhysicsStep(BSParam.PhysicsTimeStep);
854
855 int simulationRealtimeMS = Util.EnvironmentTickCountSubtract(beginSimulationRealtimeMS);
856 int simulationTimeVsRealtimeDifferenceMS = ((int)(BSParam.PhysicsTimeStep*1000f)) - simulationRealtimeMS;
857
858 if (simulationTimeVsRealtimeDifferenceMS > 0)
859 {
860 // The simulation of the time interval took less than realtime.
861 // Do a wait for the rest of realtime.
862 m_updateWaitEvent.WaitOne(simulationTimeVsRealtimeDifferenceMS);
863 //Thread.Sleep(simulationTimeVsRealtimeDifferenceMS);
864 }
865 else
866 {
867 // The simulation took longer than realtime.
868 // Do some scaling of simulation time.
869 // TODO.
870 DetailLog("{0},BulletSPluginPhysicsThread,longerThanRealtime={1}", BSScene.DetailLogZero, simulationTimeVsRealtimeDifferenceMS);
871 }
872
873 Watchdog.UpdateThread();
874 }
875
876 Watchdog.RemoveThread();
877 }
878
879 #endregion // Simulation
880
881 public override void GetResults() { }
882
883 #region Terrain
884
885 public override void SetTerrain(float[] heightMap) {
886 TerrainManager.SetTerrain(heightMap);
887 }
888
889 public override void SetWaterLevel(float baseheight)
890 {
891 SimpleWaterLevel = baseheight;
892 }
893
894 public override void DeleteTerrain()
895 {
896 // m_log.DebugFormat("{0}: DeleteTerrain()", LogHeader);
897 }
898
899 // Although no one seems to check this, I do support combining.
900 public override bool SupportsCombining()
901 {
902 return TerrainManager.SupportsCombining();
903 }
904 // This call says I am a child to region zero in a mega-region. 'pScene' is that
905 // of region zero, 'offset' is my offset from regions zero's origin, and
906 // 'extents' is the largest XY that is handled in my region.
907 public override void Combine(PhysicsScene pScene, Vector3 offset, Vector3 extents)
908 {
909 TerrainManager.Combine(pScene, offset, extents);
910 }
911
912 // Unhook all the combining that I know about.
913 public override void UnCombine(PhysicsScene pScene)
914 {
915 TerrainManager.UnCombine(pScene);
916 }
917
918 #endregion // Terrain
919
920 public override Dictionary<uint, float> GetTopColliders()
921 {
922 Dictionary<uint, float> topColliders;
923
924 lock (PhysObjects)
925 {
926 foreach (KeyValuePair<uint, BSPhysObject> kvp in PhysObjects)
927 {
928 kvp.Value.ComputeCollisionScore();
929 }
930
931 List<BSPhysObject> orderedPrims = new List<BSPhysObject>(PhysObjects.Values);
932 orderedPrims.OrderByDescending(p => p.CollisionScore);
933 topColliders = orderedPrims.Take(25).ToDictionary(p => p.LocalID, p => p.CollisionScore);
934 }
935
936 return topColliders;
937 }
938
939 public override bool IsThreaded { get { return false; } }
940
941 #region Extensions
942 public override object Extension(string pFunct, params object[] pParams)
943 {
944 DetailLog("{0} BSScene.Extension,op={1}", DetailLogZero, pFunct);
945 return base.Extension(pFunct, pParams);
946 }
947 #endregion // Extensions
948
949 public static string PrimitiveBaseShapeToString(PrimitiveBaseShape pbs)
950 {
951 float pathShearX = pbs.PathShearX < 128 ? (float)pbs.PathShearX * 0.01f : (float)(pbs.PathShearX - 256) * 0.01f;
952 float pathShearY = pbs.PathShearY < 128 ? (float)pbs.PathShearY * 0.01f : (float)(pbs.PathShearY - 256) * 0.01f;
953 float pathBegin = (float)pbs.PathBegin * 2.0e-5f;
954 float pathEnd = 1.0f - (float)pbs.PathEnd * 2.0e-5f;
955 float pathScaleX = (float)(200 - pbs.PathScaleX) * 0.01f;
956 float pathScaleY = (float)(200 - pbs.PathScaleY) * 0.01f;
957 float pathTaperX = pbs.PathTaperX * 0.01f;
958 float pathTaperY = pbs.PathTaperY * 0.01f;
959
960 float profileBegin = (float)pbs.ProfileBegin * 2.0e-5f;
961 float profileEnd = 1.0f - (float)pbs.ProfileEnd * 2.0e-5f;
962 float profileHollow = (float)pbs.ProfileHollow * 2.0e-5f;
963 if (profileHollow > 0.95f)
964 profileHollow = 0.95f;
965
966 StringBuilder buff = new StringBuilder();
967 buff.Append("shape=");
968 buff.Append(((ProfileShape)pbs.ProfileShape).ToString());
969 buff.Append(",");
970 buff.Append("hollow=");
971 buff.Append(((HollowShape)pbs.HollowShape).ToString());
972 buff.Append(",");
973 buff.Append("pathCurve=");
974 buff.Append(((Extrusion)pbs.PathCurve).ToString());
975 buff.Append(",");
976 buff.Append("profCurve=");
977 buff.Append(((Extrusion)pbs.ProfileCurve).ToString());
978 buff.Append(",");
979 buff.Append("profHollow=");
980 buff.Append(profileHollow.ToString());
981 buff.Append(",");
982 buff.Append("pathBegEnd=");
983 buff.Append(pathBegin.ToString());
984 buff.Append("/");
985 buff.Append(pathEnd.ToString());
986 buff.Append(",");
987 buff.Append("profileBegEnd=");
988 buff.Append(profileBegin.ToString());
989 buff.Append("/");
990 buff.Append(profileEnd.ToString());
991 buff.Append(",");
992 buff.Append("scaleXY=");
993 buff.Append(pathScaleX.ToString());
994 buff.Append("/");
995 buff.Append(pathScaleY.ToString());
996 buff.Append(",");
997 buff.Append("shearXY=");
998 buff.Append(pathShearX.ToString());
999 buff.Append("/");
1000 buff.Append(pathShearY.ToString());
1001 buff.Append(",");
1002 buff.Append("taperXY=");
1003 buff.Append(pbs.PathTaperX.ToString());
1004 buff.Append("/");
1005 buff.Append(pbs.PathTaperY.ToString());
1006 buff.Append(",");
1007 buff.Append("skew=");
1008 buff.Append(pbs.PathSkew.ToString());
1009 buff.Append(",");
1010 buff.Append("twist/Beg=");
1011 buff.Append(pbs.PathTwist.ToString());
1012 buff.Append("/");
1013 buff.Append(pbs.PathTwistBegin.ToString());
1014
1015 return buff.ToString();
1016 }
1017
1018 #region Taints
1019 // The simulation execution order is:
1020 // Simulate()
1021 // DoOneTimeTaints
1022 // TriggerPreStepEvent
1023 // DoOneTimeTaints
1024 // Step()
1025 // ProcessAndSendToSimulatorCollisions
1026 // ProcessAndSendToSimulatorPropertyUpdates
1027 // TriggerPostStepEvent
1028
1029 // Calls to the PhysicsActors can't directly call into the physics engine
1030 // because it might be busy. We delay changes to a known time.
1031 // We rely on C#'s closure to save and restore the context for the delegate.
1032 public void TaintedObject(string pOriginator, string pIdent, TaintCallback pCallback)
1033 {
1034 TaintedObject(false /*inTaintTime*/, pOriginator, pIdent, pCallback);
1035 }
1036 public void TaintedObject(uint pOriginator, String pIdent, TaintCallback pCallback)
1037 {
1038 TaintedObject(false /*inTaintTime*/, m_physicsLoggingEnabled ? pOriginator.ToString() : BSScene.DetailLogZero, pIdent, pCallback);
1039 }
1040 public void TaintedObject(bool inTaintTime, String pIdent, TaintCallback pCallback)
1041 {
1042 TaintedObject(inTaintTime, BSScene.DetailLogZero, pIdent, pCallback);
1043 }
1044 public void TaintedObject(bool inTaintTime, uint pOriginator, String pIdent, TaintCallback pCallback)
1045 {
1046 TaintedObject(inTaintTime, m_physicsLoggingEnabled ? pOriginator.ToString() : BSScene.DetailLogZero, pIdent, pCallback);
1047 }
1048 // Sometimes a potentially tainted operation can be used in and out of taint time.
1049 // This routine executes the command immediately if in taint-time otherwise it is queued.
1050 public void TaintedObject(bool inTaintTime, string pOriginator, string pIdent, TaintCallback pCallback)
1051 {
1052 if (!m_initialized) return;
1053
1054 if (inTaintTime)
1055 pCallback();
1056 else
1057 {
1058 lock (_taintLock)
1059 {
1060 _taintOperations.Add(new TaintCallbackEntry(pOriginator, pIdent, pCallback));
1061 }
1062 }
1063 }
1064
1065 private void TriggerPreStepEvent(float timeStep)
1066 {
1067 PreStepAction actions = BeforeStep;
1068 if (actions != null)
1069 actions(timeStep);
1070
1071 }
1072
1073 private void TriggerPostStepEvent(float timeStep)
1074 {
1075 PostStepAction actions = AfterStep;
1076 if (actions != null)
1077 actions(timeStep);
1078
1079 }
1080
1081 // When someone tries to change a property on a BSPrim or BSCharacter, the object queues
1082 // a callback into itself to do the actual property change. That callback is called
1083 // here just before the physics engine is called to step the simulation.
1084 public void ProcessTaints()
1085 {
1086 ProcessRegularTaints();
1087 ProcessPostTaintTaints();
1088 }
1089
1090 private void ProcessRegularTaints()
1091 {
1092 if (m_initialized && _taintOperations.Count > 0) // save allocating new list if there is nothing to process
1093 {
1094 // swizzle a new list into the list location so we can process what's there
1095 List<TaintCallbackEntry> oldList;
1096 lock (_taintLock)
1097 {
1098 oldList = _taintOperations;
1099 _taintOperations = new List<TaintCallbackEntry>();
1100 }
1101
1102 foreach (TaintCallbackEntry tcbe in oldList)
1103 {
1104 try
1105 {
1106 DetailLog("{0},BSScene.ProcessTaints,doTaint,id={1}", tcbe.originator, tcbe.ident); // DEBUG DEBUG DEBUG
1107 tcbe.callback();
1108 }
1109 catch (Exception e)
1110 {
1111 m_log.ErrorFormat("{0}: ProcessTaints: {1}: Exception: {2}", LogHeader, tcbe.ident, e);
1112 }
1113 }
1114 oldList.Clear();
1115 }
1116 }
1117
1118 // Schedule an update to happen after all the regular taints are processed.
1119 // Note that new requests for the same operation ("ident") for the same object ("ID")
1120 // will replace any previous operation by the same object.
1121 public void PostTaintObject(String ident, uint ID, TaintCallback callback)
1122 {
1123 string IDAsString = ID.ToString();
1124 string uniqueIdent = ident + "-" + IDAsString;
1125 lock (_taintLock)
1126 {
1127 _postTaintOperations[uniqueIdent] = new TaintCallbackEntry(IDAsString, uniqueIdent, callback);
1128 }
1129
1130 return;
1131 }
1132
1133 // Taints that happen after the normal taint processing but before the simulation step.
1134 private void ProcessPostTaintTaints()
1135 {
1136 if (m_initialized && _postTaintOperations.Count > 0)
1137 {
1138 Dictionary<string, TaintCallbackEntry> oldList;
1139 lock (_taintLock)
1140 {
1141 oldList = _postTaintOperations;
1142 _postTaintOperations = new Dictionary<string, TaintCallbackEntry>();
1143 }
1144
1145 foreach (KeyValuePair<string,TaintCallbackEntry> kvp in oldList)
1146 {
1147 try
1148 {
1149 DetailLog("{0},BSScene.ProcessPostTaintTaints,doTaint,id={1}", DetailLogZero, kvp.Key); // DEBUG DEBUG DEBUG
1150 kvp.Value.callback();
1151 }
1152 catch (Exception e)
1153 {
1154 m_log.ErrorFormat("{0}: ProcessPostTaintTaints: {1}: Exception: {2}", LogHeader, kvp.Key, e);
1155 }
1156 }
1157 oldList.Clear();
1158 }
1159 }
1160
1161 // Only used for debugging. Does not change state of anything so locking is not necessary.
1162 public bool AssertInTaintTime(string whereFrom)
1163 {
1164 if (!InTaintTime)
1165 {
1166 DetailLog("{0},BSScene.AssertInTaintTime,NOT IN TAINT TIME,Region={1},Where={2}", DetailLogZero, RegionName, whereFrom);
1167 m_log.ErrorFormat("{0} NOT IN TAINT TIME!! Region={1}, Where={2}", LogHeader, RegionName, whereFrom);
1168 // Util.PrintCallStack(DetailLog);
1169 }
1170 return InTaintTime;
1171 }
1172
1173 #endregion // Taints
1174
1175 #region IPhysicsParameters
1176 // Get the list of parameters this physics engine supports
1177 public PhysParameterEntry[] GetParameterList()
1178 {
1179 BSParam.BuildParameterTable();
1180 return BSParam.SettableParameters;
1181 }
1182
1183 // Set parameter on a specific or all instances.
1184 // Return 'false' if not able to set the parameter.
1185 // Setting the value in the m_params block will change the value the physics engine
1186 // will use the next time since it's pinned and shared memory.
1187 // Some of the values require calling into the physics engine to get the new
1188 // value activated ('terrainFriction' for instance).
1189 public bool SetPhysicsParameter(string parm, string val, uint localID)
1190 {
1191 bool ret = false;
1192
1193 BSParam.ParameterDefnBase theParam;
1194 if (BSParam.TryGetParameter(parm, out theParam))
1195 {
1196 // Set the value in the C# code
1197 theParam.SetValue(this, val);
1198
1199 // Optionally set the parameter in the unmanaged code
1200 if (theParam.HasSetOnObject)
1201 {
1202 // update all the localIDs specified
1203 // If the local ID is APPLY_TO_NONE, just change the default value
1204 // If the localID is APPLY_TO_ALL change the default value and apply the new value to all the lIDs
1205 // If the localID is a specific object, apply the parameter change to only that object
1206 List<uint> objectIDs = new List<uint>();
1207 switch (localID)
1208 {
1209 case PhysParameterEntry.APPLY_TO_NONE:
1210 // This will cause a call into the physical world if some operation is specified (SetOnObject).
1211 objectIDs.Add(TERRAIN_ID);
1212 TaintedUpdateParameter(parm, objectIDs, val);
1213 break;
1214 case PhysParameterEntry.APPLY_TO_ALL:
1215 lock (PhysObjects) objectIDs = new List<uint>(PhysObjects.Keys);
1216 TaintedUpdateParameter(parm, objectIDs, val);
1217 break;
1218 default:
1219 // setting only one localID
1220 objectIDs.Add(localID);
1221 TaintedUpdateParameter(parm, objectIDs, val);
1222 break;
1223 }
1224 }
1225
1226 ret = true;
1227 }
1228 return ret;
1229 }
1230
1231 // schedule the actual updating of the paramter to when the phys engine is not busy
1232 private void TaintedUpdateParameter(string parm, List<uint> lIDs, string val)
1233 {
1234 string xval = val;
1235 List<uint> xlIDs = lIDs;
1236 string xparm = parm;
1237 TaintedObject(DetailLogZero, "BSScene.UpdateParameterSet", delegate() {
1238 BSParam.ParameterDefnBase thisParam;
1239 if (BSParam.TryGetParameter(xparm, out thisParam))
1240 {
1241 if (thisParam.HasSetOnObject)
1242 {
1243 foreach (uint lID in xlIDs)
1244 {
1245 BSPhysObject theObject = null;
1246 if (PhysObjects.TryGetValue(lID, out theObject))
1247 thisParam.SetOnObject(this, theObject);
1248 }
1249 }
1250 }
1251 });
1252 }
1253
1254 // Get parameter.
1255 // Return 'false' if not able to get the parameter.
1256 public bool GetPhysicsParameter(string parm, out string value)
1257 {
1258 string val = String.Empty;
1259 bool ret = false;
1260 BSParam.ParameterDefnBase theParam;
1261 if (BSParam.TryGetParameter(parm, out theParam))
1262 {
1263 val = theParam.GetValue(this);
1264 ret = true;
1265 }
1266 value = val;
1267 return ret;
1268 }
1269
1270 #endregion IPhysicsParameters
1271
1272 // Invoke the detailed logger and output something if it's enabled.
1273 public void DetailLog(string msg, params Object[] args)
1274 {
1275 PhysicsLogging.Write(msg, args);
1276 }
1277 // Used to fill in the LocalID when there isn't one. It's the correct number of characters.
1278 public const string DetailLogZero = "0000000000";
1279
1280}
1281}