diff options
author | Robert Adams | 2012-10-30 09:12:07 -0700 |
---|---|---|
committer | Robert Adams | 2012-11-03 21:14:49 -0700 |
commit | 52be581f71b3c8da6113e4f4b193694683e6f8cc (patch) | |
tree | acaa7c98779c3387321f40d59d8edf6865af48bd /OpenSim | |
parent | BulletSim: Use the PostTaints operation to build the linkset once before the ... (diff) | |
download | opensim-SC_OLD-52be581f71b3c8da6113e4f4b193694683e6f8cc.zip opensim-SC_OLD-52be581f71b3c8da6113e4f4b193694683e6f8cc.tar.gz opensim-SC_OLD-52be581f71b3c8da6113e4f4b193694683e6f8cc.tar.bz2 opensim-SC_OLD-52be581f71b3c8da6113e4f4b193694683e6f8cc.tar.xz |
BulletSim: remove center-of-mass setting for linksets because it causes the constraint calculation to pull the objects together.
Diffstat (limited to 'OpenSim')
5 files changed, 33 insertions, 54 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSConstraint6Dof.cs b/OpenSim/Region/Physics/BulletSPlugin/BSConstraint6Dof.cs index 3c37d76..23ef052 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSConstraint6Dof.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSConstraint6Dof.cs | |||
@@ -71,8 +71,7 @@ public sealed class BSConstraint6Dof : BSConstraint | |||
71 | BSScene.DetailLogZero, world.worldID, | 71 | BSScene.DetailLogZero, world.worldID, |
72 | obj1.ID, obj1.ptr.ToString("X"), obj2.ID, obj2.ptr.ToString("X")); | 72 | obj1.ID, obj1.ptr.ToString("X"), obj2.ID, obj2.ptr.ToString("X")); |
73 | world.physicsScene.Logger.ErrorFormat("{0} Attempt to build 6DOF constraint with missing bodies: wID={1}, rID={2}, rBody={3}, cID={4}, cBody={5}", | 73 | world.physicsScene.Logger.ErrorFormat("{0} Attempt to build 6DOF constraint with missing bodies: wID={1}, rID={2}, rBody={3}, cID={4}, cBody={5}", |
74 | "[BULLETSIM 6DOF CONSTRAINT]", world.worldID, | 74 | LogHeader, world.worldID, obj1.ID, obj1.ptr.ToString("X"), obj2.ID, obj2.ptr.ToString("X")); |
75 | obj1.ID, obj1.ptr.ToString("X"), obj2.ID, obj2.ptr.ToString("X")); | ||
76 | m_enabled = false; | 75 | m_enabled = false; |
77 | } | 76 | } |
78 | else | 77 | else |
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs index 187951e..6d84fcc 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs | |||
@@ -132,7 +132,6 @@ public abstract class BSLinkset | |||
132 | // Cannot remove the root from a linkset. | 132 | // Cannot remove the root from a linkset. |
133 | return this; | 133 | return this; |
134 | } | 134 | } |
135 | |||
136 | RemoveChildFromLinkset(child); | 135 | RemoveChildFromLinkset(child); |
137 | } | 136 | } |
138 | 137 | ||
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs index 6c1fa2a..ecb6ad4 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs | |||
@@ -43,20 +43,16 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
43 | 43 | ||
44 | // When physical properties are changed the linkset needs to recalculate | 44 | // When physical properties are changed the linkset needs to recalculate |
45 | // its internal properties. | 45 | // its internal properties. |
46 | // This is queued in such a way that the | 46 | // This is queued in the 'post taint' queue so the |
47 | // refresh will only happen once after all the other taints are applied. | 47 | // refresh will happen once after all the other taints are applied. |
48 | public override void Refresh(BSPhysObject requestor) | 48 | public override void Refresh(BSPhysObject requestor) |
49 | { | 49 | { |
50 | // If there are no children, there are no constraints to recompute. | ||
51 | if (!HasAnyChildren) | ||
52 | return; | ||
53 | |||
54 | // Queue to happen after all the other taint processing | 50 | // Queue to happen after all the other taint processing |
55 | PhysicsScene.PostTaintObject("BSLinksetContraints.Refresh", requestor.LocalID, delegate() | 51 | PhysicsScene.PostTaintObject("BSLinksetContraints.Refresh", requestor.LocalID, delegate() |
56 | { | 52 | { |
57 | RecomputeLinksetConstraints(); | 53 | if (HasAnyChildren && IsRoot(requestor)) |
54 | RecomputeLinksetConstraints(); | ||
58 | }); | 55 | }); |
59 | |||
60 | } | 56 | } |
61 | 57 | ||
62 | // The object is going dynamic (physical). Do any setup necessary | 58 | // The object is going dynamic (physical). Do any setup necessary |
@@ -71,9 +67,10 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
71 | return false; | 67 | return false; |
72 | } | 68 | } |
73 | 69 | ||
74 | // The object is going static (non-physical). Do any setup necessary | 70 | // The object is going static (non-physical). Do any setup necessary for a static linkset. |
75 | // for a static linkset. | ||
76 | // Return 'true' if any properties updated on the passed object. | 71 | // Return 'true' if any properties updated on the passed object. |
72 | // This doesn't normally happen -- OpenSim removes the objects from the physical | ||
73 | // world if it is a static linkset. | ||
77 | // Called at taint-time! | 74 | // Called at taint-time! |
78 | public override bool MakeStatic(BSPhysObject child) | 75 | public override bool MakeStatic(BSPhysObject child) |
79 | { | 76 | { |
@@ -87,21 +84,21 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
87 | // Nothing to do for constraints on property updates | 84 | // Nothing to do for constraints on property updates |
88 | } | 85 | } |
89 | 86 | ||
90 | // Routine used when rebuilding the body of the root of the linkset | 87 | // Routine called when rebuilding the body of some member of the linkset. |
91 | // Destroy all the constraints have have been made to root. | 88 | // Destroy all the constraints have have been made to root and set |
92 | // This is called when the root body is changing. | 89 | // up to rebuild the constraints before the next simulation step. |
93 | // Returns 'true' of something eas actually removed and would need restoring | 90 | // Returns 'true' of something was actually removed and would need restoring |
94 | // Called at taint-time!! | 91 | // Called at taint-time!! |
95 | public override bool RemoveBodyDependencies(BSPrim child) | 92 | public override bool RemoveBodyDependencies(BSPrim child) |
96 | { | 93 | { |
97 | bool ret = false; | 94 | bool ret = false; |
98 | 95 | ||
96 | DetailLog("{0},BSLinksetConstraint.RemoveBodyDependencies,removeChildrenForRoot,rID={1},rBody={2}", | ||
97 | child.LocalID, LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X")); | ||
98 | |||
99 | lock (m_linksetActivityLock) | 99 | lock (m_linksetActivityLock) |
100 | { | 100 | { |
101 | // Just undo all the constraints for this linkset. Rebuild at the end of the step. | 101 | // Just undo all the constraints for this linkset. Rebuild at the end of the step. |
102 | DetailLog("{0},BSLinksetConstraint.RemoveBodyDependencies,removeChildrenForRoot,rID={1},rBody={2}", | ||
103 | child.LocalID, LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X")); | ||
104 | |||
105 | ret = PhysicallyUnlinkAllChildrenFromRoot(LinksetRoot); | 102 | ret = PhysicallyUnlinkAllChildrenFromRoot(LinksetRoot); |
106 | // Cause the constraints, et al to be rebuilt before the next simulation step. | 103 | // Cause the constraints, et al to be rebuilt before the next simulation step. |
107 | Refresh(LinksetRoot); | 104 | Refresh(LinksetRoot); |
@@ -114,13 +111,12 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
114 | // Called at taint-time!! | 111 | // Called at taint-time!! |
115 | public override void RestoreBodyDependencies(BSPrim child) | 112 | public override void RestoreBodyDependencies(BSPrim child) |
116 | { | 113 | { |
117 | // The Refresh operation will build any missing constraints. | 114 | // The Refresh operation queued by RemoveBodyDependencies() will build any missing constraints. |
118 | } | 115 | } |
119 | 116 | ||
120 | // ================================================================ | 117 | // ================================================================ |
121 | // Below this point is internal magic | ||
122 | 118 | ||
123 | // I am the root of a linkset and a new child is being added | 119 | // Add a new child to the linkset. |
124 | // Called while LinkActivity is locked. | 120 | // Called while LinkActivity is locked. |
125 | protected override void AddChildToLinkset(BSPhysObject child) | 121 | protected override void AddChildToLinkset(BSPhysObject child) |
126 | { | 122 | { |
@@ -131,7 +127,7 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
131 | BSPhysObject rootx = LinksetRoot; // capture the root as of now | 127 | BSPhysObject rootx = LinksetRoot; // capture the root as of now |
132 | BSPhysObject childx = child; | 128 | BSPhysObject childx = child; |
133 | 129 | ||
134 | DetailLog("{0},AddChildToLinkset,call,child={1}", LinksetRoot.LocalID, child.LocalID); | 130 | DetailLog("{0},BSLinksetConstraints.AddChildToLinkset,call,child={1}", LinksetRoot.LocalID, child.LocalID); |
135 | 131 | ||
136 | // Cause constraints and assorted properties to be recomputed before the next simulation step. | 132 | // Cause constraints and assorted properties to be recomputed before the next simulation step. |
137 | Refresh(LinksetRoot); | 133 | Refresh(LinksetRoot); |
@@ -150,7 +146,7 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
150 | RemoveChildFromLinkset(pchild); | 146 | RemoveChildFromLinkset(pchild); |
151 | } | 147 | } |
152 | 148 | ||
153 | // I am the root of a linkset and one of my children is being removed. | 149 | // Remove the specified child from the linkset. |
154 | // Safe to call even if the child is not really in my linkset. | 150 | // Safe to call even if the child is not really in my linkset. |
155 | protected override void RemoveChildFromLinkset(BSPhysObject child) | 151 | protected override void RemoveChildFromLinkset(BSPhysObject child) |
156 | { | 152 | { |
@@ -159,12 +155,12 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
159 | BSPhysObject rootx = LinksetRoot; // capture the root and body as of now | 155 | BSPhysObject rootx = LinksetRoot; // capture the root and body as of now |
160 | BSPhysObject childx = child; | 156 | BSPhysObject childx = child; |
161 | 157 | ||
162 | DetailLog("{0},RemoveChildFromLinkset,call,rID={1},rBody={2},cID={3},cBody={4}", | 158 | DetailLog("{0},BSLinksetConstraints.RemoveChildFromLinkset,call,rID={1},rBody={2},cID={3},cBody={4}", |
163 | childx.LocalID, | 159 | childx.LocalID, |
164 | rootx.LocalID, rootx.BSBody.ptr.ToString("X"), | 160 | rootx.LocalID, rootx.BSBody.ptr.ToString("X"), |
165 | childx.LocalID, childx.BSBody.ptr.ToString("X")); | 161 | childx.LocalID, childx.BSBody.ptr.ToString("X")); |
166 | 162 | ||
167 | PhysicsScene.TaintedObject("RemoveChildFromLinkset", delegate() | 163 | PhysicsScene.TaintedObject("BSLinksetConstraints.RemoveChildFromLinkset", delegate() |
168 | { | 164 | { |
169 | PhysicallyUnlinkAChildFromRoot(rootx, childx); | 165 | PhysicallyUnlinkAChildFromRoot(rootx, childx); |
170 | }); | 166 | }); |
@@ -173,7 +169,7 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
173 | } | 169 | } |
174 | else | 170 | else |
175 | { | 171 | { |
176 | // This will happen if we remove the root of the linkset first. Non-fatal occurance. | 172 | // Non-fatal occurance. |
177 | // PhysicsScene.Logger.ErrorFormat("{0}: Asked to remove child from linkset that was not in linkset", LogHeader); | 173 | // PhysicsScene.Logger.ErrorFormat("{0}: Asked to remove child from linkset that was not in linkset", LogHeader); |
178 | } | 174 | } |
179 | return; | 175 | return; |
@@ -215,7 +211,7 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
215 | /* NOTE: below is an attempt to build constraint with full frame computation, etc. | 211 | /* NOTE: below is an attempt to build constraint with full frame computation, etc. |
216 | * Using the midpoint is easier since it lets the Bullet code manipulate the transforms | 212 | * Using the midpoint is easier since it lets the Bullet code manipulate the transforms |
217 | * of the objects. | 213 | * of the objects. |
218 | * Code left as a warning to future programmers. | 214 | * Code left for future programmers. |
219 | // ================================================================================== | 215 | // ================================================================================== |
220 | // relative position normalized to the root prim | 216 | // relative position normalized to the root prim |
221 | OMV.Quaternion invThisOrientation = OMV.Quaternion.Inverse(rootPrim.Orientation); | 217 | OMV.Quaternion invThisOrientation = OMV.Quaternion.Inverse(rootPrim.Orientation); |
@@ -225,8 +221,6 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
225 | OMV.Quaternion childRelativeRotation = invThisOrientation * childPrim.Orientation; | 221 | OMV.Quaternion childRelativeRotation = invThisOrientation * childPrim.Orientation; |
226 | OMV.Quaternion inverseChildRelativeRotation = OMV.Quaternion.Inverse(childRelativeRotation); | 222 | OMV.Quaternion inverseChildRelativeRotation = OMV.Quaternion.Inverse(childRelativeRotation); |
227 | 223 | ||
228 | // create a constraint that allows no freedom of movement between the two objects | ||
229 | // http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=4818 | ||
230 | DetailLog("{0},BSLinksetConstraint.PhysicallyLinkAChildToRoot,taint,root={1},child={2}", rootPrim.LocalID, rootPrim.LocalID, childPrim.LocalID); | 224 | DetailLog("{0},BSLinksetConstraint.PhysicallyLinkAChildToRoot,taint,root={1},child={2}", rootPrim.LocalID, rootPrim.LocalID, childPrim.LocalID); |
231 | BS6DofConstraint constrain = new BS6DofConstraint( | 225 | BS6DofConstraint constrain = new BS6DofConstraint( |
232 | PhysicsScene.World, rootPrim.Body, childPrim.Body, | 226 | PhysicsScene.World, rootPrim.Body, childPrim.Body, |
@@ -234,11 +228,6 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
234 | OMV.Quaternion.Inverse(rootPrim.Orientation), | 228 | OMV.Quaternion.Inverse(rootPrim.Orientation), |
235 | OMV.Vector3.Zero, | 229 | OMV.Vector3.Zero, |
236 | OMV.Quaternion.Inverse(childPrim.Orientation), | 230 | OMV.Quaternion.Inverse(childPrim.Orientation), |
237 | // A point half way between the parent and child | ||
238 | // childRelativePosition/2, | ||
239 | // childRelativeRotation, | ||
240 | // childRelativePosition/2, | ||
241 | // inverseChildRelativeRotation, | ||
242 | true, | 231 | true, |
243 | true | 232 | true |
244 | ); | 233 | ); |
@@ -264,9 +253,9 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
264 | return constrain; | 253 | return constrain; |
265 | } | 254 | } |
266 | 255 | ||
267 | // Remove linkage between myself and a particular child | 256 | // Remove linkage between the linkset root and a particular child |
268 | // The root and child bodies are passed in because we need to remove the constraint between | 257 | // The root and child bodies are passed in because we need to remove the constraint between |
269 | // the bodies that were at unlink time. | 258 | // the bodies that were present at unlink time. |
270 | // Called at taint time! | 259 | // Called at taint time! |
271 | private bool PhysicallyUnlinkAChildFromRoot(BSPhysObject rootPrim, BSPhysObject childPrim) | 260 | private bool PhysicallyUnlinkAChildFromRoot(BSPhysObject rootPrim, BSPhysObject childPrim) |
272 | { | 261 | { |
@@ -288,44 +277,36 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
288 | } | 277 | } |
289 | 278 | ||
290 | // Remove linkage between myself and any possible children I might have. | 279 | // Remove linkage between myself and any possible children I might have. |
280 | // Returns 'true' of any constraints were destroyed. | ||
291 | // Called at taint time! | 281 | // Called at taint time! |
292 | private bool PhysicallyUnlinkAllChildrenFromRoot(BSPhysObject rootPrim) | 282 | private bool PhysicallyUnlinkAllChildrenFromRoot(BSPhysObject rootPrim) |
293 | { | 283 | { |
294 | DetailLog("{0},BSLinksetConstraint.PhysicallyUnlinkAllChildren,taint", rootPrim.LocalID); | 284 | DetailLog("{0},BSLinksetConstraint.PhysicallyUnlinkAllChildren,taint", rootPrim.LocalID); |
295 | bool ret = false; | ||
296 | 285 | ||
297 | if (PhysicsScene.Constraints.RemoveAndDestroyConstraint(rootPrim.BSBody)) | 286 | return PhysicsScene.Constraints.RemoveAndDestroyConstraint(rootPrim.BSBody); |
298 | { | ||
299 | ret = true; | ||
300 | } | ||
301 | return ret; | ||
302 | } | 287 | } |
303 | 288 | ||
304 | // Call each of the constraints that make up this linkset and recompute the | 289 | // Call each of the constraints that make up this linkset and recompute the |
305 | // various transforms and variables. Create constraints of not created yet. | 290 | // various transforms and variables. Create constraints of not created yet. |
306 | // Called before the simulation step to make sure the constraint based linkset | 291 | // Called before the simulation step to make sure the constraint based linkset |
307 | // is all initialized. | 292 | // is all initialized. |
308 | // Must only be called at taint time!! | 293 | // Called at taint time!! |
309 | private void RecomputeLinksetConstraints() | 294 | private void RecomputeLinksetConstraints() |
310 | { | 295 | { |
311 | float linksetMass = LinksetMass; | 296 | float linksetMass = LinksetMass; |
312 | LinksetRoot.UpdatePhysicalMassProperties(linksetMass); | 297 | LinksetRoot.UpdatePhysicalMassProperties(linksetMass); |
313 | 298 | ||
314 | // For a multiple object linkset, set everybody's center of mass to the set's center of mass | 299 | // DEBUG: see of inter-linkset collisions are causing problems |
315 | OMV.Vector3 centerOfMass = ComputeLinksetCenterOfMass(); | ||
316 | BulletSimAPI.SetCenterOfMassByPosRot2(LinksetRoot.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
317 | |||
318 | // BulletSimAPI.SetCollisionFilterMask2(LinksetRoot.BSBody.ptr, | 300 | // BulletSimAPI.SetCollisionFilterMask2(LinksetRoot.BSBody.ptr, |
319 | // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); | 301 | // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); |
320 | DetailLog("{0},BSLinksetConstraint.RecomputeLinksetConstraints,setCenterOfMass,COM={1},rBody={2},linksetMass={3}", | 302 | DetailLog("{0},BSLinksetConstraint.RecomputeLinksetConstraints,setCenterOfMass,rBody={1},linksetMass={2}", |
321 | LinksetRoot.LocalID, centerOfMass, LinksetRoot.BSBody.ptr.ToString("X"), linksetMass); | 303 | LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X"), linksetMass); |
322 | 304 | ||
323 | foreach (BSPhysObject child in m_children) | 305 | foreach (BSPhysObject child in m_children) |
324 | { | 306 | { |
325 | // A child in the linkset physically shows the mass of the whole linkset. | 307 | // A child in the linkset physically shows the mass of the whole linkset. |
326 | // This allows Bullet to apply enough force on the child to move the whole linkset. | 308 | // This allows Bullet to apply enough force on the child to move the whole linkset. |
327 | // (Also do the mass stuff before recomputing the constraint so mass is not zero.) | 309 | // (Also do the mass stuff before recomputing the constraint so mass is not zero.) |
328 | BulletSimAPI.SetCenterOfMassByPosRot2(child.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
329 | child.UpdatePhysicalMassProperties(linksetMass); | 310 | child.UpdatePhysicalMassProperties(linksetMass); |
330 | 311 | ||
331 | BSConstraint constrain; | 312 | BSConstraint constrain; |
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs index ad09a61..7851a40 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs | |||
@@ -634,7 +634,7 @@ public sealed class BSPrim : BSPhysObject | |||
634 | // had been automatically disabled when the mass was set to zero. | 634 | // had been automatically disabled when the mass was set to zero. |
635 | Linkset.Refresh(this); | 635 | Linkset.Refresh(this); |
636 | 636 | ||
637 | DetailLog("{0},BSPrim.UpdatePhysicalParameters,exit,static={1},solid={2},mass={3},collide={4},cf={5:X},body={6},shape={7}", | 637 | DetailLog("{0},BSPrim.UpdatePhysicalParameters,taintExit,static={1},solid={2},mass={3},collide={4},cf={5:X},body={6},shape={7}", |
638 | LocalID, IsStatic, IsSolid, _mass, SubscribedEvents(), CurrentCollisionFlags, BSBody, BSShape); | 638 | LocalID, IsStatic, IsSolid, _mass, SubscribedEvents(), CurrentCollisionFlags, BSBody, BSShape); |
639 | } | 639 | } |
640 | 640 | ||
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs index 1c0e6f5..1219fc0 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs | |||
@@ -109,7 +109,7 @@ public sealed class BSShapeCollection : IDisposable | |||
109 | prim.BSShape, shapeData, bodyCallback); | 109 | prim.BSShape, shapeData, bodyCallback); |
110 | ret = newGeom || newBody; | 110 | ret = newGeom || newBody; |
111 | } | 111 | } |
112 | DetailLog("{0},BSShapeCollection.GetBodyAndShape,force={1},ret={2},body={3},shape={4}", | 112 | DetailLog("{0},BSShapeCollection.GetBodyAndShape,taintExit,force={1},ret={2},body={3},shape={4}", |
113 | prim.LocalID, forceRebuild, ret, prim.BSBody, prim.BSShape); | 113 | prim.LocalID, forceRebuild, ret, prim.BSBody, prim.BSShape); |
114 | 114 | ||
115 | return ret; | 115 | return ret; |