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, 166 insertions, 265 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
index 087b9bb..3a92f93 100755
--- a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
+++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
@@ -32,35 +32,78 @@ using OMV = OpenMetaverse;
32 32
33namespace OpenSim.Region.Physics.BulletSPlugin 33namespace OpenSim.Region.Physics.BulletSPlugin
34{ 34{
35public class BSLinkset 35public abstract class BSLinkset
36{ 36{
37 private static string LogHeader = "[BULLETSIM LINKSET]"; 37 // private static string LogHeader = "[BULLETSIM LINKSET]";
38 38
39 private BSPrim m_linksetRoot; 39 public enum LinksetImplementation
40 public BSPrim LinksetRoot { get { return m_linksetRoot; } } 40 {
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; }
41 66
42 private BSScene m_physicsScene; 67 public BSScene PhysicsScene { get; private set; }
43 public BSScene PhysicsScene { get { return m_physicsScene; } }
44 68
45 // The children under the root in this linkset 69 static int m_nextLinksetID = 1;
46 private List<BSPrim> m_children; 70 public int LinksetID { get; private set; }
71
72 // The children under the root in this linkset.
73 protected HashSet<BSPhysObject> m_children;
47 74
48 // We lock the diddling of linkset classes to prevent any badness. 75 // We lock the diddling of linkset classes to prevent any badness.
49 // This locks the modification of the instances of this class. Changes 76 // This locks the modification of the instances of this class. Changes
50 // to the physical representation is done via the tainting mechenism. 77 // to the physical representation is done via the tainting mechenism.
51 private object m_linksetActivityLock = new Object(); 78 protected 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 }
52 86
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
53 // We keep the prim's mass in the linkset structure since it could be dependent on other prims 94 // We keep the prim's mass in the linkset structure since it could be dependent on other prims
54 private float m_mass; 95 protected float m_mass;
55 public float LinksetMass 96 public float LinksetMass
56 { 97 {
57 get 98 get
58 { 99 {
59 m_mass = ComputeLinksetMass(); 100 m_mass = ComputeLinksetMass();
60 return m_mass; 101 return m_mass;
61 } 102 }
62 } 103 }
63 104
105 public virtual bool LinksetIsColliding { get { return false; } }
106
64 public OMV.Vector3 CenterOfMass 107 public OMV.Vector3 CenterOfMass
65 { 108 {
66 get { return ComputeLinksetCenterOfMass(); } 109 get { return ComputeLinksetCenterOfMass(); }
@@ -71,23 +114,30 @@ public class BSLinkset
71 get { return ComputeLinksetGeometricCenter(); } 114 get { return ComputeLinksetGeometricCenter(); }
72 } 115 }
73 116
74 public BSLinkset(BSScene scene, BSPrim parent) 117 protected void Initialize(BSScene scene, BSPhysObject parent)
75 { 118 {
76 // A simple linkset of one (no children) 119 // A simple linkset of one (no children)
77 m_physicsScene = scene; 120 LinksetID = m_nextLinksetID++;
78 m_linksetRoot = parent; 121 // We create LOTS of linksets.
79 m_children = new List<BSPrim>(); 122 if (m_nextLinksetID <= 0)
80 m_mass = parent.MassRaw; 123 m_nextLinksetID = 1;
124 PhysicsScene = scene;
125 LinksetRoot = parent;
126 m_children = new HashSet<BSPhysObject>();
127 m_mass = parent.RawMass;
81 } 128 }
82 129
83 // Link to a linkset where the child knows the parent. 130 // Link to a linkset where the child knows the parent.
84 // Parent changing should not happen so do some sanity checking. 131 // Parent changing should not happen so do some sanity checking.
85 // We return the parent's linkset so the child can track its membership. 132 // We return the parent's linkset so the child can track its membership.
86 public BSLinkset AddMeToLinkset(BSPrim child) 133 // Called at runtime.
134 public BSLinkset AddMeToLinkset(BSPhysObject child)
87 { 135 {
88 lock (m_linksetActivityLock) 136 lock (m_linksetActivityLock)
89 { 137 {
90 AddChildToLinkset(child); 138 // Don't add the root to its own linkset
139 if (!IsRoot(child))
140 AddChildToLinkset(child);
91 } 141 }
92 return this; 142 return this;
93 } 143 }
@@ -95,36 +145,27 @@ public class BSLinkset
95 // Remove a child from a linkset. 145 // Remove a child from a linkset.
96 // Returns a new linkset for the child which is a linkset of one (just the 146 // Returns a new linkset for the child which is a linkset of one (just the
97 // orphened child). 147 // orphened child).
98 public BSLinkset RemoveMeFromLinkset(BSPrim child) 148 // Called at runtime.
149 public BSLinkset RemoveMeFromLinkset(BSPhysObject child)
99 { 150 {
100 lock (m_linksetActivityLock) 151 lock (m_linksetActivityLock)
101 { 152 {
102 if (IsRoot(child)) 153 if (IsRoot(child))
103 { 154 {
104 // if root of linkset, take the linkset apart 155 // Cannot remove the root from a linkset.
105 while (m_children.Count > 0) 156 return this;
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);
117 } 157 }
158 RemoveChildFromLinkset(child);
118 } 159 }
119 160
120 // The child is down to a linkset of just itself 161 // The child is down to a linkset of just itself
121 return new BSLinkset(PhysicsScene, child); 162 return BSLinkset.Factory(PhysicsScene, child);
122 } 163 }
123 164
124 // Return 'true' if the passed object is the root object of this linkset 165 // Return 'true' if the passed object is the root object of this linkset
125 public bool IsRoot(BSPrim requestor) 166 public bool IsRoot(BSPhysObject requestor)
126 { 167 {
127 return (requestor.LocalID == m_linksetRoot.LocalID); 168 return (requestor.LocalID == LinksetRoot.LocalID);
128 } 169 }
129 170
130 public int NumberOfChildren { get { return m_children.Count; } } 171 public int NumberOfChildren { get { return m_children.Count; } }
@@ -133,12 +174,14 @@ public class BSLinkset
133 public bool HasAnyChildren { get { return (m_children.Count > 0); } } 174 public bool HasAnyChildren { get { return (m_children.Count > 0); } }
134 175
135 // Return 'true' if this child is in this linkset 176 // Return 'true' if this child is in this linkset
136 public bool HasChild(BSPrim child) 177 public bool HasChild(BSPhysObject child)
137 { 178 {
138 bool ret = false; 179 bool ret = false;
139 lock (m_linksetActivityLock) 180 lock (m_linksetActivityLock)
140 { 181 {
141 foreach (BSPrim bp in m_children) 182 ret = m_children.Contains(child);
183 /* Safer version but the above should work
184 foreach (BSPhysObject bp in m_children)
142 { 185 {
143 if (child.LocalID == bp.LocalID) 186 if (child.LocalID == bp.LocalID)
144 { 187 {
@@ -146,274 +189,132 @@ public class BSLinkset
146 break; 189 break;
147 } 190 }
148 } 191 }
192 */
149 } 193 }
150 return ret; 194 return ret;
151 } 195 }
152 196
153 private float ComputeLinksetMass() 197 // Perform an action on each member of the linkset including root prim.
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)
154 { 201 {
155 float mass = m_linksetRoot.MassRaw; 202 bool ret = false;
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
168 lock (m_linksetActivityLock) 203 lock (m_linksetActivityLock)
169 { 204 {
170 foreach (BSPrim bp in m_children) 205 action(LinksetRoot);
206 foreach (BSPhysObject po in m_children)
171 { 207 {
172 com += bp.Position * bp.MassRaw; 208 if (action(po))
173 totalMass += bp.MassRaw; 209 break;
174 } 210 }
175 if (totalMass != 0f)
176 com /= totalMass;
177 } 211 }
178 212 return ret;
179 return com;
180 } 213 }
181 214
182 private OMV.Vector3 ComputeLinksetGeometricCenter() 215 // I am the root of a linkset and a new child is being added
183 { 216 // Called while LinkActivity is locked.
184 OMV.Vector3 com = m_linksetRoot.Position; 217 protected abstract void AddChildToLinkset(BSPhysObject child);
185
186 lock (m_linksetActivityLock)
187 {
188 foreach (BSPrim bp in m_children)
189 {
190 com += bp.Position * bp.MassRaw;
191 }
192 com /= (m_children.Count + 1);
193 }
194 218
195 return com; 219 // I am the root of a linkset and one of my children is being removed.
196 } 220 // Safe to call even if the child is not really in my linkset.
221 protected abstract void RemoveChildFromLinkset(BSPhysObject child);
197 222
198 // When physical properties are changed the linkset needs to recalculate 223 // When physical properties are changed the linkset needs to recalculate
199 // its internal properties. 224 // its internal properties.
200 public void Refresh(BSPrim requestor) 225 // May be called at runtime or taint-time.
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()
201 { 261 {
202 // If there are no children, there aren't any constraints to recompute 262 float mass = LinksetRoot.RawMass;
203 if (!HasAnyChildren) 263 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)
225 { 264 {
226 foreach (BSPrim child in m_children) 265 lock (m_linksetActivityLock)
227 { 266 {
228 BSConstraint constrain; 267 foreach (BSPhysObject bp in m_children)
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
236 { 268 {
237 // Non-fatal error that can happen when children are being added to the linkset but 269 mass += bp.RawMass;
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);
243 } 270 }
244 } 271 }
245 } 272 }
246 return false; 273 return mass;
247 } 274 }
248 275
249 // I am the root of a linkset and a new child is being added 276 protected virtual OMV.Vector3 ComputeLinksetCenterOfMass()
250 // Called while LinkActivity is locked.
251 private void AddChildToLinkset(BSPrim child)
252 { 277 {
253 if (!HasChild(child)) 278 OMV.Vector3 com;
279 lock (m_linksetActivityLock)
254 { 280 {
255 m_children.Add(child); 281 com = LinksetRoot.Position * LinksetRoot.RawMass;
282 float totalMass = LinksetRoot.RawMass;
256 283
257 BSPrim rootx = LinksetRoot; // capture the root as of now 284 foreach (BSPhysObject bp in m_children)
258 BSPrim childx = child;
259 m_physicsScene.TaintedObject("AddChildToLinkset", delegate()
260 { 285 {
261 // DebugLog("{0}: AddChildToLinkset: adding child {1} to {2}", LogHeader, child.LocalID, m_linksetRoot.LocalID); 286 com += bp.Position * bp.RawMass;
262 // DetailLog("{0},AddChildToLinkset,taint,child={1}", m_linksetRoot.LocalID, child.LocalID); 287 totalMass += bp.RawMass;
263 PhysicallyLinkAChildToRoot(rootx, childx); // build the physical binding between me and the child 288 }
264 }); 289 if (totalMass != 0f)
290 com /= totalMass;
265 } 291 }
266 return;
267 }
268 292
269 // Forcefully removing a child from a linkset. 293 return com;
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);
278 } 294 }
279 295
280 // I am the root of a linkset and one of my children is being removed. 296 protected virtual OMV.Vector3 ComputeLinksetGeometricCenter()
281 // Safe to call even if the child is not really in my linkset.
282 private void RemoveChildFromLinkset(BSPrim child)
283 { 297 {
284 if (m_children.Remove(child)) 298 OMV.Vector3 com;
299 lock (m_linksetActivityLock)
285 { 300 {
286 BSPrim rootx = LinksetRoot; // capture the root as of now 301 com = LinksetRoot.Position;
287 BSPrim childx = child;
288 m_physicsScene.TaintedObject("RemoveChildFromLinkset", delegate()
289 {
290 // DebugLog("{0}: RemoveChildFromLinkset: Removing constraint to {1}", LogHeader, child.LocalID);
291 // DetailLog("{0},RemoveChildFromLinkset,taint,child={1}", m_linksetRoot.LocalID, child.LocalID);
292
293 PhysicallyUnlinkAChildFromRoot(rootx, childx);
294 });
295 302
296 RecomputeLinksetConstraintVariables(); 303 foreach (BSPhysObject bp in m_children)
297 } 304 {
298 else 305 com += bp.Position * bp.RawMass;
299 { 306 }
300 // This will happen if we remove the root of the linkset first. Non-fatal occurance. 307 com /= (m_children.Count + 1);
301 // PhysicsScene.Logger.ErrorFormat("{0}: Asked to remove child from linkset that was not in linkset", LogHeader);
302 } 308 }
303 return;
304 }
305
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 309
381 // Remove linkage between myself and a particular child 310 return com;
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);
411 } 311 }
412 312
413 // Invoke the detailed logger and output something if it's enabled. 313 // Invoke the detailed logger and output something if it's enabled.
414 private void DetailLog(string msg, params Object[] args) 314 protected void DetailLog(string msg, params Object[] args)
415 { 315 {
416 m_physicsScene.PhysicsLogging.Write(msg, args); 316 if (PhysicsScene.PhysicsLogging.Enabled)
317 PhysicsScene.DetailLog(msg, args);
417 } 318 }
418 319
419} 320}