org.jbox2d.dynamics.joints.WheelJoint Maven / Gradle / Ivy
/*******************************************************************************
* 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.MathUtils;
import org.jbox2d.common.Rot;
import org.jbox2d.common.Settings;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.SolverData;
import org.jbox2d.pooling.IWorldPool;
//Linear constraint (point-to-line)
//d = pB - pA = xB + rB - xA - rA
//C = dot(ay, d)
//Cdot = dot(d, cross(wA, ay)) + dot(ay, vB + cross(wB, rB) - vA - cross(wA, rA))
// = -dot(ay, vA) - dot(cross(d + rA, ay), wA) + dot(ay, vB) + dot(cross(rB, ay), vB)
//J = [-ay, -cross(d + rA, ay), ay, cross(rB, ay)]
//Spring linear constraint
//C = dot(ax, d)
//Cdot = = -dot(ax, vA) - dot(cross(d + rA, ax), wA) + dot(ax, vB) + dot(cross(rB, ax), vB)
//J = [-ax -cross(d+rA, ax) ax cross(rB, ax)]
//Motor rotational constraint
//Cdot = wB - wA
//J = [0 0 -1 0 0 1]
/**
* A wheel joint. This joint provides two degrees of freedom: translation along an axis fixed in
* bodyA and rotation in the plane. You can use a joint limit to restrict the range of motion and a
* joint motor to drive the rotation or to model rotational friction. This joint is designed for
* vehicle suspensions.
*
* @author Daniel Murphy
*/
public class WheelJoint extends Joint {
private float m_frequencyHz;
private float m_dampingRatio;
// Solver shared
private final Vec2 m_localAnchorA = new Vec2();
private final Vec2 m_localAnchorB = new Vec2();
private final Vec2 m_localXAxisA = new Vec2();
private final Vec2 m_localYAxisA = new Vec2();
private float m_impulse;
private float m_motorImpulse;
private float m_springImpulse;
private float m_maxMotorTorque;
private float m_motorSpeed;
private boolean m_enableMotor;
// Solver temp
private int m_indexA;
private int m_indexB;
private final Vec2 m_localCenterA = new Vec2();
private final Vec2 m_localCenterB = new Vec2();
private float m_invMassA;
private float m_invMassB;
private float m_invIA;
private float m_invIB;
private final Vec2 m_ax = new Vec2();
private final Vec2 m_ay = new Vec2();
private float m_sAx, m_sBx;
private float m_sAy, m_sBy;
private float m_mass;
private float m_motorMass;
private float m_springMass;
private float m_bias;
private float m_gamma;
protected WheelJoint(IWorldPool argPool, WheelJointDef def) {
super(argPool, def);
m_localAnchorA.set(def.localAnchorA);
m_localAnchorB.set(def.localAnchorB);
m_localXAxisA.set(def.localAxisA);
Vec2.crossToOutUnsafe(1.0f, m_localXAxisA, m_localYAxisA);
m_motorMass = 0.0f;
m_motorImpulse = 0.0f;
m_maxMotorTorque = def.maxMotorTorque;
m_motorSpeed = def.motorSpeed;
m_enableMotor = def.enableMotor;
m_frequencyHz = def.frequencyHz;
m_dampingRatio = def.dampingRatio;
}
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) {
final Vec2 temp = pool.popVec2();
temp.set(m_ay).mulLocal(m_impulse);
argOut.set(m_ax).mulLocal(m_springImpulse).addLocal(temp).mulLocal(inv_dt);
pool.pushVec2(1);
}
@Override
public float getReactionTorque(float inv_dt) {
return inv_dt * m_motorImpulse;
}
public float getJointTranslation() {
Body b1 = m_bodyA;
Body b2 = m_bodyB;
Vec2 p1 = pool.popVec2();
Vec2 p2 = pool.popVec2();
Vec2 axis = pool.popVec2();
b1.getWorldPointToOut(m_localAnchorA, p1);
b2.getWorldPointToOut(m_localAnchorA, p2);
p2.subLocal(p1);
b1.getWorldVectorToOut(m_localXAxisA, axis);
float translation = Vec2.dot(p2, axis);
pool.pushVec2(3);
return translation;
}
/** For serialization */
public Vec2 getLocalAxisA() {
return m_localXAxisA;
}
public float getJointSpeed() {
return m_bodyA.m_angularVelocity - m_bodyB.m_angularVelocity;
}
public boolean isMotorEnabled() {
return m_enableMotor;
}
public void enableMotor(boolean flag) {
m_bodyA.setAwake(true);
m_bodyB.setAwake(true);
m_enableMotor = flag;
}
public void setMotorSpeed(float speed) {
m_bodyA.setAwake(true);
m_bodyB.setAwake(true);
m_motorSpeed = speed;
}
public float getMotorSpeed() {
return m_motorSpeed;
}
public float getMaxMotorTorque() {
return m_maxMotorTorque;
}
public void setMaxMotorTorque(float torque) {
m_bodyA.setAwake(true);
m_bodyB.setAwake(true);
m_maxMotorTorque = torque;
}
public float getMotorTorque(float inv_dt) {
return m_motorImpulse * inv_dt;
}
public void setSpringFrequencyHz(float hz) {
m_frequencyHz = hz;
}
public float getSpringFrequencyHz() {
return m_frequencyHz;
}
public void setSpringDampingRatio(float ratio) {
m_dampingRatio = ratio;
}
public float getSpringDampingRatio() {
return m_dampingRatio;
}
// pooling
private final Vec2 rA = new Vec2();
private final Vec2 rB = new Vec2();
private final Vec2 d = new Vec2();
@Override
public void initVelocityConstraints(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;
float mA = m_invMassA, mB = m_invMassB;
float iA = m_invIA, iB = m_invIB;
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 temp = pool.popVec2();
qA.set(aA);
qB.set(aB);
// Compute the effective masses.
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);
// Point to line constraint
{
Rot.mulToOut(qA, m_localYAxisA, m_ay);
m_sAy = Vec2.cross(temp.set(d).addLocal(rA), m_ay);
m_sBy = Vec2.cross(rB, m_ay);
m_mass = mA + mB + iA * m_sAy * m_sAy + iB * m_sBy * m_sBy;
if (m_mass > 0.0f) {
m_mass = 1.0f / m_mass;
}
}
// Spring constraint
m_springMass = 0.0f;
m_bias = 0.0f;
m_gamma = 0.0f;
if (m_frequencyHz > 0.0f) {
Rot.mulToOut(qA, m_localXAxisA, m_ax);
m_sAx = Vec2.cross(temp.set(d).addLocal(rA), m_ax);
m_sBx = Vec2.cross(rB, m_ax);
float invMass = mA + mB + iA * m_sAx * m_sAx + iB * m_sBx * m_sBx;
if (invMass > 0.0f) {
m_springMass = 1.0f / invMass;
float C = Vec2.dot(d, m_ax);
// Frequency
float omega = 2.0f * MathUtils.PI * m_frequencyHz;
// Damping coefficient
float d = 2.0f * m_springMass * m_dampingRatio * omega;
// Spring stiffness
float k = m_springMass * omega * omega;
// magic formulas
float h = data.step.dt;
m_gamma = h * (d + h * k);
if (m_gamma > 0.0f) {
m_gamma = 1.0f / m_gamma;
}
m_bias = C * h * k * m_gamma;
m_springMass = invMass + m_gamma;
if (m_springMass > 0.0f) {
m_springMass = 1.0f / m_springMass;
}
}
} else {
m_springImpulse = 0.0f;
}
// Rotational motor
if (m_enableMotor) {
m_motorMass = iA + iB;
if (m_motorMass > 0.0f) {
m_motorMass = 1.0f / m_motorMass;
}
} else {
m_motorMass = 0.0f;
m_motorImpulse = 0.0f;
}
if (data.step.warmStarting) {
final Vec2 P = pool.popVec2();
// Account for variable time step.
m_impulse *= data.step.dtRatio;
m_springImpulse *= data.step.dtRatio;
m_motorImpulse *= data.step.dtRatio;
P.x = m_impulse * m_ay.x + m_springImpulse * m_ax.x;
P.y = m_impulse * m_ay.y + m_springImpulse * m_ax.y;
float LA = m_impulse * m_sAy + m_springImpulse * m_sAx + m_motorImpulse;
float LB = m_impulse * m_sBy + m_springImpulse * m_sBx + m_motorImpulse;
vA.x -= m_invMassA * P.x;
vA.y -= m_invMassA * P.y;
wA -= m_invIA * LA;
vB.x += m_invMassB * P.x;
vB.y += m_invMassB * P.y;
wB += m_invIB * LB;
pool.pushVec2(1);
} else {
m_impulse = 0.0f;
m_springImpulse = 0.0f;
m_motorImpulse = 0.0f;
}
pool.pushRot(2);
pool.pushVec2(1);
// data.velocities[m_indexA].v = vA;
data.velocities[m_indexA].w = wA;
// data.velocities[m_indexB].v = vB;
data.velocities[m_indexB].w = wB;
}
@Override
public void solveVelocityConstraints(SolverData data) {
float mA = m_invMassA, mB = m_invMassB;
float iA = m_invIA, iB = m_invIB;
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;
final Vec2 temp = pool.popVec2();
final Vec2 P = pool.popVec2();
// Solve spring constraint
{
float Cdot = Vec2.dot(m_ax, temp.set(vB).subLocal(vA)) + m_sBx * wB - m_sAx * wA;
float impulse = -m_springMass * (Cdot + m_bias + m_gamma * m_springImpulse);
m_springImpulse += impulse;
P.x = impulse * m_ax.x;
P.y = impulse * m_ax.y;
float LA = impulse * m_sAx;
float LB = impulse * m_sBx;
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;
}
// Solve rotational motor constraint
{
float Cdot = wB - wA - m_motorSpeed;
float impulse = -m_motorMass * Cdot;
float oldImpulse = m_motorImpulse;
float maxImpulse = data.step.dt * m_maxMotorTorque;
m_motorImpulse = MathUtils.clamp(m_motorImpulse + impulse, -maxImpulse, maxImpulse);
impulse = m_motorImpulse - oldImpulse;
wA -= iA * impulse;
wB += iB * impulse;
}
// Solve point to line constraint
{
float Cdot = Vec2.dot(m_ay, temp.set(vB).subLocal(vA)) + m_sBy * wB - m_sAy * wA;
float impulse = -m_mass * Cdot;
m_impulse += impulse;
P.x = impulse * m_ay.x;
P.y = impulse * m_ay.y;
float LA = impulse * m_sAy;
float LB = impulse * m_sBy;
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 = vA;
data.velocities[m_indexA].w = wA;
// data.velocities[m_indexB].v = vB;
data.velocities[m_indexB].w = wB;
}
@Override
public boolean solvePositionConstraints(SolverData data) {
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;
final Rot qA = pool.popRot();
final Rot qB = pool.popRot();
final Vec2 temp = pool.popVec2();
qA.set(aA);
qB.set(aB);
Rot.mulToOut(qA, temp.set(m_localAnchorA).subLocal(m_localCenterA), rA);
Rot.mulToOut(qB, temp.set(m_localAnchorB).subLocal(m_localCenterB), rB);
d.set(cB).subLocal(cA).addLocal(rB).subLocal(rA);
Vec2 ay = pool.popVec2();
Rot.mulToOut(qA, m_localYAxisA, ay);
float sAy = Vec2.cross(temp.set(d).addLocal(rA), ay);
float sBy = Vec2.cross(rB, ay);
float C = Vec2.dot(d, ay);
float k = m_invMassA + m_invMassB + m_invIA * m_sAy * m_sAy + m_invIB * m_sBy * m_sBy;
float impulse;
if (k != 0.0f) {
impulse = -C / k;
} else {
impulse = 0.0f;
}
final Vec2 P = pool.popVec2();
P.x = impulse * ay.x;
P.y = impulse * ay.y;
float LA = impulse * sAy;
float LB = impulse * sBy;
cA.x -= m_invMassA * P.x;
cA.y -= m_invMassA * P.y;
aA -= m_invIA * LA;
cB.x += m_invMassB * P.x;
cB.y += m_invMassB * P.y;
aB += m_invIB * LB;
pool.pushVec2(3);
pool.pushRot(2);
// data.positions[m_indexA].c = cA;
data.positions[m_indexA].a = aA;
// data.positions[m_indexB].c = cB;
data.positions[m_indexB].a = aB;
return MathUtils.abs(C) <= Settings.linearSlop;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy