All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bulletphysics.dynamics.constraintsolver.SequentialImpulseConstraintSolver Maven / Gradle / Ivy

The newest version!
/*
 * Java port of Bullet (c) 2008 Martin Dvorak 
 *
 * Bullet Continuous Collision Detection and Physics Library
 * Copyright (c) 2003-2008 Erwin Coumans  http://www.bulletphysics.com/
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose, 
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 * 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.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

package com.bulletphysics.dynamics.constraintsolver;

import com.bulletphysics.BulletGlobals;
import com.bulletphysics.BulletStats;
import com.bulletphysics.ContactDestroyedCallback;
import com.bulletphysics.util.ObjectPool;
import com.bulletphysics.collision.broadphase.Dispatcher;
import com.bulletphysics.collision.dispatch.CollisionObject;
import com.bulletphysics.collision.narrowphase.ManifoldPoint;
import com.bulletphysics.collision.narrowphase.PersistentManifold;
import com.bulletphysics.dynamics.RigidBody;
import com.bulletphysics.linearmath.IDebugDraw;
import com.bulletphysics.linearmath.MiscUtil;
import com.bulletphysics.linearmath.Transform;
import com.bulletphysics.linearmath.TransformUtil;
import com.bulletphysics.util.IntArrayList;
import com.bulletphysics.util.ObjectArrayList;
import cz.advel.stack.Stack;
import cz.advel.stack.StaticAlloc;
import javax.vecmath.Matrix3f;
import javax.vecmath.Vector3f;

/**
 * SequentialImpulseConstraintSolver uses a Propagation Method and Sequentially applies impulses.
 * The approach is the 3D version of Erin Catto's GDC 2006 tutorial. See http://www.gphysics.com

* * Although Sequential Impulse is more intuitive, it is mathematically equivalent to Projected * Successive Overrelaxation (iterative LCP).

* * Applies impulses for combined restitution and penetration recovery and to simulate friction. * * @author jezek2 */ public class SequentialImpulseConstraintSolver extends ConstraintSolver { private static final int MAX_CONTACT_SOLVER_TYPES = ContactConstraintEnum.MAX_CONTACT_SOLVER_TYPES.ordinal(); private static final int SEQUENTIAL_IMPULSE_MAX_SOLVER_POINTS = 16384; private OrderIndex[] gOrder = new OrderIndex[SEQUENTIAL_IMPULSE_MAX_SOLVER_POINTS]; private int totalCpd = 0; { for (int i=0; i bodiesPool = ObjectPool.get(SolverBody.class); private final ObjectPool constraintsPool = ObjectPool.get(SolverConstraint.class); private final ObjectPool jacobiansPool = ObjectPool.get(JacobianEntry.class); private final ObjectArrayList tmpSolverBodyPool = new ObjectArrayList(); private final ObjectArrayList tmpSolverConstraintPool = new ObjectArrayList(); private final ObjectArrayList tmpSolverFrictionConstraintPool = new ObjectArrayList(); private final IntArrayList orderTmpConstraintPool = new IntArrayList(); private final IntArrayList orderFrictionConstraintPool = new IntArrayList(); protected final ContactSolverFunc[][] contactDispatch = new ContactSolverFunc[MAX_CONTACT_SOLVER_TYPES][MAX_CONTACT_SOLVER_TYPES]; protected final ContactSolverFunc[][] frictionDispatch = new ContactSolverFunc[MAX_CONTACT_SOLVER_TYPES][MAX_CONTACT_SOLVER_TYPES]; // btSeed2 is used for re-arranging the constraint rows. improves convergence/quality of friction protected long btSeed2 = 0L; public SequentialImpulseConstraintSolver() { BulletGlobals.setContactDestroyedCallback(new ContactDestroyedCallback() { public boolean contactDestroyed(Object userPersistentData) { assert (userPersistentData != null); ConstraintPersistentData cpd = (ConstraintPersistentData) userPersistentData; //btAlignedFree(cpd); totalCpd--; //printf("totalCpd = %i. DELETED Ptr %x\n",totalCpd,userPersistentData); return true; } }); // initialize default friction/contact funcs int i, j; for (i = 0; i < MAX_CONTACT_SOLVER_TYPES; i++) { for (j = 0; j < MAX_CONTACT_SOLVER_TYPES; j++) { contactDispatch[i][j] = ContactConstraint.resolveSingleCollision; frictionDispatch[i][j] = ContactConstraint.resolveSingleFriction; } } } public long rand2() { btSeed2 = (1664525L * btSeed2 + 1013904223L) & 0xffffffff; return btSeed2; } // See ODE: adam's all-int straightforward(?) dRandInt (0..n-1) public int randInt2(int n) { // seems good; xor-fold and modulus long un = n; long r = rand2(); // note: probably more aggressive than it needs to be -- might be // able to get away without one or two of the innermost branches. if (un <= 0x00010000L) { r ^= (r >>> 16); if (un <= 0x00000100L) { r ^= (r >>> 8); if (un <= 0x00000010L) { r ^= (r >>> 4); if (un <= 0x00000004L) { r ^= (r >>> 2); if (un <= 0x00000002L) { r ^= (r >>> 1); } } } } } // TODO: check modulo C vs Java mismatch return (int) Math.abs(r % un); } private void initSolverBody(SolverBody solverBody, CollisionObject collisionObject) { RigidBody rb = RigidBody.upcast(collisionObject); if (rb != null) { rb.getAngularVelocity(solverBody.angularVelocity); solverBody.centerOfMassPosition.set(collisionObject.getWorldTransform(Stack.alloc(Transform.class)).origin); solverBody.friction = collisionObject.getFriction(); solverBody.invMass = rb.getInvMass(); rb.getLinearVelocity(solverBody.linearVelocity); solverBody.originalBody = rb; solverBody.angularFactor = rb.getAngularFactor(); } else { solverBody.angularVelocity.set(0f, 0f, 0f); solverBody.centerOfMassPosition.set(collisionObject.getWorldTransform(Stack.alloc(Transform.class)).origin); solverBody.friction = collisionObject.getFriction(); solverBody.invMass = 0f; solverBody.linearVelocity.set(0f, 0f, 0f); solverBody.originalBody = null; solverBody.angularFactor = 1f; } solverBody.pushVelocity.set(0f, 0f, 0f); solverBody.turnVelocity.set(0f, 0f, 0f); } private float restitutionCurve(float rel_vel, float restitution) { float rest = restitution * -rel_vel; return rest; } private void resolveSplitPenetrationImpulseCacheFriendly( SolverBody body1, SolverBody body2, SolverConstraint contactConstraint, ContactSolverInfo solverInfo) { if (contactConstraint.penetration < solverInfo.splitImpulsePenetrationThreshold) { BulletStats.gNumSplitImpulseRecoveries++; float normalImpulse; // Optimized version of projected relative velocity, use precomputed cross products with normal // body1.getVelocityInLocalPoint(contactConstraint.m_rel_posA,vel1); // body2.getVelocityInLocalPoint(contactConstraint.m_rel_posB,vel2); // btVector3 vel = vel1 - vel2; // btScalar rel_vel = contactConstraint.m_contactNormal.dot(vel); float rel_vel; float vel1Dotn = contactConstraint.contactNormal.dot(body1.pushVelocity) + contactConstraint.relpos1CrossNormal.dot(body1.turnVelocity); float vel2Dotn = contactConstraint.contactNormal.dot(body2.pushVelocity) + contactConstraint.relpos2CrossNormal.dot(body2.turnVelocity); rel_vel = vel1Dotn - vel2Dotn; float positionalError = -contactConstraint.penetration * solverInfo.erp2 / solverInfo.timeStep; // btScalar positionalError = contactConstraint.m_penetration; float velocityError = contactConstraint.restitution - rel_vel;// * damping; float penetrationImpulse = positionalError * contactConstraint.jacDiagABInv; float velocityImpulse = velocityError * contactConstraint.jacDiagABInv; normalImpulse = penetrationImpulse + velocityImpulse; // See Erin Catto's GDC 2006 paper: Clamp the accumulated impulse float oldNormalImpulse = contactConstraint.appliedPushImpulse; float sum = oldNormalImpulse + normalImpulse; contactConstraint.appliedPushImpulse = 0f > sum ? 0f : sum; normalImpulse = contactConstraint.appliedPushImpulse - oldNormalImpulse; Vector3f tmp = Stack.alloc(Vector3f.class); tmp.scale(body1.invMass, contactConstraint.contactNormal); body1.internalApplyPushImpulse(tmp, contactConstraint.angularComponentA, normalImpulse); tmp.scale(body2.invMass, contactConstraint.contactNormal); body2.internalApplyPushImpulse(tmp, contactConstraint.angularComponentB, -normalImpulse); } } /** * velocity + friction * response between two dynamic objects with friction */ private float resolveSingleCollisionCombinedCacheFriendly( SolverBody body1, SolverBody body2, SolverConstraint contactConstraint, ContactSolverInfo solverInfo) { float normalImpulse; { // Optimized version of projected relative velocity, use precomputed cross products with normal // body1.getVelocityInLocalPoint(contactConstraint.m_rel_posA,vel1); // body2.getVelocityInLocalPoint(contactConstraint.m_rel_posB,vel2); // btVector3 vel = vel1 - vel2; // btScalar rel_vel = contactConstraint.m_contactNormal.dot(vel); float rel_vel; float vel1Dotn = contactConstraint.contactNormal.dot(body1.linearVelocity) + contactConstraint.relpos1CrossNormal.dot(body1.angularVelocity); float vel2Dotn = contactConstraint.contactNormal.dot(body2.linearVelocity) + contactConstraint.relpos2CrossNormal.dot(body2.angularVelocity); rel_vel = vel1Dotn - vel2Dotn; float positionalError = 0.f; if (!solverInfo.splitImpulse || (contactConstraint.penetration > solverInfo.splitImpulsePenetrationThreshold)) { positionalError = -contactConstraint.penetration * solverInfo.erp / solverInfo.timeStep; } float velocityError = contactConstraint.restitution - rel_vel;// * damping; float penetrationImpulse = positionalError * contactConstraint.jacDiagABInv; float velocityImpulse = velocityError * contactConstraint.jacDiagABInv; normalImpulse = penetrationImpulse + velocityImpulse; // See Erin Catto's GDC 2006 paper: Clamp the accumulated impulse float oldNormalImpulse = contactConstraint.appliedImpulse; float sum = oldNormalImpulse + normalImpulse; contactConstraint.appliedImpulse = 0f > sum ? 0f : sum; normalImpulse = contactConstraint.appliedImpulse - oldNormalImpulse; Vector3f tmp = Stack.alloc(Vector3f.class); tmp.scale(body1.invMass, contactConstraint.contactNormal); body1.internalApplyImpulse(tmp, contactConstraint.angularComponentA, normalImpulse); tmp.scale(body2.invMass, contactConstraint.contactNormal); body2.internalApplyImpulse(tmp, contactConstraint.angularComponentB, -normalImpulse); } return normalImpulse; } private float resolveSingleFrictionCacheFriendly( SolverBody body1, SolverBody body2, SolverConstraint contactConstraint, ContactSolverInfo solverInfo, float appliedNormalImpulse) { float combinedFriction = contactConstraint.friction; float limit = appliedNormalImpulse * combinedFriction; if (appliedNormalImpulse > 0f) //friction { float j1; { float rel_vel; float vel1Dotn = contactConstraint.contactNormal.dot(body1.linearVelocity) + contactConstraint.relpos1CrossNormal.dot(body1.angularVelocity); float vel2Dotn = contactConstraint.contactNormal.dot(body2.linearVelocity) + contactConstraint.relpos2CrossNormal.dot(body2.angularVelocity); rel_vel = vel1Dotn - vel2Dotn; // calculate j that moves us to zero relative velocity j1 = -rel_vel * contactConstraint.jacDiagABInv; //#define CLAMP_ACCUMULATED_FRICTION_IMPULSE 1 //#ifdef CLAMP_ACCUMULATED_FRICTION_IMPULSE float oldTangentImpulse = contactConstraint.appliedImpulse; contactConstraint.appliedImpulse = oldTangentImpulse + j1; if (limit < contactConstraint.appliedImpulse) { contactConstraint.appliedImpulse = limit; } else { if (contactConstraint.appliedImpulse < -limit) { contactConstraint.appliedImpulse = -limit; } } j1 = contactConstraint.appliedImpulse - oldTangentImpulse; // #else // if (limit < j1) // { // j1 = limit; // } else // { // if (j1 < -limit) // j1 = -limit; // } // #endif //GEN_set_min(contactConstraint.m_appliedImpulse, limit); //GEN_set_max(contactConstraint.m_appliedImpulse, -limit); } Vector3f tmp = Stack.alloc(Vector3f.class); tmp.scale(body1.invMass, contactConstraint.contactNormal); body1.internalApplyImpulse(tmp, contactConstraint.angularComponentA, j1); tmp.scale(body2.invMass, contactConstraint.contactNormal); body2.internalApplyImpulse(tmp, contactConstraint.angularComponentB, -j1); } return 0f; } @StaticAlloc protected void addFrictionConstraint(Vector3f normalAxis, int solverBodyIdA, int solverBodyIdB, int frictionIndex, ManifoldPoint cp, Vector3f rel_pos1, Vector3f rel_pos2, CollisionObject colObj0, CollisionObject colObj1, float relaxation) { RigidBody body0 = RigidBody.upcast(colObj0); RigidBody body1 = RigidBody.upcast(colObj1); SolverConstraint solverConstraint = constraintsPool.get(); tmpSolverFrictionConstraintPool.add(solverConstraint); solverConstraint.contactNormal.set(normalAxis); solverConstraint.solverBodyIdA = solverBodyIdA; solverConstraint.solverBodyIdB = solverBodyIdB; solverConstraint.constraintType = SolverConstraintType.SOLVER_FRICTION_1D; solverConstraint.frictionIndex = frictionIndex; solverConstraint.friction = cp.combinedFriction; solverConstraint.originalContactPoint = null; solverConstraint.appliedImpulse = 0f; solverConstraint.appliedPushImpulse = 0f; solverConstraint.penetration = 0f; Vector3f ftorqueAxis1 = Stack.alloc(Vector3f.class); Matrix3f tmpMat = Stack.alloc(Matrix3f.class); { ftorqueAxis1.cross(rel_pos1, solverConstraint.contactNormal); solverConstraint.relpos1CrossNormal.set(ftorqueAxis1); if (body0 != null) { solverConstraint.angularComponentA.set(ftorqueAxis1); body0.getInvInertiaTensorWorld(tmpMat).transform(solverConstraint.angularComponentA); } else { solverConstraint.angularComponentA.set(0f, 0f, 0f); } } { ftorqueAxis1.cross(rel_pos2, solverConstraint.contactNormal); solverConstraint.relpos2CrossNormal.set(ftorqueAxis1); if (body1 != null) { solverConstraint.angularComponentB.set(ftorqueAxis1); body1.getInvInertiaTensorWorld(tmpMat).transform(solverConstraint.angularComponentB); } else { solverConstraint.angularComponentB.set(0f, 0f, 0f); } } //#ifdef COMPUTE_IMPULSE_DENOM // btScalar denom0 = rb0->computeImpulseDenominator(pos1,solverConstraint.m_contactNormal); // btScalar denom1 = rb1->computeImpulseDenominator(pos2,solverConstraint.m_contactNormal); //#else Vector3f vec = Stack.alloc(Vector3f.class); float denom0 = 0f; float denom1 = 0f; if (body0 != null) { vec.cross(solverConstraint.angularComponentA, rel_pos1); denom0 = body0.getInvMass() + normalAxis.dot(vec); } if (body1 != null) { vec.cross(solverConstraint.angularComponentB, rel_pos2); denom1 = body1.getInvMass() + normalAxis.dot(vec); } //#endif //COMPUTE_IMPULSE_DENOM float denom = relaxation / (denom0 + denom1); solverConstraint.jacDiagABInv = denom; } public float solveGroupCacheFriendlySetup(ObjectArrayList bodies, int numBodies, ObjectArrayList manifoldPtr, int manifold_offset, int numManifolds, ObjectArrayList constraints, int constraints_offset, int numConstraints, ContactSolverInfo infoGlobal, IDebugDraw debugDrawer/*,btStackAlloc* stackAlloc*/) { BulletStats.pushProfile("solveGroupCacheFriendlySetup"); try { if ((numConstraints + numManifolds) == 0) { // printf("empty\n"); return 0f; } PersistentManifold manifold = null; CollisionObject colObj0 = null, colObj1 = null; //btRigidBody* rb0=0,*rb1=0; // //#ifdef FORCE_REFESH_CONTACT_MANIFOLDS // // BEGIN_PROFILE("refreshManifolds"); // // int i; // // // // for (i=0;igetBody1(); // rb0 = (btRigidBody*)manifold->getBody0(); // // manifold->refreshContactPoints(rb0->getCenterOfMassTransform(),rb1->getCenterOfMassTransform()); // // } // // END_PROFILE("refreshManifolds"); // //#endif //FORCE_REFESH_CONTACT_MANIFOLDS Transform tmpTrans = Stack.alloc(Transform.class); //int sizeofSB = sizeof(btSolverBody); //int sizeofSC = sizeof(btSolverConstraint); //if (1) { //if m_stackAlloc, try to pack bodies/constraints to speed up solving // btBlock* sablock; // sablock = stackAlloc->beginBlock(); // int memsize = 16; // unsigned char* stackMemory = stackAlloc->allocate(memsize); // todo: use stack allocator for this temp memory //int minReservation = numManifolds * 2; //m_tmpSolverBodyPool.reserve(minReservation); //don't convert all bodies, only the one we need so solver the constraints /* { for (int i=0;igetIslandTag() >= 0)) { btAssert(rb->getCompanionId() < 0); int solverBodyId = m_tmpSolverBodyPool.size(); btSolverBody& solverBody = m_tmpSolverBodyPool.expand(); initSolverBody(&solverBody,rb); rb->setCompanionId(solverBodyId); } } } */ //m_tmpSolverConstraintPool.reserve(minReservation); //m_tmpSolverFrictionConstraintPool.reserve(minReservation); { int i; Vector3f rel_pos1 = Stack.alloc(Vector3f.class); Vector3f rel_pos2 = Stack.alloc(Vector3f.class); Vector3f pos1 = Stack.alloc(Vector3f.class); Vector3f pos2 = Stack.alloc(Vector3f.class); Vector3f vel = Stack.alloc(Vector3f.class); Vector3f torqueAxis0 = Stack.alloc(Vector3f.class); Vector3f torqueAxis1 = Stack.alloc(Vector3f.class); Vector3f vel1 = Stack.alloc(Vector3f.class); Vector3f vel2 = Stack.alloc(Vector3f.class); Vector3f frictionDir1 = Stack.alloc(Vector3f.class); Vector3f frictionDir2 = Stack.alloc(Vector3f.class); Vector3f vec = Stack.alloc(Vector3f.class); Matrix3f tmpMat = Stack.alloc(Matrix3f.class); for (i = 0; i < numManifolds; i++) { manifold = manifoldPtr.getQuick(manifold_offset+i); colObj0 = (CollisionObject) manifold.getBody0(); colObj1 = (CollisionObject) manifold.getBody1(); int solverBodyIdA = -1; int solverBodyIdB = -1; if (manifold.getNumContacts() != 0) { if (colObj0.getIslandTag() >= 0) { if (colObj0.getCompanionId() >= 0) { // body has already been converted solverBodyIdA = colObj0.getCompanionId(); } else { solverBodyIdA = tmpSolverBodyPool.size(); SolverBody solverBody = bodiesPool.get(); tmpSolverBodyPool.add(solverBody); initSolverBody(solverBody, colObj0); colObj0.setCompanionId(solverBodyIdA); } } else { // create a static body solverBodyIdA = tmpSolverBodyPool.size(); SolverBody solverBody = bodiesPool.get(); tmpSolverBodyPool.add(solverBody); initSolverBody(solverBody, colObj0); } if (colObj1.getIslandTag() >= 0) { if (colObj1.getCompanionId() >= 0) { solverBodyIdB = colObj1.getCompanionId(); } else { solverBodyIdB = tmpSolverBodyPool.size(); SolverBody solverBody = bodiesPool.get(); tmpSolverBodyPool.add(solverBody); initSolverBody(solverBody, colObj1); colObj1.setCompanionId(solverBodyIdB); } } else { // create a static body solverBodyIdB = tmpSolverBodyPool.size(); SolverBody solverBody = bodiesPool.get(); tmpSolverBodyPool.add(solverBody); initSolverBody(solverBody, colObj1); } } float relaxation; for (int j = 0; j < manifold.getNumContacts(); j++) { ManifoldPoint cp = manifold.getContactPoint(j); if (cp.getDistance() <= 0f) { cp.getPositionWorldOnA(pos1); cp.getPositionWorldOnB(pos2); rel_pos1.sub(pos1, colObj0.getWorldTransform(tmpTrans).origin); rel_pos2.sub(pos2, colObj1.getWorldTransform(tmpTrans).origin); relaxation = 1f; float rel_vel; int frictionIndex = tmpSolverConstraintPool.size(); { SolverConstraint solverConstraint = constraintsPool.get(); tmpSolverConstraintPool.add(solverConstraint); RigidBody rb0 = RigidBody.upcast(colObj0); RigidBody rb1 = RigidBody.upcast(colObj1); solverConstraint.solverBodyIdA = solverBodyIdA; solverConstraint.solverBodyIdB = solverBodyIdB; solverConstraint.constraintType = SolverConstraintType.SOLVER_CONTACT_1D; solverConstraint.originalContactPoint = cp; torqueAxis0.cross(rel_pos1, cp.normalWorldOnB); if (rb0 != null) { solverConstraint.angularComponentA.set(torqueAxis0); rb0.getInvInertiaTensorWorld(tmpMat).transform(solverConstraint.angularComponentA); } else { solverConstraint.angularComponentA.set(0f, 0f, 0f); } torqueAxis1.cross(rel_pos2, cp.normalWorldOnB); if (rb1 != null) { solverConstraint.angularComponentB.set(torqueAxis1); rb1.getInvInertiaTensorWorld(tmpMat).transform(solverConstraint.angularComponentB); } else { solverConstraint.angularComponentB.set(0f, 0f, 0f); } { //#ifdef COMPUTE_IMPULSE_DENOM //btScalar denom0 = rb0->computeImpulseDenominator(pos1,cp.m_normalWorldOnB); //btScalar denom1 = rb1->computeImpulseDenominator(pos2,cp.m_normalWorldOnB); //#else float denom0 = 0f; float denom1 = 0f; if (rb0 != null) { vec.cross(solverConstraint.angularComponentA, rel_pos1); denom0 = rb0.getInvMass() + cp.normalWorldOnB.dot(vec); } if (rb1 != null) { vec.cross(solverConstraint.angularComponentB, rel_pos2); denom1 = rb1.getInvMass() + cp.normalWorldOnB.dot(vec); } //#endif //COMPUTE_IMPULSE_DENOM float denom = relaxation / (denom0 + denom1); solverConstraint.jacDiagABInv = denom; } solverConstraint.contactNormal.set(cp.normalWorldOnB); solverConstraint.relpos1CrossNormal.cross(rel_pos1, cp.normalWorldOnB); solverConstraint.relpos2CrossNormal.cross(rel_pos2, cp.normalWorldOnB); if (rb0 != null) { rb0.getVelocityInLocalPoint(rel_pos1, vel1); } else { vel1.set(0f, 0f, 0f); } if (rb1 != null) { rb1.getVelocityInLocalPoint(rel_pos2, vel2); } else { vel2.set(0f, 0f, 0f); } vel.sub(vel1, vel2); rel_vel = cp.normalWorldOnB.dot(vel); solverConstraint.penetration = Math.min(cp.getDistance() + infoGlobal.linearSlop, 0f); //solverConstraint.m_penetration = cp.getDistance(); solverConstraint.friction = cp.combinedFriction; solverConstraint.restitution = restitutionCurve(rel_vel, cp.combinedRestitution); if (solverConstraint.restitution <= 0f) { solverConstraint.restitution = 0f; } float penVel = -solverConstraint.penetration / infoGlobal.timeStep; if (solverConstraint.restitution > penVel) { solverConstraint.penetration = 0f; } Vector3f tmp = Stack.alloc(Vector3f.class); // warm starting (or zero if disabled) if ((infoGlobal.solverMode & SolverMode.SOLVER_USE_WARMSTARTING) != 0) { solverConstraint.appliedImpulse = cp.appliedImpulse * infoGlobal.warmstartingFactor; if (rb0 != null) { tmp.scale(rb0.getInvMass(), solverConstraint.contactNormal); tmpSolverBodyPool.getQuick(solverConstraint.solverBodyIdA).internalApplyImpulse(tmp, solverConstraint.angularComponentA, solverConstraint.appliedImpulse); } if (rb1 != null) { tmp.scale(rb1.getInvMass(), solverConstraint.contactNormal); tmpSolverBodyPool.getQuick(solverConstraint.solverBodyIdB).internalApplyImpulse(tmp, solverConstraint.angularComponentB, -solverConstraint.appliedImpulse); } } else { solverConstraint.appliedImpulse = 0f; } solverConstraint.appliedPushImpulse = 0f; solverConstraint.frictionIndex = tmpSolverFrictionConstraintPool.size(); if (!cp.lateralFrictionInitialized) { cp.lateralFrictionDir1.scale(rel_vel, cp.normalWorldOnB); cp.lateralFrictionDir1.sub(vel, cp.lateralFrictionDir1); float lat_rel_vel = cp.lateralFrictionDir1.lengthSquared(); if (lat_rel_vel > BulletGlobals.FLT_EPSILON)//0.0f) { cp.lateralFrictionDir1.scale(1f / (float) Math.sqrt(lat_rel_vel)); addFrictionConstraint(cp.lateralFrictionDir1, solverBodyIdA, solverBodyIdB, frictionIndex, cp, rel_pos1, rel_pos2, colObj0, colObj1, relaxation); cp.lateralFrictionDir2.cross(cp.lateralFrictionDir1, cp.normalWorldOnB); cp.lateralFrictionDir2.normalize(); //?? addFrictionConstraint(cp.lateralFrictionDir2, solverBodyIdA, solverBodyIdB, frictionIndex, cp, rel_pos1, rel_pos2, colObj0, colObj1, relaxation); } else { // re-calculate friction direction every frame, todo: check if this is really needed TransformUtil.planeSpace1(cp.normalWorldOnB, cp.lateralFrictionDir1, cp.lateralFrictionDir2); addFrictionConstraint(cp.lateralFrictionDir1, solverBodyIdA, solverBodyIdB, frictionIndex, cp, rel_pos1, rel_pos2, colObj0, colObj1, relaxation); addFrictionConstraint(cp.lateralFrictionDir2, solverBodyIdA, solverBodyIdB, frictionIndex, cp, rel_pos1, rel_pos2, colObj0, colObj1, relaxation); } cp.lateralFrictionInitialized = true; } else { addFrictionConstraint(cp.lateralFrictionDir1, solverBodyIdA, solverBodyIdB, frictionIndex, cp, rel_pos1, rel_pos2, colObj0, colObj1, relaxation); addFrictionConstraint(cp.lateralFrictionDir2, solverBodyIdA, solverBodyIdB, frictionIndex, cp, rel_pos1, rel_pos2, colObj0, colObj1, relaxation); } { SolverConstraint frictionConstraint1 = tmpSolverFrictionConstraintPool.getQuick(solverConstraint.frictionIndex); if ((infoGlobal.solverMode & SolverMode.SOLVER_USE_WARMSTARTING) != 0) { frictionConstraint1.appliedImpulse = cp.appliedImpulseLateral1 * infoGlobal.warmstartingFactor; if (rb0 != null) { tmp.scale(rb0.getInvMass(), frictionConstraint1.contactNormal); tmpSolverBodyPool.getQuick(solverConstraint.solverBodyIdA).internalApplyImpulse(tmp, frictionConstraint1.angularComponentA, frictionConstraint1.appliedImpulse); } if (rb1 != null) { tmp.scale(rb1.getInvMass(), frictionConstraint1.contactNormal); tmpSolverBodyPool.getQuick(solverConstraint.solverBodyIdB).internalApplyImpulse(tmp, frictionConstraint1.angularComponentB, -frictionConstraint1.appliedImpulse); } } else { frictionConstraint1.appliedImpulse = 0f; } } { SolverConstraint frictionConstraint2 = tmpSolverFrictionConstraintPool.getQuick(solverConstraint.frictionIndex + 1); if ((infoGlobal.solverMode & SolverMode.SOLVER_USE_WARMSTARTING) != 0) { frictionConstraint2.appliedImpulse = cp.appliedImpulseLateral2 * infoGlobal.warmstartingFactor; if (rb0 != null) { tmp.scale(rb0.getInvMass(), frictionConstraint2.contactNormal); tmpSolverBodyPool.getQuick(solverConstraint.solverBodyIdA).internalApplyImpulse(tmp, frictionConstraint2.angularComponentA, frictionConstraint2.appliedImpulse); } if (rb1 != null) { tmp.scale(rb1.getInvMass(), frictionConstraint2.contactNormal); tmpSolverBodyPool.getQuick(solverConstraint.solverBodyIdB).internalApplyImpulse(tmp, frictionConstraint2.angularComponentB, -frictionConstraint2.appliedImpulse); } } else { frictionConstraint2.appliedImpulse = 0f; } } } } } } } } // TODO: btContactSolverInfo info = infoGlobal; { int j; for (j = 0; j < numConstraints; j++) { TypedConstraint constraint = constraints.getQuick(constraints_offset+j); constraint.buildJacobian(); } } int numConstraintPool = tmpSolverConstraintPool.size(); int numFrictionPool = tmpSolverFrictionConstraintPool.size(); // todo: use stack allocator for such temporarily memory, same for solver bodies/constraints MiscUtil.resize(orderTmpConstraintPool, numConstraintPool, 0); MiscUtil.resize(orderFrictionConstraintPool, numFrictionPool, 0); { int i; for (i = 0; i < numConstraintPool; i++) { orderTmpConstraintPool.set(i, i); } for (i = 0; i < numFrictionPool; i++) { orderFrictionConstraintPool.set(i, i); } } return 0f; } finally { BulletStats.popProfile(); } } public float solveGroupCacheFriendlyIterations(ObjectArrayList bodies, int numBodies, ObjectArrayList manifoldPtr, int manifold_offset, int numManifolds, ObjectArrayList constraints, int constraints_offset, int numConstraints, ContactSolverInfo infoGlobal, IDebugDraw debugDrawer/*,btStackAlloc* stackAlloc*/) { BulletStats.pushProfile("solveGroupCacheFriendlyIterations"); try { int numConstraintPool = tmpSolverConstraintPool.size(); int numFrictionPool = tmpSolverFrictionConstraintPool.size(); // should traverse the contacts random order... int iteration; { for (iteration = 0; iteration < infoGlobal.numIterations; iteration++) { int j; if ((infoGlobal.solverMode & SolverMode.SOLVER_RANDMIZE_ORDER) != 0) { if ((iteration & 7) == 0) { for (j = 0; j < numConstraintPool; ++j) { int tmp = orderTmpConstraintPool.get(j); int swapi = randInt2(j + 1); orderTmpConstraintPool.set(j, orderTmpConstraintPool.get(swapi)); orderTmpConstraintPool.set(swapi, tmp); } for (j = 0; j < numFrictionPool; ++j) { int tmp = orderFrictionConstraintPool.get(j); int swapi = randInt2(j + 1); orderFrictionConstraintPool.set(j, orderFrictionConstraintPool.get(swapi)); orderFrictionConstraintPool.set(swapi, tmp); } } } for (j = 0; j < numConstraints; j++) { TypedConstraint constraint = constraints.getQuick(constraints_offset+j); // todo: use solver bodies, so we don't need to copy from/to btRigidBody if ((constraint.getRigidBodyA().getIslandTag() >= 0) && (constraint.getRigidBodyA().getCompanionId() >= 0)) { tmpSolverBodyPool.getQuick(constraint.getRigidBodyA().getCompanionId()).writebackVelocity(); } if ((constraint.getRigidBodyB().getIslandTag() >= 0) && (constraint.getRigidBodyB().getCompanionId() >= 0)) { tmpSolverBodyPool.getQuick(constraint.getRigidBodyB().getCompanionId()).writebackVelocity(); } constraint.solveConstraint(infoGlobal.timeStep); if ((constraint.getRigidBodyA().getIslandTag() >= 0) && (constraint.getRigidBodyA().getCompanionId() >= 0)) { tmpSolverBodyPool.getQuick(constraint.getRigidBodyA().getCompanionId()).readVelocity(); } if ((constraint.getRigidBodyB().getIslandTag() >= 0) && (constraint.getRigidBodyB().getCompanionId() >= 0)) { tmpSolverBodyPool.getQuick(constraint.getRigidBodyB().getCompanionId()).readVelocity(); } } { int numPoolConstraints = tmpSolverConstraintPool.size(); for (j = 0; j < numPoolConstraints; j++) { SolverConstraint solveManifold = tmpSolverConstraintPool.getQuick(orderTmpConstraintPool.get(j)); resolveSingleCollisionCombinedCacheFriendly(tmpSolverBodyPool.getQuick(solveManifold.solverBodyIdA), tmpSolverBodyPool.getQuick(solveManifold.solverBodyIdB), solveManifold, infoGlobal); } } { int numFrictionPoolConstraints = tmpSolverFrictionConstraintPool.size(); for (j = 0; j < numFrictionPoolConstraints; j++) { SolverConstraint solveManifold = tmpSolverFrictionConstraintPool.getQuick(orderFrictionConstraintPool.get(j)); float totalImpulse = tmpSolverConstraintPool.getQuick(solveManifold.frictionIndex).appliedImpulse + tmpSolverConstraintPool.getQuick(solveManifold.frictionIndex).appliedPushImpulse; resolveSingleFrictionCacheFriendly(tmpSolverBodyPool.getQuick(solveManifold.solverBodyIdA), tmpSolverBodyPool.getQuick(solveManifold.solverBodyIdB), solveManifold, infoGlobal, totalImpulse); } } } if (infoGlobal.splitImpulse) { for (iteration = 0; iteration < infoGlobal.numIterations; iteration++) { { int numPoolConstraints = tmpSolverConstraintPool.size(); int j; for (j = 0; j < numPoolConstraints; j++) { SolverConstraint solveManifold = tmpSolverConstraintPool.getQuick(orderTmpConstraintPool.get(j)); resolveSplitPenetrationImpulseCacheFriendly(tmpSolverBodyPool.getQuick(solveManifold.solverBodyIdA), tmpSolverBodyPool.getQuick(solveManifold.solverBodyIdB), solveManifold, infoGlobal); } } } } } return 0f; } finally { BulletStats.popProfile(); } } public float solveGroupCacheFriendly(ObjectArrayList bodies, int numBodies, ObjectArrayList manifoldPtr, int manifold_offset, int numManifolds, ObjectArrayList constraints, int constraints_offset, int numConstraints, ContactSolverInfo infoGlobal, IDebugDraw debugDrawer/*,btStackAlloc* stackAlloc*/) { solveGroupCacheFriendlySetup(bodies, numBodies, manifoldPtr, manifold_offset, numManifolds, constraints, constraints_offset, numConstraints, infoGlobal, debugDrawer/*, stackAlloc*/); solveGroupCacheFriendlyIterations(bodies, numBodies, manifoldPtr, manifold_offset, numManifolds, constraints, constraints_offset, numConstraints, infoGlobal, debugDrawer/*, stackAlloc*/); int numPoolConstraints = tmpSolverConstraintPool.size(); for (int j=0; j bodies, int numBodies, ObjectArrayList manifoldPtr, int manifold_offset, int numManifolds, ObjectArrayList constraints, int constraints_offset, int numConstraints, ContactSolverInfo infoGlobal, IDebugDraw debugDrawer, Dispatcher dispatcher) { BulletStats.pushProfile("solveGroup"); try { // TODO: solver cache friendly if ((infoGlobal.solverMode & SolverMode.SOLVER_CACHE_FRIENDLY) != 0) { // you need to provide at least some bodies // SimpleDynamicsWorld needs to switch off SOLVER_CACHE_FRIENDLY assert (bodies != null); assert (numBodies != 0); float value = solveGroupCacheFriendly(bodies, numBodies, manifoldPtr, manifold_offset, numManifolds, constraints, constraints_offset, numConstraints, infoGlobal, debugDrawer/*,stackAlloc*/); return value; } ContactSolverInfo info = new ContactSolverInfo(infoGlobal); int numiter = infoGlobal.numIterations; int totalPoints = 0; { short j; for (j = 0; j < numManifolds; j++) { PersistentManifold manifold = manifoldPtr.getQuick(manifold_offset+j); prepareConstraints(manifold, info, debugDrawer); for (short p = 0; p < manifoldPtr.getQuick(manifold_offset+j).getNumContacts(); p++) { gOrder[totalPoints].manifoldIndex = j; gOrder[totalPoints].pointIndex = p; totalPoints++; } } } { int j; for (j = 0; j < numConstraints; j++) { TypedConstraint constraint = constraints.getQuick(constraints_offset+j); constraint.buildJacobian(); } } // should traverse the contacts random order... int iteration; { for (iteration = 0; iteration < numiter; iteration++) { int j; if ((infoGlobal.solverMode & SolverMode.SOLVER_RANDMIZE_ORDER) != 0) { if ((iteration & 7) == 0) { for (j = 0; j < totalPoints; ++j) { // JAVA NOTE: swaps references instead of copying values (but that's fine in this context) OrderIndex tmp = gOrder[j]; int swapi = randInt2(j + 1); gOrder[j] = gOrder[swapi]; gOrder[swapi] = tmp; } } } for (j = 0; j < numConstraints; j++) { TypedConstraint constraint = constraints.getQuick(constraints_offset+j); constraint.solveConstraint(info.timeStep); } for (j = 0; j < totalPoints; j++) { PersistentManifold manifold = manifoldPtr.getQuick(manifold_offset+gOrder[j].manifoldIndex); solve((RigidBody) manifold.getBody0(), (RigidBody) manifold.getBody1(), manifold.getContactPoint(gOrder[j].pointIndex), info, iteration, debugDrawer); } for (j = 0; j < totalPoints; j++) { PersistentManifold manifold = manifoldPtr.getQuick(manifold_offset+gOrder[j].manifoldIndex); solveFriction((RigidBody) manifold.getBody0(), (RigidBody) manifold.getBody1(), manifold.getContactPoint(gOrder[j].pointIndex), info, iteration, debugDrawer); } } } return 0f; } finally { BulletStats.popProfile(); } } protected void prepareConstraints(PersistentManifold manifoldPtr, ContactSolverInfo info, IDebugDraw debugDrawer) { RigidBody body0 = (RigidBody) manifoldPtr.getBody0(); RigidBody body1 = (RigidBody) manifoldPtr.getBody1(); // only necessary to refresh the manifold once (first iteration). The integration is done outside the loop { //#ifdef FORCE_REFESH_CONTACT_MANIFOLDS //manifoldPtr->refreshContactPoints(body0->getCenterOfMassTransform(),body1->getCenterOfMassTransform()); //#endif //FORCE_REFESH_CONTACT_MANIFOLDS int numpoints = manifoldPtr.getNumContacts(); BulletStats.gTotalContactPoints += numpoints; Vector3f tmpVec = Stack.alloc(Vector3f.class); Matrix3f tmpMat3 = Stack.alloc(Matrix3f.class); Vector3f pos1 = Stack.alloc(Vector3f.class); Vector3f pos2 = Stack.alloc(Vector3f.class); Vector3f rel_pos1 = Stack.alloc(Vector3f.class); Vector3f rel_pos2 = Stack.alloc(Vector3f.class); Vector3f vel1 = Stack.alloc(Vector3f.class); Vector3f vel2 = Stack.alloc(Vector3f.class); Vector3f vel = Stack.alloc(Vector3f.class); Vector3f totalImpulse = Stack.alloc(Vector3f.class); Vector3f torqueAxis0 = Stack.alloc(Vector3f.class); Vector3f torqueAxis1 = Stack.alloc(Vector3f.class); Vector3f ftorqueAxis0 = Stack.alloc(Vector3f.class); Vector3f ftorqueAxis1 = Stack.alloc(Vector3f.class); for (int i = 0; i < numpoints; i++) { ManifoldPoint cp = manifoldPtr.getContactPoint(i); if (cp.getDistance() <= 0f) { cp.getPositionWorldOnA(pos1); cp.getPositionWorldOnB(pos2); rel_pos1.sub(pos1, body0.getCenterOfMassPosition(tmpVec)); rel_pos2.sub(pos2, body1.getCenterOfMassPosition(tmpVec)); // this jacobian entry is re-used for all iterations Matrix3f mat1 = body0.getCenterOfMassTransform(Stack.alloc(Transform.class)).basis; mat1.transpose(); Matrix3f mat2 = body1.getCenterOfMassTransform(Stack.alloc(Transform.class)).basis; mat2.transpose(); JacobianEntry jac = jacobiansPool.get(); jac.init(mat1, mat2, rel_pos1, rel_pos2, cp.normalWorldOnB, body0.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)), body0.getInvMass(), body1.getInvInertiaDiagLocal(Stack.alloc(Vector3f.class)), body1.getInvMass()); float jacDiagAB = jac.getDiagonal(); jacobiansPool.release(jac); ConstraintPersistentData cpd = (ConstraintPersistentData) cp.userPersistentData; if (cpd != null) { // might be invalid cpd.persistentLifeTime++; if (cpd.persistentLifeTime != cp.getLifeTime()) { //printf("Invalid: cpd->m_persistentLifeTime = %i cp.getLifeTime() = %i\n",cpd->m_persistentLifeTime,cp.getLifeTime()); //new (cpd) btConstraintPersistentData; cpd.reset(); cpd.persistentLifeTime = cp.getLifeTime(); } else { //printf("Persistent: cpd->m_persistentLifeTime = %i cp.getLifeTime() = %i\n",cpd->m_persistentLifeTime,cp.getLifeTime()); } } else { // todo: should this be in a pool? //void* mem = btAlignedAlloc(sizeof(btConstraintPersistentData),16); //cpd = new (mem)btConstraintPersistentData; cpd = new ConstraintPersistentData(); //assert(cpd != null); totalCpd++; //printf("totalCpd = %i Created Ptr %x\n",totalCpd,cpd); cp.userPersistentData = cpd; cpd.persistentLifeTime = cp.getLifeTime(); //printf("CREATED: %x . cpd->m_persistentLifeTime = %i cp.getLifeTime() = %i\n",cpd,cpd->m_persistentLifeTime,cp.getLifeTime()); } assert (cpd != null); cpd.jacDiagABInv = 1f / jacDiagAB; // Dependent on Rigidbody A and B types, fetch the contact/friction response func // perhaps do a similar thing for friction/restutution combiner funcs... cpd.frictionSolverFunc = frictionDispatch[body0.frictionSolverType][body1.frictionSolverType]; cpd.contactSolverFunc = contactDispatch[body0.contactSolverType][body1.contactSolverType]; body0.getVelocityInLocalPoint(rel_pos1, vel1); body1.getVelocityInLocalPoint(rel_pos2, vel2); vel.sub(vel1, vel2); float rel_vel; rel_vel = cp.normalWorldOnB.dot(vel); float combinedRestitution = cp.combinedRestitution; cpd.penetration = cp.getDistance(); ///btScalar(info.m_numIterations); cpd.friction = cp.combinedFriction; cpd.restitution = restitutionCurve(rel_vel, combinedRestitution); if (cpd.restitution <= 0f) { cpd.restitution = 0f; } // restitution and penetration work in same direction so // rel_vel float penVel = -cpd.penetration / info.timeStep; if (cpd.restitution > penVel) { cpd.penetration = 0f; } float relaxation = info.damping; if ((info.solverMode & SolverMode.SOLVER_USE_WARMSTARTING) != 0) { cpd.appliedImpulse *= relaxation; } else { cpd.appliedImpulse = 0f; } // for friction cpd.prevAppliedImpulse = cpd.appliedImpulse; // re-calculate friction direction every frame, todo: check if this is really needed TransformUtil.planeSpace1(cp.normalWorldOnB, cpd.frictionWorldTangential0, cpd.frictionWorldTangential1); //#define NO_FRICTION_WARMSTART 1 //#ifdef NO_FRICTION_WARMSTART cpd.accumulatedTangentImpulse0 = 0f; cpd.accumulatedTangentImpulse1 = 0f; //#endif //NO_FRICTION_WARMSTART float denom0 = body0.computeImpulseDenominator(pos1, cpd.frictionWorldTangential0); float denom1 = body1.computeImpulseDenominator(pos2, cpd.frictionWorldTangential0); float denom = relaxation / (denom0 + denom1); cpd.jacDiagABInvTangent0 = denom; denom0 = body0.computeImpulseDenominator(pos1, cpd.frictionWorldTangential1); denom1 = body1.computeImpulseDenominator(pos2, cpd.frictionWorldTangential1); denom = relaxation / (denom0 + denom1); cpd.jacDiagABInvTangent1 = denom; //btVector3 totalImpulse = // //#ifndef NO_FRICTION_WARMSTART // //cpd->m_frictionWorldTangential0*cpd->m_accumulatedTangentImpulse0+ // //cpd->m_frictionWorldTangential1*cpd->m_accumulatedTangentImpulse1+ // //#endif //NO_FRICTION_WARMSTART // cp.normalWorldOnB*cpd.appliedImpulse; totalImpulse.scale(cpd.appliedImpulse, cp.normalWorldOnB); /// { torqueAxis0.cross(rel_pos1, cp.normalWorldOnB); cpd.angularComponentA.set(torqueAxis0); body0.getInvInertiaTensorWorld(tmpMat3).transform(cpd.angularComponentA); torqueAxis1.cross(rel_pos2, cp.normalWorldOnB); cpd.angularComponentB.set(torqueAxis1); body1.getInvInertiaTensorWorld(tmpMat3).transform(cpd.angularComponentB); } { ftorqueAxis0.cross(rel_pos1, cpd.frictionWorldTangential0); cpd.frictionAngularComponent0A.set(ftorqueAxis0); body0.getInvInertiaTensorWorld(tmpMat3).transform(cpd.frictionAngularComponent0A); } { ftorqueAxis1.cross(rel_pos1, cpd.frictionWorldTangential1); cpd.frictionAngularComponent1A.set(ftorqueAxis1); body0.getInvInertiaTensorWorld(tmpMat3).transform(cpd.frictionAngularComponent1A); } { ftorqueAxis0.cross(rel_pos2, cpd.frictionWorldTangential0); cpd.frictionAngularComponent0B.set(ftorqueAxis0); body1.getInvInertiaTensorWorld(tmpMat3).transform(cpd.frictionAngularComponent0B); } { ftorqueAxis1.cross(rel_pos2, cpd.frictionWorldTangential1); cpd.frictionAngularComponent1B.set(ftorqueAxis1); body1.getInvInertiaTensorWorld(tmpMat3).transform(cpd.frictionAngularComponent1B); } /// // apply previous frames impulse on both bodies body0.applyImpulse(totalImpulse, rel_pos1); tmpVec.negate(totalImpulse); body1.applyImpulse(tmpVec, rel_pos2); } } } } public float solveCombinedContactFriction(RigidBody body0, RigidBody body1, ManifoldPoint cp, ContactSolverInfo info, int iter, IDebugDraw debugDrawer) { float maxImpulse = 0f; { if (cp.getDistance() <= 0f) { { //btConstraintPersistentData* cpd = (btConstraintPersistentData*) cp.m_userPersistentData; float impulse = ContactConstraint.resolveSingleCollisionCombined(body0, body1, cp, info); if (maxImpulse < impulse) { maxImpulse = impulse; } } } } return maxImpulse; } protected float solve(RigidBody body0, RigidBody body1, ManifoldPoint cp, ContactSolverInfo info, int iter, IDebugDraw debugDrawer) { float maxImpulse = 0f; { if (cp.getDistance() <= 0f) { { ConstraintPersistentData cpd = (ConstraintPersistentData) cp.userPersistentData; float impulse = cpd.contactSolverFunc.resolveContact(body0, body1, cp, info); if (maxImpulse < impulse) { maxImpulse = impulse; } } } } return maxImpulse; } protected float solveFriction(RigidBody body0, RigidBody body1, ManifoldPoint cp, ContactSolverInfo info, int iter, IDebugDraw debugDrawer) { { if (cp.getDistance() <= 0f) { ConstraintPersistentData cpd = (ConstraintPersistentData) cp.userPersistentData; cpd.frictionSolverFunc.resolveContact(body0, body1, cp, info); } } return 0f; } @Override public void reset() { btSeed2 = 0; } /** * Advanced: Override the default contact solving function for contacts, for certain types of rigidbody
* See RigidBody.contactSolverType and RigidBody.frictionSolverType */ public void setContactSolverFunc(ContactSolverFunc func, int type0, int type1) { contactDispatch[type0][type1] = func; } /** * Advanced: Override the default friction solving function for contacts, for certain types of rigidbody
* See RigidBody.contactSolverType and RigidBody.frictionSolverType */ public void setFrictionSolverFunc(ContactSolverFunc func, int type0, int type1) { frictionDispatch[type0][type1] = func; } public void setRandSeed(long seed) { btSeed2 = seed; } public long getRandSeed() { return btSeed2; } //////////////////////////////////////////////////////////////////////////// private static class OrderIndex { public int manifoldIndex; public int pointIndex; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy