/*******************************************************************************
 * Copyright (c) 2013, Daniel Murphy
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 	* Redistributions of source code must retain the above copyright notice,
 * 	  this list of conditions and the following disclaimer.
 * 	* Redistributions in binary form must reproduce the above copyright notice,
 * 	  this list of conditions and the following disclaimer in the documentation
 * 	  and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
package org.jbox2d.dynamics.joints;

import org.jbox2d.common.Mat22;
import org.jbox2d.common.Mat33;
import org.jbox2d.common.MathUtils;
import org.jbox2d.common.Rot;
import org.jbox2d.common.Settings;
import org.jbox2d.common.Vec2;
import org.jbox2d.common.Vec3;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.SolverData;
import org.jbox2d.pooling.IWorldPool;

//Linear constraint (point-to-line)
//d = p2 - p1 = x2 + r2 - x1 - r1
//C = dot(perp, d)
//Cdot = dot(d, cross(w1, perp)) + dot(perp, v2 + cross(w2, r2) - v1 - cross(w1, r1))
//   = -dot(perp, v1) - dot(cross(d + r1, perp), w1) + dot(perp, v2) + dot(cross(r2, perp), v2)
//J = [-perp, -cross(d + r1, perp), perp, cross(r2,perp)]
//
//Angular constraint
//C = a2 - a1 + a_initial
//Cdot = w2 - w1
//J = [0 0 -1 0 0 1]
//
//K = J * invM * JT
//
//J = [-a -s1 a s2]
//  [0  -1  0  1]
//a = perp
//s1 = cross(d + r1, a) = cross(p2 - x1, a)
//s2 = cross(r2, a) = cross(p2 - x2, a)


//Motor/Limit linear constraint
//C = dot(ax1, d)
//Cdot = = -dot(ax1, v1) - dot(cross(d + r1, ax1), w1) + dot(ax1, v2) + dot(cross(r2, ax1), v2)
//J = [-ax1 -cross(d+r1,ax1) ax1 cross(r2,ax1)]

//Block Solver
//We develop a block solver that includes the joint limit. This makes the limit stiff (inelastic) even
//when the mass has poor distribution (leading to large torques about the joint anchor points).
//
//The Jacobian has 3 rows:
//J = [-uT -s1 uT s2] // linear
//  [0   -1   0  1] // angular
//  [-vT -a1 vT a2] // limit
//
//u = perp
//v = axis
//s1 = cross(d + r1, u), s2 = cross(r2, u)
//a1 = cross(d + r1, v), a2 = cross(r2, v)

//M * (v2 - v1) = JT * df
//J * v2 = bias
//
//v2 = v1 + invM * JT * df
//J * (v1 + invM * JT * df) = bias
//K * df = bias - J * v1 = -Cdot
//K = J * invM * JT
//Cdot = J * v1 - bias
//
//Now solve for f2.
//df = f2 - f1
//K * (f2 - f1) = -Cdot
//f2 = invK * (-Cdot) + f1
//
//Clamp accumulated limit impulse.
//lower: f2(3) = max(f2(3), 0)
//upper: f2(3) = min(f2(3), 0)
//
//Solve for correct f2(1:2)
//K(1:2, 1:2) * f2(1:2) = -Cdot(1:2) - K(1:2,3) * f2(3) + K(1:2,1:3) * f1
//                    = -Cdot(1:2) - K(1:2,3) * f2(3) + K(1:2,1:2) * f1(1:2) + K(1:2,3) * f1(3)
//K(1:2, 1:2) * f2(1:2) = -Cdot(1:2) - K(1:2,3) * (f2(3) - f1(3)) + K(1:2,1:2) * f1(1:2)
//f2(1:2) = invK(1:2,1:2) * (-Cdot(1:2) - K(1:2,3) * (f2(3) - f1(3))) + f1(1:2)
//
//Now compute impulse to be applied:
//df = f2 - f1

/**
 * A prismatic joint. This joint provides one degree of freedom: translation along an axis fixed in
 * bodyA. Relative rotation is prevented. You can use a joint limit to restrict the range of motion
 * and a joint motor to drive the motion or to model joint friction.
 * 
 * @author Daniel
 */
public class PrismaticJoint extends Joint {

