aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs')
-rwxr-xr-xOpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs431
1 files changed, 265 insertions, 166 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
index 3a92f93..087b9bb 100755
--- a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
+++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
@@ -32,78 +32,35 @@ using OMV = OpenMetaverse;
32 32
33namespace OpenSim.Region.Physics.BulletSPlugin 33namespace OpenSim.Region.Physics.BulletSPlugin
34{ 34{
35public abstract class BSLinkset 35public class BSLinkset
36{ 36{
37 // private static string LogHeader = "[BULLETSIM LINKSET]"; 37 private static string LogHeader = "[BULLETSIM LINKSET]";
38 38
39 public enum LinksetImplementation 39 private BSPrim m_linksetRoot;
40 { 40 public BSPrim LinksetRoot { get { return m_linksetRoot; } }
41 Constraint = 0, // linkset tied together with constraints
42 Compound = 1, // linkset tied together as a compound object
43 Manual = 2 // linkset tied together manually (code moves all the pieces)
44 }
45 // Create the correct type of linkset for this child
46 public static BSLinkset Factory(BSScene physScene, BSPhysObject parent)
47 {
48 BSLinkset ret = null;
49
50 switch ((int)physScene.Params.linksetImplementation)
51 {
52 case (int)LinksetImplementation.Compound:
53 ret = new BSLinksetCompound(physScene, parent);
54 break;
55 case (int)LinksetImplementation.Manual:
56 // ret = new BSLinksetManual(physScene, parent);
57 break;
58 default:
59 ret = new BSLinksetConstraints(physScene, parent);
60 break;
61 }
62 return ret;
63 }
64
65 public BSPhysObject LinksetRoot { get; protected set; }
66 41
67 public BSScene PhysicsScene { get; private set; } 42 private BSScene m_physicsScene;
43 public BSScene PhysicsScene { get { return m_physicsScene; } }
68 44
69 static int m_nextLinksetID = 1; 45 // The children under the root in this linkset
70 public int LinksetID { get; private set; } 46 private List<BSPrim> m_children;
71
72 // The children under the root in this linkset.
73 protected HashSet<BSPhysObject> m_children;
74 47
75 // We lock the diddling of linkset classes to prevent any badness. 48 // We lock the diddling of linkset classes to prevent any badness.
76 // This locks the modification of the instances of this class. Changes 49 // This locks the modification of the instances of this class. Changes
77 // to the physical representation is done via the tainting mechenism. 50 // to the physical representation is done via the tainting mechenism.
78 protected object m_linksetActivityLock = new Object(); 51 private object m_linksetActivityLock = new Object();
79
80 // Some linksets have a preferred physical shape.
81 // Returns SHAPE_UNKNOWN if there is no preference. Causes the correct shape to be selected.
82 public virtual ShapeData.PhysicsShapeType PreferredPhysicalShape(BSPhysObject requestor)
83 {
84 return ShapeData.PhysicsShapeType.SHAPE_UNKNOWN;
85 }
86 52
87 // Linksets move around the children so the linkset might need to compute the child position
88 public virtual OMV.Vector3 Position(BSPhysObject member)
89 { return member.RawPosition; }
90 public virtual OMV.Quaternion Orientation(BSPhysObject member)
91 { return member.RawOrientation; }
92 // TODO: does this need to be done for Velocity and RotationalVelocityy?
93
94 // We keep the prim's mass in the linkset structure since it could be dependent on other prims 53 // We keep the prim's mass in the linkset structure since it could be dependent on other prims
95 protected float m_mass; 54 private float m_mass;
96 public float LinksetMass 55 public float LinksetMass
97 { 56 {
98 get 57 get
99 { 58 {
100 m_mass = ComputeLinksetMass(); 59 m_mass = ComputeLinksetMass();
101 return m_mass; 60 return m_mass;
102 } 61 }
103 } 62 }
104 63
105 public virtual bool LinksetIsColliding { get { return false; } }
106
107 public OMV.Vector3 CenterOfMass 64 public OMV.Vector3 CenterOfMass
108 { 65 {
109 get { return ComputeLinksetCenterOfMass(); } 66 get { return ComputeLinksetCenterOfMass(); }
@@ -114,30 +71,23 @@ public abstract class BSLinkset
114 get { return ComputeLinksetGeometricCenter(); } 71 get { return ComputeLinksetGeometricCenter(); }
115 } 72 }
116 73
117 protected void Initialize(BSScene scene, BSPhysObject parent) 74 public BSLinkset(BSScene scene, BSPrim parent)
118 { 75 {
119 // A simple linkset of one (no children) 76 // A simple linkset of one (no children)
120 LinksetID = m_nextLinksetID++; 77 m_physicsScene = scene;
121 // We create LOTS of linksets. 78 m_linksetRoot = parent;
122 if (m_nextLinksetID <= 0) 79 m_children = new List<BSPrim>();
123 m_nextLinksetID = 1; 80 m_mass = parent.MassRaw;
124 PhysicsScene = scene;
125 LinksetRoot = parent;
126 m_children = new HashSet<BSPhysObject>();
127 m_mass = parent.RawMass;
128 } 81 }
129 82
130 // Link to a linkset where the child knows the parent. 83 // Link to a linkset where the child knows the parent.
131 // Parent changing should not happen so do some sanity checking. 84 // Parent changing should not happen so do some sanity checking.
132 // We return the parent's linkset so the child can track its membership. 85 // We return the parent's linkset so the child can track its membership.
133 // Called at runtime. 86 public BSLinkset AddMeToLinkset(BSPrim child)
134 public BSLinkset AddMeToLinkset(BSPhysObject child)
135 { 87 {
136 lock (m_linksetActivityLock) 88 lock (m_linksetActivityLock)
137 { 89 {
138 // Don't add the root to its own linkset 90 AddChildToLinkset(child);
139 if (!IsRoot(child))
140 AddChildToLinkset(child);
141 } 91 }
142 return this; 92 return this;
143 } 93 }
@@ -145,27 +95,36 @@ public abstract class BSLinkset
145 // Remove a child from a linkset. 95 // Remove a child from a linkset.
146 // Returns a new linkset for the child which is a linkset of one (just the 96 // Returns a new linkset for the child which is a linkset of one (just the
147 // orphened child). 97 // orphened child).
148 // Called at runtime. 98 public BSLinkset RemoveMeFromLinkset(BSPrim child)
149 public BSLinkset RemoveMeFromLinkset(BSPhysObject child)
150 { 99 {
151 lock (m_linksetActivityLock) 100 lock (m_linksetActivityLock)
152 { 101 {
153 if (IsRoot(child)) 102 if (IsRoot(child))
154 { 103 {
155 // Cannot remove the root from a linkset. 104 // if root of linkset, take the linkset apart
156 return this; 105 while (m_children.Count > 0)
106 {
107 // Note that we don't do a foreach because the remove routine
108 // takes it out of the list.
109 RemoveChildFromOtherLinkset(m_children[0]);
110 }
111 m_children.Clear(); // just to make sure
112 }
113 else
114 {
115 // Just removing a child from an existing linkset
116 RemoveChildFromLinkset(child);
157 } 117 }
158 RemoveChildFromLinkset(child);
159 } 118 }
160 119
161 // The child is down to a linkset of just itself 120 // The child is down to a linkset of just itself
162 return BSLinkset.Factory(PhysicsScene, child); 121 return new BSLinkset(PhysicsScene, child);
163 } 122 }
164 123
165 // Return 'true' if the passed object is the root object of this linkset 124 // Return 'true' if the passed object is the root object of this linkset
166 public bool IsRoot(BSPhysObject requestor) 125 public bool IsRoot(BSPrim requestor)
167 { 126 {
168 return (requestor.LocalID == LinksetRoot.LocalID); 127 return (requestor.LocalID == m_linksetRoot.LocalID);
169 } 128 }
170 129
171 public int NumberOfChildren { get { return m_children.Count; } } 130 public int NumberOfChildren { get { return m_children.Count; } }
@@ -174,14 +133,12 @@ public abstract class BSLinkset
174 public bool HasAnyChildren { get { return (m_children.Count > 0); } } 133 public bool HasAnyChildren { get { return (m_children.Count > 0); } }
175 134
176 // Return 'true' if this child is in this linkset 135 // Return 'true' if this child is in this linkset
177 public bool HasChild(BSPhysObject child) 136 public bool HasChild(BSPrim child)
178 { 137 {
179 bool ret = false; 138 bool ret = false;
180 lock (m_linksetActivityLock) 139 lock (m_linksetActivityLock)
181 { 140 {
182 ret = m_children.Contains(child); 141 foreach (BSPrim bp in m_children)
183 /* Safer version but the above should work
184 foreach (BSPhysObject bp in m_children)
185 { 142 {
186 if (child.LocalID == bp.LocalID) 143 if (child.LocalID == bp.LocalID)
187 { 144 {
@@ -189,132 +146,274 @@ public abstract class BSLinkset
189 break; 146 break;
190 } 147 }
191 } 148 }
192 */
193 } 149 }
194 return ret; 150 return ret;
195 } 151 }
196 152
197 // Perform an action on each member of the linkset including root prim. 153 private float ComputeLinksetMass()
198 // Depends on the action on whether this should be done at taint time.
199 public delegate bool ForEachMemberAction(BSPhysObject obj);
200 public virtual bool ForEachMember(ForEachMemberAction action)
201 { 154 {
202 bool ret = false; 155 float mass = m_linksetRoot.MassRaw;
156 foreach (BSPrim bp in m_children)
157 {
158 mass += bp.MassRaw;
159 }
160 return mass;
161 }
162
163 private OMV.Vector3 ComputeLinksetCenterOfMass()
164 {
165 OMV.Vector3 com = m_linksetRoot.Position * m_linksetRoot.MassRaw;
166 float totalMass = m_linksetRoot.MassRaw;
167
203 lock (m_linksetActivityLock) 168 lock (m_linksetActivityLock)
204 { 169 {
205 action(LinksetRoot); 170 foreach (BSPrim bp in m_children)
206 foreach (BSPhysObject po in m_children)
207 { 171 {
208 if (action(po)) 172 com += bp.Position * bp.MassRaw;
209 break; 173 totalMass += bp.MassRaw;
210 } 174 }
175 if (totalMass != 0f)
176 com /= totalMass;
211 } 177 }
212 return ret; 178
179 return com;
213 } 180 }
214 181
215 // I am the root of a linkset and a new child is being added 182 private OMV.Vector3 ComputeLinksetGeometricCenter()
216 // Called while LinkActivity is locked. 183 {
217 protected abstract void AddChildToLinkset(BSPhysObject child); 184 OMV.Vector3 com = m_linksetRoot.Position;
218 185
219 // I am the root of a linkset and one of my children is being removed. 186 lock (m_linksetActivityLock)
220 // Safe to call even if the child is not really in my linkset. 187 {
221 protected abstract void RemoveChildFromLinkset(BSPhysObject child); 188 foreach (BSPrim bp in m_children)
189 {
190 com += bp.Position * bp.MassRaw;
191 }
192 com /= (m_children.Count + 1);
193 }
194
195 return com;
196 }
222 197
223 // When physical properties are changed the linkset needs to recalculate 198 // When physical properties are changed the linkset needs to recalculate
224 // its internal properties. 199 // its internal properties.
225 // May be called at runtime or taint-time. 200 public void Refresh(BSPrim requestor)
226 public abstract void Refresh(BSPhysObject requestor);
227
228 // The object is going dynamic (physical). Do any setup necessary
229 // for a dynamic linkset.
230 // Only the state of the passed object can be modified. The rest of the linkset
231 // has not yet been fully constructed.
232 // Return 'true' if any properties updated on the passed object.
233 // Called at taint-time!
234 public abstract bool MakeDynamic(BSPhysObject child);
235
236 // The object is going static (non-physical). Do any setup necessary
237 // for a static linkset.
238 // Return 'true' if any properties updated on the passed object.
239 // Called at taint-time!
240 public abstract bool MakeStatic(BSPhysObject child);
241
242 // Called when a parameter update comes from the physics engine for any object
243 // of the linkset is received.
244 // Called at taint-time!!
245 public abstract void UpdateProperties(BSPhysObject physObject);
246
247 // Routine used when rebuilding the body of the root of the linkset
248 // Destroy all the constraints have have been made to root.
249 // This is called when the root body is changing.
250 // Returns 'true' of something was actually removed and would need restoring
251 // Called at taint-time!!
252 public abstract bool RemoveBodyDependencies(BSPrim child);
253
254 // Companion to RemoveBodyDependencies(). If RemoveBodyDependencies() returns 'true',
255 // this routine will restore the removed constraints.
256 // Called at taint-time!!
257 public abstract void RestoreBodyDependencies(BSPrim child);
258
259 // ================================================================
260 protected virtual float ComputeLinksetMass()
261 { 201 {
262 float mass = LinksetRoot.RawMass; 202 // If there are no children, there aren't any constraints to recompute
263 if (HasAnyChildren) 203 if (!HasAnyChildren)
204 return;
205
206 // Only the root does the recomputation
207 if (IsRoot(requestor))
208 {
209 PhysicsScene.TaintedObject("BSLinkSet.Refresh", delegate()
210 {
211 RecomputeLinksetConstraintVariables();
212 });
213 }
214 }
215
216 // Call each of the constraints that make up this linkset and recompute the
217 // various transforms and variables. Used when objects are added or removed
218 // from a linkset to make sure the constraints know about the new mass and
219 // geometry.
220 // Must only be called at taint time!!
221 private bool RecomputeLinksetConstraintVariables()
222 {
223 float linksetMass = LinksetMass;
224 lock (m_linksetActivityLock)
264 { 225 {
265 lock (m_linksetActivityLock) 226 foreach (BSPrim child in m_children)
266 { 227 {
267 foreach (BSPhysObject bp in m_children) 228 BSConstraint constrain;
229 if (m_physicsScene.Constraints.TryGetConstraint(LinksetRoot.Body, child.Body, out constrain))
230 {
231 // DetailLog("{0},BSLinkset.RecomputeLinksetConstraintVariables,taint,child={1},mass={2},A={3},B={4}",
232 // LinksetRoot.LocalID, child.LocalID, linksetMass, constrain.Body1.ID, constrain.Body2.ID);
233 constrain.RecomputeConstraintVariables(linksetMass);
234 }
235 else
268 { 236 {
269 mass += bp.RawMass; 237 // Non-fatal error that can happen when children are being added to the linkset but
238 // their constraints have not been created yet.
239 // Caused by the fact that m_children is built at run time but building constraints
240 // happens at taint time.
241 // m_physicsScene.Logger.ErrorFormat("[BULLETSIM LINKSET] RecomputeLinksetConstraintVariables: constraint not found for root={0}, child={1}",
242 // m_linksetRoot.Body.ID, child.Body.ID);
270 } 243 }
271 } 244 }
272 } 245 }
273 return mass; 246 return false;
274 } 247 }
275 248
276 protected virtual OMV.Vector3 ComputeLinksetCenterOfMass() 249 // I am the root of a linkset and a new child is being added
250 // Called while LinkActivity is locked.
251 private void AddChildToLinkset(BSPrim child)
277 { 252 {
278 OMV.Vector3 com; 253 if (!HasChild(child))
279 lock (m_linksetActivityLock)
280 { 254 {
281 com = LinksetRoot.Position * LinksetRoot.RawMass; 255 m_children.Add(child);
282 float totalMass = LinksetRoot.RawMass;
283 256
284 foreach (BSPhysObject bp in m_children) 257 BSPrim rootx = LinksetRoot; // capture the root as of now
258 BSPrim childx = child;
259 m_physicsScene.TaintedObject("AddChildToLinkset", delegate()
285 { 260 {
286 com += bp.Position * bp.RawMass; 261 // DebugLog("{0}: AddChildToLinkset: adding child {1} to {2}", LogHeader, child.LocalID, m_linksetRoot.LocalID);
287 totalMass += bp.RawMass; 262 // DetailLog("{0},AddChildToLinkset,taint,child={1}", m_linksetRoot.LocalID, child.LocalID);
288 } 263 PhysicallyLinkAChildToRoot(rootx, childx); // build the physical binding between me and the child
289 if (totalMass != 0f) 264 });
290 com /= totalMass;
291 } 265 }
266 return;
267 }
292 268
293 return com; 269 // Forcefully removing a child from a linkset.
270 // This is not being called by the child so we have to make sure the child doesn't think
271 // it's still connected to the linkset.
272 // Normal OpenSimulator operation will never do this because other SceneObjectPart information
273 // has to be updated also (like pointer to prim's parent).
274 private void RemoveChildFromOtherLinkset(BSPrim pchild)
275 {
276 pchild.Linkset = new BSLinkset(m_physicsScene, pchild);
277 RemoveChildFromLinkset(pchild);
294 } 278 }
295 279
296 protected virtual OMV.Vector3 ComputeLinksetGeometricCenter() 280 // I am the root of a linkset and one of my children is being removed.
281 // Safe to call even if the child is not really in my linkset.
282 private void RemoveChildFromLinkset(BSPrim child)
297 { 283 {
298 OMV.Vector3 com; 284 if (m_children.Remove(child))
299 lock (m_linksetActivityLock)
300 { 285 {
301 com = LinksetRoot.Position; 286 BSPrim rootx = LinksetRoot; // capture the root as of now
302 287 BSPrim childx = child;
303 foreach (BSPhysObject bp in m_children) 288 m_physicsScene.TaintedObject("RemoveChildFromLinkset", delegate()
304 { 289 {
305 com += bp.Position * bp.RawMass; 290 // DebugLog("{0}: RemoveChildFromLinkset: Removing constraint to {1}", LogHeader, child.LocalID);
306 } 291 // DetailLog("{0},RemoveChildFromLinkset,taint,child={1}", m_linksetRoot.LocalID, child.LocalID);
307 com /= (m_children.Count + 1); 292
293 PhysicallyUnlinkAChildFromRoot(rootx, childx);
294 });
295
296 RecomputeLinksetConstraintVariables();
297 }
298 else
299 {
300 // This will happen if we remove the root of the linkset first. Non-fatal occurance.
301 // PhysicsScene.Logger.ErrorFormat("{0}: Asked to remove child from linkset that was not in linkset", LogHeader);
308 } 302 }
303 return;
304 }
309 305
310 return com; 306 // Create a constraint between me (root of linkset) and the passed prim (the child).
307 // Called at taint time!
308 private void PhysicallyLinkAChildToRoot(BSPrim rootPrim, BSPrim childPrim)
309 {
310 // Zero motion for children so they don't interpolate
311 childPrim.ZeroMotion();
312
313 // Relative position normalized to the root prim
314 // Essentually a vector pointing from center of rootPrim to center of childPrim
315 OMV.Vector3 childRelativePosition = childPrim.Position - rootPrim.Position;
316
317 // real world coordinate of midpoint between the two objects
318 OMV.Vector3 midPoint = rootPrim.Position + (childRelativePosition / 2);
319
320 // create a constraint that allows no freedom of movement between the two objects
321 // http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=4818
322 // DebugLog("{0}: CreateLinkset: Adding a constraint between root prim {1} and child prim {2}", LogHeader, LocalID, childPrim.LocalID);
323 DetailLog("{0},PhysicallyLinkAChildToRoot,taint,root={1},child={2},rLoc={3},cLoc={4},midLoc={5}",
324 rootPrim.LocalID, rootPrim.LocalID, childPrim.LocalID, rootPrim.Position, childPrim.Position, midPoint);
325 BS6DofConstraint constrain = new BS6DofConstraint(
326 m_physicsScene.World, rootPrim.Body, childPrim.Body,
327 midPoint,
328 true,
329 true
330 );
331 /* NOTE: attempt to build constraint with full frame computation, etc.
332 * Using the midpoint is easier since it lets the Bullet code use the transforms
333 * of the objects.
334 * Code left here as an example.
335 // ==================================================================================
336 // relative position normalized to the root prim
337 OMV.Quaternion invThisOrientation = OMV.Quaternion.Inverse(rootPrim.Orientation);
338 OMV.Vector3 childRelativePosition = (childPrim.Position - rootPrim.Position) * invThisOrientation;
339
340 // relative rotation of the child to the parent
341 OMV.Quaternion childRelativeRotation = invThisOrientation * childPrim.Orientation;
342 OMV.Quaternion inverseChildRelativeRotation = OMV.Quaternion.Inverse(childRelativeRotation);
343
344 // create a constraint that allows no freedom of movement between the two objects
345 // http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=4818
346 // DebugLog("{0}: CreateLinkset: Adding a constraint between root prim {1} and child prim {2}", LogHeader, LocalID, childPrim.LocalID);
347 DetailLog("{0},PhysicallyLinkAChildToRoot,taint,root={1},child={2}", rootPrim.LocalID, rootPrim.LocalID, childPrim.LocalID);
348 BS6DofConstraint constrain = new BS6DofConstraint(
349 PhysicsScene.World, rootPrim.Body, childPrim.Body,
350 OMV.Vector3.Zero,
351 OMV.Quaternion.Inverse(rootPrim.Orientation),
352 OMV.Vector3.Zero,
353 OMV.Quaternion.Inverse(childPrim.Orientation),
354 // A point half way between the parent and child
355 // childRelativePosition/2,
356 // childRelativeRotation,
357 // childRelativePosition/2,
358 // inverseChildRelativeRotation,
359 true,
360 true
361 );
362 // ==================================================================================
363 */
364
365 m_physicsScene.Constraints.AddConstraint(constrain);
366
367 // zero linear and angular limits makes the objects unable to move in relation to each other
368 constrain.SetLinearLimits(OMV.Vector3.Zero, OMV.Vector3.Zero);
369 constrain.SetAngularLimits(OMV.Vector3.Zero, OMV.Vector3.Zero);
370
371 // tweek the constraint to increase stability
372 constrain.UseFrameOffset(PhysicsScene.BoolNumeric(PhysicsScene.Params.linkConstraintUseFrameOffset));
373 constrain.TranslationalLimitMotor(PhysicsScene.BoolNumeric(PhysicsScene.Params.linkConstraintEnableTransMotor),
374 PhysicsScene.Params.linkConstraintTransMotorMaxVel,
375 PhysicsScene.Params.linkConstraintTransMotorMaxForce);
376 constrain.SetCFMAndERP(PhysicsScene.Params.linkConstraintCFM, PhysicsScene.Params.linkConstraintERP);
377
378 RecomputeLinksetConstraintVariables();
379 }
380
381 // Remove linkage between myself and a particular child
382 // Called at taint time!
383 private void PhysicallyUnlinkAChildFromRoot(BSPrim rootPrim, BSPrim childPrim)
384 {
385 // DebugLog("{0}: PhysicallyUnlinkAChildFromRoot: RemoveConstraint between root prim {1} and child prim {2}",
386 // LogHeader, rootPrim.LocalID, childPrim.LocalID);
387 DetailLog("{0},PhysicallyUnlinkAChildFromRoot,taint,root={1},child={2}", rootPrim.LocalID, rootPrim.LocalID, childPrim.LocalID);
388
389 // Find the constraint for this link and get rid of it from the overall collection and from my list
390 m_physicsScene.Constraints.RemoveAndDestroyConstraint(rootPrim.Body, childPrim.Body);
391
392 // Make the child refresh its location
393 BulletSimAPI.PushUpdate2(childPrim.Body.Ptr);
394 }
395
396 // Remove linkage between myself and any possible children I might have
397 // Called at taint time!
398 private void PhysicallyUnlinkAllChildrenFromRoot(BSPrim rootPrim)
399 {
400 // DebugLog("{0}: PhysicallyUnlinkAllChildren:", LogHeader);
401 DetailLog("{0},PhysicallyUnlinkAllChildren,taint", rootPrim.LocalID);
402
403 m_physicsScene.Constraints.RemoveAndDestroyConstraint(rootPrim.Body);
404 }
405
406 // Invoke the detailed logger and output something if it's enabled.
407 private void DebugLog(string msg, params Object[] args)
408 {
409 if (m_physicsScene.ShouldDebugLog)
410 m_physicsScene.Logger.DebugFormat(msg, args);
311 } 411 }
312 412
313 // Invoke the detailed logger and output something if it's enabled. 413 // Invoke the detailed logger and output something if it's enabled.
314 protected void DetailLog(string msg, params Object[] args) 414 private void DetailLog(string msg, params Object[] args)
315 { 415 {
316 if (PhysicsScene.PhysicsLogging.Enabled) 416 m_physicsScene.PhysicsLogging.Write(msg, args);
317 PhysicsScene.DetailLog(msg, args);
318 } 417 }
319 418
320} 419}