/*
 * Copyright (C) 2012 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#include <algorithm>
#include <limits>
#include <string>

#include <ignition/math/Helpers.hh>

#include "gazebo/common/Assert.hh"
#include "gazebo/common/Console.hh"
#include "gazebo/common/Exception.hh"

#include "gazebo/physics/bullet/BulletLink.hh"
#include "gazebo/physics/bullet/BulletPhysics.hh"
#include "gazebo/physics/bullet/BulletTypes.hh"
#include "gazebo/physics/bullet/BulletScrewJoint.hh"

namespace gazebo
{
  namespace physics
  {
    class btScrewConstraint : public btSliderConstraint
    {
      public: btScrewConstraint(btRigidBody &_rbA, btRigidBody &_rbB,
          const btTransform &_frameInA, const btTransform &_frameInB,
          bool _useLinearReferenceFrameA)
          : btSliderConstraint(_rbA, _rbB, _frameInA, _frameInB,
              _useLinearReferenceFrameA), threadPitch(1.0) {}

      public: btScrewConstraint(btRigidBody &_rbB,
          const btTransform &_frameInB, bool _useLinearReferenceFrameA)
          : btSliderConstraint(_rbB, _frameInB, _useLinearReferenceFrameA),
          threadPitch(1.0) {}

      public: virtual void getInfo1(btConstraintInfo1 *_info)
      {
        this->_getInfo1NonVirtual(_info);
      }

      public: virtual void getInfo2(btConstraintInfo2 *_info)
      {
        this->_getInfo2NonVirtual(
            _info,
            m_rbA.getCenterOfMassTransform(),
            m_rbB.getCenterOfMassTransform(),
            m_rbA.getLinearVelocity(),
            m_rbB.getLinearVelocity(),
            m_rbA.getInvMass(),
            m_rbB.getInvMass());
      }

      public: void _getInfo1NonVirtual(btConstraintInfo1* info);
      public: void _getInfo2NonVirtual(
          btConstraintInfo2* info,
          const btTransform& transA,
          const btTransform& transB,
          const btVector3& linVelA,
          const btVector3& linVelB,
          btScalar rbAinvMass, btScalar rbBinvMass);

      public: btScalar getLinearPosition();
      public: btScalar getAngularPosition();

      // needed non-const version for SetForce
      public: btRigidBody& getRigidBodyA();
      public: btRigidBody& getRigidBodyB();

      public: virtual void setThreadPitch(double _threadPitch)
      {
        this->threadPitch = -_threadPitch;
      }

      public: virtual double getThreadPitch() const
      {
        return -this->threadPitch;
      }

      private: double threadPitch;
    };
  }
}

using namespace gazebo;
using namespace physics;

//////////////////////////////////////////////////
BulletScrewJoint::BulletScrewJoint(btDynamicsWorld *_world, BasePtr _parent)
    : ScrewJoint<BulletJoint>(_parent), bulletScrew(nullptr)
{
  GZ_ASSERT(_world, "bullet world pointer is null");
  this->bulletWorld = _world;
}

//////////////////////////////////////////////////
BulletScrewJoint::~BulletScrewJoint()
{
}

//////////////////////////////////////////////////
void BulletScrewJoint::Load(sdf::ElementPtr _sdf)
{
  ScrewJoint<BulletJoint>::Load(_sdf);
  this->SetThreadPitch(0, this->threadPitch);
}

//////////////////////////////////////////////////
ignition::math::Vector3d BulletScrewJoint::Anchor(
    const unsigned int /*index*/) const
{
  gzerr << "BulletScrewJoint::Anchor not implemented, return 0 vector.\n";
  return ignition::math::Vector3d::Zero;
}

//////////////////////////////////////////////////
void BulletScrewJoint::SetAnchor(const unsigned int /*index*/,
    const ignition::math::Vector3d &/*_anchor*/)
{
  gzerr << "BulletScrewJoint::SetAnchor not implemented.\n";
}

