Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/physics/src/orxonox/objects/worldentities/WorldEntity.cc @ 2433

Last change on this file since 2433 was 2433, checked in by rgrieder, 15 years ago

Replaced most Exception throwing considering physics with warnings. Actions are simply ignored then.

  • Property svn:eol-style set to native
File size: 24.0 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Fabian 'x3n' Landau
24 *      Reto Grieder (physics)
25 *   Co-authors:
26 *      ...
27 *
28 */
29
30#include "OrxonoxStableHeaders.h"
31#include "WorldEntity.h"
32
33#include <cassert>
34#include <OgreSceneNode.h>
35#include <OgreSceneManager.h>
36#include "BulletDynamics/Dynamics/btRigidBody.h"
37
38#include "util/Exception.h"
39#include "util/Convert.h"
40#include "core/CoreIncludes.h"
41#include "core/XMLPort.h"
42
43#include "objects/Scene.h"
44#include "objects/collisionshapes/CompoundCollisionShape.h"
45
46namespace orxonox
47{
48    const Vector3 WorldEntity::FRONT = Vector3::NEGATIVE_UNIT_Z;
49    const Vector3 WorldEntity::BACK  = Vector3::UNIT_Z;
50    const Vector3 WorldEntity::LEFT  = Vector3::NEGATIVE_UNIT_X;
51    const Vector3 WorldEntity::RIGHT = Vector3::UNIT_X;
52    const Vector3 WorldEntity::DOWN  = Vector3::NEGATIVE_UNIT_Y;
53    const Vector3 WorldEntity::UP    = Vector3::UNIT_Y;
54
55    WorldEntity::WorldEntity(BaseObject* creator) : BaseObject(creator), network::Synchronisable(creator)
56    {
57        RegisterObject(WorldEntity);
58
59        assert(this->getScene());
60        assert(this->getScene()->getRootSceneNode());
61
62        this->node_ = this->getScene()->getRootSceneNode()->createChildSceneNode();
63
64        this->parent_ = 0;
65        this->parentID_ = (unsigned int)-1;
66
67        this->node_->setPosition(Vector3::ZERO);
68        this->node_->setOrientation(Quaternion::IDENTITY);
69
70        // Default behaviour does not include physics
71        this->physicalBody_ = 0;
72        this->bPhysicsActive_ = false;
73        this->collisionShape_ = new CompoundCollisionShape(this);
74        this->collisionType_ = None;
75        this->collisionTypeSynchronised_ = None;
76        this->mass_           = 0;
77        this->childrenMass_   = 0;
78        // Use bullet default values
79        this->restitution_    = 0;
80        this->angularFactor_  = 1;
81        this->linearDamping_  = 0;
82        this->angularDamping_ = 0;
83        this->friction_       = 0.5;
84
85        this->registerVariables();
86    }
87
88    WorldEntity::~WorldEntity()
89    {
90        if (this->isInitialized())
91        {
92            this->node_->detachAllObjects();
93            if (this->getScene()->getSceneManager())
94                this->getScene()->getSceneManager()->destroySceneNode(this->node_->getName());
95
96            // TODO: Detach from parent and detach all children.
97
98            if (this->physicalBody_)
99            {
100                this->deactivatePhysics();
101                delete this->physicalBody_;
102            }
103            delete this->collisionShape_;
104        }
105    }
106
107    void WorldEntity::XMLPort(Element& xmlelement, XMLPort::Mode mode)
108    {
109        SUPER(WorldEntity, XMLPort, xmlelement, mode);
110
111        XMLPortParamTemplate(WorldEntity, "position",    setPosition,    getPosition,    xmlelement, mode, const Vector3&);
112        XMLPortParamTemplate(WorldEntity, "orientation", setOrientation, getOrientation, xmlelement, mode, const Quaternion&);
113        XMLPortParamTemplate(WorldEntity, "scale3D",     setScale3D,     getScale3D,     xmlelement, mode, const Vector3&);
114        XMLPortParam        (WorldEntity, "scale",       setScale,       getScale,       xmlelement, mode);
115        XMLPortParamLoadOnly(WorldEntity, "lookat",      lookAt_xmlport,       xmlelement, mode);
116        XMLPortParamLoadOnly(WorldEntity, "direction",   setDirection_xmlport, xmlelement, mode);
117        XMLPortParamLoadOnly(WorldEntity, "yaw",         yaw_xmlport,          xmlelement, mode);
118        XMLPortParamLoadOnly(WorldEntity, "pitch",       pitch_xmlport,        xmlelement, mode);
119        XMLPortParamLoadOnly(WorldEntity, "roll",        roll_xmlport,         xmlelement, mode);
120
121        // Physics
122        XMLPortParam(WorldEntity, "collisionType",  setCollisionTypeStr, getCollisionTypeStr, xmlelement, mode);
123        XMLPortParam(WorldEntity, "mass",           setMass,             getMass,             xmlelement, mode);
124        XMLPortParam(WorldEntity, "restitution",    setRestitution,      getRestitution,      xmlelement, mode);
125        XMLPortParam(WorldEntity, "angularFactor",  setAngularFactor,    getAngularFactor,    xmlelement, mode);
126        XMLPortParam(WorldEntity, "linearDamping",  setLinearDamping,    getLinearDamping,    xmlelement, mode);
127        XMLPortParam(WorldEntity, "angularDamping", setAngularDamping,   getAngularDamping,   xmlelement, mode);
128        XMLPortParam(WorldEntity, "friction",       setFriction,         getFriction,         xmlelement, mode);
129
130        // Other attached WorldEntities
131        XMLPortObject(WorldEntity, WorldEntity, "attached", attach, getAttachedObject, xmlelement, mode);
132        // Attached collision shapes
133        XMLPortObject(WorldEntity, CollisionShape, "collisionShapes", attachCollisionShape, getAttachedCollisionShape, xmlelement, mode);
134    }
135
136    void WorldEntity::registerVariables()
137    {
138        REGISTERDATA(this->bActive_,        network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::changedActivity));
139        REGISTERDATA(this->bVisible_,       network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::changedVisibility));
140
141        REGISTERDATA(this->getScale3D(),    network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::scaleChanged));
142
143        REGISTERDATA((int&)this->collisionTypeSynchronised_,
144                                            network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::collisionTypeChanged));
145        REGISTERDATA(this->mass_,           network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::massChanged));
146        REGISTERDATA(this->restitution_,    network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::restitutionChanged));
147        REGISTERDATA(this->angularFactor_,  network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::angularFactorChanged));
148        REGISTERDATA(this->linearDamping_,  network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::linearDampingChanged));
149        REGISTERDATA(this->angularDamping_, network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::angularDampingChanged));
150        REGISTERDATA(this->friction_,       network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::frictionChanged));
151        REGISTERDATA(this->bPhysicsActiveSynchronised_,
152                                            network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::physicsActivityChanged));
153
154        REGISTERDATA(this->parentID_,       network::direction::toclient, new network::NetworkCallback<WorldEntity>(this, &WorldEntity::parentChanged));
155    }
156
157    void WorldEntity::parentChanged()
158    {
159        WorldEntity* parent = dynamic_cast<WorldEntity*>(Synchronisable::getSynchronisable(this->parentID_));
160        if (parent)
161            this->attachToParent(parent);
162    }
163
164    void WorldEntity::collisionTypeChanged()
165    {
166        if (this->collisionTypeSynchronised_ != Dynamic &&
167            this->collisionTypeSynchronised_ != Kinematic &&
168            this->collisionTypeSynchronised_ != Static &&
169            this->collisionTypeSynchronised_ != None)
170        {
171            CCOUT(1) << "Error when collsion Type was received over network. Unknown enum value:" << this->collisionTypeSynchronised_ << std::endl;
172        }
173        else if (this->collisionTypeSynchronised_ != collisionType_)
174        {
175            if (this->parent_)
176                CCOUT(2) << "Warning: Network connection tried to set the collision type of an attached WE. Ignoring." << std::endl;
177            else
178                this->setCollisionType(this->collisionTypeSynchronised_);
179        }
180    }
181
182    void WorldEntity::physicsActivityChanged()
183    {
184        if (this->bPhysicsActiveSynchronised_)
185            this->activatePhysics();
186        else
187            this->deactivatePhysics();
188    }
189
190    void WorldEntity::attach(WorldEntity* object)
191    {
192        // check first whether attaching is even allowed
193        if (object->hasPhysics())
194        {
195            if (!this->hasPhysics())
196            {
197                COUT(2) << "Warning: Cannot attach a physical object to a non physical one." << std::endl;
198                return;
199            }
200            else if (object->isDynamic())
201            {
202                COUT(2) << "Warning: Cannot attach a dynamic object to a WorldEntity." << std::endl;
203                return;
204            }
205            else if (object->isKinematic() && this->isDynamic())
206            {
207                COUT(2) << "Warning: Cannot attach a kinematic object to a dynamic one." << std::endl;
208                return;
209            }
210            else if (object->isKinematic())
211            {
212                COUT(2) << "Warning: Cannot attach a kinematic object to a static or kinematic one: Not yet implemented." << std::endl;
213                return;
214            }
215            else
216            {
217                object->deactivatePhysics();
218            }
219        }
220
221        if (object->getParent())
222            object->detachFromParent();
223        else
224        {
225            Ogre::Node* parent = object->node_->getParent();
226            if (parent)
227                parent->removeChild(object->node_);
228        }
229
230        this->node_->addChild(object->node_);
231        this->children_.insert(object);
232        object->parent_ = this;
233        object->parentID_ = this->getObjectID();
234
235        // collision shapes
236        this->attachCollisionShape(object->getCollisionShape());
237        // mass
238        this->childrenMass_ += object->getMass();
239        recalculateMassProps();
240    }
241
242    void WorldEntity::detach(WorldEntity* object)
243    {
244        // collision shapes
245        this->detachCollisionShape(object->getCollisionShape());
246        // mass
247        if (object->getMass() > 0.0f)
248        {
249            this->childrenMass_ -= object->getMass();
250            recalculateMassProps();
251        }
252
253        this->node_->removeChild(object->node_);
254        this->children_.erase(object);
255        object->parent_ = 0;
256        object->parentID_ = (unsigned int)-1;
257//        this->getScene()->getRootSceneNode()->addChild(object->node_);
258
259        // Note: It is possible that the object has physics but was disabled when attaching
260        object->activatePhysics();
261    }
262
263    WorldEntity* WorldEntity::getAttachedObject(unsigned int index) const
264    {
265        unsigned int i = 0;
266        for (std::set<WorldEntity*>::const_iterator it = this->children_.begin(); it != this->children_.end(); ++it)
267        {
268            if (i == index)
269                return (*it);
270            ++i;
271        }
272        return 0;
273    }
274
275    void WorldEntity::attachOgreObject(Ogre::MovableObject* object)
276    {
277        this->node_->attachObject(object);
278    }
279
280    void WorldEntity::detachOgreObject(Ogre::MovableObject* object)
281    {
282        this->node_->detachObject(object);
283    }
284
285    Ogre::MovableObject* WorldEntity::detachOgreObject(const Ogre::String& name)
286    {
287        return this->node_->detachObject(name);
288    }
289
290    void WorldEntity::attachCollisionShape(CollisionShape* shape)
291    {
292        this->collisionShape_->addChildShape(shape);
293        // Note: this->collisionShape_ already notifies us of any changes.
294    }
295
296    void WorldEntity::detachCollisionShape(CollisionShape* shape)
297    {
298        this->collisionShape_->removeChildShape(shape);
299        // Note: this->collisionShape_ already notifies us of any changes.
300    }
301
302    CollisionShape* WorldEntity::getAttachedCollisionShape(unsigned int index) const
303    {
304        return this->collisionShape_->getChildShape(index);
305    }
306
307    void WorldEntity::activatePhysics()
308    {
309        if (this->isActive() && this->hasPhysics() && !this->isPhysicsActive() && !this->parent_)
310        {
311            this->getScene()->addRigidBody(this->physicalBody_);
312            this->bPhysicsActive_ = true;
313        }
314    }
315
316    void WorldEntity::deactivatePhysics()
317    {
318        if (this->isPhysicsActive())
319        {
320            this->getScene()->removeRigidBody(this->physicalBody_);
321            this->bPhysicsActive_ = false;
322        }
323    }
324
325    bool WorldEntity::addedToPhysicalWorld() const
326    {
327        return this->physicalBody_ && this->physicalBody_->isInWorld();
328    }
329
330#ifndef _NDEBUG
331    const Vector3& WorldEntity::getPosition() const
332    {
333        return this->node_->getPosition();
334    }
335
336    const Quaternion& WorldEntity::getOrientation() const
337    {
338        return this->node_->getOrientation();
339    }
340
341    const Vector3& WorldEntity::getScale3D() const
342    {
343        return this->node_->getScale();
344    }
345#endif
346
347    const Vector3& WorldEntity::getWorldPosition() const
348    {
349        return this->node_->_getDerivedPosition();
350    }
351
352    const Quaternion& WorldEntity::getWorldOrientation() const
353    {
354        return this->node_->_getDerivedOrientation();
355    }
356
357    void WorldEntity::translate(const Vector3& distance, TransformSpace::Space relativeTo)
358    {
359        switch (relativeTo)
360        {
361        case TransformSpace::Local:
362            // position is relative to parent so transform downwards
363            this->setPosition(this->getPosition() + this->getOrientation() * distance);
364            break;
365        case TransformSpace::Parent:
366            this->setPosition(this->getPosition() + distance);
367            break;
368        case TransformSpace::World:
369            // position is relative to parent so transform upwards
370            if (this->node_->getParent())
371                setPosition(getPosition() + (node_->getParent()->_getDerivedOrientation().Inverse() * distance)
372                    / node_->getParent()->_getDerivedScale());
373            else
374                this->setPosition(this->getPosition() + distance);
375            break;
376        }
377    }
378
379    void WorldEntity::rotate(const Quaternion& rotation, TransformSpace::Space relativeTo)
380    {
381        switch(relativeTo)
382        {
383        case TransformSpace::Local:
384            this->setOrientation(this->getOrientation() * rotation);
385            break;
386        case TransformSpace::Parent:
387            // Rotations are normally relative to local axes, transform up
388            this->setOrientation(rotation * this->getOrientation());
389            break;
390        case TransformSpace::World:
391            // Rotations are normally relative to local axes, transform up
392            this->setOrientation(this->getOrientation() * this->getWorldOrientation().Inverse()
393                * rotation * this->getWorldOrientation());
394            break;
395        }
396    }
397
398    void WorldEntity::lookAt(const Vector3& target, TransformSpace::Space relativeTo, const Vector3& localDirectionVector)
399    {
400        Vector3 origin;
401        switch (relativeTo)
402        {
403        case TransformSpace::Local:
404            origin = Vector3::ZERO;
405            break;
406        case TransformSpace::Parent:
407            origin = this->getPosition();
408            break;
409        case TransformSpace::World:
410            origin = this->getWorldPosition();
411            break;
412        }
413        this->setDirection(target - origin, relativeTo, localDirectionVector);
414    }
415
416    void WorldEntity::setDirection(const Vector3& direction, TransformSpace::Space relativeTo, const Vector3& localDirectionVector)
417    {
418        Quaternion savedOrientation(this->getOrientation());
419        Ogre::Node::TransformSpace ogreRelativeTo;
420        switch (relativeTo)
421        {
422        case TransformSpace::Local:
423            ogreRelativeTo = Ogre::Node::TS_LOCAL; break;
424        case TransformSpace::Parent:
425            ogreRelativeTo = Ogre::Node::TS_PARENT; break;
426        case TransformSpace::World:
427            ogreRelativeTo = Ogre::Node::TS_WORLD; break;
428        }
429        this->node_->setDirection(direction, ogreRelativeTo, localDirectionVector);
430        Quaternion newOrientation(this->node_->getOrientation());
431        this->node_->setOrientation(savedOrientation);
432        this->setOrientation(newOrientation);
433    }
434
435    void WorldEntity::setScale3D(const Vector3& scale)
436    {
437        if (this->hasPhysics())
438        {
439            CCOUT(2) << "Warning: Cannot set the scale of a physical object: Not yet implemented." << std::endl;
440            return;
441        }
442
443        this->node_->setScale(scale);
444    }
445
446    void WorldEntity::setCollisionType(CollisionType type)
447    {
448        // If we are already attached to a parent, this would be a bad idea..
449        if (this->parent_)
450        {
451            CCOUT(2) << "Warning: Cannot set the collision type of a WorldEntity with a parent." << std::endl;
452            return;
453        }
454        else if (this->addedToPhysicalWorld())
455        {
456            CCOUT(2) << "Warning: Cannot set the collision type at run time." << std::endl;
457            return;
458        }
459
460        // Check for type legality. Could be StaticEntity or MobileEntity
461        if (!this->isCollisionTypeLegal(type))
462            return; // exception gets issued anyway
463        if (type != None && !this->getScene()->hasPhysics())
464        {
465            CCOUT(2) << "Warning: Cannot have physical bodies in a non physical scene." << std::endl;
466            return;
467        }
468
469        // Check whether we have to create or destroy.
470        if (type != None && this->collisionType_ == None)
471        {
472            // Check whether there was some scaling applied.
473            if (!this->node_->getScale().positionEquals(Vector3(1, 1, 1), 0.001))
474            {
475                CCOUT(2) << "Warning: Cannot create a physical body if there is scaling applied to the node: Not yet implemented." << std::endl;
476                return;
477            }
478
479            // Create new rigid body
480            btRigidBody::btRigidBodyConstructionInfo bodyConstructionInfo(0, this, this->collisionShape_->getCollisionShape());
481            this->physicalBody_ = new btRigidBody(bodyConstructionInfo);
482            this->physicalBody_->setUserPointer(this);
483            this->physicalBody_->setActivationState(DISABLE_DEACTIVATION);
484        }
485        else if (type == None && this->collisionType_ != None)
486        {
487            // Destroy rigid body
488            assert(this->physicalBody_);
489            deactivatePhysics();
490            delete this->physicalBody_;
491            this->physicalBody_ = 0;
492            this->collisionType_ = None;
493            this->collisionTypeSynchronised_ = None;
494            return;
495        }
496
497        // Change type
498        switch (type)
499        {
500        case Dynamic:
501            this->physicalBody_->setCollisionFlags(this->physicalBody_->getCollisionFlags() & !(btCollisionObject::CF_STATIC_OBJECT | btCollisionObject::CF_KINEMATIC_OBJECT));
502            break;
503        case Kinematic:
504            this->physicalBody_->setCollisionFlags(this->physicalBody_->getCollisionFlags() & !btCollisionObject::CF_STATIC_OBJECT | btCollisionObject::CF_KINEMATIC_OBJECT);
505            break;
506        case Static:
507            this->physicalBody_->setCollisionFlags(this->physicalBody_->getCollisionFlags() & !btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT);
508            break;
509        case None:
510            return; // this->collisionType_ was None too
511        }
512
513        // Only sets this->collisionShape_
514        // However the assertion is to ensure that the internal bullet setting is right
515        updateCollisionType();
516        assert(this->collisionType_ == type);
517
518        // update mass and inertia tensor
519        recalculateMassProps();
520        resetPhysicsProps();
521        activatePhysics();
522    }
523
524    void WorldEntity::setCollisionTypeStr(const std::string& typeStr)
525    {
526        std::string typeStrLower = getLowercase(typeStr);
527        CollisionType type;
528        if (typeStrLower == "dynamic")
529            type = Dynamic;
530        else if (typeStrLower == "static")
531            type = Static;
532        else if (typeStrLower == "kinematic")
533            type = Kinematic;
534        else if (typeStrLower == "none")
535            type = None;
536        else
537            ThrowException(ParseError, std::string("Attempting to set an unknown collision type: '") + typeStr + "'.");
538        this->setCollisionType(type);
539    }
540
541    std::string WorldEntity::getCollisionTypeStr() const
542    {
543        switch (this->getCollisionType())
544        {
545            case Dynamic:
546                return "dynamic";
547            case Kinematic:
548                return "kinematic";
549            case Static:
550                return "static";
551            case None:
552                return "none";
553            default:
554                assert(false);
555                return "";
556        }
557    }
558
559    void WorldEntity::updateCollisionType()
560    {
561        if (!this->physicalBody_)
562            this->collisionType_ = None;
563        else if (this->physicalBody_->isKinematicObject())
564            this->collisionType_ = Kinematic;
565        else if (this->physicalBody_->isStaticObject())
566            this->collisionType_ = Static;
567        else
568            this->collisionType_ = Dynamic;
569        this->collisionTypeSynchronised_ = this->collisionType_;
570    }
571
572    void WorldEntity::notifyChildMassChanged() // Called by a child WE
573    {
574        // Note: CollisionShape changes of a child get handled over the internal CompoundCollisionShape already
575        // Recalculate mass
576        this->childrenMass_ = 0.0f;
577        for (std::set<WorldEntity*>::const_iterator it = this->children_.begin(); it != this->children_.end(); ++it)
578            this->childrenMass_ += (*it)->getMass();
579        recalculateMassProps();
580        // Notify parent WE
581        if (this->parent_)
582            parent_->notifyChildMassChanged();
583    }
584
585    void WorldEntity::notifyCollisionShapeChanged() // called by this->collisionShape_
586    {
587        if (hasPhysics())
588        {
589            // Bullet doesn't like sudden changes of the collision shape, so we remove and add it again
590            if (this->addedToPhysicalWorld())
591            {
592                this->deactivatePhysics();
593                this->physicalBody_->setCollisionShape(this->collisionShape_->getCollisionShape());
594                this->activatePhysics();
595            }
596            else
597                this->physicalBody_->setCollisionShape(this->collisionShape_->getCollisionShape());
598        }
599        recalculateMassProps();
600    }
601
602    void WorldEntity::recalculateMassProps()
603    {
604        if (this->hasPhysics())
605        {
606            if (this->isStatic())
607            {
608                // Just set everything to zero
609                this->physicalBody_->setMassProps(0.0f, btVector3(0, 0, 0));
610            }
611            else if ((this->mass_ + this->childrenMass_) == 0.0f)
612            {
613                // Use default values to avoid very large or very small values
614                CCOUT(4) << "Warning: Setting the internal physical mass to 1.0 because mass_ is 0.0." << std::endl;
615                this->physicalBody_->setMassProps(1.0f, this->collisionShape_->getLocalInertia(1.0f));
616            }
617            else
618            {
619                float mass = this->mass_ + this->childrenMass_;
620                this->physicalBody_->setMassProps(mass, this->collisionShape_->getLocalInertia(mass));
621            }
622        }
623    }
624
625    void WorldEntity::resetPhysicsProps()
626    {
627        if (this->hasPhysics())
628        {
629            this->physicalBody_->setRestitution(this->restitution_);
630            this->physicalBody_->setAngularFactor(this->angularFactor_);
631            this->physicalBody_->setDamping(this->linearDamping_, this->angularDamping_);
632            this->physicalBody_->setFriction(this->friction_);
633        }
634    }
635}
Note: See TracBrowser for help on using the repository browser.