/*
|
* Java port of Bullet (c) 2008 Martin Dvorak <jezek2@advel.cz>
|
*
|
* 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;
|
|
import com.bulletphysics.collision.dispatch.CollisionWorld.LocalConvexResult;
|
import java.util.Comparator;
|
import com.bulletphysics.BulletGlobals;
|
import com.bulletphysics.BulletStats;
|
import com.bulletphysics.collision.broadphase.BroadphaseInterface;
|
import com.bulletphysics.collision.broadphase.BroadphasePair;
|
import com.bulletphysics.collision.broadphase.BroadphaseProxy;
|
import com.bulletphysics.collision.broadphase.CollisionFilterGroups;
|
import com.bulletphysics.collision.broadphase.Dispatcher;
|
import com.bulletphysics.collision.broadphase.DispatcherInfo;
|
import com.bulletphysics.collision.broadphase.OverlappingPairCache;
|
import com.bulletphysics.collision.dispatch.CollisionConfiguration;
|
import com.bulletphysics.collision.dispatch.CollisionObject;
|
import com.bulletphysics.collision.dispatch.CollisionWorld;
|
import com.bulletphysics.collision.dispatch.CollisionWorld.ClosestConvexResultCallback;
|
import com.bulletphysics.collision.dispatch.SimulationIslandManager;
|
import com.bulletphysics.collision.narrowphase.ManifoldPoint;
|
import com.bulletphysics.collision.narrowphase.PersistentManifold;
|
import com.bulletphysics.collision.shapes.CollisionShape;
|
import com.bulletphysics.collision.shapes.InternalTriangleIndexCallback;
|
import com.bulletphysics.collision.shapes.SphereShape;
|
import com.bulletphysics.collision.shapes.TriangleCallback;
|
import com.bulletphysics.dynamics.constraintsolver.ConstraintSolver;
|
import com.bulletphysics.dynamics.constraintsolver.ContactSolverInfo;
|
import com.bulletphysics.dynamics.constraintsolver.SequentialImpulseConstraintSolver;
|
import com.bulletphysics.dynamics.constraintsolver.TypedConstraint;
|
import com.bulletphysics.dynamics.vehicle.RaycastVehicle;
|
import com.bulletphysics.linearmath.CProfileManager;
|
import com.bulletphysics.linearmath.DebugDrawModes;
|
import com.bulletphysics.linearmath.IDebugDraw;
|
import com.bulletphysics.linearmath.MiscUtil;
|
import com.bulletphysics.linearmath.ScalarUtil;
|
import com.bulletphysics.linearmath.Transform;
|
import com.bulletphysics.linearmath.TransformUtil;
|
import com.bulletphysics.util.ObjectArrayList;
|
import cz.advel.stack.Stack;
|
import javax.vecmath.Vector3f;
|
|
/**
|
* DiscreteDynamicsWorld provides discrete rigid body simulation.
|
*
|
* @author jezek2
|
*/
|
public class DiscreteDynamicsWorld extends DynamicsWorld {
|
|
protected ConstraintSolver constraintSolver;
|
protected SimulationIslandManager islandManager;
|
protected final ObjectArrayList<TypedConstraint> constraints = new ObjectArrayList<TypedConstraint>();
|
protected final Vector3f gravity = new Vector3f(0f, -10f, 0f);
|
|
//for variable timesteps
|
protected float localTime = 1f / 60f;
|
//for variable timesteps
|
|
protected boolean ownsIslandManager;
|
protected boolean ownsConstraintSolver;
|
|
protected ObjectArrayList<RaycastVehicle> vehicles = new ObjectArrayList<RaycastVehicle>();
|
|
protected ObjectArrayList<ActionInterface> actions = new ObjectArrayList<ActionInterface>();
|
|
protected int profileTimings = 0;
|
|
public DiscreteDynamicsWorld(Dispatcher dispatcher, BroadphaseInterface pairCache, ConstraintSolver constraintSolver, CollisionConfiguration collisionConfiguration) {
|
super(dispatcher, pairCache, collisionConfiguration);
|
this.constraintSolver = constraintSolver;
|
|
if (this.constraintSolver == null) {
|
this.constraintSolver = new SequentialImpulseConstraintSolver();
|
ownsConstraintSolver = true;
|
}
|
else {
|
ownsConstraintSolver = false;
|
}
|
|
{
|
islandManager = new SimulationIslandManager();
|
}
|
|
ownsIslandManager = true;
|
}
|
|
protected void saveKinematicState(float timeStep) {
|
for (int i = 0; i < collisionObjects.size(); i++) {
|
CollisionObject colObj = collisionObjects.getQuick(i);
|
RigidBody body = RigidBody.upcast(colObj);
|
if (body != null) {
|
//Transform predictedTrans = new Transform();
|
if (body.getActivationState() != CollisionObject.ISLAND_SLEEPING) {
|
if (body.isKinematicObject()) {
|
// to calculate velocities next frame
|
body.saveKinematicState(timeStep);
|
}
|
}
|
}
|
}
|
}
|
|
@Override
|
public void debugDrawWorld() {
|
if (getDebugDrawer() != null && (getDebugDrawer().getDebugMode() & DebugDrawModes.DRAW_CONTACT_POINTS) != 0) {
|
int numManifolds = getDispatcher().getNumManifolds();
|
Vector3f color = Stack.alloc(Vector3f.class);
|
color.set(0f, 0f, 0f);
|
for (int i = 0; i < numManifolds; i++) {
|
PersistentManifold contactManifold = getDispatcher().getManifoldByIndexInternal(i);
|
//btCollisionObject* obA = static_cast<btCollisionObject*>(contactManifold->getBody0());
|
//btCollisionObject* obB = static_cast<btCollisionObject*>(contactManifold->getBody1());
|
|
int numContacts = contactManifold.getNumContacts();
|
for (int j = 0; j < numContacts; j++) {
|
ManifoldPoint cp = contactManifold.getContactPoint(j);
|
getDebugDrawer().drawContactPoint(cp.positionWorldOnB, cp.normalWorldOnB, cp.getDistance(), cp.getLifeTime(), color);
|
}
|
}
|
}
|
|
if (getDebugDrawer() != null && (getDebugDrawer().getDebugMode() & (DebugDrawModes.DRAW_WIREFRAME | DebugDrawModes.DRAW_AABB)) != 0) {
|
int i;
|
|
Transform tmpTrans = Stack.alloc(Transform.class);
|
Vector3f minAabb = Stack.alloc(Vector3f.class);
|
Vector3f maxAabb = Stack.alloc(Vector3f.class);
|
Vector3f colorvec = Stack.alloc(Vector3f.class);
|
|
// todo: iterate over awake simulation islands!
|
for (i = 0; i < collisionObjects.size(); i++) {
|
CollisionObject colObj = collisionObjects.getQuick(i);
|
if (getDebugDrawer() != null && (getDebugDrawer().getDebugMode() & DebugDrawModes.DRAW_WIREFRAME) != 0) {
|
Vector3f color = Stack.alloc(Vector3f.class);
|
color.set(255f, 255f, 255f);
|
switch (colObj.getActivationState()) {
|
case CollisionObject.ACTIVE_TAG:
|
color.set(255f, 255f, 255f);
|
break;
|
case CollisionObject.ISLAND_SLEEPING:
|
color.set(0f, 255f, 0f);
|
break;
|
case CollisionObject.WANTS_DEACTIVATION:
|
color.set(0f, 255f, 255f);
|
break;
|
case CollisionObject.DISABLE_DEACTIVATION:
|
color.set(255f, 0f, 0f);
|
break;
|
case CollisionObject.DISABLE_SIMULATION:
|
color.set(255f, 255f, 0f);
|
break;
|
default: {
|
color.set(255f, 0f, 0f);
|
}
|
}
|
|
debugDrawObject(colObj.getWorldTransform(tmpTrans), colObj.getCollisionShape(), color);
|
}
|
if (debugDrawer != null && (debugDrawer.getDebugMode() & DebugDrawModes.DRAW_AABB) != 0) {
|
colorvec.set(1f, 0f, 0f);
|
colObj.getCollisionShape().getAabb(colObj.getWorldTransform(tmpTrans), minAabb, maxAabb);
|
debugDrawer.drawAabb(minAabb, maxAabb, colorvec);
|
}
|
}
|
|
Vector3f wheelColor = Stack.alloc(Vector3f.class);
|
Vector3f wheelPosWS = Stack.alloc(Vector3f.class);
|
Vector3f axle = Stack.alloc(Vector3f.class);
|
Vector3f tmp = Stack.alloc(Vector3f.class);
|
|
for (i = 0; i < vehicles.size(); i++) {
|
for (int v = 0; v < vehicles.getQuick(i).getNumWheels(); v++) {
|
wheelColor.set(0, 255, 255);
|
if (vehicles.getQuick(i).getWheelInfo(v).raycastInfo.isInContact) {
|
wheelColor.set(0, 0, 255);
|
}
|
else {
|
wheelColor.set(255, 0, 255);
|
}
|
|
wheelPosWS.set(vehicles.getQuick(i).getWheelInfo(v).worldTransform.origin);
|
|
axle.set(
|
vehicles.getQuick(i).getWheelInfo(v).worldTransform.basis.getElement(0, vehicles.getQuick(i).getRightAxis()),
|
vehicles.getQuick(i).getWheelInfo(v).worldTransform.basis.getElement(1, vehicles.getQuick(i).getRightAxis()),
|
vehicles.getQuick(i).getWheelInfo(v).worldTransform.basis.getElement(2, vehicles.getQuick(i).getRightAxis()));
|
|
|
//m_vehicles[i]->getWheelInfo(v).m_raycastInfo.m_wheelAxleWS
|
//debug wheels (cylinders)
|
tmp.add(wheelPosWS, axle);
|
debugDrawer.drawLine(wheelPosWS, tmp, wheelColor);
|
debugDrawer.drawLine(wheelPosWS, vehicles.getQuick(i).getWheelInfo(v).raycastInfo.contactPointWS, wheelColor);
|
}
|
}
|
|
if (getDebugDrawer() != null && getDebugDrawer().getDebugMode() != 0) {
|
for (i=0; i<actions.size(); i++) {
|
actions.getQuick(i).debugDraw(debugDrawer);
|
}
|
}
|
}
|
}
|
|
@Override
|
public void clearForces() {
|
// todo: iterate over awake simulation islands!
|
for (int i = 0; i < collisionObjects.size(); i++) {
|
CollisionObject colObj = collisionObjects.getQuick(i);
|
|
RigidBody body = RigidBody.upcast(colObj);
|
if (body != null) {
|
body.clearForces();
|
}
|
}
|
}
|
|
/**
|
* Apply gravity, call this once per timestep.
|
*/
|
public void applyGravity() {
|
// todo: iterate over awake simulation islands!
|
for (int i = 0; i < collisionObjects.size(); i++) {
|
CollisionObject colObj = collisionObjects.getQuick(i);
|
|
RigidBody body = RigidBody.upcast(colObj);
|
if (body != null && body.isActive()) {
|
body.applyGravity();
|
}
|
}
|
}
|
|
protected void synchronizeMotionStates() {
|
Transform interpolatedTransform = Stack.alloc(Transform.class);
|
|
Transform tmpTrans = Stack.alloc(Transform.class);
|
Vector3f tmpLinVel = Stack.alloc(Vector3f.class);
|
Vector3f tmpAngVel = Stack.alloc(Vector3f.class);
|
|
// todo: iterate over awake simulation islands!
|
for (int i = 0; i < collisionObjects.size(); i++) {
|
CollisionObject colObj = collisionObjects.getQuick(i);
|
|
RigidBody body = RigidBody.upcast(colObj);
|
if (body != null && body.getMotionState() != null && !body.isStaticOrKinematicObject())
|
{
|
// we need to call the update at least once, even for sleeping objects
|
// otherwise the 'graphics' transform never updates properly
|
// so todo: add 'dirty' flag
|
//if (body->getActivationState() != ISLAND_SLEEPING)
|
{
|
TransformUtil.integrateTransform(
|
body.getInterpolationWorldTransform(tmpTrans),
|
body.getInterpolationLinearVelocity(tmpLinVel),
|
body.getInterpolationAngularVelocity(tmpAngVel),
|
localTime * body.getHitFraction(), interpolatedTransform);
|
body.getMotionState().setWorldTransform(interpolatedTransform);
|
}
|
}
|
}
|
|
if (getDebugDrawer() != null && (getDebugDrawer().getDebugMode() & DebugDrawModes.DRAW_WIREFRAME) != 0) {
|
for (int i = 0; i < vehicles.size(); i++) {
|
for (int v = 0; v < vehicles.getQuick(i).getNumWheels(); v++) {
|
// synchronize the wheels with the (interpolated) chassis worldtransform
|
vehicles.getQuick(i).updateWheelTransform(v, true);
|
}
|
}
|
}
|
}
|
|
@Override
|
public int stepSimulation(float timeStep, int maxSubSteps, float fixedTimeStep) {
|
startProfiling(timeStep);
|
|
long t0 = System.nanoTime();
|
|
BulletStats.pushProfile("stepSimulation");
|
try {
|
int numSimulationSubSteps = 0;
|
|
if (maxSubSteps != 0) {
|
// fixed timestep with interpolation
|
localTime += timeStep;
|
if (localTime >= fixedTimeStep) {
|
numSimulationSubSteps = (int) (localTime / fixedTimeStep);
|
localTime -= numSimulationSubSteps * fixedTimeStep;
|
}
|
}
|
else {
|
//variable timestep
|
fixedTimeStep = timeStep;
|
localTime = timeStep;
|
if (ScalarUtil.fuzzyZero(timeStep)) {
|
numSimulationSubSteps = 0;
|
maxSubSteps = 0;
|
}
|
else {
|
numSimulationSubSteps = 1;
|
maxSubSteps = 1;
|
}
|
}
|
|
// process some debugging flags
|
if (getDebugDrawer() != null) {
|
BulletGlobals.setDeactivationDisabled((getDebugDrawer().getDebugMode() & DebugDrawModes.NO_DEACTIVATION) != 0);
|
}
|
if (numSimulationSubSteps != 0) {
|
saveKinematicState(fixedTimeStep);
|
|
applyGravity();
|
|
// clamp the number of substeps, to prevent simulation grinding spiralling down to a halt
|
int clampedSimulationSteps = (numSimulationSubSteps > maxSubSteps) ? maxSubSteps : numSimulationSubSteps;
|
|
for (int i = 0; i < clampedSimulationSteps; i++) {
|
internalSingleStepSimulation(fixedTimeStep);
|
synchronizeMotionStates();
|
}
|
}
|
|
synchronizeMotionStates();
|
|
clearForces();
|
|
//#ifndef BT_NO_PROFILE
|
CProfileManager.incrementFrameCounter();
|
//#endif //BT_NO_PROFILE
|
|
return numSimulationSubSteps;
|
}
|
finally {
|
BulletStats.popProfile();
|
|
BulletStats.stepSimulationTime = (System.nanoTime() - t0) / 1000000;
|
}
|
}
|
|
protected void internalSingleStepSimulation(float timeStep) {
|
BulletStats.pushProfile("internalSingleStepSimulation");
|
try {
|
// apply gravity, predict motion
|
predictUnconstraintMotion(timeStep);
|
|
DispatcherInfo dispatchInfo = getDispatchInfo();
|
|
dispatchInfo.timeStep = timeStep;
|
dispatchInfo.stepCount = 0;
|
dispatchInfo.debugDraw = getDebugDrawer();
|
|
// perform collision detection
|
performDiscreteCollisionDetection();
|
|
calculateSimulationIslands();
|
|
getSolverInfo().timeStep = timeStep;
|
|
// solve contact and other joint constraints
|
solveConstraints(getSolverInfo());
|
|
//CallbackTriggers();
|
|
// integrate transforms
|
integrateTransforms(timeStep);
|
|
// update vehicle simulation
|
updateActions(timeStep);
|
|
// update vehicle simulation
|
updateVehicles(timeStep);
|
|
updateActivationState(timeStep);
|
|
if (internalTickCallback != null) {
|
internalTickCallback.internalTick(this, timeStep);
|
}
|
}
|
finally {
|
BulletStats.popProfile();
|
}
|
}
|
|
@Override
|
public void setGravity(Vector3f gravity) {
|
this.gravity.set(gravity);
|
for (int i = 0; i < collisionObjects.size(); i++) {
|
CollisionObject colObj = collisionObjects.getQuick(i);
|
RigidBody body = RigidBody.upcast(colObj);
|
if (body != null) {
|
body.setGravity(gravity);
|
}
|
}
|
}
|
|
@Override
|
public Vector3f getGravity(Vector3f out) {
|
out.set(gravity);
|
return out;
|
}
|
|
@Override
|
public void removeRigidBody(RigidBody body) {
|
removeCollisionObject(body);
|
}
|
|
@Override
|
public void addRigidBody(RigidBody body) {
|
if (!body.isStaticOrKinematicObject()) {
|
body.setGravity(gravity);
|
}
|
|
if (body.getCollisionShape() != null) {
|
boolean isDynamic = !(body.isStaticObject() || body.isKinematicObject());
|
short collisionFilterGroup = isDynamic ? (short) CollisionFilterGroups.DEFAULT_FILTER : (short) CollisionFilterGroups.STATIC_FILTER;
|
short collisionFilterMask = isDynamic ? (short) CollisionFilterGroups.ALL_FILTER : (short) (CollisionFilterGroups.ALL_FILTER ^ CollisionFilterGroups.STATIC_FILTER);
|
|
addCollisionObject(body, collisionFilterGroup, collisionFilterMask);
|
}
|
}
|
|
public void addRigidBody(RigidBody body, short group, short mask) {
|
if (!body.isStaticOrKinematicObject()) {
|
body.setGravity(gravity);
|
}
|
|
if (body.getCollisionShape() != null) {
|
addCollisionObject(body, group, mask);
|
}
|
}
|
|
public void updateActions(float timeStep) {
|
BulletStats.pushProfile("updateActions");
|
try {
|
for (int i=0; i<actions.size(); i++) {
|
actions.getQuick(i).updateAction(this, timeStep);
|
}
|
}
|
finally {
|
BulletStats.popProfile();
|
}
|
}
|
|
protected void updateVehicles(float timeStep) {
|
BulletStats.pushProfile("updateVehicles");
|
try {
|
for (int i = 0; i < vehicles.size(); i++) {
|
RaycastVehicle vehicle = vehicles.getQuick(i);
|
vehicle.updateVehicle(timeStep);
|
}
|
}
|
finally {
|
BulletStats.popProfile();
|
}
|
}
|
|
protected void updateActivationState(float timeStep) {
|
BulletStats.pushProfile("updateActivationState");
|
try {
|
Vector3f tmp = Stack.alloc(Vector3f.class);
|
|
for (int i=0; i<collisionObjects.size(); i++) {
|
CollisionObject colObj = collisionObjects.getQuick(i);
|
RigidBody body = RigidBody.upcast(colObj);
|
if (body != null) {
|
body.updateDeactivation(timeStep);
|
|
if (body.wantsSleeping()) {
|
if (body.isStaticOrKinematicObject()) {
|
body.setActivationState(CollisionObject.ISLAND_SLEEPING);
|
}
|
else {
|
if (body.getActivationState() == CollisionObject.ACTIVE_TAG) {
|
body.setActivationState(CollisionObject.WANTS_DEACTIVATION);
|
}
|
if (body.getActivationState() == CollisionObject.ISLAND_SLEEPING) {
|
tmp.set(0f, 0f, 0f);
|
body.setAngularVelocity(tmp);
|
body.setLinearVelocity(tmp);
|
}
|
}
|
}
|
else {
|
if (body.getActivationState() != CollisionObject.DISABLE_DEACTIVATION) {
|
body.setActivationState(CollisionObject.ACTIVE_TAG);
|
}
|
}
|
}
|
}
|
}
|
finally {
|
BulletStats.popProfile();
|
}
|
}
|
|
@Override
|
public void addConstraint(TypedConstraint constraint, boolean disableCollisionsBetweenLinkedBodies) {
|
constraints.add(constraint);
|
if (disableCollisionsBetweenLinkedBodies) {
|
constraint.getRigidBodyA().addConstraintRef(constraint);
|
constraint.getRigidBodyB().addConstraintRef(constraint);
|
}
|
}
|
|
@Override
|
public void removeConstraint(TypedConstraint constraint) {
|
constraints.remove(constraint);
|
constraint.getRigidBodyA().removeConstraintRef(constraint);
|
constraint.getRigidBodyB().removeConstraintRef(constraint);
|
}
|
|
@Override
|
public void addAction(ActionInterface action) {
|
actions.add(action);
|
}
|
|
@Override
|
public void removeAction(ActionInterface action) {
|
actions.remove(action);
|
}
|
|
@Override
|
public void addVehicle(RaycastVehicle vehicle) {
|
vehicles.add(vehicle);
|
}
|
|
@Override
|
public void removeVehicle(RaycastVehicle vehicle) {
|
vehicles.remove(vehicle);
|
}
|
|
private static int getConstraintIslandId(TypedConstraint lhs) {
|
int islandId;
|
|
CollisionObject rcolObj0 = lhs.getRigidBodyA();
|
CollisionObject rcolObj1 = lhs.getRigidBodyB();
|
islandId = rcolObj0.getIslandTag() >= 0 ? rcolObj0.getIslandTag() : rcolObj1.getIslandTag();
|
return islandId;
|
}
|
|
private static class InplaceSolverIslandCallback extends SimulationIslandManager.IslandCallback {
|
public ContactSolverInfo solverInfo;
|
public ConstraintSolver solver;
|
public ObjectArrayList<TypedConstraint> sortedConstraints;
|
public int numConstraints;
|
public IDebugDraw debugDrawer;
|
//public StackAlloc* m_stackAlloc;
|
public Dispatcher dispatcher;
|
|
public void init(ContactSolverInfo solverInfo, ConstraintSolver solver, ObjectArrayList<TypedConstraint> sortedConstraints, int numConstraints, IDebugDraw debugDrawer, Dispatcher dispatcher) {
|
this.solverInfo = solverInfo;
|
this.solver = solver;
|
this.sortedConstraints = sortedConstraints;
|
this.numConstraints = numConstraints;
|
this.debugDrawer = debugDrawer;
|
this.dispatcher = dispatcher;
|
}
|
|
public void processIsland(ObjectArrayList<CollisionObject> bodies, int numBodies, ObjectArrayList<PersistentManifold> manifolds, int manifolds_offset, int numManifolds, int islandId) {
|
if (islandId < 0) {
|
// we don't split islands, so all constraints/contact manifolds/bodies are passed into the solver regardless the island id
|
solver.solveGroup(bodies, numBodies, manifolds, manifolds_offset, numManifolds, sortedConstraints, 0, numConstraints, solverInfo, debugDrawer/*,m_stackAlloc*/, dispatcher);
|
}
|
else {
|
// also add all non-contact constraints/joints for this island
|
//ObjectArrayList<TypedConstraint> startConstraint = null;
|
int startConstraint_idx = -1;
|
int numCurConstraints = 0;
|
int i;
|
|
// find the first constraint for this island
|
for (i = 0; i < numConstraints; i++) {
|
if (getConstraintIslandId(sortedConstraints.getQuick(i)) == islandId) {
|
//startConstraint = &m_sortedConstraints[i];
|
//startConstraint = sortedConstraints.subList(i, sortedConstraints.size());
|
startConstraint_idx = i;
|
break;
|
}
|
}
|
// count the number of constraints in this island
|
for (; i < numConstraints; i++) {
|
if (getConstraintIslandId(sortedConstraints.getQuick(i)) == islandId) {
|
numCurConstraints++;
|
}
|
}
|
|
// only call solveGroup if there is some work: avoid virtual function call, its overhead can be excessive
|
if ((numManifolds + numCurConstraints) > 0) {
|
solver.solveGroup(bodies, numBodies, manifolds, manifolds_offset, numManifolds, sortedConstraints, startConstraint_idx, numCurConstraints, solverInfo, debugDrawer/*,m_stackAlloc*/, dispatcher);
|
}
|
}
|
}
|
}
|
|
private ObjectArrayList<TypedConstraint> sortedConstraints = new ObjectArrayList<TypedConstraint>();
|
private InplaceSolverIslandCallback solverCallback = new InplaceSolverIslandCallback();
|
|
protected void solveConstraints(ContactSolverInfo solverInfo) {
|
BulletStats.pushProfile("solveConstraints");
|
try {
|
// sorted version of all btTypedConstraint, based on islandId
|
sortedConstraints.clear();
|
for (int i=0; i<constraints.size(); i++) {
|
sortedConstraints.add(constraints.getQuick(i));
|
}
|
//Collections.sort(sortedConstraints, sortConstraintOnIslandPredicate);
|
MiscUtil.quickSort(sortedConstraints, sortConstraintOnIslandPredicate);
|
|
ObjectArrayList<TypedConstraint> constraintsPtr = getNumConstraints() != 0 ? sortedConstraints : null;
|
|
solverCallback.init(solverInfo, constraintSolver, constraintsPtr, sortedConstraints.size(), debugDrawer/*,m_stackAlloc*/, dispatcher1);
|
|
constraintSolver.prepareSolve(getCollisionWorld().getNumCollisionObjects(), getCollisionWorld().getDispatcher().getNumManifolds());
|
|
// solve all the constraints for this island
|
islandManager.buildAndProcessIslands(getCollisionWorld().getDispatcher(), getCollisionWorld().getCollisionObjectArray(), solverCallback);
|
|
constraintSolver.allSolved(solverInfo, debugDrawer/*, m_stackAlloc*/);
|
}
|
finally {
|
BulletStats.popProfile();
|
}
|
}
|
|
protected void calculateSimulationIslands() {
|
BulletStats.pushProfile("calculateSimulationIslands");
|
try {
|
getSimulationIslandManager().updateActivationState(getCollisionWorld(), getCollisionWorld().getDispatcher());
|
|
{
|
int i;
|
int numConstraints = constraints.size();
|
for (i = 0; i < numConstraints; i++) {
|
TypedConstraint constraint = constraints.getQuick(i);
|
|
RigidBody colObj0 = constraint.getRigidBodyA();
|
RigidBody colObj1 = constraint.getRigidBodyB();
|
|
if (((colObj0 != null) && (!colObj0.isStaticOrKinematicObject())) &&
|
((colObj1 != null) && (!colObj1.isStaticOrKinematicObject())))
|
{
|
if (colObj0.isActive() || colObj1.isActive()) {
|
getSimulationIslandManager().getUnionFind().unite((colObj0).getIslandTag(), (colObj1).getIslandTag());
|
}
|
}
|
}
|
}
|
|
// Store the island id in each body
|
getSimulationIslandManager().storeIslandActivationState(getCollisionWorld());
|
}
|
finally {
|
BulletStats.popProfile();
|
}
|
}
|
|
protected void integrateTransforms(float timeStep) {
|
BulletStats.pushProfile("integrateTransforms");
|
try {
|
Vector3f tmp = Stack.alloc(Vector3f.class);
|
Transform tmpTrans = Stack.alloc(Transform.class);
|
|
Transform predictedTrans = Stack.alloc(Transform.class);
|
for (int i=0; i<collisionObjects.size(); i++) {
|
CollisionObject colObj = collisionObjects.getQuick(i);
|
RigidBody body = RigidBody.upcast(colObj);
|
if (body != null) {
|
body.setHitFraction(1f);
|
|
if (body.isActive() && (!body.isStaticOrKinematicObject())) {
|
body.predictIntegratedTransform(timeStep, predictedTrans);
|
|
tmp.sub(predictedTrans.origin, body.getWorldTransform(tmpTrans).origin);
|
float squareMotion = tmp.lengthSquared();
|
|
if (body.getCcdSquareMotionThreshold() != 0f && body.getCcdSquareMotionThreshold() < squareMotion) {
|
BulletStats.pushProfile("CCD motion clamping");
|
try {
|
if (body.getCollisionShape().isConvex()) {
|
BulletStats.gNumClampedCcdMotions++;
|
|
ClosestNotMeConvexResultCallback sweepResults = new ClosestNotMeConvexResultCallback(body, body.getWorldTransform(tmpTrans).origin, predictedTrans.origin, getBroadphase().getOverlappingPairCache(), getDispatcher());
|
//ConvexShape convexShape = (ConvexShape)body.getCollisionShape();
|
SphereShape tmpSphere = new SphereShape(body.getCcdSweptSphereRadius()); //btConvexShape* convexShape = static_cast<btConvexShape*>(body->getCollisionShape());
|
|
sweepResults.collisionFilterGroup = body.getBroadphaseProxy().collisionFilterGroup;
|
sweepResults.collisionFilterMask = body.getBroadphaseProxy().collisionFilterMask;
|
|
convexSweepTest(tmpSphere, body.getWorldTransform(tmpTrans), predictedTrans, sweepResults);
|
// JAVA NOTE: added closestHitFraction test to prevent objects being stuck
|
if (sweepResults.hasHit() && (sweepResults.closestHitFraction > 0.0001f)) {
|
body.setHitFraction(sweepResults.closestHitFraction);
|
body.predictIntegratedTransform(timeStep * body.getHitFraction(), predictedTrans);
|
body.setHitFraction(0f);
|
//System.out.printf("clamped integration to hit fraction = %f\n", sweepResults.closestHitFraction);
|
}
|
}
|
}
|
finally {
|
BulletStats.popProfile();
|
}
|
}
|
|
body.proceedToTransform(predictedTrans);
|
}
|
}
|
}
|
}
|
finally {
|
BulletStats.popProfile();
|
}
|
}
|
|
protected void predictUnconstraintMotion(float timeStep) {
|
BulletStats.pushProfile("predictUnconstraintMotion");
|
try {
|
Transform tmpTrans = Stack.alloc(Transform.class);
|
|
for (int i = 0; i < collisionObjects.size(); i++) {
|
CollisionObject colObj = collisionObjects.getQuick(i);
|
RigidBody body = RigidBody.upcast(colObj);
|
if (body != null) {
|
if (!body.isStaticOrKinematicObject()) {
|
if (body.isActive()) {
|
body.integrateVelocities(timeStep);
|
// damping
|
body.applyDamping(timeStep);
|
|
body.predictIntegratedTransform(timeStep, body.getInterpolationWorldTransform(tmpTrans));
|
}
|
}
|
}
|
}
|
}
|
finally {
|
BulletStats.popProfile();
|
}
|
}
|
|
protected void startProfiling(float timeStep) {
|
//#ifndef BT_NO_PROFILE
|
CProfileManager.reset();
|
//#endif //BT_NO_PROFILE
|
}
|
|
protected void debugDrawSphere(float radius, Transform transform, Vector3f color) {
|
Vector3f start = (Vector3f) Stack.alloc(transform.origin);
|
|
Vector3f xoffs = (Vector3f) Stack.alloc(Vector3f.class);
|
xoffs.set(radius, 0, 0);
|
transform.basis.transform(xoffs);
|
Vector3f yoffs = (Vector3f) Stack.alloc(Vector3f.class);
|
yoffs.set(0, radius, 0);
|
transform.basis.transform(yoffs);
|
Vector3f zoffs = (Vector3f) Stack.alloc(Vector3f.class);
|
zoffs.set(0, 0, radius);
|
transform.basis.transform(zoffs);
|
|
Vector3f tmp1 = (Vector3f) Stack.alloc(Vector3f.class);
|
Vector3f tmp2 = (Vector3f) Stack.alloc(Vector3f.class);
|
|
// XY
|
tmp1.sub(start, xoffs);
|
tmp2.add(start, yoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
tmp1.add(start, yoffs);
|
tmp2.add(start, xoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
tmp1.add(start, xoffs);
|
tmp2.sub(start, yoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
tmp1.sub(start, yoffs);
|
tmp2.sub(start, xoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
|
// XZ
|
tmp1.sub(start, xoffs);
|
tmp2.add(start, zoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
tmp1.add(start, zoffs);
|
tmp2.add(start, xoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
tmp1.add(start, xoffs);
|
tmp2.sub(start, zoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
tmp1.sub(start, zoffs);
|
tmp2.sub(start, xoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
|
// YZ
|
tmp1.sub(start, yoffs);
|
tmp2.add(start, zoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
tmp1.add(start, zoffs);
|
tmp2.add(start, yoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
tmp1.add(start, yoffs);
|
tmp2.sub(start, zoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
tmp1.sub(start, zoffs);
|
tmp2.sub(start, yoffs);
|
getDebugDrawer().drawLine(tmp1, tmp2, color);
|
}
|
|
public void debugDrawObject(Transform worldTransform, CollisionShape shape, Vector3f color) {
|
Vector3f tmp = Stack.alloc(Vector3f.class);
|
Vector3f tmp2 = Stack.alloc(Vector3f.class);
|
|
// Draw a small simplex at the center of the object
|
{
|
Vector3f start = (Vector3f) Stack.alloc(worldTransform.origin);
|
|
tmp.set(1f, 0f, 0f);
|
worldTransform.basis.transform(tmp);
|
tmp.add(start);
|
tmp2.set(1f, 0f, 0f);
|
getDebugDrawer().drawLine(start, tmp, tmp2);
|
|
tmp.set(0f, 1f, 0f);
|
worldTransform.basis.transform(tmp);
|
tmp.add(start);
|
tmp2.set(0f, 1f, 0f);
|
getDebugDrawer().drawLine(start, tmp, tmp2);
|
|
tmp.set(0f, 0f, 1f);
|
worldTransform.basis.transform(tmp);
|
tmp.add(start);
|
tmp2.set(0f, 0f, 1f);
|
getDebugDrawer().drawLine(start, tmp, tmp2);
|
}
|
|
// JAVA TODO: debugDrawObject, note that this commented code is from old version, use actual version when implementing
|
|
// if (shape->getShapeType() == COMPOUND_SHAPE_PROXYTYPE)
|
// {
|
// const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shape);
|
// for (int i=compoundShape->getNumChildShapes()-1;i>=0;i--)
|
// {
|
// btTransform childTrans = compoundShape->getChildTransform(i);
|
// const btCollisionShape* colShape = compoundShape->getChildShape(i);
|
// debugDrawObject(worldTransform*childTrans,colShape,color);
|
// }
|
//
|
// } else
|
// {
|
// switch (shape->getShapeType())
|
// {
|
//
|
// case SPHERE_SHAPE_PROXYTYPE:
|
// {
|
// const btSphereShape* sphereShape = static_cast<const btSphereShape*>(shape);
|
// btScalar radius = sphereShape->getMargin();//radius doesn't include the margin, so draw with margin
|
//
|
// debugDrawSphere(radius, worldTransform, color);
|
// break;
|
// }
|
// case MULTI_SPHERE_SHAPE_PROXYTYPE:
|
// {
|
// const btMultiSphereShape* multiSphereShape = static_cast<const btMultiSphereShape*>(shape);
|
//
|
// for (int i = multiSphereShape->getSphereCount()-1; i>=0;i--)
|
// {
|
// btTransform childTransform = worldTransform;
|
// childTransform.getOrigin() += multiSphereShape->getSpherePosition(i);
|
// debugDrawSphere(multiSphereShape->getSphereRadius(i), childTransform, color);
|
// }
|
//
|
// break;
|
// }
|
// case CAPSULE_SHAPE_PROXYTYPE:
|
// {
|
// const btCapsuleShape* capsuleShape = static_cast<const btCapsuleShape*>(shape);
|
//
|
// btScalar radius = capsuleShape->getRadius();
|
// btScalar halfHeight = capsuleShape->getHalfHeight();
|
//
|
// // Draw the ends
|
// {
|
// btTransform childTransform = worldTransform;
|
// childTransform.getOrigin() = worldTransform * btVector3(0,halfHeight,0);
|
// debugDrawSphere(radius, childTransform, color);
|
// }
|
//
|
// {
|
// btTransform childTransform = worldTransform;
|
// childTransform.getOrigin() = worldTransform * btVector3(0,-halfHeight,0);
|
// debugDrawSphere(radius, childTransform, color);
|
// }
|
//
|
// // Draw some additional lines
|
// btVector3 start = worldTransform.getOrigin();
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * btVector3(-radius,halfHeight,0),start+worldTransform.getBasis() * btVector3(-radius,-halfHeight,0), color);
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * btVector3(radius,halfHeight,0),start+worldTransform.getBasis() * btVector3(radius,-halfHeight,0), color);
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * btVector3(0,halfHeight,-radius),start+worldTransform.getBasis() * btVector3(0,-halfHeight,-radius), color);
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * btVector3(0,halfHeight,radius),start+worldTransform.getBasis() * btVector3(0,-halfHeight,radius), color);
|
//
|
// break;
|
// }
|
// case CONE_SHAPE_PROXYTYPE:
|
// {
|
// const btConeShape* coneShape = static_cast<const btConeShape*>(shape);
|
// btScalar radius = coneShape->getRadius();//+coneShape->getMargin();
|
// btScalar height = coneShape->getHeight();//+coneShape->getMargin();
|
// btVector3 start = worldTransform.getOrigin();
|
//
|
// int upAxis= coneShape->getConeUpIndex();
|
//
|
//
|
// btVector3 offsetHeight(0,0,0);
|
// offsetHeight[upAxis] = height * btScalar(0.5);
|
// btVector3 offsetRadius(0,0,0);
|
// offsetRadius[(upAxis+1)%3] = radius;
|
// btVector3 offset2Radius(0,0,0);
|
// offset2Radius[(upAxis+2)%3] = radius;
|
//
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * (offsetHeight),start+worldTransform.getBasis() * (-offsetHeight+offsetRadius),color);
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * (offsetHeight),start+worldTransform.getBasis() * (-offsetHeight-offsetRadius),color);
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * (offsetHeight),start+worldTransform.getBasis() * (-offsetHeight+offset2Radius),color);
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * (offsetHeight),start+worldTransform.getBasis() * (-offsetHeight-offset2Radius),color);
|
//
|
//
|
//
|
// break;
|
//
|
// }
|
// case CYLINDER_SHAPE_PROXYTYPE:
|
// {
|
// const btCylinderShape* cylinder = static_cast<const btCylinderShape*>(shape);
|
// int upAxis = cylinder->getUpAxis();
|
// btScalar radius = cylinder->getRadius();
|
// btScalar halfHeight = cylinder->getHalfExtentsWithMargin()[upAxis];
|
// btVector3 start = worldTransform.getOrigin();
|
// btVector3 offsetHeight(0,0,0);
|
// offsetHeight[upAxis] = halfHeight;
|
// btVector3 offsetRadius(0,0,0);
|
// offsetRadius[(upAxis+1)%3] = radius;
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * (offsetHeight+offsetRadius),start+worldTransform.getBasis() * (-offsetHeight+offsetRadius),color);
|
// getDebugDrawer()->drawLine(start+worldTransform.getBasis() * (offsetHeight-offsetRadius),start+worldTransform.getBasis() * (-offsetHeight-offsetRadius),color);
|
// break;
|
// }
|
// default:
|
// {
|
//
|
// if (shape->isConcave())
|
// {
|
// btConcaveShape* concaveMesh = (btConcaveShape*) shape;
|
//
|
// //todo pass camera, for some culling
|
// btVector3 aabbMax(btScalar(1e30),btScalar(1e30),btScalar(1e30));
|
// btVector3 aabbMin(btScalar(-1e30),btScalar(-1e30),btScalar(-1e30));
|
//
|
// DebugDrawcallback drawCallback(getDebugDrawer(),worldTransform,color);
|
// concaveMesh->processAllTriangles(&drawCallback,aabbMin,aabbMax);
|
//
|
// }
|
//
|
// if (shape->getShapeType() == CONVEX_TRIANGLEMESH_SHAPE_PROXYTYPE)
|
// {
|
// btConvexTriangleMeshShape* convexMesh = (btConvexTriangleMeshShape*) shape;
|
// //todo: pass camera for some culling
|
// btVector3 aabbMax(btScalar(1e30),btScalar(1e30),btScalar(1e30));
|
// btVector3 aabbMin(btScalar(-1e30),btScalar(-1e30),btScalar(-1e30));
|
// //DebugDrawcallback drawCallback;
|
// DebugDrawcallback drawCallback(getDebugDrawer(),worldTransform,color);
|
// convexMesh->getMeshInterface()->InternalProcessAllTriangles(&drawCallback,aabbMin,aabbMax);
|
// }
|
//
|
//
|
// /// for polyhedral shapes
|
// if (shape->isPolyhedral())
|
// {
|
// btPolyhedralConvexShape* polyshape = (btPolyhedralConvexShape*) shape;
|
//
|
// int i;
|
// for (i=0;i<polyshape->getNumEdges();i++)
|
// {
|
// btPoint3 a,b;
|
// polyshape->getEdge(i,a,b);
|
// btVector3 wa = worldTransform * a;
|
// btVector3 wb = worldTransform * b;
|
// getDebugDrawer()->drawLine(wa,wb,color);
|
//
|
// }
|
//
|
//
|
// }
|
// }
|
// }
|
// }
|
}
|
|
@Override
|
public void setConstraintSolver(ConstraintSolver solver) {
|
if (ownsConstraintSolver) {
|
//btAlignedFree( m_constraintSolver);
|
}
|
ownsConstraintSolver = false;
|
constraintSolver = solver;
|
}
|
|
@Override
|
public ConstraintSolver getConstraintSolver() {
|
return constraintSolver;
|
}
|
|
@Override
|
public int getNumConstraints() {
|
return constraints.size();
|
}
|
|
@Override
|
public TypedConstraint getConstraint(int index) {
|
return constraints.getQuick(index);
|
}
|
|
// JAVA NOTE: not part of the original api
|
@Override
|
public int getNumActions() {
|
return actions.size();
|
}
|
|
// JAVA NOTE: not part of the original api
|
@Override
|
public ActionInterface getAction(int index) {
|
return actions.getQuick(index);
|
}
|
|
public SimulationIslandManager getSimulationIslandManager() {
|
return islandManager;
|
}
|
|
public CollisionWorld getCollisionWorld() {
|
return this;
|
}
|
|
@Override
|
public DynamicsWorldType getWorldType() {
|
return DynamicsWorldType.DISCRETE_DYNAMICS_WORLD;
|
}
|
|
public void setNumTasks(int numTasks) {
|
}
|
|
////////////////////////////////////////////////////////////////////////////
|
|
private static final Comparator<TypedConstraint> sortConstraintOnIslandPredicate = new Comparator<TypedConstraint>() {
|
public int compare(TypedConstraint lhs, TypedConstraint rhs) {
|
int rIslandId0, lIslandId0;
|
rIslandId0 = getConstraintIslandId(rhs);
|
lIslandId0 = getConstraintIslandId(lhs);
|
return lIslandId0 < rIslandId0? -1 : +1;
|
}
|
};
|
|
// private static class DebugDrawcallback implements TriangleCallback, InternalTriangleIndexCallback {
|
// private IDebugDraw debugDrawer;
|
// private final Vector3f color = new Vector3f();
|
// private final Transform worldTrans = new Transform();
|
//
|
// public DebugDrawcallback(IDebugDraw debugDrawer, Transform worldTrans, Vector3f color) {
|
// this.debugDrawer = debugDrawer;
|
// this.worldTrans.set(worldTrans);
|
// this.color.set(color);
|
// }
|
//
|
// public void internalProcessTriangleIndex(Vector3f[] triangle, int partId, int triangleIndex) {
|
// processTriangle(triangle,partId,triangleIndex);
|
// }
|
//
|
// private final Vector3f wv0 = new Vector3f(),wv1 = new Vector3f(),wv2 = new Vector3f();
|
//
|
// public void processTriangle(Vector3f[] triangle, int partId, int triangleIndex) {
|
// wv0.set(triangle[0]);
|
// worldTrans.transform(wv0);
|
// wv1.set(triangle[1]);
|
// worldTrans.transform(wv1);
|
// wv2.set(triangle[2]);
|
// worldTrans.transform(wv2);
|
//
|
// debugDrawer.drawLine(wv0, wv1, color);
|
// debugDrawer.drawLine(wv1, wv2, color);
|
// debugDrawer.drawLine(wv2, wv0, color);
|
// }
|
// }
|
|
private static class ClosestNotMeConvexResultCallback extends ClosestConvexResultCallback {
|
private CollisionObject me;
|
private float allowedPenetration = 0f;
|
private OverlappingPairCache pairCache;
|
private Dispatcher dispatcher;
|
|
public ClosestNotMeConvexResultCallback(CollisionObject me, Vector3f fromA, Vector3f toA, OverlappingPairCache pairCache, Dispatcher dispatcher) {
|
super(fromA, toA);
|
this.me = me;
|
this.pairCache = pairCache;
|
this.dispatcher = dispatcher;
|
}
|
|
@Override
|
public float addSingleResult(LocalConvexResult convexResult, boolean normalInWorldSpace) {
|
if (convexResult.hitCollisionObject == me) {
|
return 1f;
|
}
|
|
Vector3f linVelA = Stack.alloc(Vector3f.class), linVelB = Stack.alloc(Vector3f.class);
|
linVelA.sub(convexToWorld, convexFromWorld);
|
linVelB.set(0f, 0f, 0f);//toB.getOrigin()-fromB.getOrigin();
|
|
Vector3f relativeVelocity = Stack.alloc(Vector3f.class);
|
relativeVelocity.sub(linVelA, linVelB);
|
// don't report time of impact for motion away from the contact normal (or causes minor penetration)
|
if (convexResult.hitNormalLocal.dot(relativeVelocity) >= -allowedPenetration) {
|
return 1f;
|
}
|
|
return super.addSingleResult(convexResult, normalInWorldSpace);
|
}
|
|
@Override
|
public boolean needsCollision(BroadphaseProxy proxy0) {
|
// don't collide with itself
|
if (proxy0.clientObject == me) {
|
return false;
|
}
|
|
// don't do CCD when the collision filters are not matching
|
if (!super.needsCollision(proxy0)) {
|
return false;
|
}
|
|
CollisionObject otherObj = (CollisionObject)proxy0.clientObject;
|
|
// call needsResponse, see http://code.google.com/p/bullet/issues/detail?id=179
|
if (dispatcher.needsResponse(me, otherObj)) {
|
// don't do CCD when there are already contact points (touching contact/penetration)
|
ObjectArrayList<PersistentManifold> manifoldArray = new ObjectArrayList<PersistentManifold>();
|
BroadphasePair collisionPair = pairCache.findPair(me.getBroadphaseHandle(), proxy0);
|
if (collisionPair != null) {
|
if (collisionPair.algorithm != null) {
|
//manifoldArray.resize(0);
|
collisionPair.algorithm.getAllContactManifolds(manifoldArray);
|
for (int j=0; j<manifoldArray.size(); j++) {
|
PersistentManifold manifold = manifoldArray.getQuick(j);
|
if (manifold.getNumContacts() > 0) {
|
return false;
|
}
|
}
|
}
|
}
|
}
|
return true;
|
}
|
}
|
|
}
|