//////////////////////////////////////////////////
void BulletScrewJoint::Init()
{
  ScrewJoint<BulletJoint>::Init();

  BulletLinkPtr bulletChildLink =
    boost::static_pointer_cast<BulletLink>(this->childLink);
  BulletLinkPtr bulletParentLink =
    boost::static_pointer_cast<BulletLink>(this->parentLink);

  // Get axis unit vector (expressed in world frame).
  ignition::math::Vector3d axis = this->initialWorldAxis;
  if (axis == ignition::math::Vector3d::Zero)
  {
    gzerr << "axis must have non-zero length, resetting to 0 0 1\n";
    axis.Set(0, 0, 1);
  }

  // Local variables used to compute pivots and axes in body-fixed frames
  // for the parent and child links.
  ignition::math::Vector3d pivotParent, pivotChild, axisParent, axisChild;
  ignition::math::Pose3d pose;
  btTransform frameParent, frameChild;
  btVector3 axis2, axis3;

  // Initialize pivots to anchorPos, which is expressed in the
  // world coordinate frame.
  pivotParent = this->anchorPos;
  pivotChild = this->anchorPos;

  // Check if parentLink exists. If not, the parent will be the world.
  if (this->parentLink)
  {
    // Compute relative pose between joint anchor and inertial frame of parent.
    pose = this->parentLink->WorldInertialPose();
    // Subtract CoG position from anchor position, both in world frame.
    pivotParent -= pose.Pos();
    // Rotate pivot offset and axis into body-fixed inertial frame of parent.
    pivotParent = pose.Rot().RotateVectorReverse(pivotParent);
    frameParent.setOrigin(BulletTypes::ConvertVector3(pivotParent));
    axisParent = pose.Rot().RotateVectorReverse(axis);
    axisParent = axisParent.Normalize();
    // The following math is based on btHingeConstraint.cpp:95-115
    btPlaneSpace1(BulletTypes::ConvertVector3(axisParent), axis2, axis3);
    frameParent.getBasis().setValue(
      axisParent.X(), axis2.x(), axis3.x(),
      axisParent.Y(), axis2.y(), axis3.y(),
      axisParent.Z(), axis2.z(), axis3.z());
  }
  // Check if childLink exists. If not, the child will be the world.
  if (this->childLink)
  {
    // Compute relative pose between joint anchor and inertial frame of child.
    pose = this->childLink->WorldInertialPose();
    // Subtract CoG position from anchor position, both in world frame.
    pivotChild -= pose.Pos();
    // Rotate pivot offset and axis into body-fixed inertial frame of child.
    pivotChild = pose.Rot().RotateVectorReverse(pivotChild);
    frameChild.setOrigin(BulletTypes::ConvertVector3(pivotChild));
    axisChild = pose.Rot().RotateVectorReverse(axis);
    axisChild = axisChild.Normalize();
    // The following math is based on btHingeConstraint.cpp:95-115
    btPlaneSpace1(BulletTypes::ConvertVector3(axisChild), axis2, axis3);
    frameChild.getBasis().setValue(
      axisChild.X(), axis2.x(), axis3.x(),
      axisChild.Y(), axis2.y(), axis3.y(),
      axisChild.Z(), axis2.z(), axis3.z());
  }

  // If both links exist, then create a joint between the two links.
  if (bulletChildLink && bulletParentLink)
  {
    // Parent and child constraint frames generated with btPlaneSpace1 may
    // differ by 90 degrees because they are underdetermined (only one axis
    // given). Here we set the child constraint frame by taking the parent
    // constraint frame (in the frame of the parent Bullet link) and rotating
    // it into the frame of the child Bullet link. This ensures that the
    // constraint frames are initially aligned.
    pose = ignition::math::Pose3d(pivotChild,
        this->childLink->WorldInertialPose().Rot().Inverse() *
        this->parentLink->WorldInertialPose().Rot() *
        BulletTypes::ConvertPoseIgn(frameParent).Rot());
    frameChild = BulletTypes::ConvertPose(pose);
    this->bulletScrew = new btScrewConstraint(
        *bulletParentLink->GetBulletLink(),
        *bulletChildLink->GetBulletLink(),
        frameParent, frameChild, true);
  }
  // If only the child exists, then create a joint between the child
  // and the world.
  else if (bulletChildLink)
  {
    this->bulletScrew = new btScrewConstraint(
        *bulletChildLink->GetBulletLink(), frameChild, true);
  }
  // If only the parent exists, then create a joint between the parent
  // and the world.
  else if (bulletParentLink)
  {
    this->bulletScrew = new btScrewConstraint(
        *bulletParentLink->GetBulletLink(), frameParent, true);
  }
  // Throw an error if no links are given.
  else
  {
    gzerr << "Unable to create a joint without links\n";
    return;
  }

  if (!this->bulletScrew)
  {
    gzerr << "unable to create bullet screw joint\n";
    return;
  }

  // Apply joint translation limits here.
  // TODO: velocity and effort limits.
  GZ_ASSERT(this->sdf != nullptr, "Joint sdf member is null");
  sdf::ElementPtr limitElem;
  limitElem = this->sdf->GetElement("axis")->GetElement("limit");
  // joint limit is set on the revolute dof in sdf,
  double upper = limitElem->Get<double>("upper");
  double lower = limitElem->Get<double>("lower");
  this->bulletScrew->setLowerAngLimit(lower);
  this->bulletScrew->setUpperAngLimit(upper);
  // enforce linear dof in bullet.
  double tp = this->threadPitch;
  if (ignition::math::equal(tp, 0.0))
  {
    gzerr << "thread pitch should not be zero (joint is a slider?)"
          << " using thread pitch = 1.0e6\n";
    tp = 1.0e6;
  }
  if (tp > 0)
  {
    this->bulletScrew->setLowerLinLimit(lower/tp);
    this->bulletScrew->setUpperLinLimit(upper/tp);
  }
  else
  {
    this->bulletScrew->setLowerLinLimit(upper/tp);
    this->bulletScrew->setUpperLinLimit(lower/tp);
  }
  this->bulletScrew->setThreadPitch(tp);

  this->constraint = this->bulletScrew;

  // Add the joint to the world
  GZ_ASSERT(this->bulletWorld, "bullet world pointer is null");
  this->bulletWorld->addConstraint(this->constraint);

  // Allows access to impulse
  this->constraint->enableFeedback(true);

  // Setup Joint force and torque feedback
  this->SetupJointFeedback();
}

//////////////////////////////////////////////////
double BulletScrewJoint::GetVelocity(unsigned int /*_index*/) const
{
  double result = 0;
  ignition::math::Vector3d globalAxis = this->GlobalAxis(0);
  if (this->childLink)
    result += globalAxis.Dot(this->childLink->WorldLinearVel());
  if (this->parentLink)
    result -= globalAxis.Dot(this->parentLink->WorldLinearVel());
  return result;
}

//////////////////////////////////////////////////
void BulletScrewJoint::SetVelocity(unsigned int _index, double _vel)
{
  ignition::math::Vector3d desiredVel;
  if (this->parentLink)
    desiredVel = this->parentLink->WorldLinearVel();
  desiredVel += _vel * this->GlobalAxis(_index);
  if (this->childLink)
    this->childLink->SetLinearVel(desiredVel);
}

