aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xOpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs309
1 files changed, 213 insertions, 96 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs
index b9c2cf9..bd03d31 100755
--- a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs
+++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs
@@ -28,22 +28,54 @@ using System;
28using System.Collections.Generic; 28using System.Collections.Generic;
29using System.Text; 29using System.Text;
30 30
31using OpenSim.Framework;
32
31using OMV = OpenMetaverse; 33using OMV = OpenMetaverse;
32 34
33namespace OpenSim.Region.Physics.BulletSPlugin 35namespace OpenSim.Region.Physics.BulletSPlugin
34{ 36{
37
38// When a child is linked, the relationship position of the child to the parent
39// is remembered so the child's world position can be recomputed when it is
40// removed from the linkset.
41sealed class BSLinksetCompoundInfo : BSLinksetInfo
42{
43 public OMV.Vector3 OffsetPos;
44 public OMV.Quaternion OffsetRot;
45 public BSLinksetCompoundInfo(OMV.Vector3 p, OMV.Quaternion r)
46 {
47 OffsetPos = p;
48 OffsetRot = r;
49 }
50 public override void Clear()
51 {
52 OffsetPos = OMV.Vector3.Zero;
53 OffsetRot = OMV.Quaternion.Identity;
54 }
55 public override string ToString()
56 {
57 StringBuilder buff = new StringBuilder();
58 buff.Append("<p=");
59 buff.Append(OffsetPos.ToString());
60 buff.Append(",r=");
61 buff.Append(OffsetRot.ToString());
62 buff.Append(">");
63 return buff.ToString();
64 }
65};
66
35public sealed class BSLinksetCompound : BSLinkset 67public sealed class BSLinksetCompound : BSLinkset
36{ 68{
37 private static string LogHeader = "[BULLETSIM LINKSET COMPOUND]"; 69 private static string LogHeader = "[BULLETSIM LINKSET COMPOUND]";
38 70
39 public BSLinksetCompound(BSScene scene, BSPhysObject parent) 71 public BSLinksetCompound(BSScene scene, BSPhysObject parent) : base(scene, parent)
40 { 72 {
41 base.Initialize(scene, parent);
42 } 73 }
43 74
44 // For compound implimented linksets, if there are children, use compound shape for the root. 75 // For compound implimented linksets, if there are children, use compound shape for the root.
45 public override BSPhysicsShapeType PreferredPhysicalShape(BSPhysObject requestor) 76 public override BSPhysicsShapeType PreferredPhysicalShape(BSPhysObject requestor)
46 { 77 {
78 // Returning 'unknown' means we don't have a preference.
47 BSPhysicsShapeType ret = BSPhysicsShapeType.SHAPE_UNKNOWN; 79 BSPhysicsShapeType ret = BSPhysicsShapeType.SHAPE_UNKNOWN;
48 if (IsRoot(requestor) && HasAnyChildren) 80 if (IsRoot(requestor) && HasAnyChildren)
49 { 81 {
@@ -55,27 +87,33 @@ public sealed class BSLinksetCompound : BSLinkset
55 87
56 // When physical properties are changed the linkset needs to recalculate 88 // When physical properties are changed the linkset needs to recalculate
57 // its internal properties. 89 // its internal properties.
58 // This is queued in the 'post taint' queue so the
59 // refresh will happen once after all the other taints are applied.
60 public override void Refresh(BSPhysObject requestor) 90 public override void Refresh(BSPhysObject requestor)
61 { 91 {
62 // External request for Refresh (from BSPrim) is not necessary 92 base.Refresh(requestor);
63 // InternalRefresh(requestor); 93
94 // Something changed so do the rebuilding thing
95 // ScheduleRebuild();
64 } 96 }
65 97
66 private void InternalRefresh(BSPhysObject requestor) 98 // Schedule a refresh to happen after all the other taint processing.
99 private void ScheduleRebuild(BSPhysObject requestor)
67 { 100 {
68 DetailLog("{0},BSLinksetCompound.Refresh,schedulingRefresh,requestor={1}", LinksetRoot.LocalID, requestor.LocalID); 101 DetailLog("{0},BSLinksetCompound.ScheduleRebuild,,rebuilding={1},hasChildren={2}",
69 // Queue to happen after all the other taint processing 102 requestor.LocalID, Rebuilding, HasAnyChildren);
70 PhysicsScene.PostTaintObject("BSLinksetCompound.Refresh", requestor.LocalID, delegate() 103 // When rebuilding, it is possible to set properties that would normally require a rebuild.
104 // If already rebuilding, don't request another rebuild.
105 // If a linkset with just a root prim (simple non-linked prim) don't bother rebuilding.
106 if (!Rebuilding && HasAnyChildren)
71 { 107 {
72 if (IsRoot(requestor) && HasAnyChildren) 108 PhysicsScene.PostTaintObject("BSLinksetCompound.ScheduleRebuild", LinksetRoot.LocalID, delegate()
73 RecomputeLinksetCompound(); 109 {
74 }); 110 if (HasAnyChildren)
111 RecomputeLinksetCompound();
112 });
113 }
75 } 114 }
76 115
77 // The object is going dynamic (physical). Do any setup necessary 116 // The object is going dynamic (physical). Do any setup necessary for a dynamic linkset.
78 // for a dynamic linkset.
79 // Only the state of the passed object can be modified. The rest of the linkset 117 // Only the state of the passed object can be modified. The rest of the linkset
80 // has not yet been fully constructed. 118 // has not yet been fully constructed.
81 // Return 'true' if any properties updated on the passed object. 119 // Return 'true' if any properties updated on the passed object.
@@ -84,12 +122,22 @@ public sealed class BSLinksetCompound : BSLinkset
84 { 122 {
85 bool ret = false; 123 bool ret = false;
86 DetailLog("{0},BSLinksetCompound.MakeDynamic,call,IsRoot={1}", child.LocalID, IsRoot(child)); 124 DetailLog("{0},BSLinksetCompound.MakeDynamic,call,IsRoot={1}", child.LocalID, IsRoot(child));
87 if (!IsRoot(child)) 125 if (IsRoot(child))
126 {
127 // The root is going dynamic. Rebuild the linkset so parts and mass get computed properly.
128 ScheduleRebuild(LinksetRoot);
129 }
130 else
88 { 131 {
89 // Physical children are removed from the world as the shape ofthe root compound 132 // The origional prims are removed from the world as the shape of the root compound
90 // shape takes over. 133 // shape takes over.
91 BulletSimAPI.AddToCollisionFlags2(child.PhysBody.ptr, CollisionFlags.CF_NO_CONTACT_RESPONSE); 134 PhysicsScene.PE.AddToCollisionFlags(child.PhysBody, CollisionFlags.CF_NO_CONTACT_RESPONSE);
92 BulletSimAPI.ForceActivationState2(child.PhysBody.ptr, ActivationState.DISABLE_SIMULATION); 135 PhysicsScene.PE.ForceActivationState(child.PhysBody, ActivationState.DISABLE_SIMULATION);
136 // We don't want collisions from the old linkset children.
137 PhysicsScene.PE.RemoveFromCollisionFlags(child.PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS);
138
139 child.PhysBody.collisionType = CollisionType.LinksetChild;
140
93 ret = true; 141 ret = true;
94 } 142 }
95 return ret; 143 return ret;
@@ -104,33 +152,39 @@ public sealed class BSLinksetCompound : BSLinkset
104 { 152 {
105 bool ret = false; 153 bool ret = false;
106 DetailLog("{0},BSLinksetCompound.MakeStatic,call,IsRoot={1}", child.LocalID, IsRoot(child)); 154 DetailLog("{0},BSLinksetCompound.MakeStatic,call,IsRoot={1}", child.LocalID, IsRoot(child));
107 if (!IsRoot(child)) 155 if (IsRoot(child))
156 {
157 ScheduleRebuild(LinksetRoot);
158 }
159 else
108 { 160 {
109 // The non-physical children can come back to life. 161 // The non-physical children can come back to life.
110 BulletSimAPI.RemoveFromCollisionFlags2(child.PhysBody.ptr, CollisionFlags.CF_NO_CONTACT_RESPONSE); 162 PhysicsScene.PE.RemoveFromCollisionFlags(child.PhysBody, CollisionFlags.CF_NO_CONTACT_RESPONSE);
111 // Don't force activation so setting of DISABLE_SIMULATION can stay. 163
112 BulletSimAPI.Activate2(child.PhysBody.ptr, false); 164 child.PhysBody.collisionType = CollisionType.LinksetChild;
165
166 // Don't force activation so setting of DISABLE_SIMULATION can stay if used.
167 PhysicsScene.PE.Activate(child.PhysBody, false);
113 ret = true; 168 ret = true;
114 } 169 }
115 return ret; 170 return ret;
116 } 171 }
117 172
118 // Called at taint-time!! 173 public override void UpdateProperties(BSPhysObject updated, bool physicalUpdate)
119 public override void UpdateProperties(BSPhysObject updated)
120 {
121 // Nothing to do for constraints on property updates
122 }
123
124 // The children move around in relationship to the root.
125 // Just grab the current values of wherever it is right now.
126 public override OMV.Vector3 Position(BSPhysObject member)
127 {
128 return BulletSimAPI.GetPosition2(member.PhysBody.ptr);
129 }
130
131 public override OMV.Quaternion Orientation(BSPhysObject member)
132 { 174 {
133 return BulletSimAPI.GetOrientation2(member.PhysBody.ptr); 175 // The user moving a child around requires the rebuilding of the linkset compound shape
176 // One problem is this happens when a border is crossed -- the simulator implementation
177 // is to store the position into the group which causes the move of the object
178 // but it also means all the child positions get updated.
179 // What would cause an unnecessary rebuild so we make sure the linkset is in a
180 // region before bothering to do a rebuild.
181 if (!IsRoot(updated)
182 && !physicalUpdate
183 && PhysicsScene.TerrainManager.IsWithinKnownTerrain(LinksetRoot.RawPosition))
184 {
185 updated.LinksetInfo = null;
186 ScheduleRebuild(updated);
187 }
134 } 188 }
135 189
136 // Routine called when rebuilding the body of some member of the linkset. 190 // Routine called when rebuilding the body of some member of the linkset.
@@ -142,24 +196,62 @@ public sealed class BSLinksetCompound : BSLinkset
142 bool ret = false; 196 bool ret = false;
143 197
144 DetailLog("{0},BSLinksetCompound.RemoveBodyDependencies,refreshIfChild,rID={1},rBody={2},isRoot={3}", 198 DetailLog("{0},BSLinksetCompound.RemoveBodyDependencies,refreshIfChild,rID={1},rBody={2},isRoot={3}",
145 child.LocalID, LinksetRoot.LocalID, LinksetRoot.PhysBody.ptr.ToString("X"), IsRoot(child)); 199 child.LocalID, LinksetRoot.LocalID, LinksetRoot.PhysBody.AddrString, IsRoot(child));
146 200
147 if (!IsRoot(child)) 201 if (!IsRoot(child))
148 { 202 {
149 // Cause the current shape to be freed and the new one to be built. 203 // Because it is a convenient time, recompute child world position and rotation based on
150 InternalRefresh(LinksetRoot); 204 // its position in the linkset.
151 ret = true; 205 RecomputeChildWorldPosition(child, true);
152 } 206 }
153 207
208 // Cannot schedule a refresh/rebuild here because this routine is called when
209 // the linkset is being rebuilt.
210 // InternalRefresh(LinksetRoot);
211
154 return ret; 212 return ret;
155 } 213 }
156 214
157 // Companion to RemoveBodyDependencies(). If RemoveBodyDependencies() returns 'true', 215 // Companion to RemoveBodyDependencies(). If RemoveBodyDependencies() returns 'true',
158 // this routine will restore the removed constraints. 216 // this routine will restore the removed constraints.
159 // Called at taint-time!! 217 // Called at taint-time!!
160 public override void RestoreBodyDependencies(BSPrim child) 218 public override void RestoreBodyDependencies(BSPrim child)
161 { 219 {
162 // The Refresh operation queued by RemoveBodyDependencies() will build any missing constraints. 220 }
221
222 // When the linkset is built, the child shape is added to the compound shape relative to the
223 // root shape. The linkset then moves around but this does not move the actual child
224 // prim. The child prim's location must be recomputed based on the location of the root shape.
225 private void RecomputeChildWorldPosition(BSPhysObject child, bool inTaintTime)
226 {
227 BSLinksetCompoundInfo lci = child.LinksetInfo as BSLinksetCompoundInfo;
228 if (lci != null)
229 {
230 if (inTaintTime)
231 {
232 OMV.Vector3 oldPos = child.RawPosition;
233 child.ForcePosition = LinksetRoot.RawPosition + lci.OffsetPos;
234 child.ForceOrientation = LinksetRoot.RawOrientation * lci.OffsetRot;
235 DetailLog("{0},BSLinksetCompound.RecomputeChildWorldPosition,oldPos={1},lci={2},newPos={3}",
236 child.LocalID, oldPos, lci, child.RawPosition);
237 }
238 else
239 {
240 // TaintedObject is not used here so the raw position is set now and not at taint-time.
241 child.Position = LinksetRoot.RawPosition + lci.OffsetPos;
242 child.Orientation = LinksetRoot.RawOrientation * lci.OffsetRot;
243 }
244 }
245 else
246 {
247 // This happens when children have been added to the linkset but the linkset
248 // has not been constructed yet. So like, at taint time, adding children to a linkset
249 // and then changing properties of the children (makePhysical, for instance)
250 // but the post-print action of actually rebuilding the linkset has not yet happened.
251 // PhysicsScene.Logger.WarnFormat("{0} Restoring linkset child position failed because of no relative position computed. ID={1}",
252 // LogHeader, child.LocalID);
253 DetailLog("{0},BSLinksetCompound.recomputeChildWorldPosition,noRelativePositonInfo", child.LocalID);
254 }
163 } 255 }
164 256
165 // ================================================================ 257 // ================================================================
@@ -174,24 +266,25 @@ public sealed class BSLinksetCompound : BSLinkset
174 266
175 DetailLog("{0},BSLinksetCompound.AddChildToLinkset,call,child={1}", LinksetRoot.LocalID, child.LocalID); 267 DetailLog("{0},BSLinksetCompound.AddChildToLinkset,call,child={1}", LinksetRoot.LocalID, child.LocalID);
176 268
177 // Cause constraints and assorted properties to be recomputed before the next simulation step. 269 // Rebuild the compound shape with the new child shape included
178 InternalRefresh(LinksetRoot); 270 ScheduleRebuild(child);
179 } 271 }
180 return; 272 return;
181 } 273 }
182 274
183 // Remove the specified child from the linkset. 275 // Remove the specified child from the linkset.
184 // Safe to call even if the child is not really in my linkset. 276 // Safe to call even if the child is not really in the linkset.
185 protected override void RemoveChildFromLinkset(BSPhysObject child) 277 protected override void RemoveChildFromLinkset(BSPhysObject child)
186 { 278 {
187 if (m_children.Remove(child)) 279 if (m_children.Remove(child))
188 { 280 {
189 DetailLog("{0},BSLinksetCompound.RemoveChildFromLinkset,call,rID={1},rBody={2},cID={3},cBody={4}", 281 DetailLog("{0},BSLinksetCompound.RemoveChildFromLinkset,call,rID={1},rBody={2},cID={3},cBody={4}",
190 child.LocalID, 282 child.LocalID,
191 LinksetRoot.LocalID, LinksetRoot.PhysBody.ptr.ToString("X"), 283 LinksetRoot.LocalID, LinksetRoot.PhysBody.AddrString,
192 child.LocalID, child.PhysBody.ptr.ToString("X")); 284 child.LocalID, child.PhysBody.AddrString);
193 285
194 // Cause the child's body to be rebuilt and thus restored to normal operation 286 // Cause the child's body to be rebuilt and thus restored to normal operation
287 RecomputeChildWorldPosition(child, false);
195 child.ForceBodyShapeRebuild(false); 288 child.ForceBodyShapeRebuild(false);
196 289
197 if (!HasAnyChildren) 290 if (!HasAnyChildren)
@@ -201,8 +294,8 @@ public sealed class BSLinksetCompound : BSLinkset
201 } 294 }
202 else 295 else
203 { 296 {
204 // Schedule a rebuild of the linkset before the next simulation tick. 297 // Rebuild the compound shape with the child removed
205 InternalRefresh(LinksetRoot); 298 ScheduleRebuild(child);
206 } 299 }
207 } 300 }
208 return; 301 return;
@@ -215,61 +308,85 @@ public sealed class BSLinksetCompound : BSLinkset
215 // Called at taint time!! 308 // Called at taint time!!
216 private void RecomputeLinksetCompound() 309 private void RecomputeLinksetCompound()
217 { 310 {
218 // Cause the root shape to be rebuilt as a compound object with just the root in it 311 try
219 LinksetRoot.ForceBodyShapeRebuild(true);
220
221 DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,start,rBody={1},rShape={2},numChildren={3}",
222 LinksetRoot.LocalID, LinksetRoot.PhysBody, LinksetRoot.PhysShape, NumberOfChildren);
223
224 // Add a shape for each of the other children in the linkset
225 ForEachMember(delegate(BSPhysObject cPrim)
226 { 312 {
227 if (!IsRoot(cPrim)) 313 // Suppress rebuilding while rebuilding
228 { 314 Rebuilding = true;
229 // Each child position and rotation is given relative to the root.
230 OMV.Quaternion invRootOrientation = OMV.Quaternion.Inverse(LinksetRoot.RawOrientation);
231 OMV.Vector3 displacementPos = (cPrim.RawPosition - LinksetRoot.RawPosition) * invRootOrientation;
232 OMV.Quaternion displacementRot = cPrim.RawOrientation * invRootOrientation;
233 315
234 DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,addMemberToShape,mID={1},mShape={2},dispPos={3},dispRot={4}", 316 // Cause the root shape to be rebuilt as a compound object with just the root in it
235 LinksetRoot.LocalID, cPrim.LocalID, cPrim.PhysShape, displacementPos, displacementRot); 317 LinksetRoot.ForceBodyShapeRebuild(true);
236 318
237 if (cPrim.PhysShape.isNativeShape) 319 DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,start,rBody={1},rShape={2},numChildren={3}",
238 { 320 LinksetRoot.LocalID, LinksetRoot.PhysBody, LinksetRoot.PhysShape, NumberOfChildren);
239 // Native shapes are not shared so we need to create a new one. 321
240 // A mesh or hull is created because scale is not available on a native shape. 322 // Add a shape for each of the other children in the linkset
241 // (TODO: Bullet does have a btScaledCollisionShape. Can that be used?) 323 ForEachMember(delegate(BSPhysObject cPrim)
242 BulletShape saveShape = cPrim.PhysShape; 324 {
243 cPrim.PhysShape.ptr = IntPtr.Zero; // Don't let the create free the child's shape 325 if (!IsRoot(cPrim))
244 PhysicsScene.Shapes.CreateGeomMeshOrHull(cPrim, null);
245 BulletShape newShape = cPrim.PhysShape;
246 cPrim.PhysShape = saveShape;
247 BulletSimAPI.AddChildShapeToCompoundShape2(LinksetRoot.PhysShape.ptr, newShape.ptr, displacementPos, displacementRot);
248 }
249 else
250 { 326 {
251 // For the shared shapes (meshes and hulls), just use the shape in the child. 327 // Compute the displacement of the child from the root of the linkset.
252 if (PhysicsScene.Shapes.ReferenceShape(cPrim.PhysShape)) 328 // This info is saved in the child prim so the relationship does not
329 // change over time and the new child position can be computed
330 // when the linkset is being disassembled (the linkset may have moved).
331 BSLinksetCompoundInfo lci = cPrim.LinksetInfo as BSLinksetCompoundInfo;
332 if (lci == null)
253 { 333 {
254 PhysicsScene.Logger.ErrorFormat("{0} Rebuilt sharable shape when building linkset! Region={1}, primID={2}, shape={3}", 334 // Each child position and rotation is given relative to the root.
255 LogHeader, PhysicsScene.RegionName, cPrim.LocalID, cPrim.PhysShape); 335 OMV.Quaternion invRootOrientation = OMV.Quaternion.Inverse(LinksetRoot.RawOrientation);
336 OMV.Vector3 displacementPos = (cPrim.RawPosition - LinksetRoot.RawPosition) * invRootOrientation;
337 OMV.Quaternion displacementRot = cPrim.RawOrientation * invRootOrientation;
338
339 // Save relative position for recomputing child's world position after moving linkset.
340 lci = new BSLinksetCompoundInfo(displacementPos, displacementRot);
341 cPrim.LinksetInfo = lci;
342 DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,creatingRelPos,lci={1}", cPrim.LocalID, lci);
256 } 343 }
257 BulletSimAPI.AddChildShapeToCompoundShape2(LinksetRoot.PhysShape.ptr, cPrim.PhysShape.ptr, displacementPos, displacementRot);
258 }
259 }
260 return false; // 'false' says to move onto the next child in the list
261 });
262 344
263 // With all of the linkset packed into the root prim, it has the mass of everyone. 345 DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,addMemberToShape,mID={1},mShape={2},dispPos={3},dispRot={4}",
264 float linksetMass = LinksetMass; 346 LinksetRoot.LocalID, cPrim.LocalID, cPrim.PhysShape, lci.OffsetPos, lci.OffsetRot);
265 LinksetRoot.UpdatePhysicalMassProperties(linksetMass);
266 347
267 BulletSimAPI.RecalculateCompoundShapeLocalAabb2(LinksetRoot.PhysShape.ptr); 348 if (cPrim.PhysShape.isNativeShape)
349 {
350 // A native shape is turning into a hull collision shape because native
351 // shapes are not shared so we have to hullify it so it will be tracked
352 // and freed at the correct time. This also solves the scaling problem
353 // (native shapes scaled but hull/meshes are assumed to not be).
354 // TODO: decide of the native shape can just be used in the compound shape.
355 // Use call to CreateGeomNonSpecial().
356 BulletShape saveShape = cPrim.PhysShape;
357 cPrim.PhysShape.Clear(); // Don't let the create free the child's shape
358 // PhysicsScene.Shapes.CreateGeomNonSpecial(true, cPrim, null);
359 PhysicsScene.Shapes.CreateGeomMeshOrHull(cPrim, null);
360 BulletShape newShape = cPrim.PhysShape;
361 cPrim.PhysShape = saveShape;
362 PhysicsScene.PE.AddChildShapeToCompoundShape(LinksetRoot.PhysShape, newShape, lci.OffsetPos, lci.OffsetRot);
363 }
364 else
365 {
366 // For the shared shapes (meshes and hulls), just use the shape in the child.
367 // The reference count added here will be decremented when the compound shape
368 // is destroyed in BSShapeCollection (the child shapes are looped over and dereferenced).
369 if (PhysicsScene.Shapes.ReferenceShape(cPrim.PhysShape))
370 {
371 PhysicsScene.Logger.ErrorFormat("{0} Rebuilt sharable shape when building linkset! Region={1}, primID={2}, shape={3}",
372 LogHeader, PhysicsScene.RegionName, cPrim.LocalID, cPrim.PhysShape);
373 }
374 PhysicsScene.PE.AddChildShapeToCompoundShape(LinksetRoot.PhysShape, cPrim.PhysShape, lci.OffsetPos, lci.OffsetRot);
375 }
376 }
377 return false; // 'false' says to move onto the next child in the list
378 });
268 379
269 // DEBUG: see of inter-linkset collisions are causing problems for constraint linksets. 380 // With all of the linkset packed into the root prim, it has the mass of everyone.
270 // BulletSimAPI.SetCollisionFilterMask2(LinksetRoot.BSBody.ptr, 381 LinksetMass = ComputeLinksetMass();
271 // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); 382 LinksetRoot.UpdatePhysicalMassProperties(LinksetMass, true);
383 }
384 finally
385 {
386 Rebuilding = false;
387 }
272 388
389 PhysicsScene.PE.RecalculateCompoundShapeLocalAabb(LinksetRoot.PhysShape);
273 } 390 }
274} 391}
275} \ No newline at end of file 392} \ No newline at end of file