diff options
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/Physics/BulletSPlugin/BSScene.cs | 1047 |
1 files changed, 262 insertions, 785 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs b/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs index 27a78d1..e6aefd5 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs | |||
@@ -26,6 +26,8 @@ | |||
26 | */ | 26 | */ |
27 | using System; | 27 | using System; |
28 | using System.Collections.Generic; | 28 | using System.Collections.Generic; |
29 | using System.Linq; | ||
30 | using System.Reflection; | ||
29 | using System.Runtime.InteropServices; | 31 | using System.Runtime.InteropServices; |
30 | using System.Text; | 32 | using System.Text; |
31 | using System.Threading; | 33 | using System.Threading; |
@@ -38,40 +40,22 @@ using Nini.Config; | |||
38 | using log4net; | 40 | using log4net; |
39 | using OpenMetaverse; | 41 | using OpenMetaverse; |
40 | 42 | ||
41 | // TODOs for BulletSim (for BSScene, BSPrim, BSCharacter and BulletSim) | ||
42 | // Test sculpties (verified that they don't work) | ||
43 | // Compute physics FPS reasonably | ||
44 | // Based on material, set density and friction | ||
45 | // Don't use constraints in linksets of non-physical objects. Means having to move children manually. | ||
46 | // Four states of prim: Physical, regular, phantom and selected. Are we modeling these correctly? | ||
47 | // In SL one can set both physical and phantom (gravity, does not effect others, makes collisions with ground) | ||
48 | // At the moment, physical and phantom causes object to drop through the terrain | ||
49 | // Physical phantom objects and related typing (collision options ) | ||
50 | // Check out llVolumeDetect. Must do something for that. | ||
51 | // Use collision masks for collision with terrain and phantom objects | ||
52 | // More efficient memory usage when passing hull information from BSPrim to BulletSim | ||
53 | // Should prim.link() and prim.delink() membership checking happen at taint time? | ||
54 | // Mesh sharing. Use meshHash to tell if we already have a hull of that shape and only create once. | ||
55 | // Do attachments need to be handled separately? Need collision events. Do not collide with VolumeDetect | ||
56 | // Implement LockAngularMotion | ||
57 | // Decide if clearing forces is the right thing to do when setting position (BulletSim::SetObjectTranslation) | ||
58 | // Remove mesh and Hull stuff. Use mesh passed to bullet and use convexdecom from bullet. | ||
59 | // Add PID movement operations. What does ScenePresence.MoveToTarget do? | ||
60 | // Check terrain size. 128 or 127? | ||
61 | // Raycast | ||
62 | // | ||
63 | namespace OpenSim.Region.Physics.BulletSPlugin | 43 | namespace OpenSim.Region.Physics.BulletSPlugin |
64 | { | 44 | { |
65 | public sealed class BSScene : PhysicsScene, IPhysicsParameters | 45 | public sealed class BSScene : PhysicsScene, IPhysicsParameters |
66 | { | 46 | { |
67 | private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); | 47 | internal static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); |
68 | private static readonly string LogHeader = "[BULLETS SCENE]"; | 48 | internal static readonly string LogHeader = "[BULLETS SCENE]"; |
69 | 49 | ||
70 | // The name of the region we're working for. | 50 | // The name of the region we're working for. |
71 | public string RegionName { get; private set; } | 51 | public string RegionName { get; private set; } |
72 | 52 | ||
73 | public string BulletSimVersion = "?"; | 53 | public string BulletSimVersion = "?"; |
74 | 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 | |||
75 | public Dictionary<uint, BSPhysObject> PhysObjects; | 59 | public Dictionary<uint, BSPhysObject> PhysObjects; |
76 | public BSShapeCollection Shapes; | 60 | public BSShapeCollection Shapes; |
77 | 61 | ||
@@ -82,32 +66,29 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
82 | // every tick so OpenSim will update its animation. | 66 | // every tick so OpenSim will update its animation. |
83 | private HashSet<BSPhysObject> m_avatars = new HashSet<BSPhysObject>(); | 67 | private HashSet<BSPhysObject> m_avatars = new HashSet<BSPhysObject>(); |
84 | 68 | ||
85 | // List of all the objects that have vehicle properties and should be called | ||
86 | // to update each physics step. | ||
87 | private List<BSPhysObject> m_vehicles = new List<BSPhysObject>(); | ||
88 | |||
89 | // let my minuions use my logger | 69 | // let my minuions use my logger |
90 | public ILog Logger { get { return m_log; } } | 70 | public ILog Logger { get { return m_log; } } |
91 | 71 | ||
92 | public IMesher mesher; | 72 | public IMesher mesher; |
93 | // Level of Detail values kept as float because that's what the Meshmerizer wants | ||
94 | public float MeshLOD { get; private set; } | ||
95 | public float MeshMegaPrimLOD { get; private set; } | ||
96 | public float MeshMegaPrimThreshold { get; private set; } | ||
97 | public float SculptLOD { get; private set; } | ||
98 | |||
99 | public uint WorldID { get; private set; } | 73 | public uint WorldID { get; private set; } |
100 | public BulletSim World { get; private set; } | 74 | public BulletWorld World { get; private set; } |
101 | 75 | ||
102 | // All the constraints that have been allocated in this instance. | 76 | // All the constraints that have been allocated in this instance. |
103 | public BSConstraintCollection Constraints { get; private set; } | 77 | public BSConstraintCollection Constraints { get; private set; } |
104 | 78 | ||
105 | // Simulation parameters | 79 | // Simulation parameters |
106 | private int m_maxSubSteps; | 80 | internal int m_maxSubSteps; |
107 | private float m_fixedTimeStep; | 81 | internal float m_fixedTimeStep; |
108 | private long m_simulationStep = 0; | 82 | internal long m_simulationStep = 0; |
83 | internal float NominalFrameRate { get; set; } | ||
109 | public long SimulationStep { get { return m_simulationStep; } } | 84 | public long SimulationStep { get { return m_simulationStep; } } |
110 | private int m_taintsToProcessPerStep; | 85 | internal float LastTimeStep { get; private set; } |
86 | |||
87 | // Physical objects can register for prestep or poststep events | ||
88 | public delegate void PreStepAction(float timeStep); | ||
89 | public delegate void PostStepAction(float timeStep); | ||
90 | public event PreStepAction BeforeStep; | ||
91 | public event PostStepAction AfterStep; | ||
111 | 92 | ||
112 | // A value of the time now so all the collision and update routines do not have to get their own | 93 | // A value of the time now so all the collision and update routines do not have to get their own |
113 | // Set to 'now' just before all the prims and actors are called for collisions and updates | 94 | // Set to 'now' just before all the prims and actors are called for collisions and updates |
@@ -121,31 +102,22 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
121 | public bool InTaintTime { get; private set; } | 102 | public bool InTaintTime { get; private set; } |
122 | 103 | ||
123 | // Pinned memory used to pass step information between managed and unmanaged | 104 | // Pinned memory used to pass step information between managed and unmanaged |
124 | private int m_maxCollisionsPerFrame; | 105 | internal int m_maxCollisionsPerFrame; |
125 | private CollisionDesc[] m_collisionArray; | 106 | internal CollisionDesc[] m_collisionArray; |
126 | private GCHandle m_collisionArrayPinnedHandle; | ||
127 | 107 | ||
128 | private int m_maxUpdatesPerFrame; | 108 | internal int m_maxUpdatesPerFrame; |
129 | private EntityProperties[] m_updateArray; | 109 | internal EntityProperties[] m_updateArray; |
130 | private GCHandle m_updateArrayPinnedHandle; | ||
131 | |||
132 | public bool ShouldMeshSculptedPrim { get; private set; } // cause scuplted prims to get meshed | ||
133 | public bool ShouldForceSimplePrimMeshing { get; private set; } // if a cube or sphere, let Bullet do internal shapes | ||
134 | public bool ShouldUseHullsForPhysicalObjects { get; private set; } // 'true' if should create hulls for physical objects | ||
135 | |||
136 | public float PID_D { get; private set; } // derivative | ||
137 | public float PID_P { get; private set; } // proportional | ||
138 | 110 | ||
139 | public const uint TERRAIN_ID = 0; // OpenSim senses terrain with a localID of zero | 111 | public const uint TERRAIN_ID = 0; // OpenSim senses terrain with a localID of zero |
140 | public const uint GROUNDPLANE_ID = 1; | 112 | public const uint GROUNDPLANE_ID = 1; |
141 | public const uint CHILDTERRAIN_ID = 2; // Terrain allocated based on our mega-prim childre start here | 113 | public const uint CHILDTERRAIN_ID = 2; // Terrain allocated based on our mega-prim childre start here |
142 | 114 | ||
143 | private float m_waterLevel; | 115 | public float SimpleWaterLevel { get; set; } |
144 | public BSTerrainManager TerrainManager { get; private set; } | 116 | public BSTerrainManager TerrainManager { get; private set; } |
145 | 117 | ||
146 | public ConfigurationParameters Params | 118 | public ConfigurationParameters Params |
147 | { | 119 | { |
148 | get { return m_params[0]; } | 120 | get { return UnmanagedParams[0]; } |
149 | } | 121 | } |
150 | public Vector3 DefaultGravity | 122 | public Vector3 DefaultGravity |
151 | { | 123 | { |
@@ -157,8 +129,6 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
157 | get { return Params.gravity; } | 129 | get { return Params.gravity; } |
158 | } | 130 | } |
159 | 131 | ||
160 | public float MaximumObjectMass { get; private set; } | ||
161 | |||
162 | // When functions in the unmanaged code must be called, it is only | 132 | // When functions in the unmanaged code must be called, it is only |
163 | // done at a known time just before the simulation step. The taint | 133 | // done at a known time just before the simulation step. The taint |
164 | // system saves all these function calls and executes them in | 134 | // system saves all these function calls and executes them in |
@@ -181,13 +151,7 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
181 | 151 | ||
182 | // A pointer to an instance if this structure is passed to the C++ code | 152 | // A pointer to an instance if this structure is passed to the C++ code |
183 | // Used to pass basic configuration values to the unmanaged code. | 153 | // Used to pass basic configuration values to the unmanaged code. |
184 | ConfigurationParameters[] m_params; | 154 | internal ConfigurationParameters[] UnmanagedParams; |
185 | GCHandle m_paramsHandle; | ||
186 | |||
187 | // Handle to the callback used by the unmanaged code to call into the managed code. | ||
188 | // Used for debug logging. | ||
189 | // Need to store the handle in a persistant variable so it won't be freed. | ||
190 | private BulletSimAPI.DebugLogCallback m_DebugLogCallbackHandle; | ||
191 | 155 | ||
192 | // Sometimes you just have to log everything. | 156 | // Sometimes you just have to log everything. |
193 | public Logging.LogWriter PhysicsLogging; | 157 | public Logging.LogWriter PhysicsLogging; |
@@ -195,15 +159,24 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
195 | private string m_physicsLoggingDir; | 159 | private string m_physicsLoggingDir; |
196 | private string m_physicsLoggingPrefix; | 160 | private string m_physicsLoggingPrefix; |
197 | private int m_physicsLoggingFileMinutes; | 161 | private int m_physicsLoggingFileMinutes; |
162 | private bool m_physicsLoggingDoFlush; | ||
163 | private bool m_physicsPhysicalDumpEnabled; | ||
164 | public int PhysicsMetricDumpFrames { get; set; } | ||
198 | // 'true' of the vehicle code is to log lots of details | 165 | // 'true' of the vehicle code is to log lots of details |
199 | public bool VehicleLoggingEnabled { get; private set; } | 166 | public bool VehicleLoggingEnabled { get; private set; } |
167 | public bool VehiclePhysicalLoggingEnabled { get; private set; } | ||
200 | 168 | ||
201 | #region Construction and Initialization | 169 | #region Construction and Initialization |
202 | public BSScene(string identifier) | 170 | public BSScene(string engineType, string identifier) |
203 | { | 171 | { |
204 | m_initialized = false; | 172 | m_initialized = false; |
205 | // we are passed the name of the region we're working for. | 173 | |
174 | // The name of the region we're working for is passed to us. Keep for identification. | ||
206 | RegionName = identifier; | 175 | RegionName = identifier; |
176 | |||
177 | // Set identifying variables in the PhysicsScene interface. | ||
178 | EngineType = engineType; | ||
179 | Name = EngineType + "/" + RegionName; | ||
207 | } | 180 | } |
208 | 181 | ||
209 | public override void Initialise(IMesher meshmerizer, IConfigSource config) | 182 | public override void Initialise(IMesher meshmerizer, IConfigSource config) |
@@ -216,17 +189,13 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
216 | Shapes = new BSShapeCollection(this); | 189 | Shapes = new BSShapeCollection(this); |
217 | 190 | ||
218 | // Allocate pinned memory to pass parameters. | 191 | // Allocate pinned memory to pass parameters. |
219 | m_params = new ConfigurationParameters[1]; | 192 | UnmanagedParams = new ConfigurationParameters[1]; |
220 | m_paramsHandle = GCHandle.Alloc(m_params, GCHandleType.Pinned); | ||
221 | 193 | ||
222 | // Set default values for physics parameters plus any overrides from the ini file | 194 | // Set default values for physics parameters plus any overrides from the ini file |
223 | GetInitialParameterValues(config); | 195 | GetInitialParameterValues(config); |
224 | 196 | ||
225 | // allocate more pinned memory close to the above in an attempt to get the memory all together | 197 | // Get the connection to the physics engine (could be native or one of many DLLs) |
226 | m_collisionArray = new CollisionDesc[m_maxCollisionsPerFrame]; | 198 | PE = SelectUnderlyingBulletEngine(BulletEngineName); |
227 | m_collisionArrayPinnedHandle = GCHandle.Alloc(m_collisionArray, GCHandleType.Pinned); | ||
228 | m_updateArray = new EntityProperties[m_maxUpdatesPerFrame]; | ||
229 | m_updateArrayPinnedHandle = GCHandle.Alloc(m_updateArray, GCHandleType.Pinned); | ||
230 | 199 | ||
231 | // Enable very detailed logging. | 200 | // Enable very detailed logging. |
232 | // By creating an empty logger when not logging, the log message invocation code | 201 | // By creating an empty logger when not logging, the log message invocation code |
@@ -234,28 +203,16 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
234 | if (m_physicsLoggingEnabled) | 203 | if (m_physicsLoggingEnabled) |
235 | { | 204 | { |
236 | PhysicsLogging = new Logging.LogWriter(m_physicsLoggingDir, m_physicsLoggingPrefix, m_physicsLoggingFileMinutes); | 205 | PhysicsLogging = new Logging.LogWriter(m_physicsLoggingDir, m_physicsLoggingPrefix, m_physicsLoggingFileMinutes); |
206 | PhysicsLogging.ErrorLogger = m_log; // for DEBUG. Let's the logger output error messages. | ||
237 | } | 207 | } |
238 | else | 208 | else |
239 | { | 209 | { |
240 | PhysicsLogging = new Logging.LogWriter(); | 210 | PhysicsLogging = new Logging.LogWriter(); |
241 | } | 211 | } |
242 | 212 | ||
243 | // If Debug logging level, enable logging from the unmanaged code | 213 | // Allocate memory for returning of the updates and collisions from the physics engine |
244 | m_DebugLogCallbackHandle = null; | 214 | m_collisionArray = new CollisionDesc[m_maxCollisionsPerFrame]; |
245 | if (m_log.IsDebugEnabled || PhysicsLogging.Enabled) | 215 | m_updateArray = new EntityProperties[m_maxUpdatesPerFrame]; |
246 | { | ||
247 | m_log.DebugFormat("{0}: Initialize: Setting debug callback for unmanaged code", LogHeader); | ||
248 | if (PhysicsLogging.Enabled) | ||
249 | // The handle is saved in a variable to make sure it doesn't get freed after this call | ||
250 | m_DebugLogCallbackHandle = new BulletSimAPI.DebugLogCallback(BulletLoggerPhysLog); | ||
251 | else | ||
252 | m_DebugLogCallbackHandle = new BulletSimAPI.DebugLogCallback(BulletLogger); | ||
253 | } | ||
254 | |||
255 | // Get the version of the DLL | ||
256 | // TODO: this doesn't work yet. Something wrong with marshaling the returned string. | ||
257 | // BulletSimVersion = BulletSimAPI.GetVersion(); | ||
258 | // m_log.WarnFormat("{0}: BulletSim.dll version='{1}'", LogHeader, BulletSimVersion); | ||
259 | 216 | ||
260 | // The bounding box for the simulated world. The origin is 0,0,0 unless we're | 217 | // The bounding box for the simulated world. The origin is 0,0,0 unless we're |
261 | // a child in a mega-region. | 218 | // a child in a mega-region. |
@@ -263,18 +220,14 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
263 | // area. It tracks active objects no matter where they are. | 220 | // area. It tracks active objects no matter where they are. |
264 | Vector3 worldExtent = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight); | 221 | Vector3 worldExtent = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight); |
265 | 222 | ||
266 | // m_log.DebugFormat("{0}: Initialize: Calling BulletSimAPI.Initialize.", LogHeader); | 223 | World = PE.Initialize(worldExtent, Params, m_maxCollisionsPerFrame, ref m_collisionArray, m_maxUpdatesPerFrame, ref m_updateArray); |
267 | World = new BulletSim(0, this, BulletSimAPI.Initialize2(worldExtent, m_paramsHandle.AddrOfPinnedObject(), | ||
268 | m_maxCollisionsPerFrame, m_collisionArrayPinnedHandle.AddrOfPinnedObject(), | ||
269 | m_maxUpdatesPerFrame, m_updateArrayPinnedHandle.AddrOfPinnedObject(), | ||
270 | m_DebugLogCallbackHandle)); | ||
271 | 224 | ||
272 | Constraints = new BSConstraintCollection(World); | 225 | Constraints = new BSConstraintCollection(World); |
273 | 226 | ||
274 | TerrainManager = new BSTerrainManager(this); | 227 | TerrainManager = new BSTerrainManager(this); |
275 | TerrainManager.CreateInitialGroundPlaneAndTerrain(); | 228 | TerrainManager.CreateInitialGroundPlaneAndTerrain(); |
276 | 229 | ||
277 | m_log.WarnFormat("{0} Linksets implemented with {1}", LogHeader, (BSLinkset.LinksetImplementation)Params.linksetImplementation); | 230 | m_log.WarnFormat("{0} Linksets implemented with {1}", LogHeader, (BSLinkset.LinksetImplementation)BSParam.LinksetImplementation); |
278 | 231 | ||
279 | InTaintTime = false; | 232 | InTaintTime = false; |
280 | m_initialized = true; | 233 | m_initialized = true; |
@@ -285,9 +238,9 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
285 | private void GetInitialParameterValues(IConfigSource config) | 238 | private void GetInitialParameterValues(IConfigSource config) |
286 | { | 239 | { |
287 | ConfigurationParameters parms = new ConfigurationParameters(); | 240 | ConfigurationParameters parms = new ConfigurationParameters(); |
288 | m_params[0] = parms; | 241 | UnmanagedParams[0] = parms; |
289 | 242 | ||
290 | SetParameterDefaultValues(); | 243 | BSParam.SetParameterDefaultValues(this); |
291 | 244 | ||
292 | if (config != null) | 245 | if (config != null) |
293 | { | 246 | { |
@@ -295,19 +248,34 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
295 | IConfig pConfig = config.Configs["BulletSim"]; | 248 | IConfig pConfig = config.Configs["BulletSim"]; |
296 | if (pConfig != null) | 249 | if (pConfig != null) |
297 | { | 250 | { |
298 | SetParameterConfigurationValues(pConfig); | 251 | BSParam.SetParameterConfigurationValues(this, pConfig); |
252 | |||
253 | // There are two Bullet implementations to choose from | ||
254 | BulletEngineName = pConfig.GetString("BulletEngine", "BulletUnmanaged"); | ||
299 | 255 | ||
300 | // Very detailed logging for physics debugging | 256 | // Very detailed logging for physics debugging |
257 | // TODO: the boolean values can be moved to the normal parameter processing. | ||
301 | m_physicsLoggingEnabled = pConfig.GetBoolean("PhysicsLoggingEnabled", false); | 258 | m_physicsLoggingEnabled = pConfig.GetBoolean("PhysicsLoggingEnabled", false); |
302 | m_physicsLoggingDir = pConfig.GetString("PhysicsLoggingDir", "."); | 259 | m_physicsLoggingDir = pConfig.GetString("PhysicsLoggingDir", "."); |
303 | m_physicsLoggingPrefix = pConfig.GetString("PhysicsLoggingPrefix", "physics-%REGIONNAME%-"); | 260 | m_physicsLoggingPrefix = pConfig.GetString("PhysicsLoggingPrefix", "physics-%REGIONNAME%-"); |
304 | m_physicsLoggingFileMinutes = pConfig.GetInt("PhysicsLoggingFileMinutes", 5); | 261 | m_physicsLoggingFileMinutes = pConfig.GetInt("PhysicsLoggingFileMinutes", 5); |
262 | m_physicsLoggingDoFlush = pConfig.GetBoolean("PhysicsLoggingDoFlush", false); | ||
263 | m_physicsPhysicalDumpEnabled = pConfig.GetBoolean("PhysicsPhysicalDumpEnabled", false); | ||
305 | // Very detailed logging for vehicle debugging | 264 | // Very detailed logging for vehicle debugging |
306 | VehicleLoggingEnabled = pConfig.GetBoolean("VehicleLoggingEnabled", false); | 265 | VehicleLoggingEnabled = pConfig.GetBoolean("VehicleLoggingEnabled", false); |
266 | VehiclePhysicalLoggingEnabled = pConfig.GetBoolean("VehiclePhysicalLoggingEnabled", false); | ||
307 | 267 | ||
308 | // Do any replacements in the parameters | 268 | // Do any replacements in the parameters |
309 | m_physicsLoggingPrefix = m_physicsLoggingPrefix.Replace("%REGIONNAME%", RegionName); | 269 | m_physicsLoggingPrefix = m_physicsLoggingPrefix.Replace("%REGIONNAME%", RegionName); |
310 | } | 270 | } |
271 | |||
272 | // The material characteristics. | ||
273 | BSMaterials.InitializeFromDefaults(Params); | ||
274 | if (pConfig != null) | ||
275 | { | ||
276 | // Let the user add new and interesting material property values. | ||
277 | BSMaterials.InitializefromParameters(pConfig); | ||
278 | } | ||
311 | } | 279 | } |
312 | } | 280 | } |
313 | 281 | ||
@@ -326,16 +294,41 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
326 | return ret; | 294 | return ret; |
327 | } | 295 | } |
328 | 296 | ||
329 | // Called directly from unmanaged code so don't do much | 297 | // Select the connection to the actual Bullet implementation. |
330 | private void BulletLogger(string msg) | 298 | // The main engine selection is the engineName up to the first hypen. |
299 | // So "Bullet-2.80-OpenCL-Intel" specifies the 'bullet' class here and the whole name | ||
300 | // is passed to the engine to do its special selection, etc. | ||
301 | private BSAPITemplate SelectUnderlyingBulletEngine(string engineName) | ||
331 | { | 302 | { |
332 | m_log.Debug("[BULLETS UNMANAGED]:" + msg); | 303 | // For the moment, do a simple switch statement. |
333 | } | 304 | // Someday do fancyness with looking up the interfaces in the assembly. |
305 | BSAPITemplate ret = null; | ||
334 | 306 | ||
335 | // Called directly from unmanaged code so don't do much | 307 | string selectionName = engineName.ToLower(); |
336 | private void BulletLoggerPhysLog(string msg) | 308 | int hyphenIndex = engineName.IndexOf("-"); |
337 | { | 309 | if (hyphenIndex > 0) |
338 | DetailLog("[BULLETS UNMANAGED]:" + msg); | 310 | selectionName = engineName.ToLower().Substring(0, hyphenIndex - 1); |
311 | |||
312 | switch (selectionName) | ||
313 | { | ||
314 | case "bulletunmanaged": | ||
315 | ret = new BSAPIUnman(engineName, this); | ||
316 | break; | ||
317 | case "bulletxna": | ||
318 | ret = new BSAPIXNA(engineName, this); | ||
319 | break; | ||
320 | } | ||
321 | |||
322 | if (ret == null) | ||
323 | { | ||
324 | m_log.ErrorFormat("{0) COULD NOT SELECT BULLET ENGINE: '[BulletSim]PhysicsEngine' must be either 'BulletUnmanaged-*' or 'BulletXNA-*'", LogHeader); | ||
325 | } | ||
326 | else | ||
327 | { | ||
328 | m_log.WarnFormat("{0} Selected bullet engine {1} -> {2}/{3}", LogHeader, engineName, ret.BulletEngineName, ret.BulletEngineVersion); | ||
329 | } | ||
330 | |||
331 | return ret; | ||
339 | } | 332 | } |
340 | 333 | ||
341 | public override void Dispose() | 334 | public override void Dispose() |
@@ -345,8 +338,6 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
345 | // make sure no stepping happens while we're deleting stuff | 338 | // make sure no stepping happens while we're deleting stuff |
346 | m_initialized = false; | 339 | m_initialized = false; |
347 | 340 | ||
348 | TerrainManager.ReleaseGroundPlaneAndTerrain(); | ||
349 | |||
350 | foreach (KeyValuePair<uint, BSPhysObject> kvp in PhysObjects) | 341 | foreach (KeyValuePair<uint, BSPhysObject> kvp in PhysObjects) |
351 | { | 342 | { |
352 | kvp.Value.Destroy(); | 343 | kvp.Value.Destroy(); |
@@ -366,8 +357,15 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
366 | Shapes = null; | 357 | Shapes = null; |
367 | } | 358 | } |
368 | 359 | ||
360 | if (TerrainManager != null) | ||
361 | { | ||
362 | TerrainManager.ReleaseGroundPlaneAndTerrain(); | ||
363 | TerrainManager.Dispose(); | ||
364 | TerrainManager = null; | ||
365 | } | ||
366 | |||
369 | // Anything left in the unmanaged code should be cleaned out | 367 | // Anything left in the unmanaged code should be cleaned out |
370 | BulletSimAPI.Shutdown2(World.ptr); | 368 | PE.Shutdown(World); |
371 | 369 | ||
372 | // Not logging any more | 370 | // Not logging any more |
373 | PhysicsLogging.Close(); | 371 | PhysicsLogging.Close(); |
@@ -389,12 +387,14 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
389 | if (!m_initialized) return null; | 387 | if (!m_initialized) return null; |
390 | 388 | ||
391 | BSCharacter actor = new BSCharacter(localID, avName, this, position, size, isFlying); | 389 | BSCharacter actor = new BSCharacter(localID, avName, this, position, size, isFlying); |
392 | lock (PhysObjects) PhysObjects.Add(localID, actor); | 390 | lock (PhysObjects) |
391 | PhysObjects.Add(localID, actor); | ||
393 | 392 | ||
394 | // TODO: Remove kludge someday. | 393 | // TODO: Remove kludge someday. |
395 | // We must generate a collision for avatars whether they collide or not. | 394 | // We must generate a collision for avatars whether they collide or not. |
396 | // This is required by OpenSim to update avatar animations, etc. | 395 | // This is required by OpenSim to update avatar animations, etc. |
397 | lock (m_avatars) m_avatars.Add(actor); | 396 | lock (m_avatars) |
397 | m_avatars.Add(actor); | ||
398 | 398 | ||
399 | return actor; | 399 | return actor; |
400 | } | 400 | } |
@@ -410,9 +410,11 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
410 | { | 410 | { |
411 | try | 411 | try |
412 | { | 412 | { |
413 | lock (PhysObjects) PhysObjects.Remove(actor.LocalID); | 413 | lock (PhysObjects) |
414 | PhysObjects.Remove(bsactor.LocalID); | ||
414 | // Remove kludge someday | 415 | // Remove kludge someday |
415 | lock (m_avatars) m_avatars.Remove(bsactor); | 416 | lock (m_avatars) |
417 | m_avatars.Remove(bsactor); | ||
416 | } | 418 | } |
417 | catch (Exception e) | 419 | catch (Exception e) |
418 | { | 420 | { |
@@ -421,13 +423,18 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
421 | bsactor.Destroy(); | 423 | bsactor.Destroy(); |
422 | // bsactor.dispose(); | 424 | // bsactor.dispose(); |
423 | } | 425 | } |
426 | else | ||
427 | { | ||
428 | m_log.ErrorFormat("{0}: Requested to remove avatar that is not a BSCharacter. ID={1}, type={2}", | ||
429 | LogHeader, actor.LocalID, actor.GetType().Name); | ||
430 | } | ||
424 | } | 431 | } |
425 | 432 | ||
426 | public override void RemovePrim(PhysicsActor prim) | 433 | public override void RemovePrim(PhysicsActor prim) |
427 | { | 434 | { |
428 | if (!m_initialized) return; | 435 | if (!m_initialized) return; |
429 | 436 | ||
430 | BSPrim bsprim = prim as BSPrim; | 437 | BSPhysObject bsprim = prim as BSPhysObject; |
431 | if (bsprim != null) | 438 | if (bsprim != null) |
432 | { | 439 | { |
433 | DetailLog("{0},RemovePrim,call", bsprim.LocalID); | 440 | DetailLog("{0},RemovePrim,call", bsprim.LocalID); |
@@ -456,9 +463,9 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
456 | 463 | ||
457 | if (!m_initialized) return null; | 464 | if (!m_initialized) return null; |
458 | 465 | ||
459 | DetailLog("{0},AddPrimShape,call", localID); | 466 | DetailLog("{0},BSScene.AddPrimShape,call", localID); |
460 | 467 | ||
461 | BSPrim prim = new BSPrim(localID, primName, this, position, size, rotation, pbs, isPhysical); | 468 | BSPhysObject prim = new BSPrimLinkable(localID, primName, this, position, size, rotation, pbs, isPhysical); |
462 | lock (PhysObjects) PhysObjects.Add(localID, prim); | 469 | lock (PhysObjects) PhysObjects.Add(localID, prim); |
463 | return prim; | 470 | return prim; |
464 | } | 471 | } |
@@ -474,41 +481,56 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
474 | // Simulate one timestep | 481 | // Simulate one timestep |
475 | public override float Simulate(float timeStep) | 482 | public override float Simulate(float timeStep) |
476 | { | 483 | { |
484 | // prevent simulation until we've been initialized | ||
485 | if (!m_initialized) return 5.0f; | ||
486 | |||
487 | LastTimeStep = timeStep; | ||
488 | |||
477 | int updatedEntityCount = 0; | 489 | int updatedEntityCount = 0; |
478 | IntPtr updatedEntitiesPtr; | ||
479 | int collidersCount = 0; | 490 | int collidersCount = 0; |
480 | IntPtr collidersPtr; | ||
481 | 491 | ||
482 | int beforeTime = 0; | 492 | int beforeTime = 0; |
483 | int simTime = 0; | 493 | int simTime = 0; |
484 | 494 | ||
485 | // prevent simulation until we've been initialized | ||
486 | if (!m_initialized) return 5.0f; | ||
487 | |||
488 | // update the prim states while we know the physics engine is not busy | 495 | // update the prim states while we know the physics engine is not busy |
489 | int numTaints = _taintOperations.Count; | 496 | int numTaints = _taintOperations.Count; |
497 | |||
498 | InTaintTime = true; // Only used for debugging so locking is not necessary. | ||
499 | |||
490 | ProcessTaints(); | 500 | ProcessTaints(); |
491 | 501 | ||
492 | // Some of the prims operate with special vehicle properties | 502 | // Some of the physical objects requre individual, pre-step calls |
493 | ProcessVehicles(timeStep); | 503 | // (vehicles and avatar movement, in particular) |
494 | ProcessTaints(); // the vehicles might have added taints | 504 | TriggerPreStepEvent(timeStep); |
505 | |||
506 | // the prestep actions might have added taints | ||
507 | numTaints += _taintOperations.Count; | ||
508 | ProcessTaints(); | ||
509 | |||
510 | InTaintTime = false; // Only used for debugging so locking is not necessary. | ||
511 | |||
512 | // The following causes the unmanaged code to output ALL the values found in ALL the objects in the world. | ||
513 | // Only enable this in a limited test world with few objects. | ||
514 | if (m_physicsPhysicalDumpEnabled) | ||
515 | PE.DumpAllInfo(World); | ||
495 | 516 | ||
496 | // step the physical world one interval | 517 | // step the physical world one interval |
497 | m_simulationStep++; | 518 | m_simulationStep++; |
498 | int numSubSteps = 0; | 519 | int numSubSteps = 0; |
499 | |||
500 | try | 520 | try |
501 | { | 521 | { |
502 | if (VehicleLoggingEnabled) DumpVehicles(); // DEBUG | 522 | if (PhysicsLogging.Enabled) |
503 | if (PhysicsLogging.Enabled) beforeTime = Util.EnvironmentTickCount(); | 523 | beforeTime = Util.EnvironmentTickCount(); |
504 | 524 | ||
505 | numSubSteps = BulletSimAPI.PhysicsStep2(World.ptr, timeStep, m_maxSubSteps, m_fixedTimeStep, | 525 | numSubSteps = PE.PhysicsStep(World, timeStep, m_maxSubSteps, m_fixedTimeStep, out updatedEntityCount, out collidersCount); |
506 | out updatedEntityCount, out updatedEntitiesPtr, out collidersCount, out collidersPtr); | ||
507 | 526 | ||
508 | if (PhysicsLogging.Enabled) simTime = Util.EnvironmentTickCountSubtract(beforeTime); | 527 | if (PhysicsLogging.Enabled) |
509 | DetailLog("{0},Simulate,call, frame={1}, nTaints={2}, simTime={3}, substeps={4}, updates={5}, colliders={6}", | 528 | { |
510 | DetailLogZero, m_simulationStep, numTaints, simTime, numSubSteps, updatedEntityCount, collidersCount); | 529 | simTime = Util.EnvironmentTickCountSubtract(beforeTime); |
511 | if (VehicleLoggingEnabled) DumpVehicles(); // DEBUG | 530 | DetailLog("{0},Simulate,call, frame={1}, nTaints={2}, simTime={3}, substeps={4}, updates={5}, colliders={6}, objWColl={7}", |
531 | DetailLogZero, m_simulationStep, numTaints, simTime, numSubSteps, | ||
532 | updatedEntityCount, collidersCount, ObjectsWithCollisions.Count); | ||
533 | } | ||
512 | } | 534 | } |
513 | catch (Exception e) | 535 | catch (Exception e) |
514 | { | 536 | { |
@@ -520,9 +542,10 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
520 | collidersCount = 0; | 542 | collidersCount = 0; |
521 | } | 543 | } |
522 | 544 | ||
523 | // Don't have to use the pointers passed back since we know it is the same pinned memory we passed in | 545 | if (PhysicsMetricDumpFrames != 0 && ((m_simulationStep % PhysicsMetricDumpFrames) == 0)) |
546 | PE.DumpPhysicsStatistics(World); | ||
524 | 547 | ||
525 | // Get a value for 'now' so all the collision and update routines don't have to get their own | 548 | // Get a value for 'now' so all the collision and update routines don't have to get their own. |
526 | SimulationNowTime = Util.EnvironmentTickCount(); | 549 | SimulationNowTime = Util.EnvironmentTickCount(); |
527 | 550 | ||
528 | // If there were collisions, process them by sending the event to the prim. | 551 | // If there were collisions, process them by sending the event to the prim. |
@@ -535,8 +558,9 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
535 | uint cB = m_collisionArray[ii].bID; | 558 | uint cB = m_collisionArray[ii].bID; |
536 | Vector3 point = m_collisionArray[ii].point; | 559 | Vector3 point = m_collisionArray[ii].point; |
537 | Vector3 normal = m_collisionArray[ii].normal; | 560 | Vector3 normal = m_collisionArray[ii].normal; |
538 | SendCollision(cA, cB, point, normal, 0.01f); | 561 | float penetration = m_collisionArray[ii].penetration; |
539 | SendCollision(cB, cA, point, -normal, 0.01f); | 562 | SendCollision(cA, cB, point, normal, penetration); |
563 | SendCollision(cB, cA, point, -normal, penetration); | ||
540 | } | 564 | } |
541 | } | 565 | } |
542 | 566 | ||
@@ -562,12 +586,16 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
562 | 586 | ||
563 | // Objects that are done colliding are removed from the ObjectsWithCollisions list. | 587 | // Objects that are done colliding are removed from the ObjectsWithCollisions list. |
564 | // Not done above because it is inside an iteration of ObjectWithCollisions. | 588 | // Not done above because it is inside an iteration of ObjectWithCollisions. |
589 | // This complex collision processing is required to create an empty collision | ||
590 | // event call after all real collisions have happened on an object. This enables | ||
591 | // the simulator to generate the 'collision end' event. | ||
565 | if (ObjectsWithNoMoreCollisions.Count > 0) | 592 | if (ObjectsWithNoMoreCollisions.Count > 0) |
566 | { | 593 | { |
567 | foreach (BSPhysObject po in ObjectsWithNoMoreCollisions) | 594 | foreach (BSPhysObject po in ObjectsWithNoMoreCollisions) |
568 | ObjectsWithCollisions.Remove(po); | 595 | ObjectsWithCollisions.Remove(po); |
569 | ObjectsWithNoMoreCollisions.Clear(); | 596 | ObjectsWithNoMoreCollisions.Clear(); |
570 | } | 597 | } |
598 | // Done with collisions. | ||
571 | 599 | ||
572 | // If any of the objects had updated properties, tell the object it has been changed by the physics engine | 600 | // If any of the objects had updated properties, tell the object it has been changed by the physics engine |
573 | if (updatedEntityCount > 0) | 601 | if (updatedEntityCount > 0) |
@@ -583,17 +611,17 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
583 | } | 611 | } |
584 | } | 612 | } |
585 | 613 | ||
586 | ProcessPostStepTaints(); | 614 | TriggerPostStepEvent(timeStep); |
587 | 615 | ||
588 | // This causes the unmanaged code to output ALL the values found in ALL the objects in the world. | 616 | // The following causes the unmanaged code to output ALL the values found in ALL the objects in the world. |
589 | // Only enable this in a limited test world with few objects. | 617 | // Only enable this in a limited test world with few objects. |
590 | // BulletSimAPI.DumpAllInfo2(World.ptr); // DEBUG DEBUG DEBUG | 618 | if (m_physicsPhysicalDumpEnabled) |
619 | PE.DumpAllInfo(World); | ||
591 | 620 | ||
592 | // The physics engine returns the number of milliseconds it simulated this call. | 621 | // The physics engine returns the number of milliseconds it simulated this call. |
593 | // These are summed and normalized to one second and divided by 1000 to give the reported physics FPS. | 622 | // These are summed and normalized to one second and divided by 1000 to give the reported physics FPS. |
594 | // We multiply by 55 to give a recognizable running rate (55 or less). | 623 | // Multiply by a fixed nominal frame rate to give a rate similar to the simulator (usually 55). |
595 | return numSubSteps * m_fixedTimeStep * 1000 * 55; | 624 | return (float)numSubSteps * m_fixedTimeStep * 1000f * NominalFrameRate; |
596 | // return timeStep * 1000 * 55; | ||
597 | } | 625 | } |
598 | 626 | ||
599 | // Something has collided | 627 | // Something has collided |
@@ -639,12 +667,7 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
639 | 667 | ||
640 | public override void SetWaterLevel(float baseheight) | 668 | public override void SetWaterLevel(float baseheight) |
641 | { | 669 | { |
642 | m_waterLevel = baseheight; | 670 | SimpleWaterLevel = baseheight; |
643 | } | ||
644 | // Someday.... | ||
645 | public float GetWaterLevelAtXYZ(Vector3 loc) | ||
646 | { | ||
647 | return m_waterLevel; | ||
648 | } | 671 | } |
649 | 672 | ||
650 | public override void DeleteTerrain() | 673 | public override void DeleteTerrain() |
@@ -675,12 +698,35 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
675 | 698 | ||
676 | public override Dictionary<uint, float> GetTopColliders() | 699 | public override Dictionary<uint, float> GetTopColliders() |
677 | { | 700 | { |
678 | return new Dictionary<uint, float>(); | 701 | Dictionary<uint, float> topColliders; |
702 | |||
703 | lock (PhysObjects) | ||
704 | { | ||
705 | foreach (KeyValuePair<uint, BSPhysObject> kvp in PhysObjects) | ||
706 | { | ||
707 | kvp.Value.ComputeCollisionScore(); | ||
708 | } | ||
709 | |||
710 | List<BSPhysObject> orderedPrims = new List<BSPhysObject>(PhysObjects.Values); | ||
711 | orderedPrims.OrderByDescending(p => p.CollisionScore); | ||
712 | topColliders = orderedPrims.Take(25).ToDictionary(p => p.LocalID, p => p.CollisionScore); | ||
713 | } | ||
714 | |||
715 | return topColliders; | ||
679 | } | 716 | } |
680 | 717 | ||
681 | public override bool IsThreaded { get { return false; } } | 718 | public override bool IsThreaded { get { return false; } } |
682 | 719 | ||
683 | #region Taints | 720 | #region Taints |
721 | // The simulation execution order is: | ||
722 | // Simulate() | ||
723 | // DoOneTimeTaints | ||
724 | // TriggerPreStepEvent | ||
725 | // DoOneTimeTaints | ||
726 | // Step() | ||
727 | // ProcessAndSendToSimulatorCollisions | ||
728 | // ProcessAndSendToSimulatorPropertyUpdates | ||
729 | // TriggerPostStepEvent | ||
684 | 730 | ||
685 | // Calls to the PhysicsActors can't directly call into the physics engine | 731 | // Calls to the PhysicsActors can't directly call into the physics engine |
686 | // because it might be busy. We delay changes to a known time. | 732 | // because it might be busy. We delay changes to a known time. |
@@ -707,58 +753,35 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
707 | TaintedObject(ident, callback); | 753 | TaintedObject(ident, callback); |
708 | } | 754 | } |
709 | 755 | ||
756 | private void TriggerPreStepEvent(float timeStep) | ||
757 | { | ||
758 | PreStepAction actions = BeforeStep; | ||
759 | if (actions != null) | ||
760 | actions(timeStep); | ||
761 | |||
762 | } | ||
763 | |||
764 | private void TriggerPostStepEvent(float timeStep) | ||
765 | { | ||
766 | PostStepAction actions = AfterStep; | ||
767 | if (actions != null) | ||
768 | actions(timeStep); | ||
769 | |||
770 | } | ||
771 | |||
710 | // When someone tries to change a property on a BSPrim or BSCharacter, the object queues | 772 | // When someone tries to change a property on a BSPrim or BSCharacter, the object queues |
711 | // a callback into itself to do the actual property change. That callback is called | 773 | // a callback into itself to do the actual property change. That callback is called |
712 | // here just before the physics engine is called to step the simulation. | 774 | // here just before the physics engine is called to step the simulation. |
713 | public void ProcessTaints() | 775 | public void ProcessTaints() |
714 | { | 776 | { |
715 | InTaintTime = true; // Only used for debugging so locking is not necessary. | ||
716 | ProcessRegularTaints(); | 777 | ProcessRegularTaints(); |
717 | ProcessPostTaintTaints(); | 778 | ProcessPostTaintTaints(); |
718 | InTaintTime = false; | ||
719 | } | 779 | } |
720 | 780 | ||
721 | private void ProcessRegularTaints() | 781 | private void ProcessRegularTaints() |
722 | { | 782 | { |
723 | if (_taintOperations.Count > 0) // save allocating new list if there is nothing to process | 783 | if (_taintOperations.Count > 0) // save allocating new list if there is nothing to process |
724 | { | 784 | { |
725 | /* | ||
726 | // Code to limit the number of taints processed per step. Meant to limit step time. | ||
727 | // Unsure if a good idea as code assumes that taints are done before the step. | ||
728 | int taintCount = m_taintsToProcessPerStep; | ||
729 | TaintCallbackEntry oneCallback = new TaintCallbackEntry(); | ||
730 | while (_taintOperations.Count > 0 && taintCount-- > 0) | ||
731 | { | ||
732 | bool gotOne = false; | ||
733 | lock (_taintLock) | ||
734 | { | ||
735 | if (_taintOperations.Count > 0) | ||
736 | { | ||
737 | oneCallback = _taintOperations[0]; | ||
738 | _taintOperations.RemoveAt(0); | ||
739 | gotOne = true; | ||
740 | } | ||
741 | } | ||
742 | if (gotOne) | ||
743 | { | ||
744 | try | ||
745 | { | ||
746 | DetailLog("{0},BSScene.ProcessTaints,doTaint,id={1}", DetailLogZero, oneCallback.ident); | ||
747 | oneCallback.callback(); | ||
748 | } | ||
749 | catch (Exception e) | ||
750 | { | ||
751 | DetailLog("{0},BSScene.ProcessTaints,doTaintException,id={1}", DetailLogZero, oneCallback.ident); // DEBUG DEBUG DEBUG | ||
752 | m_log.ErrorFormat("{0}: ProcessTaints: {1}: Exception: {2}", LogHeader, oneCallback.ident, e); | ||
753 | } | ||
754 | } | ||
755 | } | ||
756 | if (_taintOperations.Count > 0) | ||
757 | { | ||
758 | DetailLog("{0},BSScene.ProcessTaints,leftTaintsOnList,numNotProcessed={1}", DetailLogZero, _taintOperations.Count); | ||
759 | } | ||
760 | */ | ||
761 | |||
762 | // swizzle a new list into the list location so we can process what's there | 785 | // swizzle a new list into the list location so we can process what's there |
763 | List<TaintCallbackEntry> oldList; | 786 | List<TaintCallbackEntry> oldList; |
764 | lock (_taintLock) | 787 | lock (_taintLock) |
@@ -797,6 +820,7 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
797 | return; | 820 | return; |
798 | } | 821 | } |
799 | 822 | ||
823 | // Taints that happen after the normal taint processing but before the simulation step. | ||
800 | private void ProcessPostTaintTaints() | 824 | private void ProcessPostTaintTaints() |
801 | { | 825 | { |
802 | if (_postTaintOperations.Count > 0) | 826 | if (_postTaintOperations.Count > 0) |
@@ -824,45 +848,6 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
824 | } | 848 | } |
825 | } | 849 | } |
826 | 850 | ||
827 | public void PostStepTaintObject(String ident, TaintCallback callback) | ||
828 | { | ||
829 | if (!m_initialized) return; | ||
830 | |||
831 | lock (_taintLock) | ||
832 | { | ||
833 | _postStepOperations.Add(new TaintCallbackEntry(ident, callback)); | ||
834 | } | ||
835 | |||
836 | return; | ||
837 | } | ||
838 | |||
839 | private void ProcessPostStepTaints() | ||
840 | { | ||
841 | if (_postStepOperations.Count > 0) | ||
842 | { | ||
843 | List<TaintCallbackEntry> oldList; | ||
844 | lock (_taintLock) | ||
845 | { | ||
846 | oldList = _postStepOperations; | ||
847 | _postStepOperations = new List<TaintCallbackEntry>(); | ||
848 | } | ||
849 | |||
850 | foreach (TaintCallbackEntry tcbe in oldList) | ||
851 | { | ||
852 | try | ||
853 | { | ||
854 | DetailLog("{0},BSScene.ProcessPostStepTaints,doTaint,id={1}", DetailLogZero, tcbe.ident); // DEBUG DEBUG DEBUG | ||
855 | tcbe.callback(); | ||
856 | } | ||
857 | catch (Exception e) | ||
858 | { | ||
859 | m_log.ErrorFormat("{0}: ProcessPostStepTaints: {1}: Exception: {2}", LogHeader, tcbe.ident, e); | ||
860 | } | ||
861 | } | ||
862 | oldList.Clear(); | ||
863 | } | ||
864 | } | ||
865 | |||
866 | // Only used for debugging. Does not change state of anything so locking is not necessary. | 851 | // Only used for debugging. Does not change state of anything so locking is not necessary. |
867 | public bool AssertInTaintTime(string whereFrom) | 852 | public bool AssertInTaintTime(string whereFrom) |
868 | { | 853 | { |
@@ -870,517 +855,19 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
870 | { | 855 | { |
871 | DetailLog("{0},BSScene.AssertInTaintTime,NOT IN TAINT TIME,Region={1},Where={2}", DetailLogZero, RegionName, whereFrom); | 856 | DetailLog("{0},BSScene.AssertInTaintTime,NOT IN TAINT TIME,Region={1},Where={2}", DetailLogZero, RegionName, whereFrom); |
872 | m_log.ErrorFormat("{0} NOT IN TAINT TIME!! Region={1}, Where={2}", LogHeader, RegionName, whereFrom); | 857 | m_log.ErrorFormat("{0} NOT IN TAINT TIME!! Region={1}, Where={2}", LogHeader, RegionName, whereFrom); |
873 | Util.PrintCallStack(); // Prints the stack into the DEBUG log file. | 858 | // Util.PrintCallStack(DetailLog); |
874 | } | 859 | } |
875 | return InTaintTime; | 860 | return InTaintTime; |
876 | } | 861 | } |
877 | 862 | ||
878 | #endregion // Taints | 863 | #endregion // Taints |
879 | 864 | ||
880 | #region Vehicles | ||
881 | |||
882 | public void VehicleInSceneTypeChanged(BSPrim vehic, Vehicle newType) | ||
883 | { | ||
884 | RemoveVehiclePrim(vehic); | ||
885 | if (newType != Vehicle.TYPE_NONE) | ||
886 | { | ||
887 | // make it so the scene will call us each tick to do vehicle things | ||
888 | AddVehiclePrim(vehic); | ||
889 | } | ||
890 | } | ||
891 | |||
892 | // Make so the scene will call this prim for vehicle actions each tick. | ||
893 | // Safe to call if prim is already in the vehicle list. | ||
894 | public void AddVehiclePrim(BSPrim vehicle) | ||
895 | { | ||
896 | lock (m_vehicles) | ||
897 | { | ||
898 | if (!m_vehicles.Contains(vehicle)) | ||
899 | { | ||
900 | m_vehicles.Add(vehicle); | ||
901 | } | ||
902 | } | ||
903 | } | ||
904 | |||
905 | // Remove a prim from our list of vehicles. | ||
906 | // Safe to call if the prim is not in the vehicle list. | ||
907 | public void RemoveVehiclePrim(BSPrim vehicle) | ||
908 | { | ||
909 | lock (m_vehicles) | ||
910 | { | ||
911 | if (m_vehicles.Contains(vehicle)) | ||
912 | { | ||
913 | m_vehicles.Remove(vehicle); | ||
914 | } | ||
915 | } | ||
916 | } | ||
917 | |||
918 | // Some prims have extra vehicle actions | ||
919 | // Called at taint time! | ||
920 | private void ProcessVehicles(float timeStep) | ||
921 | { | ||
922 | foreach (BSPhysObject pobj in m_vehicles) | ||
923 | { | ||
924 | pobj.StepVehicle(timeStep); | ||
925 | } | ||
926 | } | ||
927 | #endregion Vehicles | ||
928 | |||
929 | #region INI and command line parameter processing | ||
930 | |||
931 | delegate void ParamUser(BSScene scene, IConfig conf, string paramName, float val); | ||
932 | delegate float ParamGet(BSScene scene); | ||
933 | delegate void ParamSet(BSScene scene, string paramName, uint localID, float val); | ||
934 | delegate void SetOnObject(BSScene scene, BSPhysObject obj, float val); | ||
935 | |||
936 | private struct ParameterDefn | ||
937 | { | ||
938 | public string name; // string name of the parameter | ||
939 | public string desc; // a short description of what the parameter means | ||
940 | public float defaultValue; // default value if not specified anywhere else | ||
941 | public ParamUser userParam; // get the value from the configuration file | ||
942 | public ParamGet getter; // return the current value stored for this parameter | ||
943 | public ParamSet setter; // set the current value for this parameter | ||
944 | public SetOnObject onObject; // set the value on an object in the physical domain | ||
945 | public ParameterDefn(string n, string d, float v, ParamUser u, ParamGet g, ParamSet s) | ||
946 | { | ||
947 | name = n; | ||
948 | desc = d; | ||
949 | defaultValue = v; | ||
950 | userParam = u; | ||
951 | getter = g; | ||
952 | setter = s; | ||
953 | onObject = null; | ||
954 | } | ||
955 | public ParameterDefn(string n, string d, float v, ParamUser u, ParamGet g, ParamSet s, SetOnObject o) | ||
956 | { | ||
957 | name = n; | ||
958 | desc = d; | ||
959 | defaultValue = v; | ||
960 | userParam = u; | ||
961 | getter = g; | ||
962 | setter = s; | ||
963 | onObject = o; | ||
964 | } | ||
965 | } | ||
966 | |||
967 | // List of all of the externally visible parameters. | ||
968 | // For each parameter, this table maps a text name to getter and setters. | ||
969 | // To add a new externally referencable/settable parameter, add the paramter storage | ||
970 | // location somewhere in the program and make an entry in this table with the | ||
971 | // getters and setters. | ||
972 | // It is easiest to find an existing definition and copy it. | ||
973 | // Parameter values are floats. Booleans are converted to a floating value. | ||
974 | // | ||
975 | // A ParameterDefn() takes the following parameters: | ||
976 | // -- the text name of the parameter. This is used for console input and ini file. | ||
977 | // -- a short text description of the parameter. This shows up in the console listing. | ||
978 | // -- a delegate for fetching the parameter from the ini file. | ||
979 | // Should handle fetching the right type from the ini file and converting it. | ||
980 | // -- a delegate for getting the value as a float | ||
981 | // -- a delegate for setting the value from a float | ||
982 | // | ||
983 | // The single letter parameters for the delegates are: | ||
984 | // s = BSScene | ||
985 | // o = BSPhysObject | ||
986 | // p = string parameter name | ||
987 | // l = localID of referenced object | ||
988 | // v = float value | ||
989 | // cf = parameter configuration class (for fetching values from ini file) | ||
990 | private ParameterDefn[] ParameterDefinitions = | ||
991 | { | ||
992 | new ParameterDefn("MeshSculptedPrim", "Whether to create meshes for sculpties", | ||
993 | ConfigurationParameters.numericTrue, | ||
994 | (s,cf,p,v) => { s.ShouldMeshSculptedPrim = cf.GetBoolean(p, s.BoolNumeric(v)); }, | ||
995 | (s) => { return s.NumericBool(s.ShouldMeshSculptedPrim); }, | ||
996 | (s,p,l,v) => { s.ShouldMeshSculptedPrim = s.BoolNumeric(v); } ), | ||
997 | new ParameterDefn("ForceSimplePrimMeshing", "If true, only use primitive meshes for objects", | ||
998 | ConfigurationParameters.numericFalse, | ||
999 | (s,cf,p,v) => { s.ShouldForceSimplePrimMeshing = cf.GetBoolean(p, s.BoolNumeric(v)); }, | ||
1000 | (s) => { return s.NumericBool(s.ShouldForceSimplePrimMeshing); }, | ||
1001 | (s,p,l,v) => { s.ShouldForceSimplePrimMeshing = s.BoolNumeric(v); } ), | ||
1002 | new ParameterDefn("UseHullsForPhysicalObjects", "If true, create hulls for physical objects", | ||
1003 | ConfigurationParameters.numericTrue, | ||
1004 | (s,cf,p,v) => { s.ShouldUseHullsForPhysicalObjects = cf.GetBoolean(p, s.BoolNumeric(v)); }, | ||
1005 | (s) => { return s.NumericBool(s.ShouldUseHullsForPhysicalObjects); }, | ||
1006 | (s,p,l,v) => { s.ShouldUseHullsForPhysicalObjects = s.BoolNumeric(v); } ), | ||
1007 | |||
1008 | new ParameterDefn("MeshLevelOfDetail", "Level of detail to render meshes (32, 16, 8 or 4. 32=most detailed)", | ||
1009 | 8f, | ||
1010 | (s,cf,p,v) => { s.MeshLOD = (float)cf.GetInt(p, (int)v); }, | ||
1011 | (s) => { return s.MeshLOD; }, | ||
1012 | (s,p,l,v) => { s.MeshLOD = v; } ), | ||
1013 | new ParameterDefn("MeshLevelOfDetailMegaPrim", "Level of detail to render meshes larger than threshold meters", | ||
1014 | 16f, | ||
1015 | (s,cf,p,v) => { s.MeshMegaPrimLOD = (float)cf.GetInt(p, (int)v); }, | ||
1016 | (s) => { return s.MeshMegaPrimLOD; }, | ||
1017 | (s,p,l,v) => { s.MeshMegaPrimLOD = v; } ), | ||
1018 | new ParameterDefn("MeshLevelOfDetailMegaPrimThreshold", "Size (in meters) of a mesh before using MeshMegaPrimLOD", | ||
1019 | 10f, | ||
1020 | (s,cf,p,v) => { s.MeshMegaPrimThreshold = (float)cf.GetInt(p, (int)v); }, | ||
1021 | (s) => { return s.MeshMegaPrimThreshold; }, | ||
1022 | (s,p,l,v) => { s.MeshMegaPrimThreshold = v; } ), | ||
1023 | new ParameterDefn("SculptLevelOfDetail", "Level of detail to render sculpties (32, 16, 8 or 4. 32=most detailed)", | ||
1024 | 32f, | ||
1025 | (s,cf,p,v) => { s.SculptLOD = (float)cf.GetInt(p, (int)v); }, | ||
1026 | (s) => { return s.SculptLOD; }, | ||
1027 | (s,p,l,v) => { s.SculptLOD = v; } ), | ||
1028 | |||
1029 | new ParameterDefn("MaxSubStep", "In simulation step, maximum number of substeps", | ||
1030 | 10f, | ||
1031 | (s,cf,p,v) => { s.m_maxSubSteps = cf.GetInt(p, (int)v); }, | ||
1032 | (s) => { return (float)s.m_maxSubSteps; }, | ||
1033 | (s,p,l,v) => { s.m_maxSubSteps = (int)v; } ), | ||
1034 | new ParameterDefn("FixedTimeStep", "In simulation step, seconds of one substep (1/60)", | ||
1035 | 1f / 60f, | ||
1036 | (s,cf,p,v) => { s.m_fixedTimeStep = cf.GetFloat(p, v); }, | ||
1037 | (s) => { return (float)s.m_fixedTimeStep; }, | ||
1038 | (s,p,l,v) => { s.m_fixedTimeStep = v; } ), | ||
1039 | new ParameterDefn("MaxCollisionsPerFrame", "Max collisions returned at end of each frame", | ||
1040 | 2048f, | ||
1041 | (s,cf,p,v) => { s.m_maxCollisionsPerFrame = cf.GetInt(p, (int)v); }, | ||
1042 | (s) => { return (float)s.m_maxCollisionsPerFrame; }, | ||
1043 | (s,p,l,v) => { s.m_maxCollisionsPerFrame = (int)v; } ), | ||
1044 | new ParameterDefn("MaxUpdatesPerFrame", "Max updates returned at end of each frame", | ||
1045 | 8000f, | ||
1046 | (s,cf,p,v) => { s.m_maxUpdatesPerFrame = cf.GetInt(p, (int)v); }, | ||
1047 | (s) => { return (float)s.m_maxUpdatesPerFrame; }, | ||
1048 | (s,p,l,v) => { s.m_maxUpdatesPerFrame = (int)v; } ), | ||
1049 | new ParameterDefn("MaxTaintsToProcessPerStep", "Number of update taints to process before each simulation step", | ||
1050 | 500f, | ||
1051 | (s,cf,p,v) => { s.m_taintsToProcessPerStep = cf.GetInt(p, (int)v); }, | ||
1052 | (s) => { return (float)s.m_taintsToProcessPerStep; }, | ||
1053 | (s,p,l,v) => { s.m_taintsToProcessPerStep = (int)v; } ), | ||
1054 | new ParameterDefn("MaxObjectMass", "Maximum object mass (10000.01)", | ||
1055 | 10000.01f, | ||
1056 | (s,cf,p,v) => { s.MaximumObjectMass = cf.GetFloat(p, v); }, | ||
1057 | (s) => { return (float)s.MaximumObjectMass; }, | ||
1058 | (s,p,l,v) => { s.MaximumObjectMass = v; } ), | ||
1059 | |||
1060 | new ParameterDefn("PID_D", "Derivitive factor for motion smoothing", | ||
1061 | 2200f, | ||
1062 | (s,cf,p,v) => { s.PID_D = cf.GetFloat(p, v); }, | ||
1063 | (s) => { return (float)s.PID_D; }, | ||
1064 | (s,p,l,v) => { s.PID_D = v; } ), | ||
1065 | new ParameterDefn("PID_P", "Parameteric factor for motion smoothing", | ||
1066 | 900f, | ||
1067 | (s,cf,p,v) => { s.PID_P = cf.GetFloat(p, v); }, | ||
1068 | (s) => { return (float)s.PID_P; }, | ||
1069 | (s,p,l,v) => { s.PID_P = v; } ), | ||
1070 | |||
1071 | new ParameterDefn("DefaultFriction", "Friction factor used on new objects", | ||
1072 | 0.5f, | ||
1073 | (s,cf,p,v) => { s.m_params[0].defaultFriction = cf.GetFloat(p, v); }, | ||
1074 | (s) => { return s.m_params[0].defaultFriction; }, | ||
1075 | (s,p,l,v) => { s.m_params[0].defaultFriction = v; } ), | ||
1076 | new ParameterDefn("DefaultDensity", "Density for new objects" , | ||
1077 | 10.000006836f, // Aluminum g/cm3 | ||
1078 | (s,cf,p,v) => { s.m_params[0].defaultDensity = cf.GetFloat(p, v); }, | ||
1079 | (s) => { return s.m_params[0].defaultDensity; }, | ||
1080 | (s,p,l,v) => { s.m_params[0].defaultDensity = v; } ), | ||
1081 | new ParameterDefn("DefaultRestitution", "Bouncyness of an object" , | ||
1082 | 0f, | ||
1083 | (s,cf,p,v) => { s.m_params[0].defaultRestitution = cf.GetFloat(p, v); }, | ||
1084 | (s) => { return s.m_params[0].defaultRestitution; }, | ||
1085 | (s,p,l,v) => { s.m_params[0].defaultRestitution = v; } ), | ||
1086 | new ParameterDefn("CollisionMargin", "Margin around objects before collisions are calculated (must be zero!)", | ||
1087 | 0f, | ||
1088 | (s,cf,p,v) => { s.m_params[0].collisionMargin = cf.GetFloat(p, v); }, | ||
1089 | (s) => { return s.m_params[0].collisionMargin; }, | ||
1090 | (s,p,l,v) => { s.m_params[0].collisionMargin = v; } ), | ||
1091 | new ParameterDefn("Gravity", "Vertical force of gravity (negative means down)", | ||
1092 | -9.80665f, | ||
1093 | (s,cf,p,v) => { s.m_params[0].gravity = cf.GetFloat(p, v); }, | ||
1094 | (s) => { return s.m_params[0].gravity; }, | ||
1095 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].gravity, p, PhysParameterEntry.APPLY_TO_NONE, v); }, | ||
1096 | (s,o,v) => { BulletSimAPI.SetGravity2(s.World.ptr, new Vector3(0f,0f,v)); } ), | ||
1097 | |||
1098 | |||
1099 | new ParameterDefn("LinearDamping", "Factor to damp linear movement per second (0.0 - 1.0)", | ||
1100 | 0f, | ||
1101 | (s,cf,p,v) => { s.m_params[0].linearDamping = cf.GetFloat(p, v); }, | ||
1102 | (s) => { return s.m_params[0].linearDamping; }, | ||
1103 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].linearDamping, p, l, v); }, | ||
1104 | (s,o,v) => { BulletSimAPI.SetDamping2(o.PhysBody.ptr, v, s.m_params[0].angularDamping); } ), | ||
1105 | new ParameterDefn("AngularDamping", "Factor to damp angular movement per second (0.0 - 1.0)", | ||
1106 | 0f, | ||
1107 | (s,cf,p,v) => { s.m_params[0].angularDamping = cf.GetFloat(p, v); }, | ||
1108 | (s) => { return s.m_params[0].angularDamping; }, | ||
1109 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].angularDamping, p, l, v); }, | ||
1110 | (s,o,v) => { BulletSimAPI.SetDamping2(o.PhysBody.ptr, s.m_params[0].linearDamping, v); } ), | ||
1111 | new ParameterDefn("DeactivationTime", "Seconds before considering an object potentially static", | ||
1112 | 0.2f, | ||
1113 | (s,cf,p,v) => { s.m_params[0].deactivationTime = cf.GetFloat(p, v); }, | ||
1114 | (s) => { return s.m_params[0].deactivationTime; }, | ||
1115 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].deactivationTime, p, l, v); }, | ||
1116 | (s,o,v) => { BulletSimAPI.SetDeactivationTime2(o.PhysBody.ptr, v); } ), | ||
1117 | new ParameterDefn("LinearSleepingThreshold", "Seconds to measure linear movement before considering static", | ||
1118 | 0.8f, | ||
1119 | (s,cf,p,v) => { s.m_params[0].linearSleepingThreshold = cf.GetFloat(p, v); }, | ||
1120 | (s) => { return s.m_params[0].linearSleepingThreshold; }, | ||
1121 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].linearSleepingThreshold, p, l, v); }, | ||
1122 | (s,o,v) => { BulletSimAPI.SetSleepingThresholds2(o.PhysBody.ptr, v, v); } ), | ||
1123 | new ParameterDefn("AngularSleepingThreshold", "Seconds to measure angular movement before considering static", | ||
1124 | 1.0f, | ||
1125 | (s,cf,p,v) => { s.m_params[0].angularSleepingThreshold = cf.GetFloat(p, v); }, | ||
1126 | (s) => { return s.m_params[0].angularSleepingThreshold; }, | ||
1127 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].angularSleepingThreshold, p, l, v); }, | ||
1128 | (s,o,v) => { BulletSimAPI.SetSleepingThresholds2(o.PhysBody.ptr, v, v); } ), | ||
1129 | new ParameterDefn("CcdMotionThreshold", "Continuious collision detection threshold (0 means no CCD)" , | ||
1130 | 0f, // set to zero to disable | ||
1131 | (s,cf,p,v) => { s.m_params[0].ccdMotionThreshold = cf.GetFloat(p, v); }, | ||
1132 | (s) => { return s.m_params[0].ccdMotionThreshold; }, | ||
1133 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].ccdMotionThreshold, p, l, v); }, | ||
1134 | (s,o,v) => { BulletSimAPI.SetCcdMotionThreshold2(o.PhysBody.ptr, v); } ), | ||
1135 | new ParameterDefn("CcdSweptSphereRadius", "Continuious collision detection test radius" , | ||
1136 | 0f, | ||
1137 | (s,cf,p,v) => { s.m_params[0].ccdSweptSphereRadius = cf.GetFloat(p, v); }, | ||
1138 | (s) => { return s.m_params[0].ccdSweptSphereRadius; }, | ||
1139 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].ccdSweptSphereRadius, p, l, v); }, | ||
1140 | (s,o,v) => { BulletSimAPI.SetCcdSweptSphereRadius2(o.PhysBody.ptr, v); } ), | ||
1141 | new ParameterDefn("ContactProcessingThreshold", "Distance between contacts before doing collision check" , | ||
1142 | 0.1f, | ||
1143 | (s,cf,p,v) => { s.m_params[0].contactProcessingThreshold = cf.GetFloat(p, v); }, | ||
1144 | (s) => { return s.m_params[0].contactProcessingThreshold; }, | ||
1145 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].contactProcessingThreshold, p, l, v); }, | ||
1146 | (s,o,v) => { BulletSimAPI.SetContactProcessingThreshold2(o.PhysBody.ptr, v); } ), | ||
1147 | |||
1148 | new ParameterDefn("TerrainImplementation", "Type of shape to use for terrain (0=heightmap, 1=mesh)", | ||
1149 | (float)BSTerrainPhys.TerrainImplementation.Mesh, | ||
1150 | (s,cf,p,v) => { s.m_params[0].terrainImplementation = cf.GetFloat(p,v); }, | ||
1151 | (s) => { return s.m_params[0].terrainImplementation; }, | ||
1152 | (s,p,l,v) => { s.m_params[0].terrainImplementation = v; } ), | ||
1153 | new ParameterDefn("TerrainFriction", "Factor to reduce movement against terrain surface" , | ||
1154 | 0.5f, | ||
1155 | (s,cf,p,v) => { s.m_params[0].terrainFriction = cf.GetFloat(p, v); }, | ||
1156 | (s) => { return s.m_params[0].terrainFriction; }, | ||
1157 | (s,p,l,v) => { s.m_params[0].terrainFriction = v; /* TODO: set on real terrain */} ), | ||
1158 | new ParameterDefn("TerrainHitFraction", "Distance to measure hit collisions" , | ||
1159 | 0.8f, | ||
1160 | (s,cf,p,v) => { s.m_params[0].terrainHitFraction = cf.GetFloat(p, v); }, | ||
1161 | (s) => { return s.m_params[0].terrainHitFraction; }, | ||
1162 | (s,p,l,v) => { s.m_params[0].terrainHitFraction = v; /* TODO: set on real terrain */ } ), | ||
1163 | new ParameterDefn("TerrainRestitution", "Bouncyness" , | ||
1164 | 0f, | ||
1165 | (s,cf,p,v) => { s.m_params[0].terrainRestitution = cf.GetFloat(p, v); }, | ||
1166 | (s) => { return s.m_params[0].terrainRestitution; }, | ||
1167 | (s,p,l,v) => { s.m_params[0].terrainRestitution = v; /* TODO: set on real terrain */ } ), | ||
1168 | new ParameterDefn("AvatarFriction", "Factor to reduce movement against an avatar. Changed on avatar recreation.", | ||
1169 | 0.2f, | ||
1170 | (s,cf,p,v) => { s.m_params[0].avatarFriction = cf.GetFloat(p, v); }, | ||
1171 | (s) => { return s.m_params[0].avatarFriction; }, | ||
1172 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].avatarFriction, p, l, v); } ), | ||
1173 | new ParameterDefn("AvatarStandingFriction", "Avatar friction when standing. Changed on avatar recreation.", | ||
1174 | 10f, | ||
1175 | (s,cf,p,v) => { s.m_params[0].avatarStandingFriction = cf.GetFloat(p, v); }, | ||
1176 | (s) => { return s.m_params[0].avatarStandingFriction; }, | ||
1177 | (s,p,l,v) => { s.m_params[0].avatarStandingFriction = v; } ), | ||
1178 | new ParameterDefn("AvatarDensity", "Density of an avatar. Changed on avatar recreation.", | ||
1179 | 60f, | ||
1180 | (s,cf,p,v) => { s.m_params[0].avatarDensity = cf.GetFloat(p, v); }, | ||
1181 | (s) => { return s.m_params[0].avatarDensity; }, | ||
1182 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].avatarDensity, p, l, v); } ), | ||
1183 | new ParameterDefn("AvatarRestitution", "Bouncyness. Changed on avatar recreation.", | ||
1184 | 0f, | ||
1185 | (s,cf,p,v) => { s.m_params[0].avatarRestitution = cf.GetFloat(p, v); }, | ||
1186 | (s) => { return s.m_params[0].avatarRestitution; }, | ||
1187 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].avatarRestitution, p, l, v); } ), | ||
1188 | new ParameterDefn("AvatarCapsuleWidth", "The distance between the sides of the avatar capsule", | ||
1189 | 0.6f, | ||
1190 | (s,cf,p,v) => { s.m_params[0].avatarCapsuleWidth = cf.GetFloat(p, v); }, | ||
1191 | (s) => { return s.m_params[0].avatarCapsuleWidth; }, | ||
1192 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].avatarCapsuleWidth, p, l, v); } ), | ||
1193 | new ParameterDefn("AvatarCapsuleDepth", "The distance between the front and back of the avatar capsule", | ||
1194 | 0.45f, | ||
1195 | (s,cf,p,v) => { s.m_params[0].avatarCapsuleDepth = cf.GetFloat(p, v); }, | ||
1196 | (s) => { return s.m_params[0].avatarCapsuleDepth; }, | ||
1197 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].avatarCapsuleDepth, p, l, v); } ), | ||
1198 | new ParameterDefn("AvatarCapsuleHeight", "Default height of space around avatar", | ||
1199 | 1.5f, | ||
1200 | (s,cf,p,v) => { s.m_params[0].avatarCapsuleHeight = cf.GetFloat(p, v); }, | ||
1201 | (s) => { return s.m_params[0].avatarCapsuleHeight; }, | ||
1202 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].avatarCapsuleHeight, p, l, v); } ), | ||
1203 | new ParameterDefn("AvatarContactProcessingThreshold", "Distance from capsule to check for collisions", | ||
1204 | 0.1f, | ||
1205 | (s,cf,p,v) => { s.m_params[0].avatarContactProcessingThreshold = cf.GetFloat(p, v); }, | ||
1206 | (s) => { return s.m_params[0].avatarContactProcessingThreshold; }, | ||
1207 | (s,p,l,v) => { s.UpdateParameterObject(ref s.m_params[0].avatarContactProcessingThreshold, p, l, v); } ), | ||
1208 | |||
1209 | |||
1210 | new ParameterDefn("MaxPersistantManifoldPoolSize", "Number of manifolds pooled (0 means default of 4096)", | ||
1211 | 0f, | ||
1212 | (s,cf,p,v) => { s.m_params[0].maxPersistantManifoldPoolSize = cf.GetFloat(p, v); }, | ||
1213 | (s) => { return s.m_params[0].maxPersistantManifoldPoolSize; }, | ||
1214 | (s,p,l,v) => { s.m_params[0].maxPersistantManifoldPoolSize = v; } ), | ||
1215 | new ParameterDefn("MaxCollisionAlgorithmPoolSize", "Number of collisions pooled (0 means default of 4096)", | ||
1216 | 0f, | ||
1217 | (s,cf,p,v) => { s.m_params[0].maxCollisionAlgorithmPoolSize = cf.GetFloat(p, v); }, | ||
1218 | (s) => { return s.m_params[0].maxCollisionAlgorithmPoolSize; }, | ||
1219 | (s,p,l,v) => { s.m_params[0].maxCollisionAlgorithmPoolSize = v; } ), | ||
1220 | new ParameterDefn("ShouldDisableContactPoolDynamicAllocation", "Enable to allow large changes in object count", | ||
1221 | ConfigurationParameters.numericFalse, | ||
1222 | (s,cf,p,v) => { s.m_params[0].shouldDisableContactPoolDynamicAllocation = s.NumericBool(cf.GetBoolean(p, s.BoolNumeric(v))); }, | ||
1223 | (s) => { return s.m_params[0].shouldDisableContactPoolDynamicAllocation; }, | ||
1224 | (s,p,l,v) => { s.m_params[0].shouldDisableContactPoolDynamicAllocation = v; } ), | ||
1225 | new ParameterDefn("ShouldForceUpdateAllAabbs", "Enable to recomputer AABBs every simulator step", | ||
1226 | ConfigurationParameters.numericFalse, | ||
1227 | (s,cf,p,v) => { s.m_params[0].shouldForceUpdateAllAabbs = s.NumericBool(cf.GetBoolean(p, s.BoolNumeric(v))); }, | ||
1228 | (s) => { return s.m_params[0].shouldForceUpdateAllAabbs; }, | ||
1229 | (s,p,l,v) => { s.m_params[0].shouldForceUpdateAllAabbs = v; } ), | ||
1230 | new ParameterDefn("ShouldRandomizeSolverOrder", "Enable for slightly better stacking interaction", | ||
1231 | ConfigurationParameters.numericTrue, | ||
1232 | (s,cf,p,v) => { s.m_params[0].shouldRandomizeSolverOrder = s.NumericBool(cf.GetBoolean(p, s.BoolNumeric(v))); }, | ||
1233 | (s) => { return s.m_params[0].shouldRandomizeSolverOrder; }, | ||
1234 | (s,p,l,v) => { s.m_params[0].shouldRandomizeSolverOrder = v; } ), | ||
1235 | new ParameterDefn("ShouldSplitSimulationIslands", "Enable splitting active object scanning islands", | ||
1236 | ConfigurationParameters.numericTrue, | ||
1237 | (s,cf,p,v) => { s.m_params[0].shouldSplitSimulationIslands = s.NumericBool(cf.GetBoolean(p, s.BoolNumeric(v))); }, | ||
1238 | (s) => { return s.m_params[0].shouldSplitSimulationIslands; }, | ||
1239 | (s,p,l,v) => { s.m_params[0].shouldSplitSimulationIslands = v; } ), | ||
1240 | new ParameterDefn("ShouldEnableFrictionCaching", "Enable friction computation caching", | ||
1241 | ConfigurationParameters.numericFalse, | ||
1242 | (s,cf,p,v) => { s.m_params[0].shouldEnableFrictionCaching = s.NumericBool(cf.GetBoolean(p, s.BoolNumeric(v))); }, | ||
1243 | (s) => { return s.m_params[0].shouldEnableFrictionCaching; }, | ||
1244 | (s,p,l,v) => { s.m_params[0].shouldEnableFrictionCaching = v; } ), | ||
1245 | new ParameterDefn("NumberOfSolverIterations", "Number of internal iterations (0 means default)", | ||
1246 | 0f, // zero says use Bullet default | ||
1247 | (s,cf,p,v) => { s.m_params[0].numberOfSolverIterations = cf.GetFloat(p, v); }, | ||
1248 | (s) => { return s.m_params[0].numberOfSolverIterations; }, | ||
1249 | (s,p,l,v) => { s.m_params[0].numberOfSolverIterations = v; } ), | ||
1250 | |||
1251 | new ParameterDefn("LinksetImplementation", "Type of linkset implementation (0=Constraint, 1=Compound, 2=Manual)", | ||
1252 | (float)BSLinkset.LinksetImplementation.Compound, | ||
1253 | (s,cf,p,v) => { s.m_params[0].linksetImplementation = cf.GetFloat(p,v); }, | ||
1254 | (s) => { return s.m_params[0].linksetImplementation; }, | ||
1255 | (s,p,l,v) => { s.m_params[0].linksetImplementation = v; } ), | ||
1256 | new ParameterDefn("LinkConstraintUseFrameOffset", "For linksets built with constraints, enable frame offsetFor linksets built with constraints, enable frame offset.", | ||
1257 | ConfigurationParameters.numericFalse, | ||
1258 | (s,cf,p,v) => { s.m_params[0].linkConstraintUseFrameOffset = s.NumericBool(cf.GetBoolean(p, s.BoolNumeric(v))); }, | ||
1259 | (s) => { return s.m_params[0].linkConstraintUseFrameOffset; }, | ||
1260 | (s,p,l,v) => { s.m_params[0].linkConstraintUseFrameOffset = v; } ), | ||
1261 | new ParameterDefn("LinkConstraintEnableTransMotor", "Whether to enable translational motor on linkset constraints", | ||
1262 | ConfigurationParameters.numericTrue, | ||
1263 | (s,cf,p,v) => { s.m_params[0].linkConstraintEnableTransMotor = s.NumericBool(cf.GetBoolean(p, s.BoolNumeric(v))); }, | ||
1264 | (s) => { return s.m_params[0].linkConstraintEnableTransMotor; }, | ||
1265 | (s,p,l,v) => { s.m_params[0].linkConstraintEnableTransMotor = v; } ), | ||
1266 | new ParameterDefn("LinkConstraintTransMotorMaxVel", "Maximum velocity to be applied by translational motor in linkset constraints", | ||
1267 | 5.0f, | ||
1268 | (s,cf,p,v) => { s.m_params[0].linkConstraintTransMotorMaxVel = cf.GetFloat(p, v); }, | ||
1269 | (s) => { return s.m_params[0].linkConstraintTransMotorMaxVel; }, | ||
1270 | (s,p,l,v) => { s.m_params[0].linkConstraintTransMotorMaxVel = v; } ), | ||
1271 | new ParameterDefn("LinkConstraintTransMotorMaxForce", "Maximum force to be applied by translational motor in linkset constraints", | ||
1272 | 0.1f, | ||
1273 | (s,cf,p,v) => { s.m_params[0].linkConstraintTransMotorMaxForce = cf.GetFloat(p, v); }, | ||
1274 | (s) => { return s.m_params[0].linkConstraintTransMotorMaxForce; }, | ||
1275 | (s,p,l,v) => { s.m_params[0].linkConstraintTransMotorMaxForce = v; } ), | ||
1276 | new ParameterDefn("LinkConstraintCFM", "Amount constraint can be violated. 0=no violation, 1=infinite. Default=0.1", | ||
1277 | 0.1f, | ||
1278 | (s,cf,p,v) => { s.m_params[0].linkConstraintCFM = cf.GetFloat(p, v); }, | ||
1279 | (s) => { return s.m_params[0].linkConstraintCFM; }, | ||
1280 | (s,p,l,v) => { s.m_params[0].linkConstraintCFM = v; } ), | ||
1281 | new ParameterDefn("LinkConstraintERP", "Amount constraint is corrected each tick. 0=none, 1=all. Default = 0.2", | ||
1282 | 0.1f, | ||
1283 | (s,cf,p,v) => { s.m_params[0].linkConstraintERP = cf.GetFloat(p, v); }, | ||
1284 | (s) => { return s.m_params[0].linkConstraintERP; }, | ||
1285 | (s,p,l,v) => { s.m_params[0].linkConstraintERP = v; } ), | ||
1286 | new ParameterDefn("LinkConstraintSolverIterations", "Number of solver iterations when computing constraint. (0 = Bullet default)", | ||
1287 | 40, | ||
1288 | (s,cf,p,v) => { s.m_params[0].linkConstraintSolverIterations = cf.GetFloat(p, v); }, | ||
1289 | (s) => { return s.m_params[0].linkConstraintSolverIterations; }, | ||
1290 | (s,p,l,v) => { s.m_params[0].linkConstraintSolverIterations = v; } ), | ||
1291 | |||
1292 | new ParameterDefn("LogPhysicsStatisticsFrames", "Frames between outputting detailed phys stats. (0 is off)", | ||
1293 | 0f, | ||
1294 | (s,cf,p,v) => { s.m_params[0].physicsLoggingFrames = cf.GetInt(p, (int)v); }, | ||
1295 | (s) => { return (float)s.m_params[0].physicsLoggingFrames; }, | ||
1296 | (s,p,l,v) => { s.m_params[0].physicsLoggingFrames = (int)v; } ), | ||
1297 | }; | ||
1298 | |||
1299 | // Convert a boolean to our numeric true and false values | ||
1300 | public float NumericBool(bool b) | ||
1301 | { | ||
1302 | return (b ? ConfigurationParameters.numericTrue : ConfigurationParameters.numericFalse); | ||
1303 | } | ||
1304 | |||
1305 | // Convert numeric true and false values to a boolean | ||
1306 | public bool BoolNumeric(float b) | ||
1307 | { | ||
1308 | return (b == ConfigurationParameters.numericTrue ? true : false); | ||
1309 | } | ||
1310 | |||
1311 | // Search through the parameter definitions and return the matching | ||
1312 | // ParameterDefn structure. | ||
1313 | // Case does not matter as names are compared after converting to lower case. | ||
1314 | // Returns 'false' if the parameter is not found. | ||
1315 | private bool TryGetParameter(string paramName, out ParameterDefn defn) | ||
1316 | { | ||
1317 | bool ret = false; | ||
1318 | ParameterDefn foundDefn = new ParameterDefn(); | ||
1319 | string pName = paramName.ToLower(); | ||
1320 | |||
1321 | foreach (ParameterDefn parm in ParameterDefinitions) | ||
1322 | { | ||
1323 | if (pName == parm.name.ToLower()) | ||
1324 | { | ||
1325 | foundDefn = parm; | ||
1326 | ret = true; | ||
1327 | break; | ||
1328 | } | ||
1329 | } | ||
1330 | defn = foundDefn; | ||
1331 | return ret; | ||
1332 | } | ||
1333 | |||
1334 | // Pass through the settable parameters and set the default values | ||
1335 | private void SetParameterDefaultValues() | ||
1336 | { | ||
1337 | foreach (ParameterDefn parm in ParameterDefinitions) | ||
1338 | { | ||
1339 | parm.setter(this, parm.name, PhysParameterEntry.APPLY_TO_NONE, parm.defaultValue); | ||
1340 | } | ||
1341 | } | ||
1342 | |||
1343 | // Get user set values out of the ini file. | ||
1344 | private void SetParameterConfigurationValues(IConfig cfg) | ||
1345 | { | ||
1346 | foreach (ParameterDefn parm in ParameterDefinitions) | ||
1347 | { | ||
1348 | parm.userParam(this, cfg, parm.name, parm.defaultValue); | ||
1349 | } | ||
1350 | } | ||
1351 | |||
1352 | private PhysParameterEntry[] SettableParameters = new PhysParameterEntry[1]; | ||
1353 | |||
1354 | // This creates an array in the correct format for returning the list of | ||
1355 | // parameters. This is used by the 'list' option of the 'physics' command. | ||
1356 | private void BuildParameterTable() | ||
1357 | { | ||
1358 | if (SettableParameters.Length < ParameterDefinitions.Length) | ||
1359 | { | ||
1360 | List<PhysParameterEntry> entries = new List<PhysParameterEntry>(); | ||
1361 | for (int ii = 0; ii < ParameterDefinitions.Length; ii++) | ||
1362 | { | ||
1363 | ParameterDefn pd = ParameterDefinitions[ii]; | ||
1364 | entries.Add(new PhysParameterEntry(pd.name, pd.desc)); | ||
1365 | } | ||
1366 | |||
1367 | // make the list in alphabetical order for estetic reasons | ||
1368 | entries.Sort(delegate(PhysParameterEntry ppe1, PhysParameterEntry ppe2) | ||
1369 | { | ||
1370 | return ppe1.name.CompareTo(ppe2.name); | ||
1371 | }); | ||
1372 | |||
1373 | SettableParameters = entries.ToArray(); | ||
1374 | } | ||
1375 | } | ||
1376 | |||
1377 | |||
1378 | #region IPhysicsParameters | 865 | #region IPhysicsParameters |
1379 | // Get the list of parameters this physics engine supports | 866 | // Get the list of parameters this physics engine supports |
1380 | public PhysParameterEntry[] GetParameterList() | 867 | public PhysParameterEntry[] GetParameterList() |
1381 | { | 868 | { |
1382 | BuildParameterTable(); | 869 | BSParam.BuildParameterTable(); |
1383 | return SettableParameters; | 870 | return BSParam.SettableParameters; |
1384 | } | 871 | } |
1385 | 872 | ||
1386 | // Set parameter on a specific or all instances. | 873 | // Set parameter on a specific or all instances. |
@@ -1389,63 +876,65 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
1389 | // will use the next time since it's pinned and shared memory. | 876 | // will use the next time since it's pinned and shared memory. |
1390 | // Some of the values require calling into the physics engine to get the new | 877 | // Some of the values require calling into the physics engine to get the new |
1391 | // value activated ('terrainFriction' for instance). | 878 | // value activated ('terrainFriction' for instance). |
1392 | public bool SetPhysicsParameter(string parm, float val, uint localID) | 879 | public bool SetPhysicsParameter(string parm, string val, uint localID) |
1393 | { | 880 | { |
1394 | bool ret = false; | 881 | bool ret = false; |
1395 | ParameterDefn theParam; | 882 | |
1396 | if (TryGetParameter(parm, out theParam)) | 883 | BSParam.ParameterDefnBase theParam; |
884 | if (BSParam.TryGetParameter(parm, out theParam)) | ||
1397 | { | 885 | { |
1398 | theParam.setter(this, parm, localID, val); | 886 | // Set the value in the C# code |
887 | theParam.SetValue(this, val); | ||
888 | |||
889 | // Optionally set the parameter in the unmanaged code | ||
890 | if (theParam.HasSetOnObject) | ||
891 | { | ||
892 | // update all the localIDs specified | ||
893 | // If the local ID is APPLY_TO_NONE, just change the default value | ||
894 | // If the localID is APPLY_TO_ALL change the default value and apply the new value to all the lIDs | ||
895 | // If the localID is a specific object, apply the parameter change to only that object | ||
896 | List<uint> objectIDs = new List<uint>(); | ||
897 | switch (localID) | ||
898 | { | ||
899 | case PhysParameterEntry.APPLY_TO_NONE: | ||
900 | // This will cause a call into the physical world if some operation is specified (SetOnObject). | ||
901 | objectIDs.Add(TERRAIN_ID); | ||
902 | TaintedUpdateParameter(parm, objectIDs, val); | ||
903 | break; | ||
904 | case PhysParameterEntry.APPLY_TO_ALL: | ||
905 | lock (PhysObjects) objectIDs = new List<uint>(PhysObjects.Keys); | ||
906 | TaintedUpdateParameter(parm, objectIDs, val); | ||
907 | break; | ||
908 | default: | ||
909 | // setting only one localID | ||
910 | objectIDs.Add(localID); | ||
911 | TaintedUpdateParameter(parm, objectIDs, val); | ||
912 | break; | ||
913 | } | ||
914 | } | ||
915 | |||
1399 | ret = true; | 916 | ret = true; |
1400 | } | 917 | } |
1401 | return ret; | 918 | return ret; |
1402 | } | 919 | } |
1403 | 920 | ||
1404 | // update all the localIDs specified | ||
1405 | // If the local ID is APPLY_TO_NONE, just change the default value | ||
1406 | // If the localID is APPLY_TO_ALL change the default value and apply the new value to all the lIDs | ||
1407 | // If the localID is a specific object, apply the parameter change to only that object | ||
1408 | private void UpdateParameterObject(ref float defaultLoc, string parm, uint localID, float val) | ||
1409 | { | ||
1410 | List<uint> objectIDs = new List<uint>(); | ||
1411 | switch (localID) | ||
1412 | { | ||
1413 | case PhysParameterEntry.APPLY_TO_NONE: | ||
1414 | defaultLoc = val; // setting only the default value | ||
1415 | // This will cause a call into the physical world if some operation is specified (SetOnObject). | ||
1416 | objectIDs.Add(TERRAIN_ID); | ||
1417 | TaintedUpdateParameter(parm, objectIDs, val); | ||
1418 | break; | ||
1419 | case PhysParameterEntry.APPLY_TO_ALL: | ||
1420 | defaultLoc = val; // setting ALL also sets the default value | ||
1421 | lock (PhysObjects) objectIDs = new List<uint>(PhysObjects.Keys); | ||
1422 | TaintedUpdateParameter(parm, objectIDs, val); | ||
1423 | break; | ||
1424 | default: | ||
1425 | // setting only one localID | ||
1426 | objectIDs.Add(localID); | ||
1427 | TaintedUpdateParameter(parm, objectIDs, val); | ||
1428 | break; | ||
1429 | } | ||
1430 | } | ||
1431 | |||
1432 | // schedule the actual updating of the paramter to when the phys engine is not busy | 921 | // schedule the actual updating of the paramter to when the phys engine is not busy |
1433 | private void TaintedUpdateParameter(string parm, List<uint> lIDs, float val) | 922 | private void TaintedUpdateParameter(string parm, List<uint> lIDs, string val) |
1434 | { | 923 | { |
1435 | float xval = val; | 924 | string xval = val; |
1436 | List<uint> xlIDs = lIDs; | 925 | List<uint> xlIDs = lIDs; |
1437 | string xparm = parm; | 926 | string xparm = parm; |
1438 | TaintedObject("BSScene.UpdateParameterSet", delegate() { | 927 | TaintedObject("BSScene.UpdateParameterSet", delegate() { |
1439 | ParameterDefn thisParam; | 928 | BSParam.ParameterDefnBase thisParam; |
1440 | if (TryGetParameter(xparm, out thisParam)) | 929 | if (BSParam.TryGetParameter(xparm, out thisParam)) |
1441 | { | 930 | { |
1442 | if (thisParam.onObject != null) | 931 | if (thisParam.HasSetOnObject) |
1443 | { | 932 | { |
1444 | foreach (uint lID in xlIDs) | 933 | foreach (uint lID in xlIDs) |
1445 | { | 934 | { |
1446 | BSPhysObject theObject = null; | 935 | BSPhysObject theObject = null; |
1447 | PhysObjects.TryGetValue(lID, out theObject); | 936 | if (PhysObjects.TryGetValue(lID, out theObject)) |
1448 | thisParam.onObject(this, theObject, xval); | 937 | thisParam.SetOnObject(this, theObject); |
1449 | } | 938 | } |
1450 | } | 939 | } |
1451 | } | 940 | } |
@@ -1454,14 +943,14 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
1454 | 943 | ||
1455 | // Get parameter. | 944 | // Get parameter. |
1456 | // Return 'false' if not able to get the parameter. | 945 | // Return 'false' if not able to get the parameter. |
1457 | public bool GetPhysicsParameter(string parm, out float value) | 946 | public bool GetPhysicsParameter(string parm, out string value) |
1458 | { | 947 | { |
1459 | float val = 0f; | 948 | string val = String.Empty; |
1460 | bool ret = false; | 949 | bool ret = false; |
1461 | ParameterDefn theParam; | 950 | BSParam.ParameterDefnBase theParam; |
1462 | if (TryGetParameter(parm, out theParam)) | 951 | if (BSParam.TryGetParameter(parm, out theParam)) |
1463 | { | 952 | { |
1464 | val = theParam.getter(this); | 953 | val = theParam.GetValue(this); |
1465 | ret = true; | 954 | ret = true; |
1466 | } | 955 | } |
1467 | value = val; | 956 | value = val; |
@@ -1470,24 +959,12 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters | |||
1470 | 959 | ||
1471 | #endregion IPhysicsParameters | 960 | #endregion IPhysicsParameters |
1472 | 961 | ||
1473 | #endregion Runtime settable parameters | ||
1474 | |||
1475 | // Debugging routine for dumping detailed physical information for vehicle prims | ||
1476 | private void DumpVehicles() | ||
1477 | { | ||
1478 | foreach (BSPrim prim in m_vehicles) | ||
1479 | { | ||
1480 | BulletSimAPI.DumpRigidBody2(World.ptr, prim.PhysBody.ptr); | ||
1481 | BulletSimAPI.DumpCollisionShape2(World.ptr, prim.PhysShape.ptr); | ||
1482 | } | ||
1483 | } | ||
1484 | |||
1485 | // Invoke the detailed logger and output something if it's enabled. | 962 | // Invoke the detailed logger and output something if it's enabled. |
1486 | public void DetailLog(string msg, params Object[] args) | 963 | public void DetailLog(string msg, params Object[] args) |
1487 | { | 964 | { |
1488 | PhysicsLogging.Write(msg, args); | 965 | PhysicsLogging.Write(msg, args); |
1489 | // Add the Flush() if debugging crashes. Gets all the messages written out. | 966 | // Add the Flush() if debugging crashes. Gets all the messages written out. |
1490 | // PhysicsLogging.Flush(); | 967 | if (m_physicsLoggingDoFlush) PhysicsLogging.Flush(); |
1491 | } | 968 | } |
1492 | // Used to fill in the LocalID when there isn't one. It's the correct number of characters. | 969 | // Used to fill in the LocalID when there isn't one. It's the correct number of characters. |
1493 | public const string DetailLogZero = "0000000000"; | 970 | public const string DetailLogZero = "0000000000"; |