//////////////////////////////////////////////////
void BulletScrewJoint::SetAxis(const unsigned int /*_index*/,
    const ignition::math::Vector3d &_axis)
{
  // Note that _axis is given in a world frame,
  // but bullet uses a body-fixed frame
  if (!this->bulletScrew)
  {
    // this hasn't been initialized yet, store axis in initialWorldAxis
    this->initialWorldAxis = this->WorldPose().Rot() * _axis;
  }
  else
  {
    gzerr << "SetAxis for existing joint is not implemented\n";
  }
}

//////////////////////////////////////////////////
void BulletScrewJoint::SetThreadPitch(unsigned int /*_index*/,
    double _threadPitch)
{
  this->SetThreadPitch(_threadPitch);
}

//////////////////////////////////////////////////
void BulletScrewJoint::SetThreadPitch(double _threadPitch)
{
  this->threadPitch = _threadPitch;
}

//////////////////////////////////////////////////
double BulletScrewJoint::GetThreadPitch()
{
  double result = this->threadPitch;
  if (this->bulletScrew)
    result = this->bulletScrew->getThreadPitch();
  else
    gzwarn << "bulletScrew not created yet, returning cached threadPitch.\n";
  return result;
}

//////////////////////////////////////////////////
double BulletScrewJoint::GetThreadPitch(unsigned int /*_index*/)
{
  return this->GetThreadPitch();
}

//////////////////////////////////////////////////
void BulletScrewJoint::SetForceImpl(unsigned int _index, double _force)
{
  if (this->bulletScrew)
  {
    // x-axis of constraint frame
    btVector3 hingeAxisLocalA =
      this->bulletScrew->getFrameOffsetA().getBasis().getColumn(0);
    btVector3 hingeAxisLocalB =
      this->bulletScrew->getFrameOffsetB().getBasis().getColumn(0);

    btVector3 hingeAxisWorldA =
      this->bulletScrew->getRigidBodyA().getWorldTransform().getBasis() *
      hingeAxisLocalA;
    btVector3 hingeAxisWorldB =
      this->bulletScrew->getRigidBodyB().getWorldTransform().getBasis() *
      hingeAxisLocalB;

    btVector3 hingeEffortA = _force * hingeAxisWorldA;
    btVector3 hingeEffortB = _force * hingeAxisWorldB;

    if (_index == 0)
    {
      this->bulletScrew->getRigidBodyA().applyTorque(-hingeEffortA);
      this->bulletScrew->getRigidBodyB().applyTorque(hingeEffortB);
    }
    else if (_index == 1)
    {
      if (this->constraint)
      {
        // TODO: switch to applyForce and specify body-fixed offset
        this->constraint->getRigidBodyA().applyCentralForce(-hingeEffortA);
        this->constraint->getRigidBodyB().applyCentralForce(hingeEffortB);
      }
      else
        gzerr << "BulletScrewJoint::constraint not created yet.\n";
    }
    else
      gzerr << "Invalid index [" << _index << "]\n";
  }
  else
    gzerr << "bulletScrew not created yet.\n";
}

//////////////////////////////////////////////////
void BulletScrewJoint::SetUpperLimit(const unsigned int _index,
                                     const double _limit)
{
  Joint::SetUpperLimit(0, _limit);

  // bulletScrew axial rotation is backward
  if (!this->bulletScrew)
  {
    gzerr << "bulletScrew not created yet.\n";
    return;
  }

  if (_index == 0)
  {
    // _index = 0: angular constraint
    double upperAng = this->bulletScrew->getUpperAngLimit();
    this->bulletScrew->setUpperAngLimit(std::max(upperAng, _limit));

    // set corresponding linear constraints
    double tp = this->threadPitch;
    if (ignition::math::equal(tp, 0.0))
    {
      gzwarn << "thread pitch should not be zero (joint is a slider?)"
             << " using thread pitch = 1.0e6\n";
      tp = 1.0e6;
    }
    // linear is angular / threadPitch
    if (tp > 0)
    {
      double lowerLin = this->bulletScrew->getLowerLinLimit();
      this->bulletScrew->setUpperLinLimit(std::max(lowerLin,
        _limit/tp));
    }
    else
    {
      // flip upper lower because thread pitch is negative
      double upperLin = this->bulletScrew->getUpperLinLimit();
      this->bulletScrew->setLowerLinLimit(std::min(upperLin,
        _limit/tp));
    }
  }
  else if (_index == 1)
  {
    // _index = 1: linear constraint
    double lowerLin = this->bulletScrew->getLowerLinLimit();
    this->bulletScrew->setUpperLinLimit(std::max(lowerLin, _limit));

    // set corresponding angular constraints
    double tp = this->threadPitch;
    // angular is linear * threadPitch
    if (tp > 0)
    {
      double lowerAng = this->bulletScrew->getLowerAngLimit();
      this->bulletScrew->setUpperAngLimit(std::max(lowerAng,
        _limit*tp));
    }
    else
    {
      // flip upper lower because thread pitch is negative
      double upperAng = this->bulletScrew->getUpperAngLimit();
      this->bulletScrew->setLowerAngLimit(std::min(upperAng,
        _limit*tp));
    }
  }
  else
  {
    gzerr << "Invalid index [" << _index << "]\n";
  }
}

