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.cs437
1 files changed, 171 insertions, 266 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
index 087b9bb..4ee047b 100755
--- a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
+++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs
@@ -32,35 +32,80 @@ 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.Constraint:
53 ret = new BSLinksetConstraints(physScene, parent);
54 break;
55 case (int)LinksetImplementation.Compound:
56 ret = new BSLinksetCompound(physScene, parent);
57 break;
58 case (int)LinksetImplementation.Manual:
59 // ret = new BSLinksetManual(physScene, parent);
60 break;
61 default:
62 ret = new BSLinksetCompound(physScene, parent);
63 break;
64 }
65 return ret;
66 }
67
68 public BSPhysObject LinksetRoot { get; protected set; }
41 69
42 private BSScene m_physicsScene; 70 public BSScene PhysicsScene { get; private set; }
43 public BSScene PhysicsScene { get { return m_physicsScene; } }
44 71
45 // The children under the root in this linkset 72 static int m_nextLinksetID = 1;
46 private List<BSPrim> m_children; 73 public int LinksetID { get; private set; }
74
75 // The children under the root in this linkset.
76 protected HashSet<BSPhysObject> m_children;
47 77
48 // We lock the diddling of linkset classes to prevent any badness. 78 // We lock the diddling of linkset classes to prevent any badness.
49 // This locks the modification of the instances of this class. Changes 79 // This locks the modification of the instances of this class. Changes
50 // to the physical representation is done via the tainting mechenism. 80 // to the physical representation is done via the tainting mechenism.
51 private object m_linksetActivityLock = new Object(); 81 protected object m_linksetActivityLock = new Object();
82
83 // Some linksets have a preferred physical shape.
84 // Returns SHAPE_UNKNOWN if there is no preference. Causes the correct shape to be selected.
85 public virtual PhysicsShapeType PreferredPhysicalShape(BSPhysObject requestor)
86 {
87 return PhysicsShapeType.SHAPE_UNKNOWN;
88 }
52 89
90 // Linksets move around the children so the linkset might need to compute the child position
91 public virtual OMV.Vector3 Position(BSPhysObject member)
92 { return member.RawPosition; }
93 public virtual OMV.Quaternion Orientation(BSPhysObject member)
94 { return member.RawOrientation; }
95 // TODO: does this need to be done for Velocity and RotationalVelocityy?
96
53 // We keep the prim's mass in the linkset structure since it could be dependent on other prims 97 // We keep the prim's mass in the linkset structure since it could be dependent on other prims
54 private float m_mass; 98 protected float m_mass;
55 public float LinksetMass 99 public float LinksetMass
56 { 100 {
57 get 101 get
58 { 102 {
59 m_mass = ComputeLinksetMass();
60 return m_mass; 103 return m_mass;
61 } 104 }
62 } 105 }
63 106
107 public virtual bool LinksetIsColliding { get { return false; } }
108
64 public OMV.Vector3 CenterOfMass 109 public OMV.Vector3 CenterOfMass
65 { 110 {
66 get { return ComputeLinksetCenterOfMass(); } 111 get { return ComputeLinksetCenterOfMass(); }
@@ -71,23 +116,31 @@ public class BSLinkset
71 get { return ComputeLinksetGeometricCenter(); } 116 get { return ComputeLinksetGeometricCenter(); }
72 } 117 }
73 118
74 public BSLinkset(BSScene scene, BSPrim parent) 119 protected void Initialize(BSScene scene, BSPhysObject parent)
75 { 120 {
76 // A simple linkset of one (no children) 121 // A simple linkset of one (no children)
77 m_physicsScene = scene; 122 LinksetID = m_nextLinksetID++;
78 m_linksetRoot = parent; 123 // We create LOTS of linksets.
79 m_children = new List<BSPrim>(); 124 if (m_nextLinksetID <= 0)
80 m_mass = parent.MassRaw; 125 m_nextLinksetID = 1;
126 PhysicsScene = scene;
127 LinksetRoot = parent;
128 m_children = new HashSet<BSPhysObject>();
129 m_mass = parent.RawMass;
81 } 130 }
82 131
83 // Link to a linkset where the child knows the parent. 132 // Link to a linkset where the child knows the parent.
84 // Parent changing should not happen so do some sanity checking. 133 // Parent changing should not happen so do some sanity checking.
85 // We return the parent's linkset so the child can track its membership. 134 // We return the parent's linkset so the child can track its membership.
86 public BSLinkset AddMeToLinkset(BSPrim child) 135 // Called at runtime.
136 public BSLinkset AddMeToLinkset(BSPhysObject child)
87 { 137 {
88 lock (m_linksetActivityLock) 138 lock (m_linksetActivityLock)
89 { 139 {
90 AddChildToLinkset(child); 140 // Don't add the root to its own linkset
141 if (!IsRoot(child))
142 AddChildToLinkset(child);
143 m_mass = ComputeLinksetMass();
91 } 144 }
92 return this; 145 return this;
93 } 146 }
@@ -95,36 +148,28 @@ public class BSLinkset
95 // Remove a child from a linkset. 148 // Remove a child from a linkset.
96 // Returns a new linkset for the child which is a linkset of one (just the 149 // Returns a new linkset for the child which is a linkset of one (just the
97 // orphened child). 150 // orphened child).
98 public BSLinkset RemoveMeFromLinkset(BSPrim child) 151 // Called at runtime.
152 public BSLinkset RemoveMeFromLinkset(BSPhysObject child)
99 { 153 {
100 lock (m_linksetActivityLock) 154 lock (m_linksetActivityLock)
101 { 155 {
102 if (IsRoot(child)) 156 if (IsRoot(child))
103 { 157 {
104 // if root of linkset, take the linkset apart 158 // Cannot remove the root from a linkset.
105 while (m_children.Count > 0) 159 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 } 160 }
161 RemoveChildFromLinkset(child);
162 m_mass = ComputeLinksetMass();
118 } 163 }
119 164
120 // The child is down to a linkset of just itself 165 // The child is down to a linkset of just itself
121 return new BSLinkset(PhysicsScene, child); 166 return BSLinkset.Factory(PhysicsScene, child);
122 } 167 }
123 168
124 // Return 'true' if the passed object is the root object of this linkset 169 // Return 'true' if the passed object is the root object of this linkset
125 public bool IsRoot(BSPrim requestor) 170 public bool IsRoot(BSPhysObject requestor)
126 { 171 {
127 return (requestor.LocalID == m_linksetRoot.LocalID); 172 return (requestor.LocalID == LinksetRoot.LocalID);
128 } 173 }
129 174
130 public int NumberOfChildren { get { return m_children.Count; } } 175 public int NumberOfChildren { get { return m_children.Count; } }
@@ -133,12 +178,14 @@ public class BSLinkset
133 public bool HasAnyChildren { get { return (m_children.Count > 0); } } 178 public bool HasAnyChildren { get { return (m_children.Count > 0); } }
134 179
135 // Return 'true' if this child is in this linkset 180 // Return 'true' if this child is in this linkset
136 public bool HasChild(BSPrim child) 181 public bool HasChild(BSPhysObject child)
137 { 182 {
138 bool ret = false; 183 bool ret = false;
139 lock (m_linksetActivityLock) 184 lock (m_linksetActivityLock)
140 { 185 {
141 foreach (BSPrim bp in m_children) 186 ret = m_children.Contains(child);
187 /* Safer version but the above should work
188 foreach (BSPhysObject bp in m_children)
142 { 189 {
143 if (child.LocalID == bp.LocalID) 190 if (child.LocalID == bp.LocalID)
144 { 191 {
@@ -146,274 +193,132 @@ public class BSLinkset
146 break; 193 break;
147 } 194 }
148 } 195 }
196 */
149 } 197 }
150 return ret; 198 return ret;
151 } 199 }
152 200
153 private float ComputeLinksetMass() 201 // Perform an action on each member of the linkset including root prim.
154 { 202 // Depends on the action on whether this should be done at taint time.
155 float mass = m_linksetRoot.MassRaw; 203 public delegate bool ForEachMemberAction(BSPhysObject obj);
156 foreach (BSPrim bp in m_children) 204 public virtual bool ForEachMember(ForEachMemberAction action)
157 {
158 mass += bp.MassRaw;
159 }
160 return mass;
161 }
162
163 private OMV.Vector3 ComputeLinksetCenterOfMass()
164 { 205 {
165 OMV.Vector3 com = m_linksetRoot.Position * m_linksetRoot.MassRaw; 206 bool ret = false;
166 float totalMass = m_linksetRoot.MassRaw;
167
168 lock (m_linksetActivityLock) 207 lock (m_linksetActivityLock)
169 { 208 {
170 foreach (BSPrim bp in m_children) 209 action(LinksetRoot);
210 foreach (BSPhysObject po in m_children)
171 { 211 {
172 com += bp.Position * bp.MassRaw; 212 if (action(po))
173 totalMass += bp.MassRaw; 213 break;
174 } 214 }
175 if (totalMass != 0f)
176 com /= totalMass;
177 } 215 }
178 216 return ret;
179 return com;
180 } 217 }
181 218
182 private OMV.Vector3 ComputeLinksetGeometricCenter() 219 // I am the root of a linkset and a new child is being added
183 { 220 // Called while LinkActivity is locked.
184 OMV.Vector3 com = m_linksetRoot.Position; 221 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 222
195 return com; 223 // I am the root of a linkset and one of my children is being removed.
196 } 224 // Safe to call even if the child is not really in my linkset.
225 protected abstract void RemoveChildFromLinkset(BSPhysObject child);
197 226
198 // When physical properties are changed the linkset needs to recalculate 227 // When physical properties are changed the linkset needs to recalculate
199 // its internal properties. 228 // its internal properties.
200 public void Refresh(BSPrim requestor) 229 // May be called at runtime or taint-time.
201 { 230 public abstract void Refresh(BSPhysObject requestor);
202 // If there are no children, there aren't any constraints to recompute 231
203 if (!HasAnyChildren) 232 // The object is going dynamic (physical). Do any setup necessary
204 return; 233 // for a dynamic linkset.
205 234 // Only the state of the passed object can be modified. The rest of the linkset
206 // Only the root does the recomputation 235 // has not yet been fully constructed.
207 if (IsRoot(requestor)) 236 // Return 'true' if any properties updated on the passed object.
208 { 237 // Called at taint-time!
209 PhysicsScene.TaintedObject("BSLinkSet.Refresh", delegate() 238 public abstract bool MakeDynamic(BSPhysObject child);
210 { 239
211 RecomputeLinksetConstraintVariables(); 240 // The object is going static (non-physical). Do any setup necessary
212 }); 241 // for a static linkset.
213 } 242 // Return 'true' if any properties updated on the passed object.
214 } 243 // Called at taint-time!
215 244 public abstract bool MakeStatic(BSPhysObject child);
216 // Call each of the constraints that make up this linkset and recompute the 245
217 // various transforms and variables. Used when objects are added or removed 246 // Called when a parameter update comes from the physics engine for any object
218 // from a linkset to make sure the constraints know about the new mass and 247 // of the linkset is received.
219 // geometry. 248 // Called at taint-time!!
220 // Must only be called at taint time!! 249 public abstract void UpdateProperties(BSPhysObject physObject);
221 private bool RecomputeLinksetConstraintVariables() 250
251 // Routine used when rebuilding the body of the root of the linkset
252 // Destroy all the constraints have have been made to root.
253 // This is called when the root body is changing.
254 // Returns 'true' of something was actually removed and would need restoring
255 // Called at taint-time!!
256 public abstract bool RemoveBodyDependencies(BSPrim child);
257
258 // Companion to RemoveBodyDependencies(). If RemoveBodyDependencies() returns 'true',
259 // this routine will restore the removed constraints.
260 // Called at taint-time!!
261 public abstract void RestoreBodyDependencies(BSPrim child);
262
263 // ================================================================
264 protected virtual float ComputeLinksetMass()
222 { 265 {
223 float linksetMass = LinksetMass; 266 float mass = LinksetRoot.RawMass;
224 lock (m_linksetActivityLock) 267 if (HasAnyChildren)
225 { 268 {
226 foreach (BSPrim child in m_children) 269 lock (m_linksetActivityLock)
227 { 270 {
228 BSConstraint constrain; 271 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 { 272 {
237 // Non-fatal error that can happen when children are being added to the linkset but 273 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 } 274 }
244 } 275 }
245 } 276 }
246 return false; 277 return mass;
247 } 278 }
248 279
249 // I am the root of a linkset and a new child is being added 280 protected virtual OMV.Vector3 ComputeLinksetCenterOfMass()
250 // Called while LinkActivity is locked.
251 private void AddChildToLinkset(BSPrim child)
252 { 281 {
253 if (!HasChild(child)) 282 OMV.Vector3 com;
283 lock (m_linksetActivityLock)
254 { 284 {
255 m_children.Add(child); 285 com = LinksetRoot.Position * LinksetRoot.RawMass;
286 float totalMass = LinksetRoot.RawMass;
256 287
257 BSPrim rootx = LinksetRoot; // capture the root as of now 288 foreach (BSPhysObject bp in m_children)
258 BSPrim childx = child;
259 m_physicsScene.TaintedObject("AddChildToLinkset", delegate()
260 { 289 {
261 // DebugLog("{0}: AddChildToLinkset: adding child {1} to {2}", LogHeader, child.LocalID, m_linksetRoot.LocalID); 290 com += bp.Position * bp.RawMass;
262 // DetailLog("{0},AddChildToLinkset,taint,child={1}", m_linksetRoot.LocalID, child.LocalID); 291 totalMass += bp.RawMass;
263 PhysicallyLinkAChildToRoot(rootx, childx); // build the physical binding between me and the child 292 }
264 }); 293 if (totalMass != 0f)
294 com /= totalMass;
265 } 295 }
266 return;
267 }
268 296
269 // Forcefully removing a child from a linkset. 297 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 } 298 }
279 299
280 // I am the root of a linkset and one of my children is being removed. 300 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 { 301 {
284 if (m_children.Remove(child)) 302 OMV.Vector3 com;
303 lock (m_linksetActivityLock)
285 { 304 {
286 BSPrim rootx = LinksetRoot; // capture the root as of now 305 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 306
296 RecomputeLinksetConstraintVariables(); 307 foreach (BSPhysObject bp in m_children)
297 } 308 {
298 else 309 com += bp.Position * bp.RawMass;
299 { 310 }
300 // This will happen if we remove the root of the linkset first. Non-fatal occurance. 311 com /= (m_children.Count + 1);
301 // PhysicsScene.Logger.ErrorFormat("{0}: Asked to remove child from linkset that was not in linkset", LogHeader);
302 } 312 }
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 313
381 // Remove linkage between myself and a particular child 314 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 } 315 }
412 316
413 // Invoke the detailed logger and output something if it's enabled. 317 // Invoke the detailed logger and output something if it's enabled.
414 private void DetailLog(string msg, params Object[] args) 318 protected void DetailLog(string msg, params Object[] args)
415 { 319 {
416 m_physicsScene.PhysicsLogging.Write(msg, args); 320 if (PhysicsScene.PhysicsLogging.Enabled)
321 PhysicsScene.DetailLog(msg, args);
417 } 322 }
418 323
419} 324}