BGE patch #28476: Character object physics type
[blender.git] / extern / bullet2 / src / BulletDynamics / Character / btKinematicCharacterController.cpp
1 /*
2 Bullet Continuous Collision Detection and Physics Library
3 Copyright (c) 2003-2008 Erwin Coumans  http://bulletphysics.com
4
5 This software is provided 'as-is', without any express or implied warranty.
6 In no event will the authors be held liable for any damages arising from the use of this software.
7 Permission is granted to anyone to use this software for any purpose, 
8 including commercial applications, and to alter it and redistribute it freely, 
9 subject to the following restrictions:
10
11 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
12 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
13 3. This notice may not be removed or altered from any source distribution.
14 */
15
16
17 #include "LinearMath/btIDebugDraw.h"
18 #include "BulletCollision/CollisionDispatch/btGhostObject.h"
19 #include "BulletCollision/CollisionShapes/btMultiSphereShape.h"
20 #include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h"
21 #include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h"
22 #include "BulletCollision/CollisionDispatch/btCollisionWorld.h"
23 #include "LinearMath/btDefaultMotionState.h"
24 #include "btKinematicCharacterController.h"
25
26
27 // static helper method
28 static btVector3
29 getNormalizedVector(const btVector3& v)
30 {
31         btVector3 n = v.normalized();
32         if (n.length() < SIMD_EPSILON) {
33                 n.setValue(0, 0, 0);
34         }
35         return n;
36 }
37
38
39 ///@todo Interact with dynamic objects,
40 ///Ride kinematicly animated platforms properly
41 ///More realistic (or maybe just a config option) falling
42 /// -> Should integrate falling velocity manually and use that in stepDown()
43 ///Support jumping
44 ///Support ducking
45 class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
46 {
47 public:
48         btKinematicClosestNotMeRayResultCallback (btCollisionObject* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
49         {
50                 m_me = me;
51         }
52
53         virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace)
54         {
55                 if (rayResult.m_collisionObject == m_me)
56                         return 1.0;
57
58                 return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace);
59         }
60 protected:
61         btCollisionObject* m_me;
62 };
63
64 class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
65 {
66 public:
67         btKinematicClosestNotMeConvexResultCallback (btCollisionObject* me, const btVector3& up, btScalar minSlopeDot)
68         : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0))
69         , m_me(me)
70         , m_up(up)
71         , m_minSlopeDot(minSlopeDot)
72         {
73         }
74
75         virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
76         {
77                 if (convexResult.m_hitCollisionObject == m_me)
78                         return btScalar(1.0);
79
80                 btVector3 hitNormalWorld;
81                 if (normalInWorldSpace)
82                 {
83                         hitNormalWorld = convexResult.m_hitNormalLocal;
84                 } else
85                 {
86                         ///need to transform normal into worldspace
87                         hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
88                 }
89
90                 btScalar dotUp = m_up.dot(hitNormalWorld);
91                 if (dotUp < m_minSlopeDot) {
92                         return btScalar(1.0);
93                 }
94
95                 return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace);
96         }
97 protected:
98         btCollisionObject* m_me;
99         const btVector3 m_up;
100         btScalar m_minSlopeDot;
101 };
102
103 /*
104  * Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal'
105  *
106  * from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html
107  */
108 btVector3 btKinematicCharacterController::computeReflectionDirection (const btVector3& direction, const btVector3& normal)
109 {
110         return direction - (btScalar(2.0) * direction.dot(normal)) * normal;
111 }
112
113 /*
114  * Returns the portion of 'direction' that is parallel to 'normal'
115  */
116 btVector3 btKinematicCharacterController::parallelComponent (const btVector3& direction, const btVector3& normal)
117 {
118         btScalar magnitude = direction.dot(normal);
119         return normal * magnitude;
120 }
121
122 /*
123  * Returns the portion of 'direction' that is perpindicular to 'normal'
124  */
125 btVector3 btKinematicCharacterController::perpindicularComponent (const btVector3& direction, const btVector3& normal)
126 {
127         return direction - parallelComponent(direction, normal);
128 }
129
130 btKinematicCharacterController::btKinematicCharacterController (btPairCachingGhostObject* ghostObject,btConvexShape* convexShape,btScalar stepHeight, int upAxis)
131 {
132         m_upAxis = upAxis;
133         m_addedMargin = 0.02;
134         m_walkDirection.setValue(0,0,0);
135         m_useGhostObjectSweepTest = true;
136         m_ghostObject = ghostObject;
137         m_stepHeight = stepHeight;
138         m_turnAngle = btScalar(0.0);
139         m_convexShape=convexShape;      
140         m_useWalkDirection = true;      // use walk direction by default, legacy behavior
141         m_velocityTimeInterval = 0.0;
142         m_verticalVelocity = 0.0;
143         m_verticalOffset = 0.0;
144         m_gravity = 9.8 * 3 ; // 3G acceleration.
145         m_fallSpeed = 55.0; // Terminal velocity of a sky diver in m/s.
146         m_jumpSpeed = 10.0; // ?
147         m_wasOnGround = false;
148         m_wasJumping = false;
149         setMaxSlope(btRadians(45.0));
150 }
151
152 btKinematicCharacterController::~btKinematicCharacterController ()
153 {
154 }
155
156 btPairCachingGhostObject* btKinematicCharacterController::getGhostObject()
157 {
158         return m_ghostObject;
159 }
160
161 bool btKinematicCharacterController::recoverFromPenetration ( btCollisionWorld* collisionWorld)
162 {
163
164         bool penetration = false;
165
166         collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
167
168         m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
169         
170         btScalar maxPen = btScalar(0.0);
171         for (int i = 0; i < m_ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++)
172         {
173                 m_manifoldArray.resize(0);
174
175                 btBroadphasePair* collisionPair = &m_ghostObject->getOverlappingPairCache()->getOverlappingPairArray()[i];
176                 
177                 if (collisionPair->m_algorithm)
178                         collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray);
179
180                 
181                 for (int j=0;j<m_manifoldArray.size();j++)
182                 {
183                         btPersistentManifold* manifold = m_manifoldArray[j];
184                         btScalar directionSign = manifold->getBody0() == m_ghostObject ? btScalar(-1.0) : btScalar(1.0);
185                         for (int p=0;p<manifold->getNumContacts();p++)
186                         {
187                                 const btManifoldPoint&pt = manifold->getContactPoint(p);
188
189                                 btScalar dist = pt.getDistance();
190
191                                 if (dist < 0.0)
192                                 {
193                                         if (dist < maxPen)
194                                         {
195                                                 maxPen = dist;
196                                                 m_touchingNormal = pt.m_normalWorldOnB * directionSign;//??
197
198                                         }
199                                         m_currentPosition += pt.m_normalWorldOnB * directionSign * dist * btScalar(0.2);
200                                         penetration = true;
201                                 } else {
202                                         //printf("touching %f\n", dist);
203                                 }
204                         }
205                         
206                         //manifold->clearManifold();
207                 }
208         }
209         btTransform newTrans = m_ghostObject->getWorldTransform();
210         newTrans.setOrigin(m_currentPosition);
211         m_ghostObject->setWorldTransform(newTrans);
212 //      printf("m_touchingNormal = %f,%f,%f\n",m_touchingNormal[0],m_touchingNormal[1],m_touchingNormal[2]);
213         return penetration;
214 }
215
216 void btKinematicCharacterController::stepUp ( btCollisionWorld* world)
217 {
218         // phase 1: up
219         btTransform start, end;
220         m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.f?m_verticalOffset:0.f));
221
222         start.setIdentity ();
223         end.setIdentity ();
224
225         /* FIXME: Handle penetration properly */
226         start.setOrigin (m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_convexShape->getMargin() + m_addedMargin));
227         end.setOrigin (m_targetPosition);
228
229         btKinematicClosestNotMeConvexResultCallback callback (m_ghostObject, -getUpAxisDirections()[m_upAxis], btScalar(0.7071));
230         callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
231         callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
232         
233         if (m_useGhostObjectSweepTest)
234         {
235                 m_ghostObject->convexSweepTest (m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration);
236         }
237         else
238         {
239                 world->convexSweepTest (m_convexShape, start, end, callback);
240         }
241         
242         if (callback.hasHit())
243         {
244                 // Only modify the position if the hit was a slope and not a wall or ceiling.
245                 if(callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > 0.0)
246                 {
247                         // we moved up only a fraction of the step height
248                         m_currentStepOffset = m_stepHeight * callback.m_closestHitFraction;
249                         m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
250                 }
251                 m_verticalVelocity = 0.0;
252                 m_verticalOffset = 0.0;
253         } else {
254                 m_currentStepOffset = m_stepHeight;
255                 m_currentPosition = m_targetPosition;
256         }
257 }
258
259 void btKinematicCharacterController::updateTargetPositionBasedOnCollision (const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag)
260 {
261         btVector3 movementDirection = m_targetPosition - m_currentPosition;
262         btScalar movementLength = movementDirection.length();
263         if (movementLength>SIMD_EPSILON)
264         {
265                 movementDirection.normalize();
266
267                 btVector3 reflectDir = computeReflectionDirection (movementDirection, hitNormal);
268                 reflectDir.normalize();
269
270                 btVector3 parallelDir, perpindicularDir;
271
272                 parallelDir = parallelComponent (reflectDir, hitNormal);
273                 perpindicularDir = perpindicularComponent (reflectDir, hitNormal);
274
275                 m_targetPosition = m_currentPosition;
276                 if (0)//tangentMag != 0.0)
277                 {
278                         btVector3 parComponent = parallelDir * btScalar (tangentMag*movementLength);
279 //                      printf("parComponent=%f,%f,%f\n",parComponent[0],parComponent[1],parComponent[2]);
280                         m_targetPosition +=  parComponent;
281                 }
282
283                 if (normalMag != 0.0)
284                 {
285                         btVector3 perpComponent = perpindicularDir * btScalar (normalMag*movementLength);
286 //                      printf("perpComponent=%f,%f,%f\n",perpComponent[0],perpComponent[1],perpComponent[2]);
287                         m_targetPosition += perpComponent;
288                 }
289         } else
290         {
291 //              printf("movementLength don't normalize a zero vector\n");
292         }
293 }
294
295 void btKinematicCharacterController::stepForwardAndStrafe ( btCollisionWorld* collisionWorld, const btVector3& walkMove)
296 {
297         // printf("m_normalizedDirection=%f,%f,%f\n",
298         //      m_normalizedDirection[0],m_normalizedDirection[1],m_normalizedDirection[2]);
299         // phase 2: forward and strafe
300         btTransform start, end;
301         m_targetPosition = m_currentPosition + walkMove;
302
303         start.setIdentity ();
304         end.setIdentity ();
305         
306         btScalar fraction = 1.0;
307         btScalar distance2 = (m_currentPosition-m_targetPosition).length2();
308 //      printf("distance2=%f\n",distance2);
309
310         if (m_touchingContact)
311         {
312                 if (m_normalizedDirection.dot(m_touchingNormal) > btScalar(0.0))
313                 {
314                         updateTargetPositionBasedOnCollision (m_touchingNormal);
315                 }
316         }
317
318         int maxIter = 10;
319
320         while (fraction > btScalar(0.01) && maxIter-- > 0)
321         {
322                 start.setOrigin (m_currentPosition);
323                 end.setOrigin (m_targetPosition);
324                 btVector3 sweepDirNegative(m_currentPosition - m_targetPosition);
325
326                 btKinematicClosestNotMeConvexResultCallback callback (m_ghostObject, sweepDirNegative, btScalar(0.0));
327                 callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
328                 callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
329
330
331                 btScalar margin = m_convexShape->getMargin();
332                 m_convexShape->setMargin(margin + m_addedMargin);
333
334
335                 if (m_useGhostObjectSweepTest)
336                 {
337                         m_ghostObject->convexSweepTest (m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
338                 } else
339                 {
340                         collisionWorld->convexSweepTest (m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
341                 }
342                 
343                 m_convexShape->setMargin(margin);
344
345                 
346                 fraction -= callback.m_closestHitFraction;
347
348                 if (callback.hasHit())
349                 {       
350                         // we moved only a fraction
351                         btScalar hitDistance;
352                         hitDistance = (callback.m_hitPointWorld - m_currentPosition).length();
353
354 //                      m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
355
356                         updateTargetPositionBasedOnCollision (callback.m_hitNormalWorld);
357                         btVector3 currentDir = m_targetPosition - m_currentPosition;
358                         distance2 = currentDir.length2();
359                         if (distance2 > SIMD_EPSILON)
360                         {
361                                 currentDir.normalize();
362                                 /* See Quake2: "If velocity is against original velocity, stop ead to avoid tiny oscilations in sloping corners." */
363                                 if (currentDir.dot(m_normalizedDirection) <= btScalar(0.0))
364                                 {
365                                         break;
366                                 }
367                         } else
368                         {
369 //                              printf("currentDir: don't normalize a zero vector\n");
370                                 break;
371                         }
372
373                 } else {
374                         // we moved whole way
375                         m_currentPosition = m_targetPosition;
376                 }
377
378         //      if (callback.m_closestHitFraction == 0.f)
379         //              break;
380
381         }
382 }
383
384 void btKinematicCharacterController::stepDown ( btCollisionWorld* collisionWorld, btScalar dt)
385 {
386         btTransform start, end;
387
388         // phase 3: down
389         /*btScalar additionalDownStep = (m_wasOnGround && !onGround()) ? m_stepHeight : 0.0;
390         btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + additionalDownStep);
391         btScalar downVelocity = (additionalDownStep == 0.0 && m_verticalVelocity<0.0?-m_verticalVelocity:0.0) * dt;
392         btVector3 gravity_drop = getUpAxisDirections()[m_upAxis] * downVelocity; 
393         m_targetPosition -= (step_drop + gravity_drop);*/
394
395         btScalar downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt;
396         if(downVelocity > 0.0 && downVelocity < m_stepHeight
397                 && (m_wasOnGround || !m_wasJumping))
398         {
399                 downVelocity = m_stepHeight;
400         }
401
402         btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity);
403         m_targetPosition -= step_drop;
404
405         start.setIdentity ();
406         end.setIdentity ();
407
408         start.setOrigin (m_currentPosition);
409         end.setOrigin (m_targetPosition);
410
411         btKinematicClosestNotMeConvexResultCallback callback (m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine);
412         callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
413         callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
414         
415         if (m_useGhostObjectSweepTest)
416         {
417                 m_ghostObject->convexSweepTest (m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
418         } else
419         {
420                 collisionWorld->convexSweepTest (m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
421         }
422
423         if (callback.hasHit())
424         {
425                 // we dropped a fraction of the height -> hit floor
426                 m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction);
427                 m_verticalVelocity = 0.0;
428                 m_verticalOffset = 0.0;
429                 m_wasJumping = false;
430         } else {
431                 // we dropped the full height
432                 
433                 m_currentPosition = m_targetPosition;
434         }
435 }
436
437
438
439 void btKinematicCharacterController::setWalkDirection
440 (
441 const btVector3& walkDirection
442 )
443 {
444         m_useWalkDirection = true;
445         m_walkDirection = walkDirection;
446         m_normalizedDirection = getNormalizedVector(m_walkDirection);
447 }
448
449
450
451 void btKinematicCharacterController::setVelocityForTimeInterval
452 (
453 const btVector3& velocity,
454 btScalar timeInterval
455 )
456 {
457 //      printf("setVelocity!\n");
458 //      printf("  interval: %f\n", timeInterval);
459 //      printf("  velocity: (%f, %f, %f)\n",
460 //               velocity.x(), velocity.y(), velocity.z());
461
462         m_useWalkDirection = false;
463         m_walkDirection = velocity;
464         m_normalizedDirection = getNormalizedVector(m_walkDirection);
465         m_velocityTimeInterval = timeInterval;
466 }
467
468
469
470 void btKinematicCharacterController::reset ()
471 {
472 }
473
474 void btKinematicCharacterController::warp (const btVector3& origin)
475 {
476         btTransform xform;
477         xform.setIdentity();
478         xform.setOrigin (origin);
479         m_ghostObject->setWorldTransform (xform);
480 }
481
482
483 void btKinematicCharacterController::preStep (  btCollisionWorld* collisionWorld)
484 {
485         
486         int numPenetrationLoops = 0;
487         m_touchingContact = false;
488         while (recoverFromPenetration (collisionWorld))
489         {
490                 numPenetrationLoops++;
491                 m_touchingContact = true;
492                 if (numPenetrationLoops > 4)
493                 {
494                         //printf("character could not recover from penetration = %d\n", numPenetrationLoops);
495                         break;
496                 }
497         }
498
499         m_currentPosition = m_ghostObject->getWorldTransform().getOrigin();
500         m_targetPosition = m_currentPosition;
501 //      printf("m_targetPosition=%f,%f,%f\n",m_targetPosition[0],m_targetPosition[1],m_targetPosition[2]);
502
503         
504 }
505
506 #include <stdio.h>
507
508 void btKinematicCharacterController::playerStep (  btCollisionWorld* collisionWorld, btScalar dt)
509 {
510 //      printf("playerStep(): ");
511 //      printf("  dt = %f", dt);
512
513         // quick check...
514         if (!m_useWalkDirection && m_velocityTimeInterval <= 0.0) {
515 //              printf("\n");
516                 return;         // no motion
517         }
518
519         m_wasOnGround = onGround();
520
521         // Update fall velocity.
522         m_verticalVelocity -= m_gravity * dt;
523         if(m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed)
524         {
525                 m_verticalVelocity = m_jumpSpeed;
526         }
527         if(m_verticalVelocity < 0.0 && btFabs(m_verticalVelocity) > btFabs(m_fallSpeed))
528         {
529                 m_verticalVelocity = -btFabs(m_fallSpeed);
530         }
531         m_verticalOffset = m_verticalVelocity * dt;
532
533
534         btTransform xform;
535         xform = m_ghostObject->getWorldTransform ();
536
537 //      printf("walkDirection(%f,%f,%f)\n",walkDirection[0],walkDirection[1],walkDirection[2]);
538 //      printf("walkSpeed=%f\n",walkSpeed);
539
540         stepUp (collisionWorld);
541         if (m_useWalkDirection) {
542                 stepForwardAndStrafe (collisionWorld, m_walkDirection);
543         } else {
544                 //printf("  time: %f", m_velocityTimeInterval);
545                 // still have some time left for moving!
546                 btScalar dtMoving =
547                         (dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval;
548                 m_velocityTimeInterval -= dt;
549
550                 // how far will we move while we are moving?
551                 btVector3 move = m_walkDirection * dtMoving;
552
553                 //printf("  dtMoving: %f", dtMoving);
554
555                 // okay, step
556                 stepForwardAndStrafe(collisionWorld, move);
557         }
558         stepDown (collisionWorld, dt);
559
560         // printf("\n");
561
562         xform.setOrigin (m_currentPosition);
563         m_ghostObject->setWorldTransform (xform);
564 }
565
566 void btKinematicCharacterController::setFallSpeed (btScalar fallSpeed)
567 {
568         m_fallSpeed = fallSpeed;
569 }
570
571 void btKinematicCharacterController::setJumpSpeed (btScalar jumpSpeed)
572 {
573         m_jumpSpeed = jumpSpeed;
574 }
575
576 void btKinematicCharacterController::setMaxJumpHeight (btScalar maxJumpHeight)
577 {
578         m_maxJumpHeight = maxJumpHeight;
579 }
580
581 bool btKinematicCharacterController::canJump () const
582 {
583         return onGround();
584 }
585
586 void btKinematicCharacterController::jump ()
587 {
588         if (!canJump())
589                 return;
590
591         m_verticalVelocity = m_jumpSpeed;
592         m_wasJumping = true;
593
594 #if 0
595         currently no jumping.
596         btTransform xform;
597         m_rigidBody->getMotionState()->getWorldTransform (xform);
598         btVector3 up = xform.getBasis()[1];
599         up.normalize ();
600         btScalar magnitude = (btScalar(1.0)/m_rigidBody->getInvMass()) * btScalar(8.0);
601         m_rigidBody->applyCentralImpulse (up * magnitude);
602 #endif
603 }
604
605 void btKinematicCharacterController::setGravity(btScalar gravity)
606 {
607         m_gravity = gravity;
608 }
609
610 btScalar btKinematicCharacterController::getGravity() const
611 {
612         return m_gravity;
613 }
614
615 void btKinematicCharacterController::setMaxSlope(btScalar slopeRadians)
616 {
617         m_maxSlopeRadians = slopeRadians;
618         m_maxSlopeCosine = btCos(slopeRadians);
619 }
620
621 btScalar btKinematicCharacterController::getMaxSlope() const
622 {
623         return m_maxSlopeRadians;
624 }
625
626 bool btKinematicCharacterController::onGround () const
627 {
628         return m_verticalVelocity == 0.0 && m_verticalOffset == 0.0;
629 }
630
631
632 btVector3* btKinematicCharacterController::getUpAxisDirections()
633 {
634         static btVector3 sUpAxisDirection[3] = { btVector3(1.0f, 0.0f, 0.0f), btVector3(0.0f, 1.0f, 0.0f), btVector3(0.0f, 0.0f, 1.0f) };
635         
636         return sUpAxisDirection;
637 }
638
639 void btKinematicCharacterController::debugDraw(btIDebugDraw* debugDrawer)
640 {
641 }