  // Solver shared
  public final Vec2 m_localAnchorA;
  public final Vec2 m_localAnchorB;
  public final Vec2 m_localXAxisA;
  public final Vec2 m_localYAxisA;
  public float m_referenceAngle;
  public final Vec3 m_impulse;
  public float m_motorImpulse;
  public float m_lowerTranslation;
  public float m_upperTranslation;
  public float m_maxMotorForce;
  public float m_motorSpeed;
  public boolean m_enableLimit;
  public boolean m_enableMotor;
  public LimitState m_limitState;

  // Solver temp
  public int m_indexA;
  public int m_indexB;
  public final Vec2 m_rA = new Vec2();
  public final Vec2 m_rB = new Vec2();
  public final Vec2 m_localCenterA = new Vec2();
  public final Vec2 m_localCenterB = new Vec2();
  public float m_invMassA;
  public float m_invMassB;
  public float m_invIA;
  public float m_invIB;
  public final Vec2 m_axis, m_perp;
  public float m_s1, m_s2;
  public float m_a1, m_a2;
  public final Mat33 m_K;
  public float m_motorMass; // effective mass for motor/limit translational constraint.

  public PrismaticJoint(IWorldPool argWorld, PrismaticJointDef def) {
    super(argWorld, def);
    m_localAnchorA = new Vec2(def.localAnchorA);
    m_localAnchorB = new Vec2(def.localAnchorB);
    m_localXAxisA = new Vec2(def.localAxisA);
    m_localXAxisA.normalize();
    m_localYAxisA = new Vec2();
    Vec2.crossToOutUnsafe(1f, m_localXAxisA, m_localYAxisA);
    m_referenceAngle = def.referenceAngle;

    m_impulse = new Vec3();
    m_motorMass = 0.0f;
    m_motorImpulse = 0.0f;

    m_lowerTranslation = def.lowerTranslation;
    m_upperTranslation = def.upperTranslation;
    m_maxMotorForce = def.maxMotorForce;
    m_motorSpeed = def.motorSpeed;
    m_enableLimit = def.enableLimit;
    m_enableMotor = def.enableMotor;
    m_limitState = LimitState.INACTIVE;

    m_K = new Mat33();
    m_axis = new Vec2();
    m_perp = new Vec2();
  }

  public Vec2 getLocalAnchorA() {
    return m_localAnchorA;
  }

  public Vec2 getLocalAnchorB() {
    return m_localAnchorB;
  }

  @Override
  public void getAnchorA(Vec2 argOut) {
    m_bodyA.getWorldPointToOut(m_localAnchorA, argOut);
  }

  @Override
  public void getAnchorB(Vec2 argOut) {
    m_bodyB.getWorldPointToOut(m_localAnchorB, argOut);
  }

  @Override
  public void getReactionForce(float inv_dt, Vec2 argOut) {
    Vec2 temp = pool.popVec2();
    temp.set(m_axis).mulLocal(m_motorImpulse + m_impulse.z);
    argOut.set(m_perp).mulLocal(m_impulse.x).addLocal(temp).mulLocal(inv_dt);
    pool.pushVec2(1);
  }

  @Override
  public float getReactionTorque(float inv_dt) {
    return inv_dt * m_impulse.y;
  }