//////////////////////////////////////////////////
void BulletScrewJoint::SetLowerLimit(const unsigned int _index,
                                     const double _limit)
{
  Joint::SetLowerLimit(0, _limit);

  // bulletScrew axial rotation is backward
  if (!this->bulletScrew)
  {
    gzerr << "bulletScrew not created yet.\n";
    return;
  }

  // bulletScrew axial rotation is backward
  if (_index == 0)
  {
    // _index = 0: angular constraint
    double upperAng = this->bulletScrew->getUpperAngLimit();
    this->bulletScrew->setLowerAngLimit(std::min(upperAng, _limit));

    // set corresponding linear constraints
    double tp = this->threadPitch;
    if (ignition::math::equal(tp, 0.0))
    {
      gzerr << "thread pitch should not be zero (joint is a slider?)"
            << " using thread pitch = 1.0e6\n";
      tp = 1.0e6;
    }
    // linear is angular / threadPitch
    if (tp > 0)
    {
      double upperLin = this->bulletScrew->getUpperLinLimit();
      this->bulletScrew->setLowerLinLimit(std::min(upperLin,
        _limit/tp));
    }
    else
    {
        // flip upper lower because thread pitch is negative
      double lowerLin = this->bulletScrew->getLowerLinLimit();
      this->bulletScrew->setUpperLinLimit(std::max(lowerLin,
        _limit/tp));
    }
  }
  else if (_index == 1)
  {
    // _index = 1: linear constraint
    double upperLin = this->bulletScrew->getUpperLinLimit();
    this->bulletScrew->setLowerLinLimit(std::min(upperLin, _limit));

    // set corresponding angular constraints
    double tp = this->threadPitch;
    // angular is linear * threadPitch
    if (tp > 0)
    {
      double upperAng = this->bulletScrew->getUpperAngLimit();
      this->bulletScrew->setLowerAngLimit(std::min(upperAng,
        _limit*tp));
    }
    else
    {
        // flip upper lower because thread pitch is negative
      double lowerAng = this->bulletScrew->getLowerAngLimit();
      this->bulletScrew->setUpperAngLimit(std::max(lowerAng,
        _limit*tp));
    }
  }
  else
  {
    gzerr << "Invalid index [" << _index << "]\n";
  }
}

//////////////////////////////////////////////////
ignition::math::Vector3d BulletScrewJoint::GlobalAxis(
    const unsigned int /*_index*/) const
{
  ignition::math::Vector3d result = this->initialWorldAxis;
  if (this->bulletScrew)
  {
    // bullet uses x-axis for slider
    btVector3 vec =
      this->bulletScrew->getRigidBodyA().getCenterOfMassTransform().getBasis()
      * this->bulletScrew->getFrameOffsetA().getBasis().getColumn(0);
    result = BulletTypes::ConvertVector3Ign(vec);
  }
  else
    gzwarn << "bulletScrew does not exist, returning fake axis\n";
  return result;
}

//////////////////////////////////////////////////
double BulletScrewJoint::PositionImpl(const unsigned int _index) const
{
  double result = ignition::math::NAN_D;
  if (this->bulletScrew)
  {
    if (_index == 0)
    {
      // angular position
      result = this->bulletScrew->getAngularPosition();
    }
    else if (_index == 1)
    {
      // linear position
      result = this->bulletScrew->getLinearPosition();
    }
    else
      gzerr << "Invalid index [" << _index << "]\n";
  }
  else
    gzerr << "bulletScrew not created yet\n";
  return result;
}

//////////////////////////////////////////////////
double BulletScrewJoint::GetParam(
  const std::string &_key, unsigned int _index)
{
  if (_key  == "thread_pitch")
    return this->threadPitch;
  else
    return BulletJoint::GetParam(_key, _index);
}

//////////////////////////////////////////////////
btScalar btScrewConstraint::getAngularPosition()
{
  this->calculateTransforms(
    m_rbA.getCenterOfMassTransform(), m_rbB.getCenterOfMassTransform());
  const btVector3 axisA0 = m_calculatedTransformA.getBasis().getColumn(1);
  const btVector3 axisA1 = m_calculatedTransformA.getBasis().getColumn(2);
  const btVector3 axisB0 = m_calculatedTransformB.getBasis().getColumn(1);
  return btAtan2(axisB0.dot(axisA1), axisB0.dot(axisA0));
}

//////////////////////////////////////////////////
btScalar btScrewConstraint::getLinearPosition()
{
  this->calculateTransforms(
    m_rbA.getCenterOfMassTransform(), m_rbB.getCenterOfMassTransform());
  return this->m_depth[0];
}

//////////////////////////////////////////////////
btRigidBody& btScrewConstraint::getRigidBodyA()
{
  return this->m_rbA;
}

//////////////////////////////////////////////////
btRigidBody& btScrewConstraint::getRigidBodyB()
{
  return this->m_rbB;
}

