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