  /**
   * Get the current joint translation, usually in meters. public float getJointTranslation() { Vec2
   * pA = pool.popVec2(); Vec2 pB = pool.popVec2(); Vec2 axis = pool.popVec2();
   * 
   * m_bodyA.getWorldPointToOut(m_localAnchorA, pA); m_bodyB.getWorldPointToOut(m_localAnchorB, pB);
   * pB.subLocal(pA); m_bodyA.getWorldVectorToOut(m_localXAxisA, axis);
   * 
   * float translation = Vec2.dot(pB, axis);
   * 
   * pool.pushVec2(3); return translation; }
   * 
   * /** Get the current joint translation speed, usually in meters per second.
   * 
   * @return
   */
  public float getJointSpeed() {
    Body bA = m_bodyA;
    Body bB = m_bodyB;

    Vec2[] pc = pool.popVec2(9);
    Vec2 temp = pc[0];
    Vec2 rA = pc[1];
    Vec2 rB = pc[2];
    Vec2 p1 = pc[3];
    Vec2 p2 = pc[4];
    Vec2 d = pc[5];
    Vec2 axis = pc[6];
    Vec2 temp2 = pc[7];
    Vec2 temp3 = pc[8];

    temp.set(m_localAnchorA).subLocal(bA.m_sweep.localCenter);
    Rot.mulToOutUnsafe(bA.m_xf.q, temp, rA);

    temp.set(m_localAnchorB).subLocal(bB.m_sweep.localCenter);
    Rot.mulToOutUnsafe(bB.m_xf.q, temp, rB);

    p1.set(bA.m_sweep.c).addLocal(rA);
    p2.set(bB.m_sweep.c).addLocal(rB);

    d.set(p2).subLocal(p1);
    Rot.mulToOutUnsafe(bA.m_xf.q, m_localXAxisA, axis);

    Vec2 vA = bA.m_linearVelocity;
    Vec2 vB = bB.m_linearVelocity;
    float wA = bA.m_angularVelocity;
    float wB = bB.m_angularVelocity;


    Vec2.crossToOutUnsafe(wA, axis, temp);
    Vec2.crossToOutUnsafe(wB, rB, temp2);
    Vec2.crossToOutUnsafe(wA, rA, temp3);

    temp2.addLocal(vB).subLocal(vA).subLocal(temp3);
    float speed = Vec2.dot(d, temp) + Vec2.dot(axis, temp2);

    pool.pushVec2(9);

    return speed;
  }

  public float getJointTranslation() {
    Vec2 pA = pool.popVec2(), pB = pool.popVec2(), axis = pool.popVec2();
    m_bodyA.getWorldPointToOut(m_localAnchorA, pA);
    m_bodyB.getWorldPointToOut(m_localAnchorB, pB);
    m_bodyA.getWorldVectorToOutUnsafe(m_localXAxisA, axis);
    pB.subLocal(pA);
    float translation = Vec2.dot(pB, axis);
    pool.pushVec2(3);
    return translation;
  }

  /**
   * Is the joint limit enabled?
   * 
   * @return
   */
  public boolean isLimitEnabled() {
    return m_enableLimit;
  }

  /**
   * Enable/disable the joint limit.
   * 
   * @param flag
   */
  public void enableLimit(boolean flag) {
    if (flag != m_enableLimit) {
      m_bodyA.setAwake(true);
      m_bodyB.setAwake(true);
      m_enableLimit = flag;
      m_impulse.z = 0.0f;
    }
  }

  /**
   * Get the lower joint limit, usually in meters.
   * 
   * @return
   */
  public float getLowerLimit() {
    return m_lowerTranslation;
  }

  /**
   * Get the upper joint limit, usually in meters.
   * 
   * @return
   */
  public float getUpperLimit() {
    return m_upperTranslation;
  }

  /**
   * Set the joint limits, usually in meters.
   * 
   * @param lower
   * @param upper
   */
  public void setLimits(float lower, float upper) {
    assert (lower <= upper);
    if (lower != m_lowerTranslation || upper != m_upperTranslation) {
      m_bodyA.setAwake(true);
      m_bodyB.setAwake(true);
      m_lowerTranslation = lower;
      m_upperTranslation = upper;
      m_impulse.z = 0.0f;
    }
  }

  /**
   * Is the joint motor enabled?
   * 
   * @return
   */
  public boolean isMotorEnabled() {
    return m_enableMotor;
  }

  /**
   * Enable/disable the joint motor.
   * 
   * @param flag
   */
  public void enableMotor(boolean flag) {
    m_bodyA.setAwake(true);
    m_bodyB.setAwake(true);
    m_enableMotor = flag;
  }

  /**
   * Set the motor speed, usually in meters per second.
   * 
   * @param speed
   */
  public void setMotorSpeed(float speed) {
    m_bodyA.setAwake(true);
    m_bodyB.setAwake(true);
    m_motorSpeed = speed;
  }

  /**
   * Get the motor speed, usually in meters per second.
   * 
   * @return
   */
  public float getMotorSpeed() {
    return m_motorSpeed;
  }