//////////////////////////////////////////////////
void btScrewConstraint::_getInfo2NonVirtual(
    btConstraintInfo2* info,
    const btTransform& transA,
    const btTransform& transB,
    const btVector3& linVelA,
    const btVector3& linVelB,
    btScalar rbAinvMass, btScalar rbBinvMass)
{
  /// This is a copy of btSliderConstraint::getInfo2NonVirtual(...)
  /// with minor changes to the ax1 direction constraint.
  /// Mainly, the axial limit constraint is always on and is
  /// changed to a screw constraint.

  /// First, always turn on
  const btTransform& trA = getCalculatedTransformA();
  const btTransform& trB = getCalculatedTransformB();

  btAssert(!m_useSolveConstraintObsolete);
  int i, s = info->rowskip;

  btScalar signFact = m_useLinearReferenceFrameA ?
    btScalar(1.0f) : btScalar(-1.0f);

  // difference between frames in WCS
  btVector3 ofs = trB.getOrigin() - trA.getOrigin();
  // now get weight factors depending on masses
  btScalar miA = rbAinvMass;
  btScalar miB = rbBinvMass;
  bool hasStaticBody = (miA < SIMD_EPSILON) || (miB < SIMD_EPSILON);
  btScalar miS = miA + miB;
  btScalar factA, factB;
  if (miS > btScalar(0.f))
  {
    factA = miB / miS;
  }
  else
  {
    factA = btScalar(0.5f);
  }
  factB = btScalar(1.0f) - factA;
  btVector3 ax1, p, q;
  btVector3 ax1A = trA.getBasis().getColumn(0);
  btVector3 ax1B = trB.getBasis().getColumn(0);
  if (m_useOffsetForConstraintFrame)
  {
    // get the desired direction of slider axis
    // as weighted sum of X-orthos of frameA and frameB in WCS
    ax1 = ax1A * factA + ax1B * factB;
    ax1.normalize();
    // construct two orthos to slider axis
    btPlaneSpace1(ax1, p, q);
  }
  else
  { // old way - use frameA
    ax1 = trA.getBasis().getColumn(0);
    // get 2 orthos to slider axis (Y, Z)
    p = trA.getBasis().getColumn(1);
    q = trA.getBasis().getColumn(2);
  }
  // make rotations around these orthos equal
  // the slider axis should be the only unconstrained
  // rotational axis, the angular velocity of the two bodies perpendicular to
  // the slider axis should be equal. thus the constraint equations are
  //    p*w1 - p*w2 = 0
  //    q*w1 - q*w2 = 0
  // where p and q are unit vectors normal to the slider axis, and w1 and w2
  // are the angular velocity vectors of the two bodies.
  info->m_J1angularAxis[0] = p[0];
  info->m_J1angularAxis[1] = p[1];
  info->m_J1angularAxis[2] = p[2];
  info->m_J1angularAxis[s+0] = q[0];
  info->m_J1angularAxis[s+1] = q[1];
  info->m_J1angularAxis[s+2] = q[2];

  info->m_J2angularAxis[0] = -p[0];
  info->m_J2angularAxis[1] = -p[1];
  info->m_J2angularAxis[2] = -p[2];
  info->m_J2angularAxis[s+0] = -q[0];
  info->m_J2angularAxis[s+1] = -q[1];
  info->m_J2angularAxis[s+2] = -q[2];
  // compute the right hand side of the constraint equation. set relative
  // body velocities along p and q to bring the slider back into alignment.
  // if ax1A,ax1B are the unit length slider axes as computed from bodyA and
  // bodyB, we need to rotate both bodies along the axis u = (ax1 x ax2).
  // if "theta" is the angle between ax1 and ax2, we need an angular velocity
  // along u to cover angle erp*theta in one step :
  //   |angular_velocity| = angle/time = erp*theta / stepsize
  //                      = (erp*fps) * theta
  //    angular_velocity  = |angular_velocity| * (ax1 x ax2) / |ax1 x ax2|
  //                      = (erp*fps) * theta * (ax1 x ax2) / sin(theta)
  // ...as ax1 and ax2 are unit length. if theta is smallish,
  // theta ~= sin(theta), so
  //    angular_velocity  = (erp*fps) * (ax1 x ax2)
  // ax1 x ax2 is in the plane space of ax1, so we project the angular
  // velocity to p and q to find the right hand side.
//  btScalar k = info->fps * info->erp * getSoftnessOrthoAng();
  btScalar currERP = (m_flags & BT_SLIDER_FLAGS_ERP_ORTANG) ?
    m_softnessOrthoAng : m_softnessOrthoAng * info->erp;
  btScalar k = info->fps * currERP;

  btVector3 u = ax1A.cross(ax1B);
  info->m_constraintError[0] = k * u.dot(p);
  info->m_constraintError[s] = k * u.dot(q);
  if (m_flags & BT_SLIDER_FLAGS_CFM_ORTANG)
  {
    info->cfm[0] = m_cfmOrthoAng;
    info->cfm[s] = m_cfmOrthoAng;
  }

  // last filled row
  int nrow = 1;
  int srow;
  btScalar limit_err;
  int limit;
  int powered;

  // next two rows.
  // we want: velA + wA x relA == velB + wB x relB ... but this would
  // result in three equations, so we project along two orthos to the
  // slider axis

  btTransform bodyA_trans = transA;
  btTransform bodyB_trans = transB;
  nrow++;
  int s2 = nrow * s;
  nrow++;
  int s3 = nrow * s;
  btVector3 tmpA(0, 0, 0), tmpB(0, 0, 0), relA(0, 0, 0),
    relB(0, 0, 0), c(0, 0, 0);
  if (m_useOffsetForConstraintFrame)
  {
    // get vector from bodyB to frameB in WCS
    relB = trB.getOrigin() - bodyB_trans.getOrigin();
    // get its projection to slider axis
    btVector3 projB = ax1 * relB.dot(ax1);
    // get vector directed from bodyB to slider axis (and orthogonal to it)
    btVector3 orthoB = relB - projB;
    // same for bodyA
    relA = trA.getOrigin() - bodyA_trans.getOrigin();
    btVector3 projA = ax1 * relA.dot(ax1);
    btVector3 orthoA = relA - projA;
    // get desired offset between frames A and B along slider axis
    btScalar sliderOffs = m_linPos - m_depth[0];
    // desired vector from projection of center of bodyA to projection of
    // center of bodyB to slider axis
    btVector3 totalDist = projA + ax1 * sliderOffs - projB;
    // get offset vectors relA and relB
    relA = orthoA + totalDist * factA;
    relB = orthoB - totalDist * factB;
    // now choose average ortho to slider axis
    p = orthoB * factA + orthoA * factB;
    btScalar len2 = p.length2();
    if (len2 > SIMD_EPSILON)
    {
      p /= btSqrt(len2);
    }
    else
    {
      p = trA.getBasis().getColumn(1);
    }
    // make one more ortho
    q = ax1.cross(p);
    // fill two rows
    tmpA = relA.cross(p);
    tmpB = relB.cross(p);
    for (i = 0; i < 3; ++i)
    {
      info->m_J1angularAxis[s2+i] = tmpA[i];
      info->m_J2angularAxis[s2+i] = -tmpB[i];
    }
    tmpA = relA.cross(q);
    tmpB = relB.cross(q);
    if (hasStaticBody && getSolveAngLimit())
    { // to make constraint between static and dynamic objects more rigid
      // remove wA (or wB) from equation if angular limit is hit
      tmpB *= factB;
      tmpA *= factA;
    }
    for (i = 0; i < 3; ++i)
    {
      info->m_J1angularAxis[s3+i] = tmpA[i];
      info->m_J2angularAxis[s3+i] = -tmpB[i];
      info->m_J1linearAxis[s2+i] = p[i];
      info->m_J1linearAxis[s3+i] = q[i];
      info->m_J2linearAxis[s2+i] = -p[i];
      info->m_J2linearAxis[s3+i] = -q[i];
    }
  }
  else
  {
    // old way - maybe incorrect if bodies are not on the slider axis
    // see discussion "Bug in slider constraint"
    // http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?f=9&t=4024&start=0
    c = bodyB_trans.getOrigin() - bodyA_trans.getOrigin();
    btVector3 tmp = c.cross(p);
    for (i = 0; i < 3; ++i)
    {
     info->m_J1angularAxis[s2+i] = factA*tmp[i];
     info->m_J2angularAxis[s2+i] = factB*tmp[i];
    }
    tmp = c.cross(q);
    for (i = 0; i < 3; ++i)
    {
      info->m_J1angularAxis[s3+i] = factA*tmp[i];
      info->m_J2angularAxis[s3+i] = factB*tmp[i];
      info->m_J1linearAxis[s2+i] = p[i];
      info->m_J1linearAxis[s3+i] = q[i];
      info->m_J2linearAxis[s2+i] = -p[i];
      info->m_J2linearAxis[s3+i] = -q[i];
    }
  }
  // compute two elements of right hand side

  //  k = info->fps * info->erp * getSoftnessOrthoLin();
  currERP = (m_flags & BT_SLIDER_FLAGS_ERP_ORTLIN) ?
    m_softnessOrthoLin : m_softnessOrthoLin * info->erp;
  k = info->fps * currERP;

  btScalar rhs = k * p.dot(ofs);
  info->m_constraintError[s2] = rhs;
  rhs = k * q.dot(ofs);
  info->m_constraintError[s3] = rhs;
  if (m_flags & BT_SLIDER_FLAGS_CFM_ORTLIN)
  {
    info->cfm[s2] = m_cfmOrthoLin;
    info->cfm[s3] = m_cfmOrthoLin;
  }

  // Screw Constraint (coupled linear and angular motion)
  {
    nrow++;
    srow = nrow * info->rowskip;
    info->m_J1linearAxis[srow+0] = -ax1[0] * this->threadPitch;
    info->m_J1linearAxis[srow+1] = -ax1[1] * this->threadPitch;
    info->m_J1linearAxis[srow+2] = -ax1[2] * this->threadPitch;

    info->m_J1angularAxis[srow+0] = ax1[0];
    info->m_J1angularAxis[srow+1] = ax1[1];
    info->m_J1angularAxis[srow+2] = ax1[2];

    info->m_J2linearAxis[srow+0] = ax1[0] * this->threadPitch;
    info->m_J2linearAxis[srow+1] = ax1[1] * this->threadPitch;
    info->m_J2linearAxis[srow+2] = ax1[2] * this->threadPitch;

    info->m_J2angularAxis[srow+0] = -ax1[0];
    info->m_J2angularAxis[srow+1] = -ax1[1];
    info->m_J2angularAxis[srow+2] = -ax1[2];

    // correction
    // rhs = k * ax1.dot(ofs);  // from hinge constraint
    btScalar lin_disp = ax1.dot(ofs);
    btScalar ang_pos = this->getAngularPosition();
    info->m_constraintError[srow] =
      -k * (lin_disp * this->threadPitch - ang_pos);
    info->cfm[srow] = -m_cfmOrthoLin;

    // debug, set cfm to 0
    // info->cfm[srow] = 0;
    // debug, set error correction to 0
    // info->m_constraintError[srow] = 0.0;
  }

  // notes: below enforces
  // joint limit
  // powered joint
  // rotation along slider axis

  // check linear limits
  limit_err = btScalar(0.0);
  limit = 0;
  if (getSolveLinLimit())
  {
    limit_err = getLinDepth() *  signFact;
    limit = (limit_err > btScalar(0.0)) ? 2 : 1;
  }
  powered = 0;
  if (getPoweredLinMotor())
  {
    powered = 1;
  }
  // if the slider has joint limits or motor, add in the extra row
  if (limit || powered)
  {
    nrow++;
    srow = nrow * info->rowskip;
    info->m_J1linearAxis[srow+0] = ax1[0];
    info->m_J1linearAxis[srow+1] = ax1[1];
    info->m_J1linearAxis[srow+2] = ax1[2];
    info->m_J2linearAxis[srow+0] = -ax1[0];
    info->m_J2linearAxis[srow+1] = -ax1[1];
    info->m_J2linearAxis[srow+2] = -ax1[2];
    // linear torque decoupling step:
    //
    // we have to be careful that the linear constraint forces (+/- ax1)
    // applied to the two bodies
    // do not create a torque couple. in other words, the points that the
    // constraint force is applied at must lie along the same ax1 axis.
    // a torque couple will result in limited slider-jointed free
    // bodies from gaining angular momentum.
    if (m_useOffsetForConstraintFrame)
    {
      // this is needed only when bodyA and bodyB are both dynamic.
      if (!hasStaticBody)
      {
        tmpA = relA.cross(ax1);
        tmpB = relB.cross(ax1);
        info->m_J1angularAxis[srow+0] = tmpA[0];
        info->m_J1angularAxis[srow+1] = tmpA[1];
        info->m_J1angularAxis[srow+2] = tmpA[2];
        info->m_J2angularAxis[srow+0] = -tmpB[0];
        info->m_J2angularAxis[srow+1] = -tmpB[1];
        info->m_J2angularAxis[srow+2] = -tmpB[2];
      }
    }
    else
    { // The old way. May be incorrect if bodies are not on the slider axis
      // Linear Torque Decoupling vector (a torque)
      btVector3 ltd;
      ltd = c.cross(ax1);
      info->m_J1angularAxis[srow+0] = factA*ltd[0];
      info->m_J1angularAxis[srow+1] = factA*ltd[1];
      info->m_J1angularAxis[srow+2] = factA*ltd[2];
      info->m_J2angularAxis[srow+0] = factB*ltd[0];
      info->m_J2angularAxis[srow+1] = factB*ltd[1];
      info->m_J2angularAxis[srow+2] = factB*ltd[2];
    }
    // right-hand part
    btScalar lostop = getLowerLinLimit();
    btScalar histop = getUpperLinLimit();

    // issue #1104:
    // if (limit && (lostop == histop)) raises warnings, using
    // a warning-less implementation.
    if (limit &&
      ignition::math::equal(lostop, histop,
      static_cast<btScalar>(std::numeric_limits<double>::epsilon())))
    {
      // the joint motor is ineffective
      powered = 0;
    }
    info->m_constraintError[srow] = 0.;
    info->m_lowerLimit[srow] = 0.;
    info->m_upperLimit[srow] = 0.;
    currERP = (m_flags & BT_SLIDER_FLAGS_ERP_LIMLIN) ?
      m_softnessLimLin : info->erp;
    if (powered)
    {
      if (m_flags & BT_SLIDER_FLAGS_CFM_DIRLIN)
      {
        info->cfm[srow] = m_cfmDirLin;
      }
      btScalar tag_vel = getTargetLinMotorVelocity();
      btScalar mot_fact = getMotorFactor(m_linPos, m_lowerLinLimit,
        m_upperLinLimit, tag_vel, info->fps * currERP);
      info->m_constraintError[srow] -=
        signFact * mot_fact * getTargetLinMotorVelocity();
      info->m_lowerLimit[srow] += -getMaxLinMotorForce() * info->fps;
      info->m_upperLimit[srow] += getMaxLinMotorForce() * info->fps;
    }
    if (limit)
    {
      k = info->fps * currERP;
      info->m_constraintError[srow] += k * limit_err;
      if (m_flags & BT_SLIDER_FLAGS_CFM_LIMLIN)
      {
        info->cfm[srow] = m_cfmLimLin;
      }
      // issue #1104:
      // if (lostop == histop) raises warnings, using
      // a warning-less implementation.
      if (ignition::math::equal(lostop, histop,
          static_cast<btScalar>(std::numeric_limits<double>::epsilon())))
      {
        // limited low and high simultaneously
        info->m_lowerLimit[srow] = -SIMD_INFINITY;
        info->m_upperLimit[srow] = SIMD_INFINITY;
      }
      else if (limit == 1)
      {
         // low limit
        info->m_lowerLimit[srow] = -SIMD_INFINITY;
        info->m_upperLimit[srow] = 0;
      }
      else
      { // high limit
        info->m_lowerLimit[srow] = 0;
        info->m_upperLimit[srow] = SIMD_INFINITY;
      }
      // bounce (we'll use slider parameter abs(1.0 - m_dampingLimLin)
      //   for that)
      btScalar bounce = btFabs(btScalar(1.0) - getDampingLimLin());
      if (bounce > btScalar(0.0))
      {
        btScalar vel = linVelA.dot(ax1);
        vel -= linVelB.dot(ax1);
        vel *= signFact;
        // only apply bounce if the velocity is incoming, and if the
        // resulting c[] exceeds what we already have.
        if (limit == 1)
        {  // low limit
          if (vel < 0)
          {
            btScalar newc = -bounce * vel;
            if (newc > info->m_constraintError[srow])
            {
              info->m_constraintError[srow] = newc;
            }
          }
        }
        else
        { // high limit - all those computations are reversed
          if (vel > 0)
          {
            btScalar newc = -bounce * vel;
            if (newc < info->m_constraintError[srow])
            {
              info->m_constraintError[srow] = newc;
            }
          }
        }
      }
      info->m_constraintError[srow] *= getSoftnessLimLin();
    }
    // line above is the end of if (limit)
  }
  // line above is the end of if linear limit

  // printf("tp: %f\n", this->threadPitch);

  // check angular limits
  limit_err = btScalar(0.0);
  limit = 0;
  if (getSolveAngLimit())
  {
    limit_err = getAngDepth();
    limit = (limit_err > btScalar(0.0)) ? 1 : 2;
  }
  // if the slider has joint limits, add in the extra row
  powered = 0;
  if (getPoweredAngMotor())
  {
    powered = 1;
  }
  if (limit || powered)
  {
    nrow++;
    srow = nrow * info->rowskip;
    info->m_J1angularAxis[srow+0] = ax1[0];
    info->m_J1angularAxis[srow+1] = ax1[1];
    info->m_J1angularAxis[srow+2] = ax1[2];

    info->m_J2angularAxis[srow+0] = -ax1[0];
    info->m_J2angularAxis[srow+1] = -ax1[1];
    info->m_J2angularAxis[srow+2] = -ax1[2];

    btScalar lostop = getLowerAngLimit();
    btScalar histop = getUpperAngLimit();
    // issue #1104:
    // if (limit && (lostop == histop)) raises warnings, using
    // a warning-less implementation.
    if (limit &&
      ignition::math::equal(lostop, histop,
      static_cast<btScalar>(std::numeric_limits<double>::epsilon())))
    {  // the joint motor is ineffective
      powered = 0;
    }
    currERP = (m_flags & BT_SLIDER_FLAGS_ERP_LIMANG) ?
      m_softnessLimAng : info->erp;
    if (powered)
    {
      if (m_flags & BT_SLIDER_FLAGS_CFM_DIRANG)
      {
        info->cfm[srow] = m_cfmDirAng;
      }
      btScalar mot_fact = getMotorFactor(m_angPos, m_lowerAngLimit,
        m_upperAngLimit, getTargetAngMotorVelocity(), info->fps * currERP);
      info->m_constraintError[srow] = mot_fact * getTargetAngMotorVelocity();
      info->m_lowerLimit[srow] = -getMaxAngMotorForce() * info->fps;
      info->m_upperLimit[srow] = getMaxAngMotorForce() * info->fps;
    }
    if (limit)
    {
      k = info->fps * currERP;
      info->m_constraintError[srow] += k * limit_err;
      if (m_flags & BT_SLIDER_FLAGS_CFM_LIMANG)
      {
        info->cfm[srow] = m_cfmLimAng;
      }
      // issue #1104:
      // if (lostop == histop) raises warnings, using
      // a warning-less implementation.
      if (ignition::math::equal(lostop, histop,
          static_cast<btScalar>(std::numeric_limits<double>::epsilon())))
      {
        // limited low and high simultaneously
        info->m_lowerLimit[srow] = -SIMD_INFINITY;
        info->m_upperLimit[srow] = SIMD_INFINITY;
      }
      else if (limit == 1)
      { // low limit
        info->m_lowerLimit[srow] = 0;
        info->m_upperLimit[srow] = SIMD_INFINITY;
      }
      else
      { // high limit
        info->m_lowerLimit[srow] = -SIMD_INFINITY;
        info->m_upperLimit[srow] = 0;
      }
      // bounce (we'll use slider parameter abs(1.0 - m_dampingLimAng)
      // for that)
      btScalar bounce = btFabs(btScalar(1.0) - getDampingLimAng());
      if (bounce > btScalar(0.0))
      {
        btScalar vel = m_rbA.getAngularVelocity().dot(ax1);
        vel -= m_rbB.getAngularVelocity().dot(ax1);
        // only apply bounce if the velocity is incoming, and if the
        // resulting c[] exceeds what we already have.
        if (limit == 1)
        {  // low limit
          if (vel < 0)
          {
            btScalar newc = -bounce * vel;
            if (newc > info->m_constraintError[srow])
            {
              info->m_constraintError[srow] = newc;
            }
          }
        }
        else
        {  // high limit - all those computations are reversed
          if (vel > 0)
          {
            btScalar newc = -bounce * vel;
            if (newc < info->m_constraintError[srow])
            {
              info->m_constraintError[srow] = newc;
            }
          }
        }
      }
      info->m_constraintError[srow] *= getSoftnessLimAng();
    }
    // line above is the end of if (limit)
  }
  // line above is the end of if angular limit or powered
}

//////////////////////////////////////////////////
void btScrewConstraint::_getInfo1NonVirtual(btConstraintInfo1* info)
{
  /// this is a modified version of
  /// void btSliderConstraint::getInfo1(btConstraintInfo1* info)
  /// with the rotational limits always turned on.
  /// The rotational limit constraint is modified to be a screw constraint
  /// in btScrewConstraint::_getInfo2NonVirtual
  /// which is a copy of btSliderConstraint::getInfo2NonVirtual.

  // info->m_numConstraintRows = 6;
  // Fixed 2 linear + 2 angular + 1 limit (even if not used)
  // info->nub = 0;

  if (m_useSolveConstraintObsolete)
  {
    info->m_numConstraintRows = 0;
    info->nub = 0;
  }
  else
  {
    // Fixed 2 linear + 2 angular
    info->m_numConstraintRows = 4;
    info->nub = 2;

    // Add constraint for screw motion, coupling linear and angular motion.
    info->m_numConstraintRows++;
    info->nub++;

    // prepare constraint
    calculateTransforms(
      m_rbA.getCenterOfMassTransform(), m_rbB.getCenterOfMassTransform());
    testLinLimits();
    if (getSolveLinLimit() || getPoweredLinMotor())
    {
      // limit 3rd linear as well
      info->m_numConstraintRows++;
      info->nub--;
    }
    testAngLimits();
    if (getSolveAngLimit() || getPoweredAngMotor())
    {
      // limit 3rd angular as well
      info->m_numConstraintRows++;
      info->nub--;
    }
  }
  // printf("m: %d\n", info->m_numConstraintRows);
}