  /**
   * Set the maximum motor force, usually in N.
   * 
   * @param force
   */
  public void setMaxMotorForce(float force) {
    m_bodyA.setAwake(true);
    m_bodyB.setAwake(true);
    m_maxMotorForce = force;
  }
  
  public float getMaxMotorForce() {
	  return m_maxMotorForce;
  }
  
  public float getReferenceAngle() {
	  return m_referenceAngle;
  }

  /**
   * Get the current motor force, usually in N.
   * 
   * @param inv_dt
   * @return
   */
  public float getMotorForce(float inv_dt) {
    return m_motorImpulse * inv_dt;
  }
  
  public Vec2 getLocalAxisA() {
	return m_localXAxisA;
  }

  @Override
  public void initVelocityConstraints(final SolverData data) {
    m_indexA = m_bodyA.m_islandIndex;
    m_indexB = m_bodyB.m_islandIndex;
    m_localCenterA.set(m_bodyA.m_sweep.localCenter);
    m_localCenterB.set(m_bodyB.m_sweep.localCenter);
    m_invMassA = m_bodyA.m_invMass;
    m_invMassB = m_bodyB.m_invMass;
    m_invIA = m_bodyA.m_invI;
    m_invIB = m_bodyB.m_invI;

    Vec2 cA = data.positions[m_indexA].c;
    float aA = data.positions[m_indexA].a;
    Vec2 vA = data.velocities[m_indexA].v;
    float wA = data.velocities[m_indexA].w;

    Vec2 cB = data.positions[m_indexB].c;
    float aB = data.positions[m_indexB].a;
    Vec2 vB = data.velocities[m_indexB].v;
    float wB = data.velocities[m_indexB].w;

    final Rot qA = pool.popRot();
    final Rot qB = pool.popRot();
    final Vec2 d = pool.popVec2();
    final Vec2 temp = pool.popVec2();
    final Vec2 rA = pool.popVec2();
    final Vec2 rB = pool.popVec2();

    qA.set(aA);
    qB.set(aB);

    // Compute the effective masses.
    Rot.mulToOutUnsafe(qA, d.set(m_localAnchorA).subLocal(m_localCenterA), rA);
    Rot.mulToOutUnsafe(qB, d.set(m_localAnchorB).subLocal(m_localCenterB), rB);
    d.set(cB).subLocal(cA).addLocal(rB).subLocal(rA);

    float mA = m_invMassA, mB = m_invMassB;
    float iA = m_invIA, iB = m_invIB;

    // Compute motor Jacobian and effective mass.
    {
      Rot.mulToOutUnsafe(qA, m_localXAxisA, m_axis);
      temp.set(d).addLocal(rA);
      m_a1 = Vec2.cross(temp, m_axis);
      m_a2 = Vec2.cross(rB, m_axis);

      m_motorMass = mA + mB + iA * m_a1 * m_a1 + iB * m_a2 * m_a2;
      if (m_motorMass > 0.0f) {
        m_motorMass = 1.0f / m_motorMass;
      }
    }

    // Prismatic constraint.
    {
      Rot.mulToOutUnsafe(qA, m_localYAxisA, m_perp);

      temp.set(d).addLocal(rA);
      m_s1 = Vec2.cross(temp, m_perp);
      m_s2 = Vec2.cross(rB, m_perp);

      float k11 = mA + mB + iA * m_s1 * m_s1 + iB * m_s2 * m_s2;
      float k12 = iA * m_s1 + iB * m_s2;
      float k13 = iA * m_s1 * m_a1 + iB * m_s2 * m_a2;
      float k22 = iA + iB;
      if (k22 == 0.0f) {
        // For bodies with fixed rotation.
        k22 = 1.0f;
      }
      float k23 = iA * m_a1 + iB * m_a2;
      float k33 = mA + mB + iA * m_a1 * m_a1 + iB * m_a2 * m_a2;

      m_K.ex.set(k11, k12, k13);
      m_K.ey.set(k12, k22, k23);
      m_K.ez.set(k13, k23, k33);
    }

    // Compute motor and limit terms.
    if (m_enableLimit) {

      float jointTranslation = Vec2.dot(m_axis, d);
      if (MathUtils.abs(m_upperTranslation - m_lowerTranslation) < 2.0f * Settings.linearSlop) {
        m_limitState = LimitState.EQUAL;
      } else if (jointTranslation <= m_lowerTranslation) {
        if (m_limitState != LimitState.AT_LOWER) {
          m_limitState = LimitState.AT_LOWER;
          m_impulse.z = 0.0f;
        }
      } else if (jointTranslation >= m_upperTranslation) {
        if (m_limitState != LimitState.AT_UPPER) {
          m_limitState = LimitState.AT_UPPER;
          m_impulse.z = 0.0f;
        }
      } else {
        m_limitState = LimitState.INACTIVE;
        m_impulse.z = 0.0f;
      }
    } else {
      m_limitState = LimitState.INACTIVE;
      m_impulse.z = 0.0f;
    }

    if (m_enableMotor == false) {
      m_motorImpulse = 0.0f;
    }

    if (data.step.warmStarting) {
      // Account for variable time step.
      m_impulse.mulLocal(data.step.dtRatio);
      m_motorImpulse *= data.step.dtRatio;

      final Vec2 P = pool.popVec2();
      temp.set(m_axis).mulLocal(m_motorImpulse + m_impulse.z);
      P.set(m_perp).mulLocal(m_impulse.x).addLocal(temp);

      float LA = m_impulse.x * m_s1 + m_impulse.y + (m_motorImpulse + m_impulse.z) * m_a1;
      float LB = m_impulse.x * m_s2 + m_impulse.y + (m_motorImpulse + m_impulse.z) * m_a2;

      vA.x -= mA * P.x;
      vA.y -= mA * P.y;
      wA -= iA * LA;

      vB.x += mB * P.x;
      vB.y += mB * P.y;
      wB += iB * LB;

      pool.pushVec2(1);
    } else {
      m_impulse.setZero();
      m_motorImpulse = 0.0f;
    }

//    data.velocities[m_indexA].v.set(vA);
    data.velocities[m_indexA].w = wA;
//    data.velocities[m_indexB].v.set(vB);
    data.velocities[m_indexB].w = wB;

    pool.pushRot(2);
    pool.pushVec2(4);
  }

  @Override
  public void solveVelocityConstraints(final SolverData data) {
    Vec2 vA = data.velocities[m_indexA].v;
    float wA = data.velocities[m_indexA].w;
    Vec2 vB = data.velocities[m_indexB].v;
    float wB = data.velocities[m_indexB].w;

    float mA = m_invMassA, mB = m_invMassB;
    float iA = m_invIA, iB = m_invIB;

    final Vec2 temp = pool.popVec2();

    // Solve linear motor constraint.
    if (m_enableMotor && m_limitState != LimitState.EQUAL) {
      temp.set(vB).subLocal(vA);
      float Cdot = Vec2.dot(m_axis, temp) + m_a2 * wB - m_a1 * wA;
      float impulse = m_motorMass * (m_motorSpeed - Cdot);
      float oldImpulse = m_motorImpulse;
      float maxImpulse = data.step.dt * m_maxMotorForce;
      m_motorImpulse = MathUtils.clamp(m_motorImpulse + impulse, -maxImpulse, maxImpulse);
      impulse = m_motorImpulse - oldImpulse;

      final Vec2 P = pool.popVec2();
      P.set(m_axis).mulLocal(impulse);
      float LA = impulse * m_a1;
      float LB = impulse * m_a2;

      vA.x -= mA * P.x;
      vA.y -= mA * P.y;
      wA -= iA * LA;

      vB.x += mB * P.x;
      vB.y += mB * P.y;
      wB += iB * LB;

      pool.pushVec2(1);
    }

    final Vec2 Cdot1 = pool.popVec2();
    temp.set(vB).subLocal(vA);
    Cdot1.x = Vec2.dot(m_perp, temp) + m_s2 * wB - m_s1 * wA;
    Cdot1.y = wB - wA;
    // System.out.println(Cdot1);

    if (m_enableLimit && m_limitState != LimitState.INACTIVE) {
      // Solve prismatic and limit constraint in block form.
      float Cdot2;
      temp.set(vB).subLocal(vA);
      Cdot2 = Vec2.dot(m_axis, temp) + m_a2 * wB - m_a1 * wA;

      final Vec3 Cdot = pool.popVec3();
      Cdot.set(Cdot1.x, Cdot1.y, Cdot2);

      final Vec3 f1 = pool.popVec3();
      final Vec3 df = pool.popVec3();

      f1.set(m_impulse);
      m_K.solve33ToOut(Cdot.negateLocal(), df);
      // Cdot.negateLocal(); not used anymore
      m_impulse.addLocal(df);

      if (m_limitState == LimitState.AT_LOWER) {
        m_impulse.z = MathUtils.max(m_impulse.z, 0.0f);
      } else if (m_limitState == LimitState.AT_UPPER) {
        m_impulse.z = MathUtils.min(m_impulse.z, 0.0f);
      }

      // f2(1:2) = invK(1:2,1:2) * (-Cdot(1:2) - K(1:2,3) * (f2(3) - f1(3))) +
      // f1(1:2)
      final Vec2 b = pool.popVec2();
      final Vec2 f2r = pool.popVec2();

      temp.set(m_K.ez.x, m_K.ez.y).mulLocal(m_impulse.z - f1.z);
      b.set(Cdot1).negateLocal().subLocal(temp);

      m_K.solve22ToOut(b, f2r);
      f2r.addLocal(f1.x, f1.y);
      m_impulse.x = f2r.x;
      m_impulse.y = f2r.y;

      df.set(m_impulse).subLocal(f1);

      final Vec2 P = pool.popVec2();
      temp.set(m_axis).mulLocal(df.z);
      P.set(m_perp).mulLocal(df.x).addLocal(temp);

      float LA = df.x * m_s1 + df.y + df.z * m_a1;
      float LB = df.x * m_s2 + df.y + df.z * m_a2;

      vA.x -= mA * P.x;
      vA.y -= mA * P.y;
      wA -= iA * LA;

      vB.x += mB * P.x;
      vB.y += mB * P.y;
      wB += iB * LB;

      pool.pushVec2(3);
      pool.pushVec3(3);
    } else {
      // Limit is inactive, just solve the prismatic constraint in block form.
      final Vec2 df = pool.popVec2();
      m_K.solve22ToOut(Cdot1.negateLocal(), df);
      Cdot1.negateLocal();

      m_impulse.x += df.x;
      m_impulse.y += df.y;

      final Vec2 P = pool.popVec2();
      P.set(m_perp).mulLocal(df.x);
      float LA = df.x * m_s1 + df.y;
      float LB = df.x * m_s2 + df.y;

      vA.x -= mA * P.x;
      vA.y -= mA * P.y;
      wA -= iA * LA;

      vB.x += mB * P.x;
      vB.y += mB * P.y;
      wB += iB * LB;

      pool.pushVec2(2);
    }

//    data.velocities[m_indexA].v.set(vA);
    data.velocities[m_indexA].w = wA;
//    data.velocities[m_indexB].v.set(vB);
    data.velocities[m_indexB].w = wB;

    pool.pushVec2(2);
  }


  @Override
  public boolean solvePositionConstraints(final SolverData data) {

    final Rot qA = pool.popRot();
    final Rot qB = pool.popRot();
    final Vec2 rA = pool.popVec2();
    final Vec2 rB = pool.popVec2();
    final Vec2 d = pool.popVec2();
    final Vec2 axis = pool.popVec2();
    final Vec2 perp = pool.popVec2();
    final Vec2 temp = pool.popVec2();
    final Vec2 C1 = pool.popVec2();

    final Vec3 impulse = pool.popVec3();

    Vec2 cA = data.positions[m_indexA].c;
    float aA = data.positions[m_indexA].a;
    Vec2 cB = data.positions[m_indexB].c;
    float aB = data.positions[m_indexB].a;

    qA.set(aA);
    qB.set(aB);

    float mA = m_invMassA, mB = m_invMassB;
    float iA = m_invIA, iB = m_invIB;

    // Compute fresh Jacobians
    Rot.mulToOutUnsafe(qA, temp.set(m_localAnchorA).subLocal(m_localCenterA), rA);
    Rot.mulToOutUnsafe(qB, temp.set(m_localAnchorB).subLocal(m_localCenterB), rB);
    d.set(cB).addLocal(rB).subLocal(cA).subLocal(rA);

    Rot.mulToOutUnsafe(qA, m_localXAxisA, axis);
    float a1 = Vec2.cross(temp.set(d).addLocal(rA), axis);
    float a2 = Vec2.cross(rB, axis);
    Rot.mulToOutUnsafe(qA, m_localYAxisA, perp);

    float s1 = Vec2.cross(temp.set(d).addLocal(rA), perp);
    float s2 = Vec2.cross(rB, perp);

    C1.x = Vec2.dot(perp, d);
    C1.y = aB - aA - m_referenceAngle;

    float linearError = MathUtils.abs(C1.x);
    float angularError = MathUtils.abs(C1.y);

    boolean active = false;
    float C2 = 0.0f;
    if (m_enableLimit) {
      float translation = Vec2.dot(axis, d);
      if (MathUtils.abs(m_upperTranslation - m_lowerTranslation) < 2.0f * Settings.linearSlop) {
        // Prevent large angular corrections
        C2 =
            MathUtils.clamp(translation, -Settings.maxLinearCorrection,
                Settings.maxLinearCorrection);
        linearError = MathUtils.max(linearError, MathUtils.abs(translation));
        active = true;
      } else if (translation <= m_lowerTranslation) {
        // Prevent large linear corrections and allow some slop.
        C2 =
            MathUtils.clamp(translation - m_lowerTranslation + Settings.linearSlop,
                -Settings.maxLinearCorrection, 0.0f);
        linearError = MathUtils.max(linearError, m_lowerTranslation - translation);
        active = true;
      } else if (translation >= m_upperTranslation) {
        // Prevent large linear corrections and allow some slop.
        C2 =
            MathUtils.clamp(translation - m_upperTranslation - Settings.linearSlop, 0.0f,
                Settings.maxLinearCorrection);
        linearError = MathUtils.max(linearError, translation - m_upperTranslation);
        active = true;
      }
    }

    if (active) {
      float k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2;
      float k12 = iA * s1 + iB * s2;
      float k13 = iA * s1 * a1 + iB * s2 * a2;
      float k22 = iA + iB;
      if (k22 == 0.0f) {
        // For fixed rotation
        k22 = 1.0f;
      }
      float k23 = iA * a1 + iB * a2;
      float k33 = mA + mB + iA * a1 * a1 + iB * a2 * a2;

      final Mat33 K = pool.popMat33();
      K.ex.set(k11, k12, k13);
      K.ey.set(k12, k22, k23);
      K.ez.set(k13, k23, k33);

      final Vec3 C = pool.popVec3();
      C.x = C1.x;
      C.y = C1.y;
      C.z = C2;

      K.solve33ToOut(C.negateLocal(), impulse);
      pool.pushVec3(1);
      pool.pushMat33(1);
    } else {
      float k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2;
      float k12 = iA * s1 + iB * s2;
      float k22 = iA + iB;
      if (k22 == 0.0f) {
        k22 = 1.0f;
      }

      final Mat22 K = pool.popMat22();
      K.ex.set(k11, k12);
      K.ey.set(k12, k22);

      // temp is impulse1
      K.solveToOut(C1.negateLocal(), temp);
      C1.negateLocal();

      impulse.x = temp.x;
      impulse.y = temp.y;
      impulse.z = 0.0f;

      pool.pushMat22(1);
    }

    float Px = impulse.x * perp.x + impulse.z * axis.x;
    float Py = impulse.x * perp.y + impulse.z * axis.y;
    float LA = impulse.x * s1 + impulse.y + impulse.z * a1;
    float LB = impulse.x * s2 + impulse.y + impulse.z * a2;

    cA.x -= mA * Px;
    cA.y -= mA * Py;
    aA -= iA * LA;
    cB.x += mB * Px;
    cB.y += mB * Py;
    aB += iB * LB;

//    data.positions[m_indexA].c.set(cA);
    data.positions[m_indexA].a = aA;
//    data.positions[m_indexB].c.set(cB);
    data.positions[m_indexB].a = aB;

    pool.pushVec2(7);
    pool.pushVec3(1);
    pool.pushRot(2);

    return linearError <= Settings.linearSlop && angularError <= Settings.angularSlop;
  }
}
